From 5a4391b3b17089e2654c6726448e6c60c1e6248f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9goire=20Geis?= Date: Tue, 10 Jul 2018 15:53:56 +0200 Subject: [PATCH 001/801] Added contrib extension to have focus follow mouse (fixes #25685). --- .../common/config/commonEditorConfig.ts | 5 ++ src/vs/editor/common/config/editorOptions.ts | 17 +++-- .../contrib/focusOnHover/focusOnHover.ts | 65 +++++++++++++++++++ src/vs/editor/editor.all.ts | 1 + src/vs/monaco.d.ts | 5 ++ 5 files changed, 89 insertions(+), 4 deletions(-) create mode 100644 src/vs/editor/contrib/focusOnHover/focusOnHover.ts diff --git a/src/vs/editor/common/config/commonEditorConfig.ts b/src/vs/editor/common/config/commonEditorConfig.ts index 9774ab0405b..c771bb7aeba 100644 --- a/src/vs/editor/common/config/commonEditorConfig.ts +++ b/src/vs/editor/common/config/commonEditorConfig.ts @@ -784,6 +784,11 @@ const editorConfiguration: IConfigurationNode = { 'default': EDITOR_MODEL_DEFAULTS.largeFileOptimizations, 'description': nls.localize('largeFileOptimizations', "Special handling for large files to disable certain memory intensive features.") }, + 'editor.focusOnHover': { + 'type': 'boolean', + 'default': false, + 'description': nls.localize('focusOnHover', 'Controls if editors should be focused when hovered.') + }, 'diffEditor.renderIndicators': { 'type': 'boolean', 'default': true, diff --git a/src/vs/editor/common/config/editorOptions.ts b/src/vs/editor/common/config/editorOptions.ts index 14365ee9925..26bafcc31a4 100644 --- a/src/vs/editor/common/config/editorOptions.ts +++ b/src/vs/editor/common/config/editorOptions.ts @@ -625,6 +625,10 @@ export interface IEditorOptions { * Controls fading out of unused variables. */ showUnused?: boolean; + /** + * Controls whether editors should be focused on hover. + */ + focusOnHover?: boolean; } /** @@ -936,6 +940,7 @@ export interface EditorContribOptions { readonly lightbulbEnabled: boolean; readonly codeActionsOnSave: ICodeActionsOnSaveOptions; readonly codeActionsOnSaveTimeout: number; + readonly focusOnHover: boolean; } /** @@ -1315,6 +1320,7 @@ export class InternalEditorOptions { && objects.equals(a.codeActionsOnSave, b.codeActionsOnSave) && a.codeActionsOnSaveTimeout === b.codeActionsOnSaveTimeout && a.lightbulbEnabled === b.lightbulbEnabled + && a.focusOnHover === b.focusOnHover ); } @@ -1676,7 +1682,7 @@ export class EditorOptionsValidator { accessibilitySupport: _stringSet<'auto' | 'on' | 'off'>(opts.accessibilitySupport, defaults.accessibilitySupport, ['auto', 'on', 'off']), showUnused: _boolean(opts.showUnused, defaults.showUnused), viewInfo: viewInfo, - contribInfo: contribInfo, + contribInfo: contribInfo }; } @@ -1909,7 +1915,8 @@ export class EditorOptionsValidator { colorDecorators: _boolean(opts.colorDecorators, defaults.colorDecorators), lightbulbEnabled: _boolean(opts.lightbulb ? opts.lightbulb.enabled : false, defaults.lightbulbEnabled), codeActionsOnSave: _booleanMap(opts.codeActionsOnSave, {}), - codeActionsOnSaveTimeout: _clampedInt(opts.codeActionsOnSaveTimeout, defaults.codeActionsOnSaveTimeout, 1, 10000) + codeActionsOnSaveTimeout: _clampedInt(opts.codeActionsOnSaveTimeout, defaults.codeActionsOnSaveTimeout, 1, 10000), + focusOnHover: _boolean(opts.focusOnHover, defaults.focusOnHover) }; } } @@ -2017,7 +2024,8 @@ export class InternalEditorOptionsFactory { colorDecorators: opts.contribInfo.colorDecorators, lightbulbEnabled: opts.contribInfo.lightbulbEnabled, codeActionsOnSave: opts.contribInfo.codeActionsOnSave, - codeActionsOnSaveTimeout: opts.contribInfo.codeActionsOnSaveTimeout + codeActionsOnSaveTimeout: opts.contribInfo.codeActionsOnSaveTimeout, + focusOnHover: opts.contribInfo.focusOnHover } }; } @@ -2496,6 +2504,7 @@ export const EDITOR_DEFAULTS: IValidatedEditorOptions = { colorDecorators: true, lightbulbEnabled: true, codeActionsOnSave: {}, - codeActionsOnSaveTimeout: 750 + codeActionsOnSaveTimeout: 750, + focusOnHover: false }, }; diff --git a/src/vs/editor/contrib/focusOnHover/focusOnHover.ts b/src/vs/editor/contrib/focusOnHover/focusOnHover.ts new file mode 100644 index 00000000000..239360c49d0 --- /dev/null +++ b/src/vs/editor/contrib/focusOnHover/focusOnHover.ts @@ -0,0 +1,65 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +'use strict'; + +import { IEditorContribution } from 'vs/editor/common/editorCommon'; +import { IConfigurationChangedEvent } from 'vs/editor/common/config/editorOptions'; +import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; +import { registerEditorContribution } from 'vs/editor/browser/editorExtensions'; +import { IDisposable } from 'vs/base/common/lifecycle'; + +export class FocusOnHoverController implements IEditorContribution { + + private static readonly ID = 'editor.contrib.focusOnHover'; + + private _editorMouseMoveHandler?: IDisposable; + private _didChangeConfigurationHandler: IDisposable; + + static get(editor: ICodeEditor): FocusOnHoverController { + return editor.getContribution(FocusOnHoverController.ID); + } + + constructor(private readonly _editor: ICodeEditor) { + this._hookEvents(); + + this._didChangeConfigurationHandler = this._editor.onDidChangeConfiguration((e: IConfigurationChangedEvent) => { + if (e.contribInfo) { + this._unhookEvents(); + this._hookEvents(); + } + }); + } + + private _hookEvents(): void { + if (this._editor.getConfiguration().contribInfo.focusOnHover) { + this._editorMouseMoveHandler = this._editor.onMouseMove(_ => this._onEditorMouseMove()); + } + } + + private _unhookEvents(): void { + if (this._editorMouseMoveHandler) { + this._editorMouseMoveHandler.dispose(); + this._editorMouseMoveHandler = null; + } + } + + private _onEditorMouseMove(): void { + if (!this._editor.hasTextFocus()) { + this._editor.focus(); + } + } + + public getId(): string { + return FocusOnHoverController.ID; + } + + public dispose(): void { + this._unhookEvents(); + this._didChangeConfigurationHandler.dispose(); + } +} + +registerEditorContribution(FocusOnHoverController); diff --git a/src/vs/editor/editor.all.ts b/src/vs/editor/editor.all.ts index cc32769c6a6..85667363e86 100644 --- a/src/vs/editor/editor.all.ts +++ b/src/vs/editor/editor.all.ts @@ -21,6 +21,7 @@ import 'vs/editor/contrib/contextmenu/contextmenu'; import 'vs/editor/contrib/cursorUndo/cursorUndo'; import 'vs/editor/contrib/dnd/dnd'; import 'vs/editor/contrib/find/findController'; +import 'vs/editor/contrib/focusOnHover/focusOnHover'; import 'vs/editor/contrib/folding/folding'; import 'vs/editor/contrib/fontZoom/fontZoom'; import 'vs/editor/contrib/format/formatActions'; diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index d176c102959..6e3f3758bf1 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -2967,6 +2967,10 @@ declare namespace monaco.editor { * Controls fading out of unused variables. */ showUnused?: boolean; + /** + * Controls whether editors should be focused on hover. + */ + focusOnHover?: boolean; } /** @@ -3218,6 +3222,7 @@ declare namespace monaco.editor { readonly lightbulbEnabled: boolean; readonly codeActionsOnSave: ICodeActionsOnSaveOptions; readonly codeActionsOnSaveTimeout: number; + readonly focusOnHover: boolean; } /** From 6c5ecf0a8c5837e90b79fe16ea8047da23c24e4f Mon Sep 17 00:00:00 2001 From: lipgloss Date: Sun, 9 Sep 2018 18:07:49 -0600 Subject: [PATCH 002/801] add workbench configuration option for minimap opacity --- src/vs/editor/browser/viewParts/minimap/minimap.ts | 6 +++++- src/vs/platform/theme/common/colorRegistry.ts | 2 ++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/vs/editor/browser/viewParts/minimap/minimap.ts b/src/vs/editor/browser/viewParts/minimap/minimap.ts index 5f8337eb5a9..faccccf7fd0 100644 --- a/src/vs/editor/browser/viewParts/minimap/minimap.ts +++ b/src/vs/editor/browser/viewParts/minimap/minimap.ts @@ -26,7 +26,7 @@ import * as viewEvents from 'vs/editor/common/view/viewEvents'; import { GlobalMouseMoveMonitor, IStandardMouseMoveEventData, standardMouseMoveMerger } from 'vs/base/browser/globalMouseMoveMonitor'; import * as platform from 'vs/base/common/platform'; import { registerThemingParticipant } from 'vs/platform/theme/common/themeService'; -import { scrollbarSliderBackground, scrollbarSliderHoverBackground, scrollbarSliderActiveBackground, scrollbarShadow } from 'vs/platform/theme/common/colorRegistry'; +import { minimapOpacity, scrollbarSliderBackground, scrollbarSliderHoverBackground, scrollbarSliderActiveBackground, scrollbarShadow } from 'vs/platform/theme/common/colorRegistry'; const enum RenderMinimap { None = 0, @@ -926,6 +926,10 @@ export class Minimap extends ViewPart { } registerThemingParticipant((theme, collector) => { + const minimapOpacityValue = theme.getColor(minimapOpacity); + if (minimapOpacityValue) { + collector.addRule(`.monaco-editor .minimap { opacity: ${minimapOpacityValue.rgba.a}; will-change: opacity; }`); + } const sliderBackground = theme.getColor(scrollbarSliderBackground); if (sliderBackground) { const halfSliderBackground = sliderBackground.transparent(0.5); diff --git a/src/vs/platform/theme/common/colorRegistry.ts b/src/vs/platform/theme/common/colorRegistry.ts index 9af7371f18a..975b802fa58 100644 --- a/src/vs/platform/theme/common/colorRegistry.ts +++ b/src/vs/platform/theme/common/colorRegistry.ts @@ -219,6 +219,8 @@ export const buttonHoverBackground = registerColor('button.hoverBackground', { d export const badgeBackground = registerColor('badge.background', { dark: '#4D4D4D', light: '#C4C4C4', hc: Color.black }, nls.localize('badgeBackground', "Badge background color. Badges are small information labels, e.g. for search results count.")); export const badgeForeground = registerColor('badge.foreground', { dark: Color.white, light: '#333', hc: Color.white }, nls.localize('badgeForeground', "Badge foreground color. Badges are small information labels, e.g. for search results count.")); +export const minimapOpacity = registerColor('minimapOpacity', { dark: null, light: null, hc: null }, nls.localize('minimapOpacity', "Opacity of minimap in hex. Color data is ignored.")); + export const scrollbarShadow = registerColor('scrollbar.shadow', { dark: '#000000', light: '#DDDDDD', hc: null }, nls.localize('scrollbarShadow', "Scrollbar shadow to indicate that the view is scrolled.")); export const scrollbarSliderBackground = registerColor('scrollbarSlider.background', { dark: Color.fromHex('#797979').transparent(0.4), light: Color.fromHex('#646464').transparent(0.4), hc: transparent(contrastBorder, 0.6) }, nls.localize('scrollbarSliderBackground', "Scrollbar slider background color.")); export const scrollbarSliderHoverBackground = registerColor('scrollbarSlider.hoverBackground', { dark: Color.fromHex('#646464').transparent(0.7), light: Color.fromHex('#646464').transparent(0.7), hc: transparent(contrastBorder, 0.8) }, nls.localize('scrollbarSliderHoverBackground', "Scrollbar slider background color when hovering.")); From b7ce554fa929979ae8c6121bd2b872ee1e32f6fb Mon Sep 17 00:00:00 2001 From: Przemyslaw Adamczewski Date: Tue, 19 Feb 2019 17:25:09 +0100 Subject: [PATCH 003/801] Fixes #40646: multi cursor copy line n times --- .../contrib/linesOperations/linesOperations.ts | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/vs/editor/contrib/linesOperations/linesOperations.ts b/src/vs/editor/contrib/linesOperations/linesOperations.ts index d1559b13f0c..4080deab4c3 100644 --- a/src/vs/editor/contrib/linesOperations/linesOperations.ts +++ b/src/vs/editor/contrib/linesOperations/linesOperations.ts @@ -37,8 +37,20 @@ abstract class AbstractCopyLinesAction extends EditorAction { public run(_accessor: ServicesAccessor, editor: ICodeEditor): void { - const commands: ICommand[] = []; - const selections = editor.getSelections() || []; + let commands: ICommand[] = []; + let selections = editor.getSelections() || []; + selections = selections.reduce((accumulator, selection) => { + const isCurrentSelectionDuplicated = accumulator.some(value => ( + value.startLineNumber === selection.startLineNumber && + value.endLineNumber === selection.endLineNumber + )); + + if (isCurrentSelectionDuplicated) { + return accumulator; + } + + return [...accumulator, selection]; + }, []); for (const selection of selections) { commands.push(new CopyLinesCommand(selection, this.down)); From 4f083a077ef2b5a2f80eae0341ec0e9e3d927ac6 Mon Sep 17 00:00:00 2001 From: Michael Gubik Date: Fri, 8 Mar 2019 19:46:21 +0800 Subject: [PATCH 004/801] Fix #62003, add option for predominant axis scrolling --- .../base/browser/ui/scrollbar/scrollableElement.ts | 10 ++++++++++ .../ui/scrollbar/scrollableElementOptions.ts | 9 +++++++++ .../viewParts/editorScrollbar/editorScrollbar.ts | 4 +++- src/vs/editor/common/config/commonEditorConfig.ts | 5 +++++ src/vs/editor/common/config/editorOptions.ts | 14 ++++++++++++-- src/vs/monaco.d.ts | 6 ++++++ 6 files changed, 45 insertions(+), 3 deletions(-) diff --git a/src/vs/base/browser/ui/scrollbar/scrollableElement.ts b/src/vs/base/browser/ui/scrollbar/scrollableElement.ts index 9b31c847f70..f82e490baa5 100644 --- a/src/vs/base/browser/ui/scrollbar/scrollableElement.ts +++ b/src/vs/base/browser/ui/scrollbar/scrollableElement.ts @@ -282,6 +282,7 @@ export abstract class AbstractScrollableElement extends Widget { this._options.handleMouseWheel = massagedOptions.handleMouseWheel; this._options.mouseWheelScrollSensitivity = massagedOptions.mouseWheelScrollSensitivity; this._options.fastScrollSensitivity = massagedOptions.fastScrollSensitivity; + this._options.scrollPredominantAxisOnly = massagedOptions.scrollPredominantAxisOnly; this._setListeningToMouseWheel(this._options.handleMouseWheel); if (!this._options.lazyRender) { @@ -329,6 +330,14 @@ export abstract class AbstractScrollableElement extends Widget { let deltaY = e.deltaY * this._options.mouseWheelScrollSensitivity; let deltaX = e.deltaX * this._options.mouseWheelScrollSensitivity; + if (this._options.scrollPredominantAxisOnly) { + if (Math.abs(deltaY) >= Math.abs(deltaX)) { + deltaX = 0; + } else { + deltaY = 0; + } + } + if (this._options.flipAxes) { [deltaY, deltaX] = [deltaX, deltaY]; } @@ -548,6 +557,7 @@ function resolveOptions(opts: ScrollableElementCreationOptions): ScrollableEleme scrollYToX: (typeof opts.scrollYToX !== 'undefined' ? opts.scrollYToX : false), mouseWheelScrollSensitivity: (typeof opts.mouseWheelScrollSensitivity !== 'undefined' ? opts.mouseWheelScrollSensitivity : 1), fastScrollSensitivity: (typeof opts.fastScrollSensitivity !== 'undefined' ? opts.fastScrollSensitivity : 5), + scrollPredominantAxisOnly: (typeof opts.scrollPredominantAxisOnly !== 'undefined' ? opts.scrollPredominantAxisOnly : true), mouseWheelSmoothScroll: (typeof opts.mouseWheelSmoothScroll !== 'undefined' ? opts.mouseWheelSmoothScroll : true), arrowSize: (typeof opts.arrowSize !== 'undefined' ? opts.arrowSize : 11), diff --git a/src/vs/base/browser/ui/scrollbar/scrollableElementOptions.ts b/src/vs/base/browser/ui/scrollbar/scrollableElementOptions.ts index 7073bee8cb5..10e13f6273e 100644 --- a/src/vs/base/browser/ui/scrollbar/scrollableElementOptions.ts +++ b/src/vs/base/browser/ui/scrollbar/scrollableElementOptions.ts @@ -55,6 +55,13 @@ export interface ScrollableElementCreationOptions { * Defaults to 5. */ fastScrollSensitivity?: number; + /** + * Whether the editor will only scroll along the predominant axis when scrolling both + * vertically and horizontally at the same time. + * Prevents horizontal drift when scrolling vertically on a trackpad. + * Defaults to true. + */ + scrollPredominantAxisOnly?: boolean; /** * Height for vertical arrows (top/bottom) and width for horizontal arrows (left/right). * Defaults to 11. @@ -113,6 +120,7 @@ export interface ScrollableElementChangeOptions { handleMouseWheel?: boolean; mouseWheelScrollSensitivity?: number; fastScrollSensitivity: number; + scrollPredominantAxisOnly: boolean; } export interface ScrollableElementResolvedOptions { @@ -125,6 +133,7 @@ export interface ScrollableElementResolvedOptions { alwaysConsumeMouseWheel: boolean; mouseWheelScrollSensitivity: number; fastScrollSensitivity: number; + scrollPredominantAxisOnly: boolean; mouseWheelSmoothScroll: boolean; arrowSize: number; listenOnDomNode: HTMLElement | null; diff --git a/src/vs/editor/browser/viewParts/editorScrollbar/editorScrollbar.ts b/src/vs/editor/browser/viewParts/editorScrollbar/editorScrollbar.ts index d9b8c4dfd8f..1f852dda2df 100644 --- a/src/vs/editor/browser/viewParts/editorScrollbar/editorScrollbar.ts +++ b/src/vs/editor/browser/viewParts/editorScrollbar/editorScrollbar.ts @@ -49,6 +49,7 @@ export class EditorScrollbar extends ViewPart { arrowSize: configScrollbarOpts.arrowSize, mouseWheelScrollSensitivity: configScrollbarOpts.mouseWheelScrollSensitivity, fastScrollSensitivity: configScrollbarOpts.fastScrollSensitivity, + scrollPredominantAxisOnly: configScrollbarOpts.scrollPredominantAxisOnly, }; this.scrollbar = this._register(new SmoothScrollableElement(linesContent.domNode, scrollbarOptions, this._context.viewLayout.scrollable)); @@ -129,7 +130,8 @@ export class EditorScrollbar extends ViewPart { const newOpts: ScrollableElementChangeOptions = { handleMouseWheel: editor.viewInfo.scrollbar.handleMouseWheel, mouseWheelScrollSensitivity: editor.viewInfo.scrollbar.mouseWheelScrollSensitivity, - fastScrollSensitivity: editor.viewInfo.scrollbar.fastScrollSensitivity + fastScrollSensitivity: editor.viewInfo.scrollbar.fastScrollSensitivity, + scrollPredominantAxisOnly: editor.viewInfo.scrollbar.scrollPredominantAxisOnly }; this.scrollbar.updateOptions(newOpts); } diff --git a/src/vs/editor/common/config/commonEditorConfig.ts b/src/vs/editor/common/config/commonEditorConfig.ts index 2fcc4babff2..782e53ff8a4 100644 --- a/src/vs/editor/common/config/commonEditorConfig.ts +++ b/src/vs/editor/common/config/commonEditorConfig.ts @@ -459,6 +459,11 @@ const editorConfiguration: IConfigurationNode = { 'default': EDITOR_DEFAULTS.viewInfo.scrollbar.fastScrollSensitivity, 'markdownDescription': nls.localize('fastScrollSensitivity', "Scrolling speed mulitiplier when pressing `Alt`.") }, + 'editor.scrollPredominantAxisOnly': { + 'type': 'boolean', + 'default': EDITOR_DEFAULTS.viewInfo.scrollbar.scrollPredominantAxisOnly, + 'description': nls.localize('scrollPredominantAxisOnly', "Scroll only along the predominant axis when scrolling both vertically and horizontally at the same time. Prevents horizontal drift when scrolling vertically on a trackpad.") + }, 'editor.multiCursorModifier': { 'type': 'string', 'enum': ['ctrlCmd', 'alt'], diff --git a/src/vs/editor/common/config/editorOptions.ts b/src/vs/editor/common/config/editorOptions.ts index f9df5b7912d..6b1c37d4303 100644 --- a/src/vs/editor/common/config/editorOptions.ts +++ b/src/vs/editor/common/config/editorOptions.ts @@ -485,6 +485,11 @@ export interface IEditorOptions { * Defaults to 5. */ fastScrollSensitivity?: number; + /** + * Enable that the editor scrolls only the predominant axis. Prevents horizontal drift when scrolling vertically on a trackpad. + * Defaults to true. + */ + scrollPredominantAxisOnly?: boolean; /** * The modifier to be used to add multiple cursors with the mouse. * Defaults to 'alt' @@ -898,6 +903,7 @@ export interface InternalEditorScrollbarOptions { readonly verticalSliderSize: number; readonly mouseWheelScrollSensitivity: number; readonly fastScrollSensitivity: number; + readonly scrollPredominantAxisOnly: boolean; } export interface InternalEditorMinimapOptions { @@ -1321,6 +1327,7 @@ export class InternalEditorOptions { && a.verticalSliderSize === b.verticalSliderSize && a.mouseWheelScrollSensitivity === b.mouseWheelScrollSensitivity && a.fastScrollSensitivity === b.fastScrollSensitivity + && a.scrollPredominantAxisOnly === b.scrollPredominantAxisOnly ); } @@ -1826,7 +1833,7 @@ export class EditorOptionsValidator { }; } - private static _sanitizeScrollbarOpts(opts: IEditorScrollbarOptions | undefined, defaults: InternalEditorScrollbarOptions, mouseWheelScrollSensitivity: number, fastScrollSensitivity: number): InternalEditorScrollbarOptions { + private static _sanitizeScrollbarOpts(opts: IEditorScrollbarOptions | undefined, defaults: InternalEditorScrollbarOptions, mouseWheelScrollSensitivity: number, fastScrollSensitivity: number, scrollPredominantAxisOnly: boolean): InternalEditorScrollbarOptions { if (typeof opts !== 'object') { return defaults; } @@ -1851,6 +1858,7 @@ export class EditorOptionsValidator { handleMouseWheel: _boolean(opts.handleMouseWheel, defaults.handleMouseWheel), mouseWheelScrollSensitivity: mouseWheelScrollSensitivity, fastScrollSensitivity: fastScrollSensitivity, + scrollPredominantAxisOnly: scrollPredominantAxisOnly, }; } @@ -2006,7 +2014,8 @@ export class EditorOptionsValidator { if (fastScrollSensitivity <= 0) { fastScrollSensitivity = defaults.scrollbar.fastScrollSensitivity; } - const scrollbar = this._sanitizeScrollbarOpts(opts.scrollbar, defaults.scrollbar, mouseWheelScrollSensitivity, fastScrollSensitivity); + let scrollPredominantAxisOnly = _boolean(opts.scrollPredominantAxisOnly, defaults.scrollbar.scrollPredominantAxisOnly); + const scrollbar = this._sanitizeScrollbarOpts(opts.scrollbar, defaults.scrollbar, mouseWheelScrollSensitivity, fastScrollSensitivity, scrollPredominantAxisOnly); const minimap = this._sanitizeMinimapOpts(opts.minimap, defaults.minimap); return { @@ -2636,6 +2645,7 @@ export const EDITOR_DEFAULTS: IValidatedEditorOptions = { handleMouseWheel: true, mouseWheelScrollSensitivity: 1, fastScrollSensitivity: 5, + scrollPredominantAxisOnly: true, }, minimap: { enabled: true, diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index 2f457a8d6a0..a26e2e69e5a 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -2816,6 +2816,11 @@ declare namespace monaco.editor { * Defaults to 5. */ fastScrollSensitivity?: number; + /** + * Enable that the editor scrolls only the predominant axis. Prevents horizontal drift when scrolling vertically on a trackpad. + * Defaults to true. + */ + scrollPredominantAxisOnly?: boolean; /** * The modifier to be used to add multiple cursors with the mouse. * Defaults to 'alt' @@ -3174,6 +3179,7 @@ declare namespace monaco.editor { readonly verticalSliderSize: number; readonly mouseWheelScrollSensitivity: number; readonly fastScrollSensitivity: number; + readonly scrollPredominantAxisOnly: boolean; } export interface InternalEditorMinimapOptions { From 78236850f3fd6370758df24a1075ba1cbd6ed8c6 Mon Sep 17 00:00:00 2001 From: Cailin Smith Date: Mon, 22 Jul 2019 17:16:56 +0200 Subject: [PATCH 005/801] Move indent right one when the highlighted line is a scope start/end Previously, the indent was always to the left of the current line. While this logic was easier to implement, it is inconsistent with the behavior expected. This commit changes it to select the scope, rather than the line's indent. This is not a complete fix for #49342, as this does not add support to semantic detection, however, this will work in probably 90% of the cases, and is a relatively straightforward fix. --- src/vs/editor/common/model/textModel.ts | 25 +++++ .../common/model/textModelWithTokens.test.ts | 98 +++++++++++++------ 2 files changed, 92 insertions(+), 31 deletions(-) diff --git a/src/vs/editor/common/model/textModel.ts b/src/vs/editor/common/model/textModel.ts index 3b7bb2d1365..aa7b8da805b 100644 --- a/src/vs/editor/common/model/textModel.ts +++ b/src/vs/editor/common/model/textModel.ts @@ -2408,6 +2408,31 @@ export class TextModel extends Disposable implements model.ITextModel { if (distance === 0) { // This is the initial line number + if (currentIndent >= 0 && this._computeIndentLevel(upLineNumber) > currentIndent) { + // This is a new scope, we have special handling here, since we want the + // child scope's indent to be active, not the parent scope's + startLineNumber = upLineNumber + 1; + endLineNumber = maxLineNumber; + for (let offsetDown = startLineNumber; offsetDown < maxLineNumber; offsetDown++) { + if (this._computeIndentLevel(offsetDown) === currentIndent) { + endLineNumber = offsetDown; + break; + } + } + return { startLineNumber, endLineNumber, indent: upLineIndentLevel + 1 }; + } + if (currentIndent >= 0 && this._computeIndentLevel(upLineNumber - 2) > currentIndent) { + // This is the end of a scope. Like above, but we walk backwards to find the parent scope start + endLineNumber = upLineNumber - 1; + startLineNumber = minLineNumber; + for (let offsetUp = endLineNumber; offsetUp >= minLineNumber; offsetUp--) { + if (this._computeIndentLevel(offsetUp - 1) === this._computeIndentLevel(upLineNumber - 1)) { + startLineNumber = offsetUp + 1; + break; + } + } + return { startLineNumber, endLineNumber, indent: upLineIndentLevel + 1 }; + } startLineNumber = upLineNumber; endLineNumber = downLineNumber; indent = upLineIndentLevel; diff --git a/src/vs/editor/test/common/model/textModelWithTokens.test.ts b/src/vs/editor/test/common/model/textModelWithTokens.test.ts index 74d93190439..fe47c6f9f57 100644 --- a/src/vs/editor/test/common/model/textModelWithTokens.test.ts +++ b/src/vs/editor/test/common/model/textModelWithTokens.test.ts @@ -433,37 +433,6 @@ suite('TextModel.getLineIndentGuide', () => { assert.deepEqual(actual, lines); - // Also test getActiveIndentGuide - for (let lineNumber = 1; lineNumber <= model.getLineCount(); lineNumber++) { - let startLineNumber = lineNumber; - let endLineNumber = lineNumber; - let indent = actualIndents[lineNumber - 1]; - - if (indent !== 0) { - for (let i = lineNumber - 1; i >= 1; i--) { - const currIndent = actualIndents[i - 1]; - if (currIndent >= indent) { - startLineNumber = i; - } else { - break; - } - } - for (let i = lineNumber + 1; i <= model.getLineCount(); i++) { - const currIndent = actualIndents[i - 1]; - if (currIndent >= indent) { - endLineNumber = i; - } else { - break; - } - } - } - - const expected = { startLineNumber, endLineNumber, indent }; - const actual = model.getActiveIndentGuide(lineNumber, 1, model.getLineCount()); - - assert.deepEqual(actual, expected, `line number ${lineNumber}`); - } - model.dispose(); } @@ -644,3 +613,70 @@ suite('TextModel.getLineIndentGuide', () => { model.dispose(); }); }); + +suite('TextModel.getActiveIndentGuide', () => { + function assertActiveIndent(lines: [Boolean, string][], selectedLine: number) { + let text = lines.map(l => l[1]).join('\n'); + let model = TextModel.createFromString(text); + + let actualActive = model.getActiveIndentGuide(selectedLine, 1, model.getLineCount()); + + let actual: [Boolean, string][] = []; + for (let line = 1; line <= model.getLineCount(); line++) { + actual[line - 1] = [actualActive.indent > 0 && line >= actualActive.startLineNumber && line <= actualActive.endLineNumber, model.getLineContent(line)]; + } + + assert.deepEqual(actual, lines); + + model.dispose(); + + } + + test('no active', () => { + assertActiveIndent([ + [false, 'A'], + [false, 'A'] + ], 1); + }); + + test('inside scope', () => { + assertActiveIndent([ + [false, 'A'], + [true, ' A'] + ], 2); + }); + + test('scope start', () => { + assertActiveIndent([ + [false, 'A'], + [true, ' A'], + [false, 'A'] + ], 1); + }); + + test('inside scope start and end', () => { + assertActiveIndent([ + [false, 'A'], + [true, ' A'], + [false, 'A'] + ], 2); + }); + + test('scope end', () => { + assertActiveIndent([ + [false, 'A'], + [true, ' A'], + [false, 'A'] + ], 3); + }); + + test('empty line', () => { + assertActiveIndent([ + [false, 'A'], + [true, ' A'], + [true, ''], + [true, ' A'], + [false, 'A'] + ], 3); + }); +}); \ No newline at end of file From 208b205337f8e82fcc004e9cce8b4d45dd84922d Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Fri, 6 Sep 2019 22:56:58 +0200 Subject: [PATCH 006/801] Remove unnecessary JSON schema --- src/vs/editor/common/config/commonEditorConfig.ts | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/vs/editor/common/config/commonEditorConfig.ts b/src/vs/editor/common/config/commonEditorConfig.ts index d2ee45b542a..af4f2074ea8 100644 --- a/src/vs/editor/common/config/commonEditorConfig.ts +++ b/src/vs/editor/common/config/commonEditorConfig.ts @@ -473,11 +473,6 @@ const editorConfiguration: IConfigurationNode = { default: true, description: nls.localize('ignoreTrimWhitespace', "Controls whether the diff editor shows changes in leading or trailing whitespace as diffs.") }, - 'editor.focusOnHover': { - 'type': 'boolean', - 'default': false, - 'description': nls.localize('focusOnHover', 'Controls if editors should be focused when hovered.') - }, 'diffEditor.renderIndicators': { type: 'boolean', default: true, From 6eeebddf880ccfa99d28d91647b37e9f12833782 Mon Sep 17 00:00:00 2001 From: Waldemar Kornewald Date: Mon, 16 Sep 2019 16:31:06 +0200 Subject: [PATCH 007/801] Support for revealing at definition This scrolls the target closer to the top, but leaves sufficient room for e.g. class/function comments, while showing more of the source below the target (i.e.. more of the class/function body). --- .../editor/browser/controller/coreCommands.ts | 6 ++- .../browser/viewParts/lines/viewLines.ts | 13 +++++- .../editor/browser/widget/codeEditorWidget.ts | 40 +++++++++++++++++++ .../editor/browser/widget/diffEditorWidget.ts | 20 ++++++++++ src/vs/editor/common/editorCommon.ts | 24 +++++++++++ src/vs/editor/common/view/viewEvents.ts | 3 +- src/vs/monaco.d.ts | 20 ++++++++++ src/vs/platform/editor/common/editor.ts | 7 ++++ src/vs/vscode.d.ts | 7 +++- .../workbench/api/browser/mainThreadEditor.ts | 3 ++ .../workbench/api/common/extHost.protocol.ts | 3 +- src/vs/workbench/api/common/extHostTypes.ts | 3 +- src/vs/workbench/common/editor.ts | 16 +++++++- 13 files changed, 156 insertions(+), 9 deletions(-) diff --git a/src/vs/editor/browser/controller/coreCommands.ts b/src/vs/editor/browser/controller/coreCommands.ts index 94ed9e159cb..918053db202 100644 --- a/src/vs/editor/browser/controller/coreCommands.ts +++ b/src/vs/editor/browser/controller/coreCommands.ts @@ -270,7 +270,8 @@ export namespace RevealLine_ { export const RawAtArgument = { Top: 'top', Center: 'center', - Bottom: 'bottom' + Bottom: 'bottom', + Definition: 'definition', }; } @@ -1477,6 +1478,9 @@ export namespace CoreNavigationCommands { case RevealLine_.RawAtArgument.Bottom: revealAt = VerticalRevealType.Bottom; break; + case RevealLine_.RawAtArgument.Definition: + revealAt = VerticalRevealType.Definition; + break; default: break; } diff --git a/src/vs/editor/browser/viewParts/lines/viewLines.ts b/src/vs/editor/browser/viewParts/lines/viewLines.ts index eb524a41b69..d9bd8cb692c 100644 --- a/src/vs/editor/browser/viewParts/lines/viewLines.ts +++ b/src/vs/editor/browser/viewParts/lines/viewLines.ts @@ -616,14 +616,23 @@ export class ViewLines extends ViewPart implements IVisibleLinesHost, let newScrollTop: number; - if (verticalType === viewEvents.VerticalRevealType.Center || verticalType === viewEvents.VerticalRevealType.CenterIfOutsideViewport) { + if (verticalType === viewEvents.VerticalRevealType.Center || verticalType === viewEvents.VerticalRevealType.CenterIfOutsideViewport + || verticalType === viewEvents.VerticalRevealType.Definition + ) { if (verticalType === viewEvents.VerticalRevealType.CenterIfOutsideViewport && viewportStartY <= boxStartY && boxEndY <= viewportEndY) { // Box is already in the viewport... do nothing newScrollTop = viewportStartY; } else { // Box is outside the viewport... center it const boxMiddleY = (boxStartY + boxEndY) / 2; - newScrollTop = Math.max(0, boxMiddleY - viewportHeight / 2); + let delta = viewportHeight * 0.5; + if (verticalType === viewEvents.VerticalRevealType.Definition) { + // Definition scrolls to 20% from the top of the viewport, but ensures a minimum amount of space from the top + // and never scrolls beyond the center. + const minSpace = 100; + delta = Math.min(delta, Math.max(minSpace, viewportHeight * 0.2)); + } + newScrollTop = Math.max(0, boxMiddleY - delta); } } else { newScrollTop = this._computeMinimumScrolling(viewportStartY, viewportEndY, boxStartY, boxEndY, verticalType === viewEvents.VerticalRevealType.Top, verticalType === viewEvents.VerticalRevealType.Bottom); diff --git a/src/vs/editor/browser/widget/codeEditorWidget.ts b/src/vs/editor/browser/widget/codeEditorWidget.ts index 6185ac85e12..3da1ddb4aa2 100644 --- a/src/vs/editor/browser/widget/codeEditorWidget.ts +++ b/src/vs/editor/browser/widget/codeEditorWidget.ts @@ -547,6 +547,10 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE this._revealLine(lineNumber, VerticalRevealType.CenterIfOutsideViewport, scrollType); } + public revealLineAtDefinition(lineNumber: number, scrollType: editorCommon.ScrollType = editorCommon.ScrollType.Smooth): void { + this._revealLine(lineNumber, VerticalRevealType.Definition, scrollType); + } + private _revealLine(lineNumber: number, revealType: VerticalRevealType, scrollType: editorCommon.ScrollType): void { if (typeof lineNumber !== 'number') { throw new Error('Invalid arguments'); @@ -587,6 +591,15 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE ); } + public revealPositionAtDefinition(position: IPosition, scrollType: editorCommon.ScrollType = editorCommon.ScrollType.Smooth): void { + this._revealPosition( + position, + VerticalRevealType.Definition, + true, + scrollType + ); + } + private _revealPosition(position: IPosition, verticalType: VerticalRevealType, revealHorizontal: boolean, scrollType: editorCommon.ScrollType): void { if (!Position.isIPosition(position)) { throw new Error('Invalid arguments'); @@ -675,6 +688,15 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE ); } + public revealLinesAtDefinition(startLineNumber: number, endLineNumber: number, scrollType: editorCommon.ScrollType = editorCommon.ScrollType.Smooth): void { + this._revealLines( + startLineNumber, + endLineNumber, + VerticalRevealType.Definition, + scrollType + ); + } + private _revealLines(startLineNumber: number, endLineNumber: number, verticalType: VerticalRevealType, scrollType: editorCommon.ScrollType): void { if (typeof startLineNumber !== 'number' || typeof endLineNumber !== 'number') { throw new Error('Invalid arguments'); @@ -715,6 +737,15 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE ); } + public revealRangeAtDefinition(range: IRange, scrollType: editorCommon.ScrollType = editorCommon.ScrollType.Smooth): void { + this._revealRange( + range, + VerticalRevealType.Definition, + true, + scrollType + ); + } + public revealRangeAtTop(range: IRange, scrollType: editorCommon.ScrollType = editorCommon.ScrollType.Smooth): void { this._revealRange( range, @@ -724,6 +755,15 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE ); } + public revealRangeNearTop(range: IRange, scrollType: editorCommon.ScrollType = editorCommon.ScrollType.Smooth): void { + this._revealRange( + range, + VerticalRevealType.Definition, + true, + scrollType + ); + } + private _revealRange(range: IRange, verticalType: VerticalRevealType, revealHorizontal: boolean, scrollType: editorCommon.ScrollType): void { if (!Range.isIRange(range)) { throw new Error('Invalid arguments'); diff --git a/src/vs/editor/browser/widget/diffEditorWidget.ts b/src/vs/editor/browser/widget/diffEditorWidget.ts index 3762f7e0b13..fa18e527848 100644 --- a/src/vs/editor/browser/widget/diffEditorWidget.ts +++ b/src/vs/editor/browser/widget/diffEditorWidget.ts @@ -695,6 +695,10 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE this.modifiedEditor.revealLineInCenterIfOutsideViewport(lineNumber, scrollType); } + public revealLineAtDefinition(lineNumber: number, scrollType: editorCommon.ScrollType = editorCommon.ScrollType.Smooth): void { + this.modifiedEditor.revealLineAtDefinition(lineNumber, scrollType); + } + public revealPosition(position: IPosition, scrollType: editorCommon.ScrollType = editorCommon.ScrollType.Smooth): void { this.modifiedEditor.revealPosition(position, scrollType); } @@ -707,6 +711,10 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE this.modifiedEditor.revealPositionInCenterIfOutsideViewport(position, scrollType); } + public revealPositionAtDefinition(position: IPosition, scrollType: editorCommon.ScrollType = editorCommon.ScrollType.Smooth): void { + this.modifiedEditor.revealPositionAtDefinition(position, scrollType); + } + public getSelection(): Selection | null { return this.modifiedEditor.getSelection(); } @@ -739,6 +747,10 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE this.modifiedEditor.revealLinesInCenterIfOutsideViewport(startLineNumber, endLineNumber, scrollType); } + public revealLinesAtDefinition(startLineNumber: number, endLineNumber: number, scrollType: editorCommon.ScrollType = editorCommon.ScrollType.Smooth): void { + this.modifiedEditor.revealLinesAtDefinition(startLineNumber, endLineNumber, scrollType); + } + public revealRange(range: IRange, scrollType: editorCommon.ScrollType = editorCommon.ScrollType.Smooth, revealVerticalInCenter: boolean = false, revealHorizontal: boolean = true): void { this.modifiedEditor.revealRange(range, scrollType, revealVerticalInCenter, revealHorizontal); } @@ -751,10 +763,18 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE this.modifiedEditor.revealRangeInCenterIfOutsideViewport(range, scrollType); } + public revealRangeAtDefinition(range: IRange, scrollType: editorCommon.ScrollType = editorCommon.ScrollType.Smooth): void { + this.modifiedEditor.revealRangeAtDefinition(range, scrollType); + } + public revealRangeAtTop(range: IRange, scrollType: editorCommon.ScrollType = editorCommon.ScrollType.Smooth): void { this.modifiedEditor.revealRangeAtTop(range, scrollType); } + public revealRangeNearTop(range: IRange, scrollType: editorCommon.ScrollType = editorCommon.ScrollType.Smooth): void { + this.modifiedEditor.revealRangeNearTop(range, scrollType); + } + public getSupportedActions(): editorCommon.IEditorAction[] { return this.modifiedEditor.getSupportedActions(); } diff --git a/src/vs/editor/common/editorCommon.ts b/src/vs/editor/common/editorCommon.ts index e11482fa25b..91f5d061c29 100644 --- a/src/vs/editor/common/editorCommon.ts +++ b/src/vs/editor/common/editorCommon.ts @@ -339,6 +339,12 @@ export interface IEditor { */ revealLineInCenterIfOutsideViewport(lineNumber: number, scrollType?: ScrollType): void; + /** + * Scroll vertically as necessary and reveal a line close to the top of the viewport, + * optimized for viewing a code definition. + */ + revealLineAtDefinition(lineNumber: number, scrollType?: ScrollType): void; + /** * Scroll vertically or horizontally as necessary and reveal a position. */ @@ -354,6 +360,12 @@ export interface IEditor { */ revealPositionInCenterIfOutsideViewport(position: IPosition, scrollType?: ScrollType): void; + /** + * Scroll vertically or horizontally as necessary and reveal a position close to the top of the viewport, + * optimized for viewing a code definition. + */ + revealPositionAtDefinition(position: IPosition, scrollType?: ScrollType): void; + /** * Returns the primary selection of the editor. */ @@ -406,6 +418,12 @@ export interface IEditor { */ revealLinesInCenterIfOutsideViewport(lineNumber: number, endLineNumber: number, scrollType?: ScrollType): void; + /** + * Scroll vertically as necessary and reveal lines close to the top of the viewport, + * optimized for viewing a code definition. + */ + revealLinesAtDefinition(lineNumber: number, endLineNumber: number, scrollType?: ScrollType): void; + /** * Scroll vertically or horizontally as necessary and reveal a range. */ @@ -426,6 +444,12 @@ export interface IEditor { */ revealRangeInCenterIfOutsideViewport(range: IRange, scrollType?: ScrollType): void; + /** + * Scroll vertically or horizontally as necessary and reveal a range close to the top of the viewport, + * optimized for viewing a code definition. + */ + revealRangeAtDefinition(range: IRange, scrollType?: ScrollType): void; + /** * Directly trigger a handler or an editor action. * @param source The source of the call. diff --git a/src/vs/editor/common/view/viewEvents.ts b/src/vs/editor/common/view/viewEvents.ts index 55c455808ad..8d832f5b596 100644 --- a/src/vs/editor/common/view/viewEvents.ts +++ b/src/vs/editor/common/view/viewEvents.ts @@ -159,7 +159,8 @@ export const enum VerticalRevealType { Center = 1, CenterIfOutsideViewport = 2, Top = 3, - Bottom = 4 + Bottom = 4, + Definition = 5, } export class ViewRevealRangeRequestEvent { diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index 52c82fe6d55..3297808e5cd 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -2108,6 +2108,11 @@ declare namespace monaco.editor { * Scroll vertically as necessary and reveal a line centered vertically only if it lies outside the viewport. */ revealLineInCenterIfOutsideViewport(lineNumber: number, scrollType?: ScrollType): void; + /** + * Scroll vertically as necessary and reveal a line close to the top of the viewport, + * optimized for viewing a code definition. + */ + revealLineAtDefinition(lineNumber: number, scrollType?: ScrollType): void; /** * Scroll vertically or horizontally as necessary and reveal a position. */ @@ -2120,6 +2125,11 @@ declare namespace monaco.editor { * Scroll vertically or horizontally as necessary and reveal a position centered vertically only if it lies outside the viewport. */ revealPositionInCenterIfOutsideViewport(position: IPosition, scrollType?: ScrollType): void; + /** + * Scroll vertically or horizontally as necessary and reveal a position close to the top of the viewport, + * optimized for viewing a code definition. + */ + revealPositionAtDefinition(position: IPosition, scrollType?: ScrollType): void; /** * Returns the primary selection of the editor. */ @@ -2165,6 +2175,11 @@ declare namespace monaco.editor { * Scroll vertically as necessary and reveal lines centered vertically only if it lies outside the viewport. */ revealLinesInCenterIfOutsideViewport(lineNumber: number, endLineNumber: number, scrollType?: ScrollType): void; + /** + * Scroll vertically as necessary and reveal lines close to the top of the viewport, + * optimized for viewing a code definition. + */ + revealLinesAtDefinition(lineNumber: number, endLineNumber: number, scrollType?: ScrollType): void; /** * Scroll vertically or horizontally as necessary and reveal a range. */ @@ -2181,6 +2196,11 @@ declare namespace monaco.editor { * Scroll vertically or horizontally as necessary and reveal a range centered vertically only if it lies outside the viewport. */ revealRangeInCenterIfOutsideViewport(range: IRange, scrollType?: ScrollType): void; + /** + * Scroll vertically or horizontally as necessary and reveal a range close to the top of the viewport, + * optimized for viewing a code definition. + */ + revealRangeAtDefinition(range: IRange, scrollType?: ScrollType): void; /** * Directly trigger a handler or an editor action. * @param source The source of the call. diff --git a/src/vs/platform/editor/common/editor.ts b/src/vs/platform/editor/common/editor.ts index 09d22bda225..6b9889ee5b8 100644 --- a/src/vs/platform/editor/common/editor.ts +++ b/src/vs/platform/editor/common/editor.ts @@ -202,6 +202,13 @@ export interface ITextEditorOptions extends IEditorOptions { /** * Option to scroll vertically or horizontally as necessary and reveal a range centered vertically only if it lies outside the viewport. + * This can't be used in combination with revealAtDefinition. */ revealInCenterIfOutsideViewport?: boolean; + + /** + * Option to scroll vertically or horizontally as necessary and reveal a range close to the top of the viewport, + * optimized for viewing a code definition. + */ + revealAtDefinition?: boolean; } diff --git a/src/vs/vscode.d.ts b/src/vs/vscode.d.ts index 2fe649dab60..ba960d5db4d 100644 --- a/src/vs/vscode.d.ts +++ b/src/vs/vscode.d.ts @@ -710,7 +710,12 @@ declare module 'vscode' { /** * The range will always be revealed at the top of the viewport. */ - AtTop = 3 + AtTop = 3, + /** + * The range will always be revealed close to the top of the viewport, + * optimized for viewing a code definition. + */ + AtDefinition = 4, } /** diff --git a/src/vs/workbench/api/browser/mainThreadEditor.ts b/src/vs/workbench/api/browser/mainThreadEditor.ts index 520801babe6..877d76b1a4b 100644 --- a/src/vs/workbench/api/browser/mainThreadEditor.ts +++ b/src/vs/workbench/api/browser/mainThreadEditor.ts @@ -415,6 +415,9 @@ export class MainThreadTextEditor { case TextEditorRevealType.AtTop: this._codeEditor.revealRangeAtTop(range, editorCommon.ScrollType.Smooth); break; + case TextEditorRevealType.AtDefinition: + this._codeEditor.revealRangeAtDefinition(range, editorCommon.ScrollType.Smooth); + break; default: console.warn(`Unknown revealType: ${revealType}`); break; diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index f8cd4bab411..0cbe408fbac 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -203,7 +203,8 @@ export enum TextEditorRevealType { Default = 0, InCenter = 1, InCenterIfOutsideViewport = 2, - AtTop = 3 + AtTop = 3, + AtDefinition = 4, } export interface IUndoStopOptions { diff --git a/src/vs/workbench/api/common/extHostTypes.ts b/src/vs/workbench/api/common/extHostTypes.ts index 57e377721b8..7a89a770faa 100644 --- a/src/vs/workbench/api/common/extHostTypes.ts +++ b/src/vs/workbench/api/common/extHostTypes.ts @@ -1420,7 +1420,8 @@ export enum TextEditorRevealType { Default = 0, InCenter = 1, InCenterIfOutsideViewport = 2, - AtTop = 3 + AtTop = 3, + AtDefinition = 4, } export enum TextEditorSelectionChangeKind { diff --git a/src/vs/workbench/common/editor.ts b/src/vs/workbench/common/editor.ts index cffa415f9c8..c31285e8c28 100644 --- a/src/vs/workbench/common/editor.ts +++ b/src/vs/workbench/common/editor.ts @@ -837,6 +837,7 @@ export class TextEditorOptions extends EditorOptions { private endColumn: number; private revealInCenterIfOutsideViewport: boolean; + private revealAtDefinition: boolean; private editorViewState: IEditorViewState | null; static from(input?: IBaseResourceInput): TextEditorOptions | undefined { @@ -872,6 +873,13 @@ export class TextEditorOptions extends EditorOptions { this.editorViewState = options.viewState as IEditorViewState; } + if (typeof options.revealAtDefinition === 'boolean') { + this.revealAtDefinition = options.revealAtDefinition; + if (options.revealInCenterIfOutsideViewport) { + throw new Error('revealInCenterIfOutsideViewport and revealAtDefinition cannot both be true'); + } + } + if (typeof options.revealInCenterIfOutsideViewport === 'boolean') { this.revealInCenterIfOutsideViewport = options.revealInCenterIfOutsideViewport; } @@ -942,7 +950,9 @@ export class TextEditorOptions extends EditorOptions { endColumn: this.endColumn }; editor.setSelection(range); - if (this.revealInCenterIfOutsideViewport) { + if (this.revealAtDefinition) { + editor.revealRangeAtDefinition(range, scrollType); + } else if (this.revealInCenterIfOutsideViewport) { editor.revealRangeInCenterIfOutsideViewport(range, scrollType); } else { editor.revealRangeInCenter(range, scrollType); @@ -956,7 +966,9 @@ export class TextEditorOptions extends EditorOptions { column: this.startColumn }; editor.setPosition(pos); - if (this.revealInCenterIfOutsideViewport) { + if (this.revealAtDefinition) { + editor.revealPositionAtDefinition(pos, scrollType); + } else if (this.revealInCenterIfOutsideViewport) { editor.revealPositionInCenterIfOutsideViewport(pos, scrollType); } else { editor.revealPositionInCenter(pos, scrollType); From 00ef6f11906f51e92d53f6645989fb1853f0681a Mon Sep 17 00:00:00 2001 From: Waldemar Kornewald Date: Mon, 16 Sep 2019 16:36:13 +0200 Subject: [PATCH 008/801] Outline: reveal at definition instead of center --- src/vs/workbench/contrib/outline/browser/outlinePanel.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/outline/browser/outlinePanel.ts b/src/vs/workbench/contrib/outline/browser/outlinePanel.ts index 55ec4cb5e4b..898cd59e462 100644 --- a/src/vs/workbench/contrib/outline/browser/outlinePanel.ts +++ b/src/vs/workbench/contrib/outline/browser/outlinePanel.ts @@ -614,7 +614,7 @@ export class OutlinePanel extends ViewletPanel { options: { preserveFocus: !focus, selection: Range.collapseToStart(element.symbol.selectionRange), - revealInCenterIfOutsideViewport: true + revealAtDefinition: true, } } as IResourceInput, aside ? SIDE_GROUP : ACTIVE_GROUP); } From 7096c1fbebd4c10860c957ac20c4b209cf805bbb Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Fri, 29 Nov 2019 08:56:01 +0100 Subject: [PATCH 009/801] notarize app --- .../darwin/product-build-darwin.yml | 27 +++++++++++++++++-- build/azure-pipelines/product-build.yml | 1 + 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/build/azure-pipelines/darwin/product-build-darwin.yml b/build/azure-pipelines/darwin/product-build-darwin.yml index 273a728927f..b59933f5afb 100644 --- a/build/azure-pipelines/darwin/product-build-darwin.yml +++ b/build/azure-pipelines/darwin/product-build-darwin.yml @@ -141,13 +141,36 @@ steps: { "keyCode": "CP-401337-Apple", "operationSetCode": "MacAppDeveloperSign", - "parameters": [ ], + "parameters": { + "Hardening": "--options=runtime" + }, + "toolName": "sign", + "toolVersion": "1.0" + } + ] + SessionTimeout: 60 + displayName: Codesign + +- task: SFP.build-tasks.custom-build-task-1.EsrpCodeSigning@1 + inputs: + ConnectedServiceName: 'ESRP CodeSign' + FolderPath: '$(agent.builddirectory)' + Pattern: 'VSCode-darwin.zip' + signConfigType: inlineSignParams + inlineOperation: | + [ + { + "keyCode": "CP-401337-Apple", + "operationSetCode": " MacAppNotarize", + "parameters": { + "BundleId": "com.microsoft.VSCodeInsiders" + }, "toolName": "sign", "toolVersion": "1.0" } ] SessionTimeout: 120 - displayName: Codesign + displayName: Notarization - script: | set -e diff --git a/build/azure-pipelines/product-build.yml b/build/azure-pipelines/product-build.yml index ecf47fa1cdd..aefcd66f2e8 100644 --- a/build/azure-pipelines/product-build.yml +++ b/build/azure-pipelines/product-build.yml @@ -101,6 +101,7 @@ jobs: - job: macOS condition: and(succeeded(), eq(variables['VSCODE_COMPILE_ONLY'], 'false'), eq(variables['VSCODE_BUILD_MACOS'], 'true')) + timeoutInMinutes: 180 pool: vmImage: macOS 10.13 dependsOn: From f47b5aaaf2d6af887c41b63462159576b3ceb7c3 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Fri, 29 Nov 2019 09:27:54 +0100 Subject: [PATCH 010/801] build: hardcode cache --- build/azure-pipelines/darwin/product-build-darwin.yml | 2 +- build/azure-pipelines/product-compile.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/build/azure-pipelines/darwin/product-build-darwin.yml b/build/azure-pipelines/darwin/product-build-darwin.yml index b59933f5afb..d2a536e7f19 100644 --- a/build/azure-pipelines/darwin/product-build-darwin.yml +++ b/build/azure-pipelines/darwin/product-build-darwin.yml @@ -1,7 +1,7 @@ steps: - script: | mkdir -p .build - echo -n $BUILD_SOURCEVERSION > .build/commit + echo -n 7096c1fbebd4c10860c957ac20c4b209cf805bbb > .build/commit echo -n $VSCODE_QUALITY > .build/quality displayName: Prepare cache flag diff --git a/build/azure-pipelines/product-compile.yml b/build/azure-pipelines/product-compile.yml index 8029f8a5661..080cf48e207 100644 --- a/build/azure-pipelines/product-compile.yml +++ b/build/azure-pipelines/product-compile.yml @@ -1,7 +1,7 @@ steps: - script: | mkdir -p .build - echo -n $BUILD_SOURCEVERSION > .build/commit + echo -n 7096c1fbebd4c10860c957ac20c4b209cf805bbb > .build/commit echo -n $VSCODE_QUALITY > .build/quality displayName: Prepare cache flag From 14be98cb2be98ada6027429d6e46e1fdb5b74f25 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Fri, 29 Nov 2019 09:43:01 +0100 Subject: [PATCH 011/801] fix build parameters --- .../darwin/product-build-darwin.yml | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/build/azure-pipelines/darwin/product-build-darwin.yml b/build/azure-pipelines/darwin/product-build-darwin.yml index d2a536e7f19..8f1288459d0 100644 --- a/build/azure-pipelines/darwin/product-build-darwin.yml +++ b/build/azure-pipelines/darwin/product-build-darwin.yml @@ -141,9 +141,12 @@ steps: { "keyCode": "CP-401337-Apple", "operationSetCode": "MacAppDeveloperSign", - "parameters": { - "Hardening": "--options=runtime" - }, + "parameters": [ + { + "parameterName": "Hardening", + "parameterValue": "--options=runtime" + } + ], "toolName": "sign", "toolVersion": "1.0" } @@ -162,9 +165,12 @@ steps: { "keyCode": "CP-401337-Apple", "operationSetCode": " MacAppNotarize", - "parameters": { - "BundleId": "com.microsoft.VSCodeInsiders" - }, + "parameters": [ + { + "parameterName": "BundleId", + "parameterValue": "com.microsoft.VSCodeInsiders" + } + ], "toolName": "sign", "toolVersion": "1.0" } From 5c253064560cfbc9e44a87a22b7d9e6d1057fac8 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Fri, 29 Nov 2019 10:00:26 +0100 Subject: [PATCH 012/801] bad spacing --- build/azure-pipelines/darwin/product-build-darwin.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/azure-pipelines/darwin/product-build-darwin.yml b/build/azure-pipelines/darwin/product-build-darwin.yml index 8f1288459d0..33bf4c3ec99 100644 --- a/build/azure-pipelines/darwin/product-build-darwin.yml +++ b/build/azure-pipelines/darwin/product-build-darwin.yml @@ -164,7 +164,7 @@ steps: [ { "keyCode": "CP-401337-Apple", - "operationSetCode": " MacAppNotarize", + "operationSetCode": "MacAppNotarize", "parameters": [ { "parameterName": "BundleId", From 7cadd81deecbba7c6c1b885b8c69710a708b5182 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Fri, 29 Nov 2019 11:46:57 +0100 Subject: [PATCH 013/801] Revert "build: hardcode cache" This reverts commit f47b5aaaf2d6af887c41b63462159576b3ceb7c3. --- build/azure-pipelines/darwin/product-build-darwin.yml | 2 +- build/azure-pipelines/product-compile.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/build/azure-pipelines/darwin/product-build-darwin.yml b/build/azure-pipelines/darwin/product-build-darwin.yml index 33bf4c3ec99..c78b5d624c9 100644 --- a/build/azure-pipelines/darwin/product-build-darwin.yml +++ b/build/azure-pipelines/darwin/product-build-darwin.yml @@ -1,7 +1,7 @@ steps: - script: | mkdir -p .build - echo -n 7096c1fbebd4c10860c957ac20c4b209cf805bbb > .build/commit + echo -n $BUILD_SOURCEVERSION > .build/commit echo -n $VSCODE_QUALITY > .build/quality displayName: Prepare cache flag diff --git a/build/azure-pipelines/product-compile.yml b/build/azure-pipelines/product-compile.yml index 080cf48e207..8029f8a5661 100644 --- a/build/azure-pipelines/product-compile.yml +++ b/build/azure-pipelines/product-compile.yml @@ -1,7 +1,7 @@ steps: - script: | mkdir -p .build - echo -n 7096c1fbebd4c10860c957ac20c4b209cf805bbb > .build/commit + echo -n $BUILD_SOURCEVERSION > .build/commit echo -n $VSCODE_QUALITY > .build/quality displayName: Prepare cache flag From 1b21dc39bcb6b728400fc3a9a8d01339eb68f3d7 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Mon, 2 Dec 2019 10:19:16 +0100 Subject: [PATCH 014/801] comment out notarization --- .../darwin/product-build-darwin.yml | 46 +++++++++---------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/build/azure-pipelines/darwin/product-build-darwin.yml b/build/azure-pipelines/darwin/product-build-darwin.yml index c78b5d624c9..08554ec9412 100644 --- a/build/azure-pipelines/darwin/product-build-darwin.yml +++ b/build/azure-pipelines/darwin/product-build-darwin.yml @@ -154,29 +154,29 @@ steps: SessionTimeout: 60 displayName: Codesign -- task: SFP.build-tasks.custom-build-task-1.EsrpCodeSigning@1 - inputs: - ConnectedServiceName: 'ESRP CodeSign' - FolderPath: '$(agent.builddirectory)' - Pattern: 'VSCode-darwin.zip' - signConfigType: inlineSignParams - inlineOperation: | - [ - { - "keyCode": "CP-401337-Apple", - "operationSetCode": "MacAppNotarize", - "parameters": [ - { - "parameterName": "BundleId", - "parameterValue": "com.microsoft.VSCodeInsiders" - } - ], - "toolName": "sign", - "toolVersion": "1.0" - } - ] - SessionTimeout: 120 - displayName: Notarization +# - task: SFP.build-tasks.custom-build-task-1.EsrpCodeSigning@1 +# inputs: +# ConnectedServiceName: 'ESRP CodeSign' +# FolderPath: '$(agent.builddirectory)' +# Pattern: 'VSCode-darwin.zip' +# signConfigType: inlineSignParams +# inlineOperation: | +# [ +# { +# "keyCode": "CP-401337-Apple", +# "operationSetCode": "MacAppNotarize", +# "parameters": [ +# { +# "parameterName": "BundleId", +# "parameterValue": "com.microsoft.VSCodeInsiders" +# } +# ], +# "toolName": "sign", +# "toolVersion": "1.0" +# } +# ] +# SessionTimeout: 120 +# displayName: Notarization - script: | set -e From 618dc0d93831648186a87df1fee75569df85490b Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Mon, 2 Dec 2019 10:55:32 +0100 Subject: [PATCH 015/801] Revert "comment out notarization" This reverts commit 1b21dc39bcb6b728400fc3a9a8d01339eb68f3d7. --- .../darwin/product-build-darwin.yml | 46 +++++++++---------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/build/azure-pipelines/darwin/product-build-darwin.yml b/build/azure-pipelines/darwin/product-build-darwin.yml index 08554ec9412..c78b5d624c9 100644 --- a/build/azure-pipelines/darwin/product-build-darwin.yml +++ b/build/azure-pipelines/darwin/product-build-darwin.yml @@ -154,29 +154,29 @@ steps: SessionTimeout: 60 displayName: Codesign -# - task: SFP.build-tasks.custom-build-task-1.EsrpCodeSigning@1 -# inputs: -# ConnectedServiceName: 'ESRP CodeSign' -# FolderPath: '$(agent.builddirectory)' -# Pattern: 'VSCode-darwin.zip' -# signConfigType: inlineSignParams -# inlineOperation: | -# [ -# { -# "keyCode": "CP-401337-Apple", -# "operationSetCode": "MacAppNotarize", -# "parameters": [ -# { -# "parameterName": "BundleId", -# "parameterValue": "com.microsoft.VSCodeInsiders" -# } -# ], -# "toolName": "sign", -# "toolVersion": "1.0" -# } -# ] -# SessionTimeout: 120 -# displayName: Notarization +- task: SFP.build-tasks.custom-build-task-1.EsrpCodeSigning@1 + inputs: + ConnectedServiceName: 'ESRP CodeSign' + FolderPath: '$(agent.builddirectory)' + Pattern: 'VSCode-darwin.zip' + signConfigType: inlineSignParams + inlineOperation: | + [ + { + "keyCode": "CP-401337-Apple", + "operationSetCode": "MacAppNotarize", + "parameters": [ + { + "parameterName": "BundleId", + "parameterValue": "com.microsoft.VSCodeInsiders" + } + ], + "toolName": "sign", + "toolVersion": "1.0" + } + ] + SessionTimeout: 120 + displayName: Notarization - script: | set -e From 6033b3bc105bafbf4b489e95226797228970e31e Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Mon, 2 Dec 2019 19:30:34 +0100 Subject: [PATCH 016/801] publish middle step build artifacts --- .../darwin/product-build-darwin.yml | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/build/azure-pipelines/darwin/product-build-darwin.yml b/build/azure-pipelines/darwin/product-build-darwin.yml index c78b5d624c9..89022cef03a 100644 --- a/build/azure-pipelines/darwin/product-build-darwin.yml +++ b/build/azure-pipelines/darwin/product-build-darwin.yml @@ -130,6 +130,12 @@ steps: pushd ../VSCode-darwin && zip -r -X -y ../VSCode-darwin.zip * && popd displayName: Archive build +- task: PublishPipelineArtifact@0 + displayName: 'Publish Pipeline Artifact' + inputs: + artifactName: darwin-unsigned + targetPath: ../VSCode-darwin.zip + - task: SFP.build-tasks.custom-build-task-1.EsrpCodeSigning@1 inputs: ConnectedServiceName: 'ESRP CodeSign' @@ -154,6 +160,12 @@ steps: SessionTimeout: 60 displayName: Codesign +- task: PublishPipelineArtifact@0 + displayName: 'Publish Pipeline Artifact' + inputs: + artifactName: darwin-signed + targetPath: ../VSCode-darwin.zip + - task: SFP.build-tasks.custom-build-task-1.EsrpCodeSigning@1 inputs: ConnectedServiceName: 'ESRP CodeSign' @@ -178,6 +190,12 @@ steps: SessionTimeout: 120 displayName: Notarization +- task: PublishPipelineArtifact@0 + displayName: 'Publish Pipeline Artifact' + inputs: + artifactName: darwin-notarized + targetPath: ../VSCode-darwin.zip + - script: | set -e VSCODE_MIXIN_PASSWORD="$(github-distro-mixin-password)" \ From a4f3af066f5754f63e1d1be6f6a5f7da007b8196 Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Mon, 2 Dec 2019 22:18:31 +0100 Subject: [PATCH 017/801] move cleanup code --- build/azure-pipelines/darwin/product-build-darwin.yml | 4 ++++ build/azure-pipelines/darwin/publish.sh | 3 --- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/build/azure-pipelines/darwin/product-build-darwin.yml b/build/azure-pipelines/darwin/product-build-darwin.yml index 89022cef03a..17b9a05317b 100644 --- a/build/azure-pipelines/darwin/product-build-darwin.yml +++ b/build/azure-pipelines/darwin/product-build-darwin.yml @@ -160,6 +160,10 @@ steps: SessionTimeout: 60 displayName: Codesign +- script: | + zip -d ../VSCode-darwin.zip "*.pkg" + displayName: Clean Archive + - task: PublishPipelineArtifact@0 displayName: 'Publish Pipeline Artifact' inputs: diff --git a/build/azure-pipelines/darwin/publish.sh b/build/azure-pipelines/darwin/publish.sh index a8067a5eefb..58f110c5df5 100755 --- a/build/azure-pipelines/darwin/publish.sh +++ b/build/azure-pipelines/darwin/publish.sh @@ -1,9 +1,6 @@ #!/usr/bin/env bash set -e -# remove pkg from archive -zip -d ../VSCode-darwin.zip "*.pkg" - # publish the build node build/azure-pipelines/common/createAsset.js \ darwin \ From e642cad1a8b1ddd5ee8a7db91fd4b37f7fb92708 Mon Sep 17 00:00:00 2001 From: zhengjiaqi01 Date: Wed, 25 Dec 2019 11:02:11 +0800 Subject: [PATCH 018/801] editor action run support params --- src/vs/editor/standalone/browser/standaloneCodeEditor.ts | 8 ++++---- src/vs/monaco.d.ts | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/vs/editor/standalone/browser/standaloneCodeEditor.ts b/src/vs/editor/standalone/browser/standaloneCodeEditor.ts index 86ce9f07ace..ecd84dda8b8 100644 --- a/src/vs/editor/standalone/browser/standaloneCodeEditor.ts +++ b/src/vs/editor/standalone/browser/standaloneCodeEditor.ts @@ -23,7 +23,7 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur import { ContextKeyExpr, IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IContextViewService, IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { ContextViewService } from 'vs/platform/contextview/browser/contextViewService'; -import { IInstantiationService, optional } from 'vs/platform/instantiation/common/instantiation'; +import { IInstantiationService, optional, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { IThemeService } from 'vs/platform/theme/common/themeService'; @@ -74,7 +74,7 @@ export interface IActionDescriptor { * Method that will be executed when the action is triggered. * @param editor The editor instance is passed in as a convenience */ - run(editor: ICodeEditor): void | Promise; + run(editor: ICodeEditor, param?: any): void | Promise; } /** @@ -226,8 +226,8 @@ export class StandaloneCodeEditor extends CodeEditorWidget implements IStandalon ); const contextMenuGroupId = _descriptor.contextMenuGroupId || null; const contextMenuOrder = _descriptor.contextMenuOrder || 0; - const run = (): Promise => { - return Promise.resolve(_descriptor.run(this)); + const run = (accessor?: ServicesAccessor, ...args: any[]): Promise => { + return Promise.resolve(_descriptor.run(this, ...args)); }; diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index 27cece82f20..02b818633f0 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -1045,7 +1045,7 @@ declare namespace monaco.editor { * Method that will be executed when the action is triggered. * @param editor The editor instance is passed in as a convenience */ - run(editor: ICodeEditor): void | Promise; + run(editor: ICodeEditor, param?: any): void | Promise; } /** From 5e1d23320d0cd269631561c372e3ca815e42a815 Mon Sep 17 00:00:00 2001 From: Dan Pock Date: Fri, 10 Jan 2020 17:23:05 -0500 Subject: [PATCH 019/801] Allow for rulers to be individually colored This change set: adds an interface for Ruler Color Options, adds a union type of that interface and number, adjust the rulers type hint to use said union type, adds a mechanism to apply boxshadow to fastDomNode, and updates the way rulers are rendered to support individual colors. I've also ensured that descriptions are provided for settings.json. So now, all nodes will provide explanations. --- src/vs/base/browser/fastDomNode.ts | 10 ++++ .../editor/browser/viewParts/rulers/rulers.ts | 16 ++++-- src/vs/editor/common/config/editorOptions.ts | 51 +++++++++++++++---- src/vs/monaco.d.ts | 9 +++- 4 files changed, 72 insertions(+), 14 deletions(-) diff --git a/src/vs/base/browser/fastDomNode.ts b/src/vs/base/browser/fastDomNode.ts index 5c688bd3496..7d4711792d3 100644 --- a/src/vs/base/browser/fastDomNode.ts +++ b/src/vs/base/browser/fastDomNode.ts @@ -27,6 +27,7 @@ export class FastDomNode { private _visibility: string; private _layerHint: boolean; private _contain: 'none' | 'strict' | 'content' | 'size' | 'layout' | 'style' | 'paint'; + private _boxShadow: string; constructor(domNode: T) { this.domNode = domNode; @@ -49,6 +50,7 @@ export class FastDomNode { this._visibility = ''; this._layerHint = false; this._contain = 'none'; + this._boxShadow = ''; } public setMaxWidth(maxWidth: number): void { @@ -208,6 +210,14 @@ export class FastDomNode { this.domNode.style.transform = this._layerHint ? 'translate3d(0px, 0px, 0px)' : ''; } + public setBoxShadow(boxShadow: string): void { + if (this._boxShadow === boxShadow) { + return; + } + this._boxShadow = boxShadow; + this.domNode.style.boxShadow = boxShadow; + } + public setContain(contain: 'none' | 'strict' | 'content' | 'size' | 'layout' | 'style' | 'paint'): void { if (this._contain === contain) { return; diff --git a/src/vs/editor/browser/viewParts/rulers/rulers.ts b/src/vs/editor/browser/viewParts/rulers/rulers.ts index 0f22de06a73..9f0490762d7 100644 --- a/src/vs/editor/browser/viewParts/rulers/rulers.ts +++ b/src/vs/editor/browser/viewParts/rulers/rulers.ts @@ -11,13 +11,13 @@ import { RenderingContext, RestrictedRenderingContext } from 'vs/editor/common/v import { ViewContext } from 'vs/editor/common/view/viewContext'; import * as viewEvents from 'vs/editor/common/view/viewEvents'; import { registerThemingParticipant } from 'vs/platform/theme/common/themeService'; -import { EditorOption } from 'vs/editor/common/config/editorOptions'; +import { EditorOption, IRulerOption } from 'vs/editor/common/config/editorOptions'; export class Rulers extends ViewPart { public domNode: FastDomNode; private readonly _renderedRulers: FastDomNode[]; - private _rulers: number[]; + private _rulers: IRulerOption[]; private _typicalHalfwidthCharacterWidth: number; constructor(context: ViewContext) { @@ -92,9 +92,19 @@ export class Rulers extends ViewPart { for (let i = 0, len = this._rulers.length; i < len; i++) { const node = this._renderedRulers[i]; + const srcNode = this._rulers[i]; + let rulerSize, rulerColor = ''; + if (typeof srcNode === 'number') { + rulerSize = srcNode; + } else { + rulerSize = srcNode.size; + rulerColor = `1px 0 0 0 ${srcNode.color} inset`; + } + + node.setBoxShadow(rulerColor); node.setHeight(Math.min(ctx.scrollHeight, 1000000)); - node.setLeft(this._rulers[i] * this._typicalHalfwidthCharacterWidth); + node.setLeft(rulerSize * this._typicalHalfwidthCharacterWidth); } } } diff --git a/src/vs/editor/common/config/editorOptions.ts b/src/vs/editor/common/config/editorOptions.ts index 3adac8cf46b..b4222fb2804 100644 --- a/src/vs/editor/common/config/editorOptions.ts +++ b/src/vs/editor/common/config/editorOptions.ts @@ -59,7 +59,7 @@ export interface IEditorOptions { * Render vertical lines at the specified columns. * Defaults to empty array. */ - rulers?: number[]; + rulers?: IRulerOption[]; /** * A string containing the word separators used when doing word navigation. * Defaults to `~!@#$%^&*()-=+[{]}\\|;:\'",.<>/? @@ -2212,6 +2212,15 @@ class EditorQuickSuggestions extends BaseEditorOption string); @@ -2285,30 +2294,52 @@ class EditorRenderLineNumbersOption extends BaseEditorOption { +class EditorRulers extends SimpleEditorOption { constructor() { - const defaults: number[] = []; + const defaults: IRulerOption[] = []; + const sizeSchema: IJSONSchema = { type: 'number', description: nls.localize('rulers.size', "Number of monospace characters at which this editor ruler will render.") }; + super( EditorOption.rulers, 'rulers', defaults, { type: 'array', - items: { - type: 'number' - }, + items: [ + sizeSchema, + { + type: [ + 'object' + ], + properties: { + size: sizeSchema, + color: { + type: 'string', + description: nls.localize('rulers.color', "Color of this editor ruler."), + format: 'color-hex' + } + } + } + ], default: defaults, description: nls.localize('rulers', "Render vertical rulers after a certain number of monospace characters. Use multiple values for multiple rulers. No rulers are drawn if array is empty.") } ); } - public validate(input: any): number[] { + public validate(input: any): IRulerOption[] { if (Array.isArray(input)) { - let rulers: number[] = []; + let rulers: IRulerOption[] = []; for (let value of input) { - rulers.push(EditorIntOption.clampedInt(value, 0, 0, 10000)); + let clamped; + if (typeof value === 'number') { + clamped = EditorIntOption.clampedInt(value, 0, 0, 10000); + } else { + clamped = value; + clamped.size = EditorIntOption.clampedInt(value.size, 0, 0, 10000); + } + rulers.push(clamped); } - rulers.sort((a, b) => a - b); + rulers.sort((a, b) => ((typeof a === 'number') ? a : a.size) - ((typeof b === 'number') ? b : b.size)); return rulers; } return this.defaultValue; diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index 38597bc5d3b..ec5911ecd43 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -2504,7 +2504,7 @@ declare namespace monaco.editor { * Render vertical lines at the specified columns. * Defaults to empty array. */ - rulers?: number[]; + rulers?: IRulerOption[]; /** * A string containing the word separators used when doing word navigation. * Defaults to `~!@#$%^&*()-=+[{]}\\|;:\'",.<>/? @@ -3282,6 +3282,13 @@ declare namespace monaco.editor { strings: boolean; } + export interface IRulerColorOption { + readonly size: number; + readonly color: string; + } + + export type IRulerOption = number | IRulerColorOption; + export type LineNumbersType = 'on' | 'off' | 'relative' | 'interval' | ((lineNumber: number) => string); /** From fbec956fc43afee912a5222bca35714a7c787953 Mon Sep 17 00:00:00 2001 From: Konstantin Solomatov Date: Fri, 10 Jan 2020 15:15:34 -0800 Subject: [PATCH 020/801] Fix link handing in extension pseudoterminals --- src/vs/workbench/api/common/extHostTerminalService.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/vs/workbench/api/common/extHostTerminalService.ts b/src/vs/workbench/api/common/extHostTerminalService.ts index f5635b83d23..8834b1e585e 100644 --- a/src/vs/workbench/api/common/extHostTerminalService.ts +++ b/src/vs/workbench/api/common/extHostTerminalService.ts @@ -277,6 +277,7 @@ export class ExtHostPseudoterminal implements ITerminalChildProcess { } this._pty.open(initialDimensions ? initialDimensions : undefined); + this._onProcessReady.fire({ pid: -1, cwd: '' }); } } From 2248fa1cbdf8e311fcea92d2fa786cd9326074c6 Mon Sep 17 00:00:00 2001 From: Dan Pock Date: Fri, 10 Jan 2020 20:47:54 -0500 Subject: [PATCH 021/801] Move that to a spot that makes more sense --- src/vs/editor/common/config/editorOptions.ts | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/src/vs/editor/common/config/editorOptions.ts b/src/vs/editor/common/config/editorOptions.ts index b4222fb2804..e5dfc53e56e 100644 --- a/src/vs/editor/common/config/editorOptions.ts +++ b/src/vs/editor/common/config/editorOptions.ts @@ -2212,15 +2212,6 @@ class EditorQuickSuggestions extends BaseEditorOption string); @@ -2294,6 +2285,13 @@ class EditorRenderLineNumbersOption extends BaseEditorOption { constructor() { From 0b1798d2cc99c4ccf872594dab43563e4157501c Mon Sep 17 00:00:00 2001 From: Dan Pock Date: Fri, 10 Jan 2020 20:50:08 -0500 Subject: [PATCH 022/801] run yarn --- src/vs/monaco.d.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index ec5911ecd43..fa6e244f57a 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -3282,6 +3282,8 @@ declare namespace monaco.editor { strings: boolean; } + export type LineNumbersType = 'on' | 'off' | 'relative' | 'interval' | ((lineNumber: number) => string); + export interface IRulerColorOption { readonly size: number; readonly color: string; @@ -3289,8 +3291,6 @@ declare namespace monaco.editor { export type IRulerOption = number | IRulerColorOption; - export type LineNumbersType = 'on' | 'off' | 'relative' | 'interval' | ((lineNumber: number) => string); - /** * Configuration options for editor scrollbars */ From 558664f8918e3cee10fb882ec842538e4546fb05 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?O=C4=9Fuzhan=20Ero=C4=9Flu?= Date: Wed, 15 Jan 2020 12:17:25 +0300 Subject: [PATCH 023/801] Added remembering end key state behaviour --- package.json | 1 + src/vs/editor/common/controller/cursorCommon.ts | 11 ++++++++--- .../common/controller/cursorMoveOperations.ts | 14 +++++++++++--- src/vs/editor/common/controller/oneCursor.ts | 6 +++--- yarn.lock | 15 +++++++++++++++ 5 files changed, 38 insertions(+), 9 deletions(-) diff --git a/package.json b/package.json index cee5e193327..4adc271a857 100644 --- a/package.json +++ b/package.json @@ -35,6 +35,7 @@ "applicationinsights": "1.0.8", "chokidar": "3.2.3", "graceful-fs": "4.1.11", + "gulp-bom": "^3.0.0", "http-proxy-agent": "^2.1.0", "https-proxy-agent": "^2.2.3", "iconv-lite": "0.5.0", diff --git a/src/vs/editor/common/controller/cursorCommon.ts b/src/vs/editor/common/controller/cursorCommon.ts index ac269997918..d981f26a0a0 100644 --- a/src/vs/editor/common/controller/cursorCommon.ts +++ b/src/vs/editor/common/controller/cursorCommon.ts @@ -277,18 +277,21 @@ export class SingleCursorState { public readonly position: Position; public readonly leftoverVisibleColumns: number; public readonly selection: Selection; + public readonly isEnd: boolean; constructor( selectionStart: Range, selectionStartLeftoverVisibleColumns: number, position: Position, leftoverVisibleColumns: number, + isEnd: boolean = false ) { this.selectionStart = selectionStart; this.selectionStartLeftoverVisibleColumns = selectionStartLeftoverVisibleColumns; this.position = position; this.leftoverVisibleColumns = leftoverVisibleColumns; this.selection = SingleCursorState._computeSelection(this.selectionStart, this.position); + this.isEnd = isEnd; } public equals(other: SingleCursorState) { @@ -304,14 +307,15 @@ export class SingleCursorState { return (!this.selection.isEmpty() || !this.selectionStart.isEmpty()); } - public move(inSelectionMode: boolean, lineNumber: number, column: number, leftoverVisibleColumns: number): SingleCursorState { + public move(inSelectionMode: boolean, lineNumber: number, column: number, leftoverVisibleColumns: number, isEnd: boolean = false): SingleCursorState { if (inSelectionMode) { // move just position return new SingleCursorState( this.selectionStart, this.selectionStartLeftoverVisibleColumns, new Position(lineNumber, column), - leftoverVisibleColumns + leftoverVisibleColumns, + isEnd ); } else { // move everything @@ -319,7 +323,8 @@ export class SingleCursorState { new Range(lineNumber, column, lineNumber, column), leftoverVisibleColumns, new Position(lineNumber, column), - leftoverVisibleColumns + leftoverVisibleColumns, + isEnd ); } } diff --git a/src/vs/editor/common/controller/cursorMoveOperations.ts b/src/vs/editor/common/controller/cursorMoveOperations.ts index 1d414dd1f32..8be77b1031c 100644 --- a/src/vs/editor/common/controller/cursorMoveOperations.ts +++ b/src/vs/editor/common/controller/cursorMoveOperations.ts @@ -122,9 +122,13 @@ export class MoveOperations { column = cursor.position.column; } + if (cursor.isEnd) { + column = model.getLineMaxColumn(lineNumber + 1); + } + let r = MoveOperations.down(config, model, lineNumber, column, cursor.leftoverVisibleColumns, linesCount, true); - return cursor.move(inSelectionMode, r.lineNumber, r.column, r.leftoverVisibleColumns); + return cursor.move(inSelectionMode, r.lineNumber, cursor.isEnd ? column : r.column, r.leftoverVisibleColumns, cursor.isEnd); } public static translateDown(config: CursorConfiguration, model: ICursorSimpleModel, cursor: SingleCursorState): SingleCursorState { @@ -174,9 +178,13 @@ export class MoveOperations { column = cursor.position.column; } + if (cursor.isEnd) { + column = model.getLineMaxColumn(lineNumber - 1); + } + let r = MoveOperations.up(config, model, lineNumber, column, cursor.leftoverVisibleColumns, linesCount, true); - return cursor.move(inSelectionMode, r.lineNumber, r.column, r.leftoverVisibleColumns); + return cursor.move(inSelectionMode, r.lineNumber, cursor.isEnd ? column : r.column, r.leftoverVisibleColumns, cursor.isEnd); } public static translateUp(config: CursorConfiguration, model: ICursorSimpleModel, cursor: SingleCursorState): SingleCursorState { @@ -214,7 +222,7 @@ export class MoveOperations { public static moveToEndOfLine(config: CursorConfiguration, model: ICursorSimpleModel, cursor: SingleCursorState, inSelectionMode: boolean): SingleCursorState { let lineNumber = cursor.position.lineNumber; let maxColumn = model.getLineMaxColumn(lineNumber); - return cursor.move(inSelectionMode, lineNumber, maxColumn, 0); + return cursor.move(inSelectionMode, lineNumber, maxColumn, 0, true); } public static moveToBeginningOfBuffer(config: CursorConfiguration, model: ICursorSimpleModel, cursor: SingleCursorState, inSelectionMode: boolean): SingleCursorState { diff --git a/src/vs/editor/common/controller/oneCursor.ts b/src/vs/editor/common/controller/oneCursor.ts index 2bb09583133..07716dbb5de 100644 --- a/src/vs/editor/common/controller/oneCursor.ts +++ b/src/vs/editor/common/controller/oneCursor.ts @@ -99,7 +99,7 @@ export class OneCursor { ); const leftoverVisibleColumns = modelState.position.equals(position) ? modelState.leftoverVisibleColumns : 0; - modelState = new SingleCursorState(selectionStart, selectionStartLeftoverVisibleColumns, position, leftoverVisibleColumns); + modelState = new SingleCursorState(selectionStart, selectionStartLeftoverVisibleColumns, position, leftoverVisibleColumns, modelState.isEnd); } if (!viewState) { @@ -108,12 +108,12 @@ export class OneCursor { const viewSelectionStart2 = context.convertModelPositionToViewPosition(new Position(modelState.selectionStart.endLineNumber, modelState.selectionStart.endColumn)); const viewSelectionStart = new Range(viewSelectionStart1.lineNumber, viewSelectionStart1.column, viewSelectionStart2.lineNumber, viewSelectionStart2.column); const viewPosition = context.convertModelPositionToViewPosition(modelState.position); - viewState = new SingleCursorState(viewSelectionStart, modelState.selectionStartLeftoverVisibleColumns, viewPosition, modelState.leftoverVisibleColumns); + viewState = new SingleCursorState(viewSelectionStart, modelState.selectionStartLeftoverVisibleColumns, viewPosition, modelState.leftoverVisibleColumns, modelState.isEnd); } else { // Validate new view state const viewSelectionStart = context.validateViewRange(viewState.selectionStart, modelState.selectionStart); const viewPosition = context.validateViewPosition(viewState.position, modelState.position); - viewState = new SingleCursorState(viewSelectionStart, modelState.selectionStartLeftoverVisibleColumns, viewPosition, modelState.leftoverVisibleColumns); + viewState = new SingleCursorState(viewSelectionStart, modelState.selectionStartLeftoverVisibleColumns, viewPosition, modelState.leftoverVisibleColumns, viewState.isEnd); } this.modelState = modelState; diff --git a/yarn.lock b/yarn.lock index 6ef2a9f7c77..1e16ef4ba3f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3996,6 +3996,14 @@ gulp-azure-storage@^0.10.0: vinyl "^2.2.0" vinyl-fs "^3.0.3" +gulp-bom@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/gulp-bom/-/gulp-bom-3.0.0.tgz#b2f1ab0ef304ff5e593665b776ba517ef7ffb4ad" + integrity sha512-iw/J94F+MVlxG64Q17BSkHsyjpY17qHl3N3A/jDdrL77zQBkhKtTiKLqM4di9CUX/qFToyyeDsOWwH+rESBgmA== + dependencies: + plugin-error "^1.0.1" + through2 "^3.0.1" + gulp-buffer@0.0.2: version "0.0.2" resolved "https://registry.yarnpkg.com/gulp-buffer/-/gulp-buffer-0.0.2.tgz#af81b4346101736b49942ec6c9fa867ffe737036" @@ -8924,6 +8932,13 @@ through2@^3.0.0: readable-stream "2 || 3" xtend "~4.0.1" +through2@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/through2/-/through2-3.0.1.tgz#39276e713c3302edf9e388dd9c812dd3b825bd5a" + integrity sha512-M96dvTalPT3YbYLaKaCuwu+j06D/8Jfib0o/PxbVt6Amhv3dUAtW6rTV1jPgJSBG83I/e04Y6xkVdVhSRhi0ww== + dependencies: + readable-stream "2 || 3" + through2@~0.2.3: version "0.2.3" resolved "https://registry.yarnpkg.com/through2/-/through2-0.2.3.tgz#eb3284da4ea311b6cc8ace3653748a52abf25a3f" From 24209c67986b3fa2c1ca295b3945391f195e8dd0 Mon Sep 17 00:00:00 2001 From: Gabriel DeBacker Date: Wed, 15 Jan 2020 13:16:19 -0800 Subject: [PATCH 024/801] Fix formatting --- .../browser/extensions.contribution.ts | 30 +++++++++++++++---- 1 file changed, 24 insertions(+), 6 deletions(-) diff --git a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts index f5fe47a7c09..08e0ec52ccb 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts @@ -232,26 +232,35 @@ CommandsRegistry.registerCommand({ schema: { 'type': ['object', 'string'] } + }, + { + name: localize('workbench.extensions.installExtension.arg.throwOnFailure', "Indicates whether to re-throw any exception as well as log it"), + schema: { + 'type': 'boolean' + } } ] }, - handler: async (accessor, arg: string | UriComponents) => { + handler: async (accessor, extensionId: string | UriComponents, throwOnFailure?: boolean) => { const extensionManagementService = accessor.get(IExtensionManagementService); const extensionGalleryService = accessor.get(IExtensionGalleryService); try { - if (typeof arg === 'string') { - const extension = await extensionGalleryService.getCompatibleExtension({ id: arg }); + if (typeof extensionId === 'string') { + const extension = await extensionGalleryService.getCompatibleExtension({ id: extensionId }); if (extension) { await extensionManagementService.installFromGallery(extension); } else { - throw new Error(localize('notFound', "Extension '{0}' not found.", arg)); + throw new Error(localize('notFound', "Extension '{0}' not found.", extensionId)); } } else { - const vsix = URI.revive(arg); + const vsix = URI.revive(extensionId); await extensionManagementService.install(vsix); } } catch (e) { onUnexpectedError(e); + if (throwOnFailure) { + throw e; + } } } }); @@ -266,10 +275,16 @@ CommandsRegistry.registerCommand({ schema: { 'type': 'string' } + }, + { + name: localize('workbench.extensions.uninstallExtension.arg.throwOnFailure', "Indicates whether to re-throw any exception as well as log it"), + schema: { + 'type': 'boolean' + } } ] }, - handler: async (accessor, id: string) => { + handler: async (accessor, id: string, throwOnFailure?: boolean) => { if (!id) { throw new Error(localize('id required', "Extension id required.")); } @@ -284,6 +299,9 @@ CommandsRegistry.registerCommand({ await extensionManagementService.uninstall(extensionToUninstall, true); } catch (e) { onUnexpectedError(e); + if (throwOnFailure) { + throw e; + } } } }); From ed92290a4f43fc583f4e6dcdca2ac3d1f710f0e1 Mon Sep 17 00:00:00 2001 From: SteVen Batten Date: Thu, 16 Jan 2020 21:18:02 -0800 Subject: [PATCH 025/801] wip: broken state --- .../api/browser/viewsExtensionPoint.ts | 5 +- .../browser/parts/panel/panelPart.ts | 11 +++ .../browser/parts/views/viewPaneContainer.ts | 45 ++++++++-- src/vs/workbench/browser/parts/views/views.ts | 88 ++++++++++++++++++- .../browser/parts/views/viewsViewlet.ts | 7 +- src/vs/workbench/common/views.ts | 6 ++ .../contrib/debug/browser/debugViewlet.ts | 6 +- .../extensions/browser/extensionsViewlet.ts | 5 +- .../contrib/files/browser/explorerViewlet.ts | 7 +- .../outline/browser/outline.contribution.ts | 72 +-------------- .../contrib/remote/browser/remote.ts | 7 +- .../contrib/scm/browser/scmViewlet.ts | 5 +- .../search/browser/search.contribution.ts | 2 +- .../contrib/search/browser/searchViewlet.ts | 4 +- 14 files changed, 172 insertions(+), 98 deletions(-) diff --git a/src/vs/workbench/api/browser/viewsExtensionPoint.ts b/src/vs/workbench/api/browser/viewsExtensionPoint.ts index 65bb4afb1dd..e198a29483d 100644 --- a/src/vs/workbench/api/browser/viewsExtensionPoint.ts +++ b/src/vs/workbench/api/browser/viewsExtensionPoint.ts @@ -8,7 +8,7 @@ import { forEach } from 'vs/base/common/collections'; import { IJSONSchema } from 'vs/base/common/jsonSchema'; import * as resources from 'vs/base/common/resources'; import { ExtensionMessageCollector, ExtensionsRegistry, IExtensionPoint, IExtensionPointUser } from 'vs/workbench/services/extensions/common/extensionsRegistry'; -import { ViewContainer, IViewsRegistry, ITreeViewDescriptor, IViewContainersRegistry, Extensions as ViewContainerExtensions, TEST_VIEW_CONTAINER_ID, IViewDescriptor, ViewContainerLocation } from 'vs/workbench/common/views'; +import { ViewContainer, IViewsRegistry, ITreeViewDescriptor, IViewContainersRegistry, Extensions as ViewContainerExtensions, TEST_VIEW_CONTAINER_ID, IViewDescriptor, ViewContainerLocation, IViewDescriptorService } from 'vs/workbench/common/views'; import { CustomTreeViewPane, CustomTreeView } from 'vs/workbench/browser/parts/views/customView'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { coalesce, } from 'vs/base/common/arrays'; @@ -325,8 +325,9 @@ class ViewsExtensionHandler implements IWorkbenchContribution { @IThemeService themeService: IThemeService, @IContextMenuService contextMenuService: IContextMenuService, @IExtensionService extensionService: IExtensionService, + @IViewDescriptorService viewDescriptorService: IViewDescriptorService ) { - super(id, `${id}.state`, { mergeViewWithContainerWhenSingleView: true }, instantiationService, configurationService, layoutService, contextMenuService, telemetryService, extensionService, themeService, storageService, contextService); + super(id, `${id}.state`, { mergeViewWithContainerWhenSingleView: true }, instantiationService, configurationService, layoutService, contextMenuService, telemetryService, extensionService, themeService, storageService, contextService, viewDescriptorService); } } diff --git a/src/vs/workbench/browser/parts/panel/panelPart.ts b/src/vs/workbench/browser/parts/panel/panelPart.ts index 4220c47084b..f5b71ab1813 100644 --- a/src/vs/workbench/browser/parts/panel/panelPart.ts +++ b/src/vs/workbench/browser/parts/panel/panelPart.ts @@ -131,6 +131,7 @@ export class PanelPart extends CompositePart implements IPanelService { getCompositePinnedAction: (compositeId: string) => this.getCompositeActions(compositeId).pinnedAction, getOnCompositeClickAction: (compositeId: string) => this.instantiationService.createInstance(PanelActivityAction, assertIsDefined(this.getPanel(compositeId))), getContextMenuActions: () => [ + ...this.getContextMenuActions(), ...PositionPanelActionConfigs // show the contextual menu item if it is not in that position .filter(({ when }) => contextKeyService.contextMatchesRules(when)) @@ -160,6 +161,16 @@ export class PanelPart extends CompositePart implements IPanelService { this.onDidRegisterPanels([...this.getPanels()]); } + private getContextMenuActions(): readonly IAction[] { + const activePanel = this.getActivePanel(); + + if (!activePanel) { + return []; + } + + return activePanel.getContextMenuActions(); + } + private onDidRegisterPanels(panels: PanelDescriptor[]): void { for (const panel of panels) { const cachedPanel = this.getCachedPanels().filter(({ id }) => id === panel.id)[0]; diff --git a/src/vs/workbench/browser/parts/views/viewPaneContainer.ts b/src/vs/workbench/browser/parts/views/viewPaneContainer.ts index 7730d4b4b7c..e264cb74b0a 100644 --- a/src/vs/workbench/browser/parts/views/viewPaneContainer.ts +++ b/src/vs/workbench/browser/parts/views/viewPaneContainer.ts @@ -25,7 +25,7 @@ import { PaneView, IPaneViewOptions, IPaneOptions, Pane, DefaultPaneDndControlle import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; import { StandardMouseEvent } from 'vs/base/browser/mouseEvent'; -import { Extensions as ViewContainerExtensions, IView, FocusedViewContext, IViewContainersRegistry, IViewDescriptor, ViewContainer } from 'vs/workbench/common/views'; +import { Extensions as ViewContainerExtensions, IView, FocusedViewContext, IViewContainersRegistry, IViewDescriptor, ViewContainer, IViewDescriptorService, Extensions as ViewsExtensions, ViewContainerLocation, IViewsService, IViewsRegistry } from 'vs/workbench/common/views'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { assertIsDefined } from 'vs/base/common/types'; @@ -307,7 +307,8 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer { @IExtensionService protected extensionService: IExtensionService, @IThemeService protected themeService: IThemeService, @IStorageService protected storageService: IStorageService, - @IWorkspaceContextService protected contextService: IWorkspaceContextService + @IWorkspaceContextService protected contextService: IWorkspaceContextService, + @IViewDescriptorService protected viewDescriptorService: IViewDescriptorService ) { super(id, themeService, storageService); @@ -389,14 +390,32 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer { } getContextMenuActions(viewDescriptor?: IViewDescriptor): IAction[] { + + if (this.isViewMergedWithContainer()) { + const viewId = this.panes[0].id; + viewDescriptor = Registry.as(ViewsExtensions.ViewsRegistry).getView(viewId)!; + } + const result: IAction[] = []; if (viewDescriptor) { - result.push({ - id: `${viewDescriptor.id}.removeView`, - label: nls.localize('hideView', "Hide"), - enabled: viewDescriptor.canToggleVisibility, - run: () => this.toggleViewVisibility(viewDescriptor.id) - }); + + if (!this.isViewMergedWithContainer()) { + result.push({ + id: `${viewDescriptor.id}.removeView`, + label: nls.localize('hideView', "Hide"), + enabled: viewDescriptor.canToggleVisibility, + run: () => this.toggleViewVisibility(viewDescriptor!.id) + }); + } + + if (viewDescriptor.canMoveView) { + result.push({ + id: `${viewDescriptor.id}.removeView`, + label: this.isViewMergedWithContainer() ? nls.localize('toggleSpecificViewLocation', "Toggle {0} View Location", viewDescriptor.name) : nls.localize('toggleViewLocation', "Toggle View Location"), + enabled: true, + run: () => this.moveView(viewDescriptor!) + }); + } } const viewToggleActions = this.viewsModel.viewDescriptors.map(viewDescriptor => ({ @@ -653,6 +672,16 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer { this.viewsModel.setVisible(viewId, visible); } + protected moveView(viewDescriptor: IViewDescriptor): void { + const viewContainerRegistry = Registry.as(ViewsExtensions.ViewContainersRegistry); + const currentLocation = viewContainerRegistry.getViewContainerLocation(this.viewContainer); + this.viewDescriptorService.moveView(viewDescriptor, currentLocation === ViewContainerLocation.Sidebar ? ViewContainerLocation.Panel : ViewContainerLocation.Sidebar); + + this.instantiationService.invokeFunction(accessor => { + const viewsService = accessor.get(IViewsService); + viewsService.openView(viewDescriptor.id, true); + }); + } private addPane(pane: ViewPane, size: number, index = this.paneItems.length - 1): void { const onDidFocus = pane.onDidFocus(() => this.lastFocusedPane = pane); diff --git a/src/vs/workbench/browser/parts/views/views.ts b/src/vs/workbench/browser/parts/views/views.ts index ba567a47619..96ab993a64e 100644 --- a/src/vs/workbench/browser/parts/views/views.ts +++ b/src/vs/workbench/browser/parts/views/views.ts @@ -23,6 +23,16 @@ import { toggleClass, addClass } from 'vs/base/browser/dom'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { IPaneComposite } from 'vs/workbench/common/panecomposite'; import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; +import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; +import { ViewPaneContainer } from 'vs/workbench/browser/parts/views/viewPaneContainer'; +import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; +import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; class CounterSet implements IReadableSet { @@ -749,7 +759,8 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor constructor( @IContextKeyService private readonly contextKeyService: IContextKeyService, - @IStorageService private readonly storageService: IStorageService + @IStorageService private readonly storageService: IStorageService, + @IExtensionService private readonly extensionService: IExtensionService ) { super(); @@ -761,6 +772,16 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor this.cachedViewToContainer = this.getCachedViewPositions(); + for (const containerId of this.cachedViewToContainer.values()) { + if (containerId.startsWith('workbench.view.service.')) { + const locationStr = containerId.substr(23); + const location = locationStr.startsWith('pnl.') ? ViewContainerLocation.Panel : ViewContainerLocation.Sidebar; + const viewId = containerId.substr(27); + + + } + } + // Register all containers that were registered before this ctor this.viewContainersRegistry.all.forEach(viewContainer => this.onDidRegisterViewContainer(viewContainer)); @@ -777,6 +798,8 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor })); this._register(this.storageService.onDidChangeStorage((e) => { this.onDidStorageChange(e); })); + + this._register(this.extensionService.onDidRegisterExtensions(() => this.onDidRegisterExtensions())); } private registerGroupedViews(groupedViews: Map): void { @@ -807,6 +830,23 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor } } + private onDidRegisterExtensions(): void { + for (const [viewId, containerId] of this.cachedViewToContainer.entries()) { + // check if cached view container is registered + if (this.viewContainersRegistry.get(containerId)) { + continue; + } + + // check if view has been registered to default location + const viewContainer = this.viewsRegistry.getViewContainer(viewId); + if (viewContainer) { + this.addViews(viewContainer, [this.viewsRegistry.getView(viewId)!]); + } + } + + this.saveViewPositionsToCache(); + } + private onDidRegisterViews(views: IViewDescriptor[], viewContainer: ViewContainer): void { // When views are registered, we need to regroup them based on the cache const regroupedViews = this.regroupViews(viewContainer.id, views); @@ -844,6 +884,10 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor this.viewsRegistry.getViewContainer(viewId); } + getDefaultContainer(viewId: string): ViewContainer | null { + return this.viewsRegistry.getViewContainer(viewId) ?? null; + } + getViewDescriptors(container: ViewContainer): ViewDescriptorCollection { let viewDescriptorCollectionItem = this.viewDescriptorCollections.get(container); if (!viewDescriptorCollectionItem) { @@ -854,6 +898,16 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor return viewDescriptorCollectionItem!.viewDescriptorCollection; } + moveView(view: IViewDescriptor, location: ViewContainerLocation): void { + const previousContainer = this.getViewContainer(view.id); + if (previousContainer && this.viewContainersRegistry.getViewContainerLocation(previousContainer) === location) { + return; + } + + const container = this.registerViewContainerForSingleView(view.id, view.name, location); + this.moveViews([view], container); + } + moveViews(views: IViewDescriptor[], viewContainer: ViewContainer): void { if (!views.length) { return; @@ -869,6 +923,34 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor } } + private registerViewContainerForSingleView(viewId: string, viewName: string, location: ViewContainerLocation): ViewContainer { + const id = `workbench.view.service.${location === ViewContainerLocation.Panel ? 'pnl' : 'sbr'}${viewId}`; + + class MovedViewPanelViewPaneContainer extends ViewPaneContainer { + constructor( + @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService, + @ITelemetryService telemetryService: ITelemetryService, + @IWorkspaceContextService protected contextService: IWorkspaceContextService, + @IStorageService protected storageService: IStorageService, + @IConfigurationService configurationService: IConfigurationService, + @IInstantiationService protected instantiationService: IInstantiationService, + @IThemeService themeService: IThemeService, + @IContextMenuService contextMenuService: IContextMenuService, + @IExtensionService extensionService: IExtensionService, + @IViewDescriptorService viewDescriptorService: IViewDescriptorService + ) { + super(id, `${id}.state`, { mergeViewWithContainerWhenSingleView: true }, instantiationService, configurationService, layoutService, contextMenuService, telemetryService, extensionService, themeService, storageService, contextService, viewDescriptorService); + } + } + + return this.viewContainersRegistry.registerViewContainer({ + id, + ctorDescriptor: new SyncDescriptor(MovedViewPanelViewPaneContainer), + name: viewName, + hideIfEmpty: true + }, location); + } + private getCachedViewPositions(): Map { return new Map(JSON.parse(this.cachedViewPositionsValue)); } @@ -942,6 +1024,10 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor } private onDidRegisterViewContainer(viewContainer: ViewContainer): void { + if (this.viewDescriptorCollections.has(viewContainer)) { + return; + } + const disposables = new DisposableStore(); const viewDescriptorCollection = disposables.add(new ViewDescriptorCollection(this.contextKeyService)); diff --git a/src/vs/workbench/browser/parts/views/viewsViewlet.ts b/src/vs/workbench/browser/parts/views/viewsViewlet.ts index 078939500ef..5a9ec03d8d2 100644 --- a/src/vs/workbench/browser/parts/views/viewsViewlet.ts +++ b/src/vs/workbench/browser/parts/views/viewsViewlet.ts @@ -8,7 +8,7 @@ import { IAction } from 'vs/base/common/actions'; import { Separator } from 'vs/base/browser/ui/actionbar/actionbar'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; -import { IViewDescriptor } from 'vs/workbench/common/views'; +import { IViewDescriptor, IViewDescriptorService } from 'vs/workbench/common/views'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; @@ -43,10 +43,11 @@ export abstract class FilterViewPaneContainer extends ViewPaneContainer { @IThemeService themeService: IThemeService, @IContextMenuService contextMenuService: IContextMenuService, @IExtensionService extensionService: IExtensionService, - @IWorkspaceContextService contextService: IWorkspaceContextService + @IWorkspaceContextService contextService: IWorkspaceContextService, + @IViewDescriptorService viewDescriptorService: IViewDescriptorService ) { - super(viewletId, `${viewletId}.state`, { mergeViewWithContainerWhenSingleView: false }, instantiationService, configurationService, layoutService, contextMenuService, telemetryService, extensionService, themeService, storageService, contextService); + super(viewletId, `${viewletId}.state`, { mergeViewWithContainerWhenSingleView: false }, instantiationService, configurationService, layoutService, contextMenuService, telemetryService, extensionService, themeService, storageService, contextService, viewDescriptorService); this._register(onDidChangeFilterValue(newFilterValue => { this.filterValue = newFilterValue; this.onFilterChanged(newFilterValue); diff --git a/src/vs/workbench/common/views.ts b/src/vs/workbench/common/views.ts index fa839f073ad..1703e2358fc 100644 --- a/src/vs/workbench/common/views.ts +++ b/src/vs/workbench/common/views.ts @@ -188,6 +188,8 @@ export interface IViewDescriptor { readonly canToggleVisibility?: boolean; + readonly canMoveView?: boolean; + // Applies only to newly created views readonly hideByDefault?: boolean; @@ -361,11 +363,15 @@ export interface IViewDescriptorService { _serviceBrand: undefined; + moveView(view: IViewDescriptor, location: ViewContainerLocation): void; + moveViews(views: IViewDescriptor[], viewContainer: ViewContainer): void; getViewDescriptors(container: ViewContainer): IViewDescriptorCollection; getViewContainer(viewId: string): ViewContainer | null; + + getDefaultContainer(viewId: string): ViewContainer | null; } // Custom views diff --git a/src/vs/workbench/contrib/debug/browser/debugViewlet.ts b/src/vs/workbench/contrib/debug/browser/debugViewlet.ts index df274ebb967..9e88fea88be 100644 --- a/src/vs/workbench/contrib/debug/browser/debugViewlet.ts +++ b/src/vs/workbench/contrib/debug/browser/debugViewlet.ts @@ -33,6 +33,7 @@ import { INotificationService } from 'vs/platform/notification/common/notificati import { TogglePanelAction } from 'vs/workbench/browser/panel'; import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; import { StartView } from 'vs/workbench/contrib/debug/browser/startView'; +import { IViewDescriptorService } from 'vs/workbench/common/views'; export class DebugViewPaneContainer extends ViewPaneContainer { @@ -59,9 +60,10 @@ export class DebugViewPaneContainer extends ViewPaneContainer { @IContextViewService private readonly contextViewService: IContextViewService, @IMenuService private readonly menuService: IMenuService, @IContextKeyService private readonly contextKeyService: IContextKeyService, - @INotificationService private readonly notificationService: INotificationService + @INotificationService private readonly notificationService: INotificationService, + @IViewDescriptorService viewDescriptorService: IViewDescriptorService ) { - super(VIEWLET_ID, `${VIEWLET_ID}.state`, { mergeViewWithContainerWhenSingleView: true }, instantiationService, configurationService, layoutService, contextMenuService, telemetryService, extensionService, themeService, storageService, contextService); + super(VIEWLET_ID, `${VIEWLET_ID}.state`, { mergeViewWithContainerWhenSingleView: true }, instantiationService, configurationService, layoutService, contextMenuService, telemetryService, extensionService, themeService, storageService, contextService, viewDescriptorService); this._register(this.debugService.onDidChangeState(state => this.onDebugServiceStateChange(state))); this._register(this.debugService.onDidNewSession(() => this.updateToolBar())); diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts b/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts index 8c450fe0b0c..7fcef790e02 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts @@ -35,7 +35,7 @@ import Severity from 'vs/base/common/severity'; import { IActivityService, NumberBadge } from 'vs/workbench/services/activity/common/activity'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { IViewsRegistry, IViewDescriptor, Extensions, ViewContainer, IViewContainersRegistry } from 'vs/workbench/common/views'; +import { IViewsRegistry, IViewDescriptor, Extensions, ViewContainer, IViewContainersRegistry, IViewDescriptorService } from 'vs/workbench/common/views'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; import { IContextKeyService, ContextKeyExpr, RawContextKey, IContextKey } from 'vs/platform/contextkey/common/contextkey'; @@ -356,8 +356,9 @@ export class ExtensionsViewPaneContainer extends ViewPaneContainer implements IE @IContextKeyService contextKeyService: IContextKeyService, @IContextMenuService contextMenuService: IContextMenuService, @IExtensionService extensionService: IExtensionService, + @IViewDescriptorService viewDescriptorService: IViewDescriptorService ) { - super(VIEWLET_ID, `${VIEWLET_ID}.state`, { mergeViewWithContainerWhenSingleView: true }, instantiationService, configurationService, layoutService, contextMenuService, telemetryService, extensionService, themeService, storageService, contextService); + super(VIEWLET_ID, `${VIEWLET_ID}.state`, { mergeViewWithContainerWhenSingleView: true }, instantiationService, configurationService, layoutService, contextMenuService, telemetryService, extensionService, themeService, storageService, contextService, viewDescriptorService); this.searchDelayer = new Delayer(500); this.nonEmptyWorkspaceContextKey = NonEmptyWorkspaceContext.bindTo(contextKeyService); diff --git a/src/vs/workbench/contrib/files/browser/explorerViewlet.ts b/src/vs/workbench/contrib/files/browser/explorerViewlet.ts index 82d603d63fb..cc45efefec9 100644 --- a/src/vs/workbench/contrib/files/browser/explorerViewlet.ts +++ b/src/vs/workbench/contrib/files/browser/explorerViewlet.ts @@ -20,7 +20,7 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey'; import { IThemeService } from 'vs/platform/theme/common/themeService'; -import { IViewsRegistry, IViewDescriptor, Extensions, ViewContainer, IViewContainersRegistry, ViewContainerLocation } from 'vs/workbench/common/views'; +import { IViewsRegistry, IViewDescriptor, Extensions, ViewContainer, IViewContainersRegistry, ViewContainerLocation, IViewDescriptorService } from 'vs/workbench/common/views'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { Disposable } from 'vs/base/common/lifecycle'; import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; @@ -163,10 +163,11 @@ export class ExplorerViewPaneContainer extends ViewPaneContainer { @IContextKeyService contextKeyService: IContextKeyService, @IThemeService themeService: IThemeService, @IContextMenuService contextMenuService: IContextMenuService, - @IExtensionService extensionService: IExtensionService + @IExtensionService extensionService: IExtensionService, + @IViewDescriptorService viewDescriptorService: IViewDescriptorService ) { - super(VIEWLET_ID, ExplorerViewPaneContainer.EXPLORER_VIEWS_STATE, { mergeViewWithContainerWhenSingleView: true }, instantiationService, configurationService, layoutService, contextMenuService, telemetryService, extensionService, themeService, storageService, contextService); + super(VIEWLET_ID, ExplorerViewPaneContainer.EXPLORER_VIEWS_STATE, { mergeViewWithContainerWhenSingleView: true }, instantiationService, configurationService, layoutService, contextMenuService, telemetryService, extensionService, themeService, storageService, contextService, viewDescriptorService); this.viewletVisibleContextKey = ExplorerViewletVisibleContext.bindTo(contextKeyService); diff --git a/src/vs/workbench/contrib/outline/browser/outline.contribution.ts b/src/vs/workbench/contrib/outline/browser/outline.contribution.ts index ae1804ccecc..ede8e9f4e6f 100644 --- a/src/vs/workbench/contrib/outline/browser/outline.contribution.ts +++ b/src/vs/workbench/contrib/outline/browser/outline.contribution.ts @@ -4,61 +4,24 @@ *--------------------------------------------------------------------------------------------*/ import { localize } from 'vs/nls'; -import { IViewsRegistry, IViewDescriptor, Extensions as ViewExtensions, ViewContainer, IViewContainersRegistry, ViewContainerLocation, IViewDescriptorService, IViewsService } from 'vs/workbench/common/views'; +import { IViewsRegistry, IViewDescriptor, Extensions as ViewExtensions } from 'vs/workbench/common/views'; import { OutlinePane } from './outlinePane'; import { Registry } from 'vs/platform/registry/common/platform'; import { IConfigurationRegistry, Extensions as ConfigurationExtensions, ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry'; import { OutlineConfigKeys, OutlineViewId } from 'vs/editor/contrib/documentSymbols/outline'; import { VIEW_CONTAINER } from 'vs/workbench/contrib/files/browser/explorerViewlet'; -import { Action } from 'vs/base/common/actions'; -import { IWorkbenchActionRegistry, Extensions as ActionsExtensions } from 'vs/workbench/common/actions'; -import { SyncActionDescriptor } from 'vs/platform/actions/common/actions'; -import { ViewPaneContainer } from 'vs/workbench/browser/parts/views/viewPaneContainer'; -import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; -import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; -import { IStorageService } from 'vs/platform/storage/common/storage'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { IThemeService } from 'vs/platform/theme/common/themeService'; -import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; -import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; // import './outlineNavigation'; export const PANEL_ID = 'panel.view.outline'; -export class OutlineViewPaneContainer extends ViewPaneContainer { - constructor( - @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService, - @ITelemetryService telemetryService: ITelemetryService, - @IWorkspaceContextService protected contextService: IWorkspaceContextService, - @IStorageService protected storageService: IStorageService, - @IConfigurationService configurationService: IConfigurationService, - @IInstantiationService protected instantiationService: IInstantiationService, - @IThemeService themeService: IThemeService, - @IContextMenuService contextMenuService: IContextMenuService, - @IExtensionService extensionService: IExtensionService, - ) { - super(PANEL_ID, `${PANEL_ID}.state`, { mergeViewWithContainerWhenSingleView: true }, instantiationService, configurationService, layoutService, contextMenuService, telemetryService, extensionService, themeService, storageService, contextService); - } -} - -export const VIEW_CONTAINER_PANEL: ViewContainer = - Registry.as(ViewExtensions.ViewContainersRegistry).registerViewContainer({ - id: PANEL_ID, - ctorDescriptor: new SyncDescriptor(OutlineViewPaneContainer), - name: localize('name', "Outline"), - hideIfEmpty: true - }, ViewContainerLocation.Panel); - - const _outlineDesc = { id: OutlineViewId, name: localize('name', "Outline"), ctorDescriptor: new SyncDescriptor(OutlinePane), canToggleVisibility: true, + canMoveView: true, hideByDefault: false, collapsed: true, order: 2, @@ -68,37 +31,6 @@ const _outlineDesc = { Registry.as(ViewExtensions.ViewsRegistry).registerViews([_outlineDesc], VIEW_CONTAINER); -export class ToggleOutlinePositionAction extends Action { - - static ID = 'outline.view.togglePosition'; - static LABEL = 'Toggle Outline View Position'; - - constructor( - id: string, - label: string, - @IViewDescriptorService private readonly viewDescriptorService: IViewDescriptorService, - @IViewsService private readonly viewsService: IViewsService - ) { - super(id, label, '', true); - } - - async run(): Promise { - const inPanel = this.viewDescriptorService.getViewContainer(_outlineDesc.id) === VIEW_CONTAINER_PANEL; - if (!inPanel) { - this.viewDescriptorService.moveViews([_outlineDesc], VIEW_CONTAINER_PANEL); - this.viewsService.openView(OutlineViewId, true); - } else { - this.viewDescriptorService.moveViews([_outlineDesc], VIEW_CONTAINER); - this.viewsService.openView(OutlineViewId, true); - } - - } -} - -Registry.as(ActionsExtensions.WorkbenchActions) - .registerWorkbenchAction(SyncActionDescriptor.create(ToggleOutlinePositionAction, ToggleOutlinePositionAction.ID, ToggleOutlinePositionAction.LABEL), 'Show Release Notes'); - - Registry.as(ConfigurationExtensions.Configuration).registerConfiguration({ 'id': 'outline', 'order': 117, diff --git a/src/vs/workbench/contrib/remote/browser/remote.ts b/src/vs/workbench/contrib/remote/browser/remote.ts index af5e22dcfef..ebf20e84685 100644 --- a/src/vs/workbench/contrib/remote/browser/remote.ts +++ b/src/vs/workbench/contrib/remote/browser/remote.ts @@ -19,7 +19,7 @@ import { IExtensionService } from 'vs/workbench/services/extensions/common/exten import { FilterViewPaneContainer } from 'vs/workbench/browser/parts/views/viewsViewlet'; import { VIEWLET_ID } from 'vs/workbench/contrib/remote/common/remote.contribution'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; -import { IViewDescriptor, IViewsRegistry, Extensions, ViewContainerLocation, IViewContainersRegistry } from 'vs/workbench/common/views'; +import { IViewDescriptor, IViewsRegistry, Extensions, ViewContainerLocation, IViewContainersRegistry, IViewDescriptorService } from 'vs/workbench/common/views'; import { Registry } from 'vs/platform/registry/common/platform'; import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { IOpenerService } from 'vs/platform/opener/common/opener'; @@ -435,9 +435,10 @@ export class RemoteViewPaneContainer extends FilterViewPaneContainer implements @IExtensionService extensionService: IExtensionService, @IRemoteExplorerService private readonly remoteExplorerService: IRemoteExplorerService, @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService, - @IContextKeyService private readonly contextKeyService: IContextKeyService + @IContextKeyService private readonly contextKeyService: IContextKeyService, + @IViewDescriptorService viewDescriptorService: IViewDescriptorService ) { - super(VIEWLET_ID, remoteExplorerService.onDidChangeTargetType, configurationService, layoutService, telemetryService, storageService, instantiationService, themeService, contextMenuService, extensionService, contextService); + super(VIEWLET_ID, remoteExplorerService.onDidChangeTargetType, configurationService, layoutService, telemetryService, storageService, instantiationService, themeService, contextMenuService, extensionService, contextService, viewDescriptorService); this.addConstantViewDescriptors([this.helpPanelDescriptor]); remoteHelpExtPoint.setHandler((extensions) => { let helpInformation: HelpInformation[] = []; diff --git a/src/vs/workbench/contrib/scm/browser/scmViewlet.ts b/src/vs/workbench/contrib/scm/browser/scmViewlet.ts index adc0b970af7..cb21f9aa0b9 100644 --- a/src/vs/workbench/contrib/scm/browser/scmViewlet.ts +++ b/src/vs/workbench/contrib/scm/browser/scmViewlet.ts @@ -25,7 +25,7 @@ import { INotificationService } from 'vs/platform/notification/common/notificati import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; -import { IViewsRegistry, Extensions } from 'vs/workbench/common/views'; +import { IViewsRegistry, Extensions, IViewDescriptorService } from 'vs/workbench/common/views'; import { Registry } from 'vs/platform/registry/common/platform'; import { nextTick } from 'vs/base/common/process'; import { RepositoryPane, RepositoryViewDescriptor } from 'vs/workbench/contrib/scm/browser/repositoryPane'; @@ -98,8 +98,9 @@ export class SCMViewPaneContainer extends ViewPaneContainer implements IViewMode @IExtensionService extensionService: IExtensionService, @IWorkspaceContextService protected contextService: IWorkspaceContextService, @IContextKeyService contextKeyService: IContextKeyService, + @IViewDescriptorService viewDescriptorService: IViewDescriptorService ) { - super(VIEWLET_ID, SCMViewPaneContainer.STATE_KEY, { mergeViewWithContainerWhenSingleView: true }, instantiationService, configurationService, layoutService, contextMenuService, telemetryService, extensionService, themeService, storageService, contextService); + super(VIEWLET_ID, SCMViewPaneContainer.STATE_KEY, { mergeViewWithContainerWhenSingleView: true }, instantiationService, configurationService, layoutService, contextMenuService, telemetryService, extensionService, themeService, storageService, contextService, viewDescriptorService); this.menus = instantiationService.createInstance(SCMMenus, undefined); this._register(this.menus.onDidChangeTitle(this.updateTitleArea, this)); diff --git a/src/vs/workbench/contrib/search/browser/search.contribution.ts b/src/vs/workbench/contrib/search/browser/search.contribution.ts index 872915b2ea2..a60f85060e3 100644 --- a/src/vs/workbench/contrib/search/browser/search.contribution.ts +++ b/src/vs/workbench/contrib/search/browser/search.contribution.ts @@ -554,7 +554,7 @@ class RegisterSearchViewContribution implements IWorkbenchContribution { } } else { Registry.as(PanelExtensions.Panels).deregisterPanel(PANEL_ID); - viewsRegistry.registerViews([{ id: VIEW_ID, name: nls.localize('search', "Search"), ctorDescriptor: new SyncDescriptor(SearchView, [SearchViewPosition.SideBar]), canToggleVisibility: false }], viewContainer); + viewsRegistry.registerViews([{ id: VIEW_ID, name: nls.localize('search', "Search"), ctorDescriptor: new SyncDescriptor(SearchView, [SearchViewPosition.SideBar]), canToggleVisibility: false, canMoveView: true }], viewContainer); if (open) { viewletService.openViewlet(VIEWLET_ID); } diff --git a/src/vs/workbench/contrib/search/browser/searchViewlet.ts b/src/vs/workbench/contrib/search/browser/searchViewlet.ts index 592a3153e67..2754b71d7b8 100644 --- a/src/vs/workbench/contrib/search/browser/searchViewlet.ts +++ b/src/vs/workbench/contrib/search/browser/searchViewlet.ts @@ -15,6 +15,7 @@ import { IExtensionService } from 'vs/workbench/services/extensions/common/exten import { VIEWLET_ID, VIEW_ID } from 'vs/workbench/services/search/common/search'; import { SearchView } from 'vs/workbench/contrib/search/browser/searchView'; import { ViewPaneContainer } from 'vs/workbench/browser/parts/views/viewPaneContainer'; +import { IViewDescriptorService } from 'vs/workbench/common/views'; export class SearchViewPaneContainer extends ViewPaneContainer { @@ -29,8 +30,9 @@ export class SearchViewPaneContainer extends ViewPaneContainer { @IThemeService themeService: IThemeService, @IContextMenuService contextMenuService: IContextMenuService, @IExtensionService extensionService: IExtensionService, + @IViewDescriptorService viewDescriptorService: IViewDescriptorService ) { - super(VIEWLET_ID, `${VIEWLET_ID}.state`, { mergeViewWithContainerWhenSingleView: true, donotShowContainerTitleWhenMergedWithContainer: true }, instantiationService, configurationService, layoutService, contextMenuService, telemetryService, extensionService, themeService, storageService, contextService); + super(VIEWLET_ID, `${VIEWLET_ID}.state`, { mergeViewWithContainerWhenSingleView: true, donotShowContainerTitleWhenMergedWithContainer: true }, instantiationService, configurationService, layoutService, contextMenuService, telemetryService, extensionService, themeService, storageService, contextService, viewDescriptorService); } getSearchView(): SearchView | undefined { From 3d2f259b1fbf72266bc9c412f6d0902e7131022a Mon Sep 17 00:00:00 2001 From: SteVen Batten Date: Fri, 17 Jan 2020 11:12:00 -0800 Subject: [PATCH 026/801] move view descriptor service and enhance cache --- src/vs/workbench/browser/parts/views/views.ts | 525 +--------------- src/vs/workbench/common/views.ts | 2 + .../views/browser/viewDescriptorService.ts | 569 ++++++++++++++++++ .../test/browser/parts/views/views.test.ts | 3 +- src/vs/workbench/workbench.common.main.ts | 1 + 5 files changed, 581 insertions(+), 519 deletions(-) create mode 100644 src/vs/workbench/services/views/browser/viewDescriptorService.ts diff --git a/src/vs/workbench/browser/parts/views/views.ts b/src/vs/workbench/browser/parts/views/views.ts index 11dcaafc9da..c907735668b 100644 --- a/src/vs/workbench/browser/parts/views/views.ts +++ b/src/vs/workbench/browser/parts/views/views.ts @@ -5,11 +5,11 @@ import 'vs/css!./media/views'; import { Disposable, IDisposable, toDisposable, DisposableStore } from 'vs/base/common/lifecycle'; -import { IViewDescriptorService, ViewContainer, IViewDescriptor, IViewContainersRegistry, Extensions as ViewExtensions, IView, IViewDescriptorCollection, IViewsRegistry, ViewContainerLocation, IViewsService } from 'vs/workbench/common/views'; +import { IViewDescriptorService, ViewContainer, IViewDescriptor, IViewContainersRegistry, Extensions as ViewExtensions, IView, ViewContainerLocation, IViewsService } from 'vs/workbench/common/views'; import { Registry } from 'vs/platform/registry/common/platform'; -import { IStorageService, StorageScope, IWorkspaceStorageChangeEvent } from 'vs/platform/storage/common/storage'; +import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; -import { IContextKeyService, IContextKeyChangeEvent, IReadableSet, IContextKey, RawContextKey, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; +import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { Event, Emitter } from 'vs/base/common/event'; import { firstIndex, move } from 'vs/base/common/arrays'; import { isUndefinedOrNull, isUndefined } from 'vs/base/common/types'; @@ -23,169 +23,8 @@ import { toggleClass, addClass } from 'vs/base/browser/dom'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { IPaneComposite } from 'vs/workbench/common/panecomposite'; import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; -import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; -import { ViewPaneContainer } from 'vs/workbench/browser/parts/views/viewPaneContainer'; -import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; -import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { IThemeService } from 'vs/platform/theme/common/themeService'; -import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; -import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; -class CounterSet implements IReadableSet { - private map = new Map(); - - add(value: T): CounterSet { - this.map.set(value, (this.map.get(value) || 0) + 1); - return this; - } - - delete(value: T): boolean { - let counter = this.map.get(value) || 0; - - if (counter === 0) { - return false; - } - - counter--; - - if (counter === 0) { - this.map.delete(value); - } else { - this.map.set(value, counter); - } - - return true; - } - - has(value: T): boolean { - return this.map.has(value); - } -} - -interface IViewItem { - viewDescriptor: IViewDescriptor; - active: boolean; -} - -class ViewDescriptorCollection extends Disposable implements IViewDescriptorCollection { - - private contextKeys = new CounterSet(); - private items: IViewItem[] = []; - - private _onDidChangeViews: Emitter<{ added: IViewDescriptor[], removed: IViewDescriptor[]; }> = this._register(new Emitter<{ added: IViewDescriptor[], removed: IViewDescriptor[]; }>()); - readonly onDidChangeViews: Event<{ added: IViewDescriptor[], removed: IViewDescriptor[]; }> = this._onDidChangeViews.event; - - private _onDidChangeActiveViews: Emitter<{ added: IViewDescriptor[], removed: IViewDescriptor[]; }> = this._register(new Emitter<{ added: IViewDescriptor[], removed: IViewDescriptor[]; }>()); - readonly onDidChangeActiveViews: Event<{ added: IViewDescriptor[], removed: IViewDescriptor[]; }> = this._onDidChangeActiveViews.event; - - get activeViewDescriptors(): IViewDescriptor[] { - return this.items - .filter(i => i.active) - .map(i => i.viewDescriptor); - } - - get allViewDescriptors(): IViewDescriptor[] { - return this.items.map(i => i.viewDescriptor); - } - - constructor( - @IContextKeyService private readonly contextKeyService: IContextKeyService, - ) { - super(); - this._register(Event.filter(contextKeyService.onDidChangeContext, e => e.affectsSome(this.contextKeys))(this.onContextChanged, this)); - } - - addViews(viewDescriptors: IViewDescriptor[]): void { - const added: IViewDescriptor[] = []; - - for (const viewDescriptor of viewDescriptors) { - const item = { - viewDescriptor, - active: this.isViewDescriptorActive(viewDescriptor) // TODO: should read from some state? - }; - - this.items.push(item); - - if (viewDescriptor.when) { - for (const key of viewDescriptor.when.keys()) { - this.contextKeys.add(key); - } - } - - if (item.active) { - added.push(viewDescriptor); - } - } - - this._onDidChangeViews.fire({ added: viewDescriptors, removed: [] }); - - if (added.length) { - this._onDidChangeActiveViews.fire({ added, removed: [] }); - } - } - - removeViews(viewDescriptors: IViewDescriptor[]): void { - const removed: IViewDescriptor[] = []; - - for (const viewDescriptor of viewDescriptors) { - const index = firstIndex(this.items, i => i.viewDescriptor.id === viewDescriptor.id); - - if (index === -1) { - continue; - } - - const item = this.items[index]; - this.items.splice(index, 1); - - if (viewDescriptor.when) { - for (const key of viewDescriptor.when.keys()) { - this.contextKeys.delete(key); - } - } - - if (item.active) { - removed.push(viewDescriptor); - } - } - - this._onDidChangeViews.fire({ added: [], removed: viewDescriptors }); - - if (removed.length) { - this._onDidChangeActiveViews.fire({ added: [], removed }); - } - } - - private onContextChanged(event: IContextKeyChangeEvent): void { - const removed: IViewDescriptor[] = []; - const added: IViewDescriptor[] = []; - - for (const item of this.items) { - const active = this.isViewDescriptorActive(item.viewDescriptor); - - if (item.active !== active) { - if (active) { - added.push(item.viewDescriptor); - } else { - removed.push(item.viewDescriptor); - } - } - - item.active = active; - } - - if (added.length || removed.length) { - this._onDidChangeActiveViews.fire({ added, removed }); - } - } - - private isViewDescriptorActive(viewDescriptor: IViewDescriptor): boolean { - return !viewDescriptor.when || this.contextKeyService.contextMatchesRules(viewDescriptor.when); - } -} export interface IViewState { visibleGlobal: boolean | undefined; @@ -485,10 +324,10 @@ export class PersistentContributableViewsModel extends ContributableViewsModel { this.onDidRemove, Event.map(this.onDidMove, ({ from, to }) => [from, to]), Event.map(this.onDidChangeViewState, viewDescriptorRef => [viewDescriptorRef])) - (viewDescriptorRefs => this.saveViewsStates(viewDescriptorRefs.map(r => r.viewDescriptor)))); + (viewDescriptorRefs => this.saveViewsStates())); } - private saveViewsStates(viewDescriptors: IViewDescriptor[]): void { + private saveViewsStates(): void { this.saveWorkspaceViewsStates(); this.saveGlobalViewsStates(); } @@ -636,7 +475,7 @@ export class ViewsService extends Disposable implements IViewsService { this.onViewsRegistered(viewDescriptorCollection.allViewDescriptors, viewContainer); this._register(viewDescriptorCollection.onDidChangeViews(({ added, removed }) => { this.onViewsRegistered(added, viewContainer); - this.onViewsDeregistered(removed, viewContainer); + this.onViewsDeregistered(removed); })); } @@ -680,7 +519,7 @@ export class ViewsService extends Disposable implements IViewsService { } } - private onViewsDeregistered(views: IViewDescriptor[], container: ViewContainer): void { + private onViewsDeregistered(views: IViewDescriptor[]): void { for (const view of views) { const disposable = this.viewDisposable.get(view); if (disposable) { @@ -726,355 +565,6 @@ export class ViewsService extends Disposable implements IViewsService { } } -export class ViewDescriptorService extends Disposable implements IViewDescriptorService { - - _serviceBrand: undefined; - - private static readonly CACHED_VIEW_POSITIONS = 'views.cachedViewPositions'; - - private readonly viewDescriptorCollections: Map; - private readonly activeViewContextKeys: Map>; - - private readonly viewsRegistry: IViewsRegistry; - private readonly viewContainersRegistry: IViewContainersRegistry; - - private cachedViewToContainer: Map; - - - private _cachedViewPositionsValue: string | undefined; - private get cachedViewPositionsValue(): string { - if (!this._cachedViewPositionsValue) { - this._cachedViewPositionsValue = this.getStoredCachedViewPositionsValue(); - } - - return this._cachedViewPositionsValue; - } - - private set cachedViewPositionsValue(value: string) { - if (this.cachedViewPositionsValue !== value) { - this._cachedViewPositionsValue = value; - this.setStoredCachedViewPositionsValue(value); - } - } - - constructor( - @IContextKeyService private readonly contextKeyService: IContextKeyService, - @IStorageService private readonly storageService: IStorageService, - @IExtensionService private readonly extensionService: IExtensionService - ) { - super(); - - this.viewDescriptorCollections = new Map(); - this.activeViewContextKeys = new Map>(); - - this.viewContainersRegistry = Registry.as(ViewExtensions.ViewContainersRegistry); - this.viewsRegistry = Registry.as(ViewExtensions.ViewsRegistry); - - this.cachedViewToContainer = this.getCachedViewPositions(); - - for (const containerId of this.cachedViewToContainer.values()) { - if (containerId.startsWith('workbench.view.service.')) { - const locationStr = containerId.substr(23); - const location = locationStr.startsWith('pnl.') ? ViewContainerLocation.Panel : ViewContainerLocation.Sidebar; - const viewId = containerId.substr(27); - - - } - } - - // Register all containers that were registered before this ctor - this.viewContainersRegistry.all.forEach(viewContainer => this.onDidRegisterViewContainer(viewContainer)); - - this._register(this.viewsRegistry.onViewsRegistered(({ views, viewContainer }) => this.onDidRegisterViews(views, viewContainer))); - this._register(this.viewsRegistry.onViewsDeregistered(({ views, viewContainer }) => this.onDidDeregisterViews(views, viewContainer))); - - this._register(this.viewsRegistry.onDidChangeContainer(({ views, from, to }) => { this.removeViews(from, views); this.addViews(to, views); })); - - this._register(this.viewContainersRegistry.onDidRegister(({ viewContainer }) => this.onDidRegisterViewContainer(viewContainer))); - this._register(this.viewContainersRegistry.onDidDeregister(({ viewContainer }) => this.onDidDeregisterViewContainer(viewContainer))); - this._register(toDisposable(() => { - this.viewDescriptorCollections.forEach(({ disposable }) => disposable.dispose()); - this.viewDescriptorCollections.clear(); - })); - - this._register(this.storageService.onDidChangeStorage((e) => { this.onDidStorageChange(e); })); - - this._register(this.extensionService.onDidRegisterExtensions(() => this.onDidRegisterExtensions())); - } - - private registerGroupedViews(groupedViews: Map): void { - // Register views that have already been registered to their correct view containers - for (const viewContainerId of groupedViews.keys()) { - const viewContainer = this.viewContainersRegistry.get(viewContainerId); - - // The container has not been registered yet - if (!viewContainer || !this.viewDescriptorCollections.has(viewContainer)) { - continue; - } - - this.addViews(viewContainer, groupedViews.get(viewContainerId)!); - } - } - - private deregisterGroupedViews(groupedViews: Map): void { - // Register views that have already been registered to their correct view containers - for (const viewContainerId of groupedViews.keys()) { - const viewContainer = this.viewContainersRegistry.get(viewContainerId); - - // The container has not been registered yet - if (!viewContainer || !this.viewDescriptorCollections.has(viewContainer)) { - continue; - } - - this.removeViews(viewContainer, groupedViews.get(viewContainerId)!); - } - } - - private onDidRegisterExtensions(): void { - for (const [viewId, containerId] of this.cachedViewToContainer.entries()) { - // check if cached view container is registered - if (this.viewContainersRegistry.get(containerId)) { - continue; - } - - // check if view has been registered to default location - const viewContainer = this.viewsRegistry.getViewContainer(viewId); - if (viewContainer) { - this.addViews(viewContainer, [this.viewsRegistry.getView(viewId)!]); - } - } - - this.saveViewPositionsToCache(); - } - - private onDidRegisterViews(views: IViewDescriptor[], viewContainer: ViewContainer): void { - // When views are registered, we need to regroup them based on the cache - const regroupedViews = this.regroupViews(viewContainer.id, views); - - // Once they are grouped, try registering them which occurs - // if the container has already been registered within this service - this.registerGroupedViews(regroupedViews); - } - - private onDidDeregisterViews(views: IViewDescriptor[], viewContainer: ViewContainer): void { - // When views are registered, we need to regroup them based on the cache - const regroupedViews = this.regroupViews(viewContainer.id, views); - this.deregisterGroupedViews(regroupedViews); - } - - private regroupViews(containerId: string, views: IViewDescriptor[]): Map { - const ret = new Map(); - - views.forEach(viewDescriptor => { - const cachedContainerId = this.cachedViewToContainer.get(viewDescriptor.id); - const correctContainerId = cachedContainerId || containerId; - - const containerViews = ret.get(correctContainerId) || []; - containerViews.push(viewDescriptor); - ret.set(correctContainerId, containerViews); - }); - - return ret; - } - - getViewContainer(viewId: string): ViewContainer | null { - const containerId = this.cachedViewToContainer.get(viewId); - return containerId ? - this.viewContainersRegistry.get(containerId) ?? null : - this.viewsRegistry.getViewContainer(viewId); - } - - getDefaultContainer(viewId: string): ViewContainer | null { - return this.viewsRegistry.getViewContainer(viewId) ?? null; - } - - getViewDescriptors(container: ViewContainer): ViewDescriptorCollection { - return this.getOrRegisterViewDescriptorCollection(container); - } - - moveView(view: IViewDescriptor, location: ViewContainerLocation): void { - const previousContainer = this.getViewContainer(view.id); - if (previousContainer && this.viewContainersRegistry.getViewContainerLocation(previousContainer) === location) { - return; - } - - const container = this.registerViewContainerForSingleView(view.id, view.name, location); - this.moveViews([view], container); - } - - moveViews(views: IViewDescriptor[], viewContainer: ViewContainer): void { - if (!views.length) { - return; - } - - const from = this.getViewContainer(views[0].id); - const to = viewContainer; - - if (from && to && from !== to) { - this.removeViews(from, views); - this.addViews(to, views); - this.saveViewPositionsToCache(); - } - } - - private registerViewContainerForSingleView(viewId: string, viewName: string, location: ViewContainerLocation): ViewContainer { - const id = `workbench.view.service.${location === ViewContainerLocation.Panel ? 'pnl' : 'sbr'}${viewId}`; - - class MovedViewPanelViewPaneContainer extends ViewPaneContainer { - constructor( - @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService, - @ITelemetryService telemetryService: ITelemetryService, - @IWorkspaceContextService protected contextService: IWorkspaceContextService, - @IStorageService protected storageService: IStorageService, - @IConfigurationService configurationService: IConfigurationService, - @IInstantiationService protected instantiationService: IInstantiationService, - @IThemeService themeService: IThemeService, - @IContextMenuService contextMenuService: IContextMenuService, - @IExtensionService extensionService: IExtensionService, - @IViewDescriptorService viewDescriptorService: IViewDescriptorService - ) { - super(id, `${id}.state`, { mergeViewWithContainerWhenSingleView: true }, instantiationService, configurationService, layoutService, contextMenuService, telemetryService, extensionService, themeService, storageService, contextService, viewDescriptorService); - } - } - - return this.viewContainersRegistry.registerViewContainer({ - id, - ctorDescriptor: new SyncDescriptor(MovedViewPanelViewPaneContainer), - name: viewName, - hideIfEmpty: true - }, location); - } - - private getCachedViewPositions(): Map { - return new Map(JSON.parse(this.cachedViewPositionsValue)); - } - - private onDidStorageChange(e: IWorkspaceStorageChangeEvent): void { - if (e.key === ViewDescriptorService.CACHED_VIEW_POSITIONS && e.scope === StorageScope.GLOBAL - && this.cachedViewPositionsValue !== this.getStoredCachedViewPositionsValue() /* This checks if current window changed the value or not */) { - this._cachedViewPositionsValue = this.getStoredCachedViewPositionsValue(); - - const newCachedPositions = this.getCachedViewPositions(); - - for (let viewId of newCachedPositions.keys()) { - const prevViewContainer = this.getViewContainer(viewId); - const newViewContainer = this.viewContainersRegistry.get(newCachedPositions.get(viewId)!); - if (prevViewContainer && newViewContainer && newViewContainer !== prevViewContainer) { - const viewDescriptor = this.viewsRegistry.getView(viewId); - if (viewDescriptor) { - // We don't call move views to avoid sending intermediate - // cached data to the window that gave us this information - this.removeViews(prevViewContainer, [viewDescriptor]); - this.addViews(newViewContainer, [viewDescriptor]); - } - } - } - - this.cachedViewToContainer = this.getCachedViewPositions(); - } - } - - private getStoredCachedViewPositionsValue(): string { - return this.storageService.get(ViewDescriptorService.CACHED_VIEW_POSITIONS, StorageScope.GLOBAL, '[]'); - } - - private setStoredCachedViewPositionsValue(value: string): void { - this.storageService.store(ViewDescriptorService.CACHED_VIEW_POSITIONS, value, StorageScope.GLOBAL); - } - - private saveViewPositionsToCache(): void { - this.viewContainersRegistry.all.forEach(viewContainer => { - const viewDescriptorCollection = this.getViewDescriptors(viewContainer); - viewDescriptorCollection.allViewDescriptors.forEach(viewDescriptor => { - this.cachedViewToContainer.set(viewDescriptor.id, viewContainer.id); - }); - }); - - this.cachedViewPositionsValue = JSON.stringify([...this.cachedViewToContainer]); - } - - private getViewsByContainer(viewContainer: ViewContainer): IViewDescriptor[] { - const result = this.viewsRegistry.getViews(viewContainer).filter(viewDescriptor => { - const cachedContainer = this.cachedViewToContainer.get(viewDescriptor.id) || viewContainer.id; - return cachedContainer === viewContainer.id; - }); - - for (const [viewId, containerId] of this.cachedViewToContainer.entries()) { - if (containerId !== viewContainer.id) { - continue; - } - - if (this.viewsRegistry.getViewContainer(viewId) === viewContainer) { - continue; - } - - const viewDescriptor = this.viewsRegistry.getView(viewId); - if (viewDescriptor) { - result.push(viewDescriptor); - } - } - - return result; - } - - private onDidRegisterViewContainer(viewContainer: ViewContainer): void { - this.getOrRegisterViewDescriptorCollection(viewContainer); - } - - private getOrRegisterViewDescriptorCollection(viewContainer: ViewContainer): ViewDescriptorCollection { - let viewDescriptorCollection = this.viewDescriptorCollections.get(viewContainer)?.viewDescriptorCollection; - - if (!viewDescriptorCollection) { - const disposables = new DisposableStore(); - viewDescriptorCollection = disposables.add(new ViewDescriptorCollection(this.contextKeyService)); - - this.onDidChangeActiveViews({ added: viewDescriptorCollection.activeViewDescriptors, removed: [] }); - viewDescriptorCollection.onDidChangeActiveViews(changed => this.onDidChangeActiveViews(changed), this, disposables); - - this.viewDescriptorCollections.set(viewContainer, { viewDescriptorCollection, disposable: disposables }); - - const viewsToRegister = this.getViewsByContainer(viewContainer); - if (viewsToRegister.length) { - this.addViews(viewContainer, viewsToRegister); - } - } - - return viewDescriptorCollection; - } - - private onDidDeregisterViewContainer(viewContainer: ViewContainer): void { - const viewDescriptorCollectionItem = this.viewDescriptorCollections.get(viewContainer); - if (viewDescriptorCollectionItem) { - viewDescriptorCollectionItem.disposable.dispose(); - this.viewDescriptorCollections.delete(viewContainer); - } - } - - private onDidChangeActiveViews({ added, removed }: { added: IViewDescriptor[], removed: IViewDescriptor[]; }): void { - added.forEach(viewDescriptor => this.getOrCreateActiveViewContextKey(viewDescriptor).set(true)); - removed.forEach(viewDescriptor => this.getOrCreateActiveViewContextKey(viewDescriptor).set(false)); - } - - private addViews(container: ViewContainer, views: IViewDescriptor[]): void { - this.getViewDescriptors(container).addViews(views); - } - - private removeViews(container: ViewContainer, views: IViewDescriptor[]): void { - const viewDescriptorCollection = this.getViewDescriptors(container); - viewDescriptorCollection.removeViews(views); - } - - private getOrCreateActiveViewContextKey(viewDescriptor: IViewDescriptor): IContextKey { - const activeContextKeyId = `${viewDescriptor.id}.active`; - let contextKey = this.activeViewContextKeys.get(activeContextKeyId); - if (!contextKey) { - contextKey = new RawContextKey(activeContextKeyId, false).bindTo(this.contextKeyService); - this.activeViewContextKeys.set(activeContextKeyId, contextKey); - } - return contextKey; - } -} - export function createFileIconThemableTreeContainerScope(container: HTMLElement, themeService: IWorkbenchThemeService): IDisposable { addClass(container, 'file-icon-themable-tree'); addClass(container, 'show-file-icons'); @@ -1088,5 +578,4 @@ export function createFileIconThemableTreeContainerScope(container: HTMLElement, return themeService.onDidFileIconThemeChange(onDidChangeFileIconTheme); } -registerSingleton(IViewDescriptorService, ViewDescriptorService); registerSingleton(IViewsService, ViewsService); diff --git a/src/vs/workbench/common/views.ts b/src/vs/workbench/common/views.ts index 1703e2358fc..5397c2a7dba 100644 --- a/src/vs/workbench/common/views.ts +++ b/src/vs/workbench/common/views.ts @@ -190,6 +190,8 @@ export interface IViewDescriptor { readonly canMoveView?: boolean; + readonly containerIcon?: string | URI; + // Applies only to newly created views readonly hideByDefault?: boolean; diff --git a/src/vs/workbench/services/views/browser/viewDescriptorService.ts b/src/vs/workbench/services/views/browser/viewDescriptorService.ts new file mode 100644 index 00000000000..051d84b4a22 --- /dev/null +++ b/src/vs/workbench/services/views/browser/viewDescriptorService.ts @@ -0,0 +1,569 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { ViewContainerLocation, IViewDescriptorService, ViewContainer, IViewsRegistry, IViewContainersRegistry, IViewDescriptor, Extensions as ViewExtensions, IViewDescriptorCollection } from 'vs/workbench/common/views'; +import { IContextKey, RawContextKey, IContextKeyService, IReadableSet, IContextKeyChangeEvent } from 'vs/platform/contextkey/common/contextkey'; +import { IStorageService, StorageScope, IWorkspaceStorageChangeEvent } from 'vs/platform/storage/common/storage'; +import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { toDisposable, DisposableStore, Disposable, IDisposable } from 'vs/base/common/lifecycle'; +import { ViewPaneContainer } from 'vs/workbench/browser/parts/views/viewPaneContainer'; +import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; +import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { Event, Emitter } from 'vs/base/common/event'; +import { firstIndex } from 'vs/base/common/arrays'; + +class CounterSet implements IReadableSet { + + private map = new Map(); + + add(value: T): CounterSet { + this.map.set(value, (this.map.get(value) || 0) + 1); + return this; + } + + delete(value: T): boolean { + let counter = this.map.get(value) || 0; + + if (counter === 0) { + return false; + } + + counter--; + + if (counter === 0) { + this.map.delete(value); + } else { + this.map.set(value, counter); + } + + return true; + } + + has(value: T): boolean { + return this.map.has(value); + } +} + +interface IViewItem { + viewDescriptor: IViewDescriptor; + active: boolean; +} + +class ViewDescriptorCollection extends Disposable implements IViewDescriptorCollection { + + private contextKeys = new CounterSet(); + private items: IViewItem[] = []; + + private _onDidChangeViews: Emitter<{ added: IViewDescriptor[], removed: IViewDescriptor[]; }> = this._register(new Emitter<{ added: IViewDescriptor[], removed: IViewDescriptor[]; }>()); + readonly onDidChangeViews: Event<{ added: IViewDescriptor[], removed: IViewDescriptor[]; }> = this._onDidChangeViews.event; + + private _onDidChangeActiveViews: Emitter<{ added: IViewDescriptor[], removed: IViewDescriptor[]; }> = this._register(new Emitter<{ added: IViewDescriptor[], removed: IViewDescriptor[]; }>()); + readonly onDidChangeActiveViews: Event<{ added: IViewDescriptor[], removed: IViewDescriptor[]; }> = this._onDidChangeActiveViews.event; + + get activeViewDescriptors(): IViewDescriptor[] { + return this.items + .filter(i => i.active) + .map(i => i.viewDescriptor); + } + + get allViewDescriptors(): IViewDescriptor[] { + return this.items.map(i => i.viewDescriptor); + } + + constructor( + @IContextKeyService private readonly contextKeyService: IContextKeyService, + ) { + super(); + this._register(Event.filter(contextKeyService.onDidChangeContext, e => e.affectsSome(this.contextKeys))(this.onContextChanged, this)); + } + + addViews(viewDescriptors: IViewDescriptor[]): void { + const added: IViewDescriptor[] = []; + + for (const viewDescriptor of viewDescriptors) { + const item = { + viewDescriptor, + active: this.isViewDescriptorActive(viewDescriptor) // TODO: should read from some state? + }; + + this.items.push(item); + + if (viewDescriptor.when) { + for (const key of viewDescriptor.when.keys()) { + this.contextKeys.add(key); + } + } + + if (item.active) { + added.push(viewDescriptor); + } + } + + this._onDidChangeViews.fire({ added: viewDescriptors, removed: [] }); + + if (added.length) { + this._onDidChangeActiveViews.fire({ added, removed: [] }); + } + } + + removeViews(viewDescriptors: IViewDescriptor[]): void { + const removed: IViewDescriptor[] = []; + + for (const viewDescriptor of viewDescriptors) { + const index = firstIndex(this.items, i => i.viewDescriptor.id === viewDescriptor.id); + + if (index === -1) { + continue; + } + + const item = this.items[index]; + this.items.splice(index, 1); + + if (viewDescriptor.when) { + for (const key of viewDescriptor.when.keys()) { + this.contextKeys.delete(key); + } + } + + if (item.active) { + removed.push(viewDescriptor); + } + } + + this._onDidChangeViews.fire({ added: [], removed: viewDescriptors }); + + if (removed.length) { + this._onDidChangeActiveViews.fire({ added: [], removed }); + } + } + + private onContextChanged(event: IContextKeyChangeEvent): void { + const removed: IViewDescriptor[] = []; + const added: IViewDescriptor[] = []; + + for (const item of this.items) { + const active = this.isViewDescriptorActive(item.viewDescriptor); + + if (item.active !== active) { + if (active) { + added.push(item.viewDescriptor); + } else { + removed.push(item.viewDescriptor); + } + } + + item.active = active; + } + + if (added.length || removed.length) { + this._onDidChangeActiveViews.fire({ added, removed }); + } + } + + private isViewDescriptorActive(viewDescriptor: IViewDescriptor): boolean { + return !viewDescriptor.when || this.contextKeyService.contextMatchesRules(viewDescriptor.when); + } +} + +interface ICachedViewContainerInfo { + containerId: string; + location?: ViewContainerLocation; + sourceViewId?: string; +} + +export class ViewDescriptorService extends Disposable implements IViewDescriptorService { + + _serviceBrand: undefined; + + private static readonly CACHED_VIEW_POSITIONS = 'views.cachedViewPositions'; + private static readonly COMMON_CONTAINER_ID_PREFIX = 'workbench.views.service'; + + private readonly viewDescriptorCollections: Map; + private readonly activeViewContextKeys: Map>; + + private readonly viewsRegistry: IViewsRegistry; + private readonly viewContainersRegistry: IViewContainersRegistry; + + private cachedViewInfo: Map; + + + private _cachedViewPositionsValue: string | undefined; + private get cachedViewPositionsValue(): string { + if (!this._cachedViewPositionsValue) { + this._cachedViewPositionsValue = this.getStoredCachedViewPositionsValue(); + } + + return this._cachedViewPositionsValue; + } + + private set cachedViewPositionsValue(value: string) { + if (this.cachedViewPositionsValue !== value) { + this._cachedViewPositionsValue = value; + this.setStoredCachedViewPositionsValue(value); + } + } + + constructor( + @IContextKeyService private readonly contextKeyService: IContextKeyService, + @IStorageService private readonly storageService: IStorageService, + @IExtensionService private readonly extensionService: IExtensionService + ) { + super(); + + this.viewDescriptorCollections = new Map(); + this.activeViewContextKeys = new Map>(); + + this.viewContainersRegistry = Registry.as(ViewExtensions.ViewContainersRegistry); + this.viewsRegistry = Registry.as(ViewExtensions.ViewsRegistry); + + this.cachedViewInfo = this.getCachedViewPositions(); + + // Register all containers that were registered before this ctor + this.viewContainersRegistry.all.forEach(viewContainer => this.onDidRegisterViewContainer(viewContainer)); + + this._register(this.viewsRegistry.onViewsRegistered(({ views, viewContainer }) => this.onDidRegisterViews(views, viewContainer))); + this._register(this.viewsRegistry.onViewsDeregistered(({ views, viewContainer }) => this.onDidDeregisterViews(views, viewContainer))); + + this._register(this.viewsRegistry.onDidChangeContainer(({ views, from, to }) => { this.removeViews(from, views); this.addViews(to, views); })); + + this._register(this.viewContainersRegistry.onDidRegister(({ viewContainer }) => this.onDidRegisterViewContainer(viewContainer))); + this._register(this.viewContainersRegistry.onDidDeregister(({ viewContainer }) => this.onDidDeregisterViewContainer(viewContainer))); + this._register(toDisposable(() => { + this.viewDescriptorCollections.forEach(({ disposable }) => disposable.dispose()); + this.viewDescriptorCollections.clear(); + })); + + this._register(this.storageService.onDidChangeStorage((e) => { this.onDidStorageChange(e); })); + + this._register(this.extensionService.onDidRegisterExtensions(() => this.onDidRegisterExtensions())); + } + + private registerGroupedViews(groupedViews: Map): void { + // Register views that have already been registered to their correct view containers + for (const viewContainerId of groupedViews.keys()) { + const viewContainer = this.viewContainersRegistry.get(viewContainerId); + + // The container has not been registered yet + if (!viewContainer || !this.viewDescriptorCollections.has(viewContainer)) { + continue; + } + + this.addViews(viewContainer, groupedViews.get(viewContainerId)!); + } + } + + private deregisterGroupedViews(groupedViews: Map): void { + // Register views that have already been registered to their correct view containers + for (const viewContainerId of groupedViews.keys()) { + const viewContainer = this.viewContainersRegistry.get(viewContainerId); + + // The container has not been registered yet + if (!viewContainer || !this.viewDescriptorCollections.has(viewContainer)) { + continue; + } + + this.removeViews(viewContainer, groupedViews.get(viewContainerId)!); + } + } + + private onDidRegisterExtensions(): void { + for (const [viewId, containerInfo] of this.cachedViewInfo.entries()) { + if (!containerInfo) { + continue; + } + + const containerId = containerInfo.containerId; + // check if cached view container is registered + if (this.viewContainersRegistry.get(containerId)) { + continue; + } + + // check if we should generate this container + if (containerInfo.sourceViewId && containerInfo.location) { + const sourceView = this.viewsRegistry.getView(containerInfo.sourceViewId); + + if (sourceView) { + this.registerViewContainerForSingleView(sourceView, containerInfo.location); + continue; + } + } + + // check if view has been registered to default location + const viewContainer = this.viewsRegistry.getViewContainer(viewId); + if (viewContainer) { + this.addViews(viewContainer, [this.viewsRegistry.getView(viewId)!]); + } + } + + this.saveViewPositionsToCache(); + } + + private onDidRegisterViews(views: IViewDescriptor[], viewContainer: ViewContainer): void { + // When views are registered, we need to regroup them based on the cache + const regroupedViews = this.regroupViews(viewContainer.id, views); + + // Once they are grouped, try registering them which occurs + // if the container has already been registered within this service + this.registerGroupedViews(regroupedViews); + } + + private onDidDeregisterViews(views: IViewDescriptor[], viewContainer: ViewContainer): void { + // When views are registered, we need to regroup them based on the cache + const regroupedViews = this.regroupViews(viewContainer.id, views); + this.deregisterGroupedViews(regroupedViews); + } + + private regroupViews(containerId: string, views: IViewDescriptor[]): Map { + const ret = new Map(); + + views.forEach(viewDescriptor => { + const correctContainerId = this.cachedViewInfo.get(viewDescriptor.id)?.containerId || containerId; + + const containerViews = ret.get(correctContainerId) || []; + containerViews.push(viewDescriptor); + ret.set(correctContainerId, containerViews); + }); + + return ret; + } + + getViewContainer(viewId: string): ViewContainer | null { + const containerId = this.cachedViewInfo.get(viewId)?.containerId; + return containerId ? + this.viewContainersRegistry.get(containerId) ?? null : + this.viewsRegistry.getViewContainer(viewId); + } + + getDefaultContainer(viewId: string): ViewContainer | null { + return this.viewsRegistry.getViewContainer(viewId) ?? null; + } + + getViewDescriptors(container: ViewContainer): ViewDescriptorCollection { + return this.getOrRegisterViewDescriptorCollection(container); + } + + moveView(view: IViewDescriptor, location: ViewContainerLocation): void { + const previousContainer = this.getViewContainer(view.id); + if (previousContainer && this.viewContainersRegistry.getViewContainerLocation(previousContainer) === location) { + return; + } + + let container = this.getDefaultContainer(view.id)!; + if (this.viewContainersRegistry.getViewContainerLocation(container) !== location) { + container = this.registerViewContainerForSingleView(view, location); + } + + this.moveViews([view], container); + } + + moveViews(views: IViewDescriptor[], viewContainer: ViewContainer): void { + if (!views.length) { + return; + } + + const from = this.getViewContainer(views[0].id); + const to = viewContainer; + + if (from && to && from !== to) { + this.removeViews(from, views); + this.addViews(to, views); + this.saveViewPositionsToCache(); + } + } + + private registerViewContainerForSingleView(sourceView: IViewDescriptor, location: ViewContainerLocation): ViewContainer { + const id = this.generateContainerIdFromSourceViewId(sourceView.id, location); + + class MovedViewPanelViewPaneContainer extends ViewPaneContainer { + constructor( + @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService, + @ITelemetryService telemetryService: ITelemetryService, + @IWorkspaceContextService protected contextService: IWorkspaceContextService, + @IStorageService protected storageService: IStorageService, + @IConfigurationService configurationService: IConfigurationService, + @IInstantiationService protected instantiationService: IInstantiationService, + @IThemeService themeService: IThemeService, + @IContextMenuService contextMenuService: IContextMenuService, + @IExtensionService extensionService: IExtensionService, + @IViewDescriptorService viewDescriptorService: IViewDescriptorService + ) { + super(id, `${id}.state`, { mergeViewWithContainerWhenSingleView: true }, instantiationService, configurationService, layoutService, contextMenuService, telemetryService, extensionService, themeService, storageService, contextService, viewDescriptorService); + } + } + + return this.viewContainersRegistry.registerViewContainer({ + id, + ctorDescriptor: new SyncDescriptor(MovedViewPanelViewPaneContainer), + name: sourceView.name, + icon: sourceView.containerIcon, + hideIfEmpty: true + }, location); + } + + private getCachedViewPositions(): Map { + return new Map(JSON.parse(this.cachedViewPositionsValue)); + } + + private onDidStorageChange(e: IWorkspaceStorageChangeEvent): void { + if (e.key === ViewDescriptorService.CACHED_VIEW_POSITIONS && e.scope === StorageScope.GLOBAL + && this.cachedViewPositionsValue !== this.getStoredCachedViewPositionsValue() /* This checks if current window changed the value or not */) { + this._cachedViewPositionsValue = this.getStoredCachedViewPositionsValue(); + + const newCachedPositions = this.getCachedViewPositions(); + + for (let viewId of newCachedPositions.keys()) { + const prevViewContainer = this.getViewContainer(viewId); + const newViewContainer = this.viewContainersRegistry.get(newCachedPositions.get(viewId)!.containerId); + if (prevViewContainer && newViewContainer && newViewContainer !== prevViewContainer) { + const viewDescriptor = this.viewsRegistry.getView(viewId); + if (viewDescriptor) { + // We don't call move views to avoid sending intermediate + // cached data to the window that gave us this information + this.removeViews(prevViewContainer, [viewDescriptor]); + this.addViews(newViewContainer, [viewDescriptor]); + } + } + } + + this.cachedViewInfo = this.getCachedViewPositions(); + } + } + + // Generated Container Id Format + // {Common Prefix}.{Uniqueness Id}.{Source View Id} + private generateContainerIdFromSourceViewId(viewId: string, location: ViewContainerLocation): string { + return `${ViewDescriptorService.COMMON_CONTAINER_ID_PREFIX}.${location === ViewContainerLocation.Panel ? 'panel' : 'sidebar'}.${viewId}`; + } + + private extractSourceViewIdFromContainerId(containerId: string): string | undefined { + if (!containerId.startsWith(ViewDescriptorService.COMMON_CONTAINER_ID_PREFIX)) { + return undefined; + } + + // Remove the common prefix for generated container ids + const prefixRemoved = containerId.substr(ViewDescriptorService.COMMON_CONTAINER_ID_PREFIX.length + 1); + + // Remove uniqueness section + const uniquenessRemoved = prefixRemoved.substr(prefixRemoved.indexOf('.') + 1); + + return uniquenessRemoved; + } + + private getStoredCachedViewPositionsValue(): string { + return this.storageService.get(ViewDescriptorService.CACHED_VIEW_POSITIONS, StorageScope.GLOBAL, '[]'); + } + + private setStoredCachedViewPositionsValue(value: string): void { + this.storageService.store(ViewDescriptorService.CACHED_VIEW_POSITIONS, value, StorageScope.GLOBAL); + } + + private saveViewPositionsToCache(): void { + this.viewContainersRegistry.all.forEach(viewContainer => { + const viewDescriptorCollection = this.getViewDescriptors(viewContainer); + viewDescriptorCollection.allViewDescriptors.forEach(viewDescriptor => { + const sourceViewId = this.extractSourceViewIdFromContainerId(viewContainer.id); + const containerLocation = this.viewContainersRegistry.getViewContainerLocation(viewContainer); + this.cachedViewInfo.set(viewDescriptor.id, { + containerId: viewContainer.id, + location: containerLocation, + sourceViewId: sourceViewId + }); + }); + }); + + this.cachedViewPositionsValue = JSON.stringify([...this.cachedViewInfo]); + } + + private getViewsByContainer(viewContainer: ViewContainer): IViewDescriptor[] { + const result = this.viewsRegistry.getViews(viewContainer).filter(viewDescriptor => { + const cachedContainer = this.cachedViewInfo.get(viewDescriptor.id)?.containerId || viewContainer.id; + return cachedContainer === viewContainer.id; + }); + + for (const [viewId, containerInfo] of this.cachedViewInfo.entries()) { + if (!containerInfo || containerInfo.containerId !== viewContainer.id) { + continue; + } + + if (this.viewsRegistry.getViewContainer(viewId) === viewContainer) { + continue; + } + + const viewDescriptor = this.viewsRegistry.getView(viewId); + if (viewDescriptor) { + result.push(viewDescriptor); + } + } + + return result; + } + + private onDidRegisterViewContainer(viewContainer: ViewContainer): void { + this.getOrRegisterViewDescriptorCollection(viewContainer); + } + + private getOrRegisterViewDescriptorCollection(viewContainer: ViewContainer): ViewDescriptorCollection { + let viewDescriptorCollection = this.viewDescriptorCollections.get(viewContainer)?.viewDescriptorCollection; + + if (!viewDescriptorCollection) { + const disposables = new DisposableStore(); + viewDescriptorCollection = disposables.add(new ViewDescriptorCollection(this.contextKeyService)); + + this.onDidChangeActiveViews({ added: viewDescriptorCollection.activeViewDescriptors, removed: [] }); + viewDescriptorCollection.onDidChangeActiveViews(changed => this.onDidChangeActiveViews(changed), this, disposables); + + this.viewDescriptorCollections.set(viewContainer, { viewDescriptorCollection, disposable: disposables }); + + const viewsToRegister = this.getViewsByContainer(viewContainer); + if (viewsToRegister.length) { + this.addViews(viewContainer, viewsToRegister); + } + } + + return viewDescriptorCollection; + } + + private onDidDeregisterViewContainer(viewContainer: ViewContainer): void { + const viewDescriptorCollectionItem = this.viewDescriptorCollections.get(viewContainer); + if (viewDescriptorCollectionItem) { + viewDescriptorCollectionItem.disposable.dispose(); + this.viewDescriptorCollections.delete(viewContainer); + } + } + + private onDidChangeActiveViews({ added, removed }: { added: IViewDescriptor[], removed: IViewDescriptor[]; }): void { + added.forEach(viewDescriptor => this.getOrCreateActiveViewContextKey(viewDescriptor).set(true)); + removed.forEach(viewDescriptor => this.getOrCreateActiveViewContextKey(viewDescriptor).set(false)); + } + + private addViews(container: ViewContainer, views: IViewDescriptor[]): void { + this.getViewDescriptors(container).addViews(views); + } + + private removeViews(container: ViewContainer, views: IViewDescriptor[]): void { + const viewDescriptorCollection = this.getViewDescriptors(container); + viewDescriptorCollection.removeViews(views); + } + + private getOrCreateActiveViewContextKey(viewDescriptor: IViewDescriptor): IContextKey { + const activeContextKeyId = `${viewDescriptor.id}.active`; + let contextKey = this.activeViewContextKeys.get(activeContextKeyId); + if (!contextKey) { + contextKey = new RawContextKey(activeContextKeyId, false).bindTo(this.contextKeyService); + this.activeViewContextKeys.set(activeContextKeyId, contextKey); + } + return contextKey; + } +} + +registerSingleton(IViewDescriptorService, ViewDescriptorService); diff --git a/src/vs/workbench/test/browser/parts/views/views.test.ts b/src/vs/workbench/test/browser/parts/views/views.test.ts index 49502d7bec9..926536d805f 100644 --- a/src/vs/workbench/test/browser/parts/views/views.test.ts +++ b/src/vs/workbench/test/browser/parts/views/views.test.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { ContributableViewsModel, ViewDescriptorService, IViewState } from 'vs/workbench/browser/parts/views/views'; +import { ContributableViewsModel, IViewState } from 'vs/workbench/browser/parts/views/views'; import { IViewsRegistry, IViewDescriptor, IViewContainersRegistry, Extensions as ViewContainerExtensions, IViewDescriptorService, ViewContainerLocation } from 'vs/workbench/common/views'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { move } from 'vs/base/common/arrays'; @@ -15,6 +15,7 @@ import { TestInstantiationService } from 'vs/platform/instantiation/test/common/ import { ContextKeyService } from 'vs/platform/contextkey/browser/contextKeyService'; import sinon = require('sinon'); import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; +import { ViewDescriptorService } from 'vs/workbench/services/views/browser/viewDescriptorService'; const container = Registry.as(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer({ id: 'test', name: 'test', ctorDescriptor: new SyncDescriptor({}) }, ViewContainerLocation.Sidebar); const ViewsRegistry = Registry.as(ViewContainerExtensions.ViewsRegistry); diff --git a/src/vs/workbench/workbench.common.main.ts b/src/vs/workbench/workbench.common.main.ts index ab49435a19b..cecac51d062 100644 --- a/src/vs/workbench/workbench.common.main.ts +++ b/src/vs/workbench/workbench.common.main.ts @@ -84,6 +84,7 @@ import 'vs/workbench/services/path/common/remotePathService'; import 'vs/workbench/services/remote/common/remoteExplorerService'; import 'vs/workbench/services/workingCopy/common/workingCopyService'; import 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; +import 'vs/workbench/services/views/browser/viewDescriptorService'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { ExtensionGalleryService } from 'vs/platform/extensionManagement/common/extensionGalleryService'; From 7ba75b66c2698789277f29437b1ded595697757f Mon Sep 17 00:00:00 2001 From: SteVen Batten Date: Fri, 17 Jan 2020 11:25:28 -0800 Subject: [PATCH 027/801] remove reliance on encoding src view id --- .../views/browser/viewDescriptorService.ts | 23 +++++-------------- 1 file changed, 6 insertions(+), 17 deletions(-) diff --git a/src/vs/workbench/services/views/browser/viewDescriptorService.ts b/src/vs/workbench/services/views/browser/viewDescriptorService.ts index 051d84b4a22..d9004015f7d 100644 --- a/src/vs/workbench/services/views/browser/viewDescriptorService.ts +++ b/src/vs/workbench/services/views/browser/viewDescriptorService.ts @@ -195,7 +195,7 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor private readonly viewContainersRegistry: IViewContainersRegistry; private cachedViewInfo: Map; - + private generatedContainerSourceViewIds: Map; private _cachedViewPositionsValue: string | undefined; private get cachedViewPositionsValue(): string { @@ -225,6 +225,7 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor this.viewContainersRegistry = Registry.as(ViewExtensions.ViewContainersRegistry); this.viewsRegistry = Registry.as(ViewExtensions.ViewsRegistry); + this.generatedContainerSourceViewIds = new Map(); this.cachedViewInfo = this.getCachedViewPositions(); @@ -442,21 +443,9 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor // Generated Container Id Format // {Common Prefix}.{Uniqueness Id}.{Source View Id} private generateContainerIdFromSourceViewId(viewId: string, location: ViewContainerLocation): string { - return `${ViewDescriptorService.COMMON_CONTAINER_ID_PREFIX}.${location === ViewContainerLocation.Panel ? 'panel' : 'sidebar'}.${viewId}`; - } - - private extractSourceViewIdFromContainerId(containerId: string): string | undefined { - if (!containerId.startsWith(ViewDescriptorService.COMMON_CONTAINER_ID_PREFIX)) { - return undefined; - } - - // Remove the common prefix for generated container ids - const prefixRemoved = containerId.substr(ViewDescriptorService.COMMON_CONTAINER_ID_PREFIX.length + 1); - - // Remove uniqueness section - const uniquenessRemoved = prefixRemoved.substr(prefixRemoved.indexOf('.') + 1); - - return uniquenessRemoved; + const result = `${ViewDescriptorService.COMMON_CONTAINER_ID_PREFIX}.${location === ViewContainerLocation.Panel ? 'panel' : 'sidebar'}.${viewId}`; + this.generatedContainerSourceViewIds.set(result, viewId); + return result; } private getStoredCachedViewPositionsValue(): string { @@ -471,7 +460,7 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor this.viewContainersRegistry.all.forEach(viewContainer => { const viewDescriptorCollection = this.getViewDescriptors(viewContainer); viewDescriptorCollection.allViewDescriptors.forEach(viewDescriptor => { - const sourceViewId = this.extractSourceViewIdFromContainerId(viewContainer.id); + const sourceViewId = this.generatedContainerSourceViewIds.get(viewContainer.id); const containerLocation = this.viewContainersRegistry.getViewContainerLocation(viewContainer); this.cachedViewInfo.set(viewDescriptor.id, { containerId: viewContainer.id, From 6e06fb1acbe726a71142a7d20e68d44fa6cd4ffc Mon Sep 17 00:00:00 2001 From: SteVen Batten Date: Fri, 17 Jan 2020 15:36:57 -0800 Subject: [PATCH 028/801] check if undefined --- .../workbench/services/views/browser/viewDescriptorService.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/services/views/browser/viewDescriptorService.ts b/src/vs/workbench/services/views/browser/viewDescriptorService.ts index d9004015f7d..72e60eb9db6 100644 --- a/src/vs/workbench/services/views/browser/viewDescriptorService.ts +++ b/src/vs/workbench/services/views/browser/viewDescriptorService.ts @@ -290,7 +290,7 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor } // check if we should generate this container - if (containerInfo.sourceViewId && containerInfo.location) { + if (containerInfo.sourceViewId && containerInfo.location !== undefined) { const sourceView = this.viewsRegistry.getView(containerInfo.sourceViewId); if (sourceView) { From d03d84f5a6aeb1d0b6196c60a3549bf720f6f9b8 Mon Sep 17 00:00:00 2001 From: Gabriel DeBacker Date: Tue, 21 Jan 2020 08:10:40 -0800 Subject: [PATCH 029/801] Throw all the time without additional parameter --- .../browser/extensions.contribution.ts | 32 +++++-------------- 1 file changed, 8 insertions(+), 24 deletions(-) diff --git a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts index 08e0ec52ccb..5f0ea863439 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts @@ -228,20 +228,14 @@ CommandsRegistry.registerCommand({ description: localize('workbench.extensions.installExtension.description', "Install the given extension"), args: [ { - name: localize('workbench.extensions.installExtension.arg.name', "Extension id or VSIX resource uri"), + name: localize('workbench.extensions.installExtension.extensionId.name', "Extension id or VSIX resource uri"), schema: { 'type': ['object', 'string'] } - }, - { - name: localize('workbench.extensions.installExtension.arg.throwOnFailure', "Indicates whether to re-throw any exception as well as log it"), - schema: { - 'type': 'boolean' - } } ] }, - handler: async (accessor, extensionId: string | UriComponents, throwOnFailure?: boolean) => { + handler: async (accessor, extensionId: string | UriComponents) => { const extensionManagementService = accessor.get(IExtensionManagementService); const extensionGalleryService = accessor.get(IExtensionGalleryService); try { @@ -258,9 +252,7 @@ CommandsRegistry.registerCommand({ } } catch (e) { onUnexpectedError(e); - if (throwOnFailure) { - throw e; - } + throw e; } } }); @@ -271,26 +263,20 @@ CommandsRegistry.registerCommand({ description: localize('workbench.extensions.uninstallExtension.description', "Uninstall the given extension"), args: [ { - name: localize('workbench.extensions.uninstallExtension.arg.name', "Id of the extension to uninstall"), + name: localize('workbench.extensions.uninstallExtension.extensionId.name', "Id of the extension to uninstall"), schema: { 'type': 'string' } - }, - { - name: localize('workbench.extensions.uninstallExtension.arg.throwOnFailure', "Indicates whether to re-throw any exception as well as log it"), - schema: { - 'type': 'boolean' - } } ] }, - handler: async (accessor, id: string, throwOnFailure?: boolean) => { - if (!id) { + handler: async (accessor, extensionId: string) => { + if (!extensionId) { throw new Error(localize('id required', "Extension id required.")); } const extensionManagementService = accessor.get(IExtensionManagementService); const installed = await extensionManagementService.getInstalled(ExtensionType.User); - const [extensionToUninstall] = installed.filter(e => areSameExtensions(e.identifier, { id })); + const [extensionToUninstall] = installed.filter(e => areSameExtensions(e.identifier, { id: extensionId })); if (!extensionToUninstall) { throw new Error(localize('notInstalled', "Extension '{0}' is not installed. Make sure you use the full extension ID, including the publisher, e.g.: ms-vscode.csharp.", id)); } @@ -299,9 +285,7 @@ CommandsRegistry.registerCommand({ await extensionManagementService.uninstall(extensionToUninstall, true); } catch (e) { onUnexpectedError(e); - if (throwOnFailure) { - throw e; - } + throw e; } } }); From 136e1b07e91ec792cc8a4af9d2baeb069dc821d3 Mon Sep 17 00:00:00 2001 From: Gabriel DeBacker Date: Tue, 21 Jan 2020 09:20:35 -0800 Subject: [PATCH 030/801] Forgot one rename --- .../contrib/extensions/browser/extensions.contribution.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts index 5f0ea863439..cb6b40bc536 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts @@ -278,7 +278,7 @@ CommandsRegistry.registerCommand({ const installed = await extensionManagementService.getInstalled(ExtensionType.User); const [extensionToUninstall] = installed.filter(e => areSameExtensions(e.identifier, { id: extensionId })); if (!extensionToUninstall) { - throw new Error(localize('notInstalled', "Extension '{0}' is not installed. Make sure you use the full extension ID, including the publisher, e.g.: ms-vscode.csharp.", id)); + throw new Error(localize('notInstalled', "Extension '{0}' is not installed. Make sure you use the full extension ID, including the publisher, e.g.: ms-vscode.csharp.", extensionId)); } try { From 18ec86253b5c1e185cedd102e00a3a7bc48e7747 Mon Sep 17 00:00:00 2001 From: SteVen Batten Date: Tue, 21 Jan 2020 11:29:51 -0800 Subject: [PATCH 031/801] remove search view moving for separate PR --- src/vs/workbench/contrib/search/browser/search.contribution.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/search/browser/search.contribution.ts b/src/vs/workbench/contrib/search/browser/search.contribution.ts index d9d215a0d4f..e54aa07912a 100644 --- a/src/vs/workbench/contrib/search/browser/search.contribution.ts +++ b/src/vs/workbench/contrib/search/browser/search.contribution.ts @@ -553,7 +553,7 @@ class RegisterSearchViewContribution implements IWorkbenchContribution { } } else { Registry.as(PanelExtensions.Panels).deregisterPanel(PANEL_ID); - viewsRegistry.registerViews([{ id: VIEW_ID, name: nls.localize('search', "Search"), ctorDescriptor: new SyncDescriptor(SearchView, [SearchViewPosition.SideBar]), canToggleVisibility: false, canMoveView: true }], viewContainer); + viewsRegistry.registerViews([{ id: VIEW_ID, name: nls.localize('search', "Search"), ctorDescriptor: new SyncDescriptor(SearchView, [SearchViewPosition.SideBar]), canToggleVisibility: false }], viewContainer); if (open) { viewletService.openViewlet(VIEWLET_ID); } From 7f42ea140da902bad280e90ffc5d136465829726 Mon Sep 17 00:00:00 2001 From: SteVen Batten Date: Wed, 22 Jan 2020 17:52:17 -0800 Subject: [PATCH 032/801] first round feedback --- .../api/browser/viewsExtensionPoint.ts | 7 +-- .../browser/parts/views/viewPaneContainer.ts | 11 ++-- .../browser/parts/views/viewsViewlet.ts | 7 +-- src/vs/workbench/common/views.ts | 4 +- .../contrib/debug/browser/debugViewlet.ts | 7 +-- .../extensions/browser/extensionsViewlet.ts | 8 +-- .../contrib/files/browser/explorerViewlet.ts | 7 +-- .../contrib/remote/browser/remote.ts | 7 +-- .../contrib/scm/browser/scmViewlet.ts | 7 +-- .../contrib/search/browser/searchViewlet.ts | 7 +-- .../views/browser/viewDescriptorService.ts | 52 ++++++------------- 11 files changed, 56 insertions(+), 68 deletions(-) diff --git a/src/vs/workbench/api/browser/viewsExtensionPoint.ts b/src/vs/workbench/api/browser/viewsExtensionPoint.ts index e198a29483d..2d80611c62c 100644 --- a/src/vs/workbench/api/browser/viewsExtensionPoint.ts +++ b/src/vs/workbench/api/browser/viewsExtensionPoint.ts @@ -8,7 +8,7 @@ import { forEach } from 'vs/base/common/collections'; import { IJSONSchema } from 'vs/base/common/jsonSchema'; import * as resources from 'vs/base/common/resources'; import { ExtensionMessageCollector, ExtensionsRegistry, IExtensionPoint, IExtensionPointUser } from 'vs/workbench/services/extensions/common/extensionsRegistry'; -import { ViewContainer, IViewsRegistry, ITreeViewDescriptor, IViewContainersRegistry, Extensions as ViewContainerExtensions, TEST_VIEW_CONTAINER_ID, IViewDescriptor, ViewContainerLocation, IViewDescriptorService } from 'vs/workbench/common/views'; +import { ViewContainer, IViewsRegistry, ITreeViewDescriptor, IViewContainersRegistry, Extensions as ViewContainerExtensions, TEST_VIEW_CONTAINER_ID, IViewDescriptor, ViewContainerLocation, IViewDescriptorService, IViewsService } from 'vs/workbench/common/views'; import { CustomTreeViewPane, CustomTreeView } from 'vs/workbench/browser/parts/views/customView'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { coalesce, } from 'vs/base/common/arrays'; @@ -325,9 +325,10 @@ class ViewsExtensionHandler implements IWorkbenchContribution { @IThemeService themeService: IThemeService, @IContextMenuService contextMenuService: IContextMenuService, @IExtensionService extensionService: IExtensionService, - @IViewDescriptorService viewDescriptorService: IViewDescriptorService + @IViewDescriptorService viewDescriptorService: IViewDescriptorService, + @IViewsService viewsService: IViewsService ) { - super(id, `${id}.state`, { mergeViewWithContainerWhenSingleView: true }, instantiationService, configurationService, layoutService, contextMenuService, telemetryService, extensionService, themeService, storageService, contextService, viewDescriptorService); + super(id, `${id}.state`, { mergeViewWithContainerWhenSingleView: true }, instantiationService, configurationService, layoutService, contextMenuService, telemetryService, extensionService, themeService, storageService, contextService, viewDescriptorService, viewsService); } } diff --git a/src/vs/workbench/browser/parts/views/viewPaneContainer.ts b/src/vs/workbench/browser/parts/views/viewPaneContainer.ts index e264cb74b0a..b3a3e019cd0 100644 --- a/src/vs/workbench/browser/parts/views/viewPaneContainer.ts +++ b/src/vs/workbench/browser/parts/views/viewPaneContainer.ts @@ -308,7 +308,8 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer { @IThemeService protected themeService: IThemeService, @IStorageService protected storageService: IStorageService, @IWorkspaceContextService protected contextService: IWorkspaceContextService, - @IViewDescriptorService protected viewDescriptorService: IViewDescriptorService + @IViewDescriptorService protected viewDescriptorService: IViewDescriptorService, + @IViewsService protected viewsService: IViewsService ) { super(id, themeService, storageService); @@ -675,12 +676,8 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer { protected moveView(viewDescriptor: IViewDescriptor): void { const viewContainerRegistry = Registry.as(ViewsExtensions.ViewContainersRegistry); const currentLocation = viewContainerRegistry.getViewContainerLocation(this.viewContainer); - this.viewDescriptorService.moveView(viewDescriptor, currentLocation === ViewContainerLocation.Sidebar ? ViewContainerLocation.Panel : ViewContainerLocation.Sidebar); - - this.instantiationService.invokeFunction(accessor => { - const viewsService = accessor.get(IViewsService); - viewsService.openView(viewDescriptor.id, true); - }); + this.viewDescriptorService.moveViewToLocation(viewDescriptor, currentLocation === ViewContainerLocation.Sidebar ? ViewContainerLocation.Panel : ViewContainerLocation.Sidebar); + this.viewsService.openView(viewDescriptor.id, true); } private addPane(pane: ViewPane, size: number, index = this.paneItems.length - 1): void { diff --git a/src/vs/workbench/browser/parts/views/viewsViewlet.ts b/src/vs/workbench/browser/parts/views/viewsViewlet.ts index 9bd58721fe8..a1fb3581236 100644 --- a/src/vs/workbench/browser/parts/views/viewsViewlet.ts +++ b/src/vs/workbench/browser/parts/views/viewsViewlet.ts @@ -8,7 +8,7 @@ import { IAction } from 'vs/base/common/actions'; import { Separator } from 'vs/base/browser/ui/actionbar/actionbar'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; -import { IViewDescriptor, IViewDescriptorService } from 'vs/workbench/common/views'; +import { IViewDescriptor, IViewDescriptorService, IViewsService } from 'vs/workbench/common/views'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; @@ -44,10 +44,11 @@ export abstract class FilterViewPaneContainer extends ViewPaneContainer { @IContextMenuService contextMenuService: IContextMenuService, @IExtensionService extensionService: IExtensionService, @IWorkspaceContextService contextService: IWorkspaceContextService, - @IViewDescriptorService viewDescriptorService: IViewDescriptorService + @IViewDescriptorService viewDescriptorService: IViewDescriptorService, + @IViewsService viewsService: IViewsService ) { - super(viewletId, `${viewletId}.state`, { mergeViewWithContainerWhenSingleView: false }, instantiationService, configurationService, layoutService, contextMenuService, telemetryService, extensionService, themeService, storageService, contextService, viewDescriptorService); + super(viewletId, `${viewletId}.state`, { mergeViewWithContainerWhenSingleView: false }, instantiationService, configurationService, layoutService, contextMenuService, telemetryService, extensionService, themeService, storageService, contextService, viewDescriptorService, viewsService); this._register(onDidChangeFilterValue(newFilterValue => { this.filterValue = newFilterValue; this.onFilterChanged(newFilterValue); diff --git a/src/vs/workbench/common/views.ts b/src/vs/workbench/common/views.ts index 5397c2a7dba..bb4ae89c1a2 100644 --- a/src/vs/workbench/common/views.ts +++ b/src/vs/workbench/common/views.ts @@ -365,9 +365,9 @@ export interface IViewDescriptorService { _serviceBrand: undefined; - moveView(view: IViewDescriptor, location: ViewContainerLocation): void; + moveViewToLocation(view: IViewDescriptor, location: ViewContainerLocation): void; - moveViews(views: IViewDescriptor[], viewContainer: ViewContainer): void; + moveViewsToContainer(views: IViewDescriptor[], viewContainer: ViewContainer): void; getViewDescriptors(container: ViewContainer): IViewDescriptorCollection; diff --git a/src/vs/workbench/contrib/debug/browser/debugViewlet.ts b/src/vs/workbench/contrib/debug/browser/debugViewlet.ts index 7011327d8da..c775ca71f69 100644 --- a/src/vs/workbench/contrib/debug/browser/debugViewlet.ts +++ b/src/vs/workbench/contrib/debug/browser/debugViewlet.ts @@ -33,7 +33,7 @@ import { INotificationService } from 'vs/platform/notification/common/notificati import { TogglePanelAction } from 'vs/workbench/browser/panel'; import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; import { StartView } from 'vs/workbench/contrib/debug/browser/startView'; -import { IViewDescriptorService } from 'vs/workbench/common/views'; +import { IViewDescriptorService, IViewsService } from 'vs/workbench/common/views'; export class DebugViewPaneContainer extends ViewPaneContainer { @@ -61,9 +61,10 @@ export class DebugViewPaneContainer extends ViewPaneContainer { @IMenuService private readonly menuService: IMenuService, @IContextKeyService private readonly contextKeyService: IContextKeyService, @INotificationService private readonly notificationService: INotificationService, - @IViewDescriptorService viewDescriptorService: IViewDescriptorService + @IViewDescriptorService viewDescriptorService: IViewDescriptorService, + @IViewsService viewsService: IViewsService ) { - super(VIEWLET_ID, `${VIEWLET_ID}.state`, { mergeViewWithContainerWhenSingleView: true }, instantiationService, configurationService, layoutService, contextMenuService, telemetryService, extensionService, themeService, storageService, contextService, viewDescriptorService); + super(VIEWLET_ID, `${VIEWLET_ID}.state`, { mergeViewWithContainerWhenSingleView: true }, instantiationService, configurationService, layoutService, contextMenuService, telemetryService, extensionService, themeService, storageService, contextService, viewDescriptorService, viewsService); this._register(this.debugService.onDidChangeState(state => this.onDebugServiceStateChange(state))); this._register(this.debugService.onDidNewSession(() => this.updateToolBar())); diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts b/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts index f0741bcf4f5..bb179927b0f 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts @@ -35,7 +35,7 @@ import Severity from 'vs/base/common/severity'; import { IActivityService, NumberBadge } from 'vs/workbench/services/activity/common/activity'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { IViewsRegistry, IViewDescriptor, Extensions, ViewContainer, IViewContainersRegistry, IViewDescriptorService } from 'vs/workbench/common/views'; +import { IViewsRegistry, IViewDescriptor, Extensions, ViewContainer, IViewContainersRegistry, IViewDescriptorService, IViewsService } from 'vs/workbench/common/views'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; import { IContextKeyService, ContextKeyExpr, RawContextKey, IContextKey } from 'vs/platform/contextkey/common/contextkey'; @@ -356,9 +356,11 @@ export class ExtensionsViewPaneContainer extends ViewPaneContainer implements IE @IContextKeyService contextKeyService: IContextKeyService, @IContextMenuService contextMenuService: IContextMenuService, @IExtensionService extensionService: IExtensionService, - @IViewDescriptorService viewDescriptorService: IViewDescriptorService + @IViewDescriptorService viewDescriptorService: IViewDescriptorService, + @IViewsService viewsService: IViewsService + ) { - super(VIEWLET_ID, `${VIEWLET_ID}.state`, { mergeViewWithContainerWhenSingleView: true }, instantiationService, configurationService, layoutService, contextMenuService, telemetryService, extensionService, themeService, storageService, contextService, viewDescriptorService); + super(VIEWLET_ID, `${VIEWLET_ID}.state`, { mergeViewWithContainerWhenSingleView: true }, instantiationService, configurationService, layoutService, contextMenuService, telemetryService, extensionService, themeService, storageService, contextService, viewDescriptorService, viewsService); this.searchDelayer = new Delayer(500); this.nonEmptyWorkspaceContextKey = NonEmptyWorkspaceContext.bindTo(contextKeyService); diff --git a/src/vs/workbench/contrib/files/browser/explorerViewlet.ts b/src/vs/workbench/contrib/files/browser/explorerViewlet.ts index cc45efefec9..a6066e3843a 100644 --- a/src/vs/workbench/contrib/files/browser/explorerViewlet.ts +++ b/src/vs/workbench/contrib/files/browser/explorerViewlet.ts @@ -20,7 +20,7 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey'; import { IThemeService } from 'vs/platform/theme/common/themeService'; -import { IViewsRegistry, IViewDescriptor, Extensions, ViewContainer, IViewContainersRegistry, ViewContainerLocation, IViewDescriptorService } from 'vs/workbench/common/views'; +import { IViewsRegistry, IViewDescriptor, Extensions, ViewContainer, IViewContainersRegistry, ViewContainerLocation, IViewDescriptorService, IViewsService } from 'vs/workbench/common/views'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { Disposable } from 'vs/base/common/lifecycle'; import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; @@ -164,10 +164,11 @@ export class ExplorerViewPaneContainer extends ViewPaneContainer { @IThemeService themeService: IThemeService, @IContextMenuService contextMenuService: IContextMenuService, @IExtensionService extensionService: IExtensionService, - @IViewDescriptorService viewDescriptorService: IViewDescriptorService + @IViewDescriptorService viewDescriptorService: IViewDescriptorService, + @IViewsService viewsService: IViewsService ) { - super(VIEWLET_ID, ExplorerViewPaneContainer.EXPLORER_VIEWS_STATE, { mergeViewWithContainerWhenSingleView: true }, instantiationService, configurationService, layoutService, contextMenuService, telemetryService, extensionService, themeService, storageService, contextService, viewDescriptorService); + super(VIEWLET_ID, ExplorerViewPaneContainer.EXPLORER_VIEWS_STATE, { mergeViewWithContainerWhenSingleView: true }, instantiationService, configurationService, layoutService, contextMenuService, telemetryService, extensionService, themeService, storageService, contextService, viewDescriptorService, viewsService); this.viewletVisibleContextKey = ExplorerViewletVisibleContext.bindTo(contextKeyService); diff --git a/src/vs/workbench/contrib/remote/browser/remote.ts b/src/vs/workbench/contrib/remote/browser/remote.ts index 6ba8b0a385d..cd2f14e7fdd 100644 --- a/src/vs/workbench/contrib/remote/browser/remote.ts +++ b/src/vs/workbench/contrib/remote/browser/remote.ts @@ -19,7 +19,7 @@ import { IExtensionService } from 'vs/workbench/services/extensions/common/exten import { FilterViewPaneContainer } from 'vs/workbench/browser/parts/views/viewsViewlet'; import { VIEWLET_ID } from 'vs/workbench/contrib/remote/common/remote.contribution'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; -import { IViewDescriptor, IViewsRegistry, Extensions, ViewContainerLocation, IViewContainersRegistry, IViewDescriptorService } from 'vs/workbench/common/views'; +import { IViewDescriptor, IViewsRegistry, Extensions, ViewContainerLocation, IViewContainersRegistry, IViewDescriptorService, IViewsService } from 'vs/workbench/common/views'; import { Registry } from 'vs/platform/registry/common/platform'; import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { IOpenerService } from 'vs/platform/opener/common/opener'; @@ -442,9 +442,10 @@ export class RemoteViewPaneContainer extends FilterViewPaneContainer implements @IRemoteExplorerService private readonly remoteExplorerService: IRemoteExplorerService, @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService, @IContextKeyService private readonly contextKeyService: IContextKeyService, - @IViewDescriptorService viewDescriptorService: IViewDescriptorService + @IViewDescriptorService viewDescriptorService: IViewDescriptorService, + @IViewsService viewsService: IViewsService ) { - super(VIEWLET_ID, remoteExplorerService.onDidChangeTargetType, configurationService, layoutService, telemetryService, storageService, instantiationService, themeService, contextMenuService, extensionService, contextService, viewDescriptorService); + super(VIEWLET_ID, remoteExplorerService.onDidChangeTargetType, configurationService, layoutService, telemetryService, storageService, instantiationService, themeService, contextMenuService, extensionService, contextService, viewDescriptorService, viewsService); this.addConstantViewDescriptors([this.helpPanelDescriptor]); remoteHelpExtPoint.setHandler((extensions) => { let helpInformation: HelpInformation[] = []; diff --git a/src/vs/workbench/contrib/scm/browser/scmViewlet.ts b/src/vs/workbench/contrib/scm/browser/scmViewlet.ts index cb21f9aa0b9..ec7f450bc18 100644 --- a/src/vs/workbench/contrib/scm/browser/scmViewlet.ts +++ b/src/vs/workbench/contrib/scm/browser/scmViewlet.ts @@ -25,7 +25,7 @@ import { INotificationService } from 'vs/platform/notification/common/notificati import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; -import { IViewsRegistry, Extensions, IViewDescriptorService } from 'vs/workbench/common/views'; +import { IViewsRegistry, Extensions, IViewDescriptorService, IViewsService } from 'vs/workbench/common/views'; import { Registry } from 'vs/platform/registry/common/platform'; import { nextTick } from 'vs/base/common/process'; import { RepositoryPane, RepositoryViewDescriptor } from 'vs/workbench/contrib/scm/browser/repositoryPane'; @@ -98,9 +98,10 @@ export class SCMViewPaneContainer extends ViewPaneContainer implements IViewMode @IExtensionService extensionService: IExtensionService, @IWorkspaceContextService protected contextService: IWorkspaceContextService, @IContextKeyService contextKeyService: IContextKeyService, - @IViewDescriptorService viewDescriptorService: IViewDescriptorService + @IViewDescriptorService viewDescriptorService: IViewDescriptorService, + @IViewsService viewsService: IViewsService ) { - super(VIEWLET_ID, SCMViewPaneContainer.STATE_KEY, { mergeViewWithContainerWhenSingleView: true }, instantiationService, configurationService, layoutService, contextMenuService, telemetryService, extensionService, themeService, storageService, contextService, viewDescriptorService); + super(VIEWLET_ID, SCMViewPaneContainer.STATE_KEY, { mergeViewWithContainerWhenSingleView: true }, instantiationService, configurationService, layoutService, contextMenuService, telemetryService, extensionService, themeService, storageService, contextService, viewDescriptorService, viewsService); this.menus = instantiationService.createInstance(SCMMenus, undefined); this._register(this.menus.onDidChangeTitle(this.updateTitleArea, this)); diff --git a/src/vs/workbench/contrib/search/browser/searchViewlet.ts b/src/vs/workbench/contrib/search/browser/searchViewlet.ts index 2754b71d7b8..0bfa4f2f10f 100644 --- a/src/vs/workbench/contrib/search/browser/searchViewlet.ts +++ b/src/vs/workbench/contrib/search/browser/searchViewlet.ts @@ -15,7 +15,7 @@ import { IExtensionService } from 'vs/workbench/services/extensions/common/exten import { VIEWLET_ID, VIEW_ID } from 'vs/workbench/services/search/common/search'; import { SearchView } from 'vs/workbench/contrib/search/browser/searchView'; import { ViewPaneContainer } from 'vs/workbench/browser/parts/views/viewPaneContainer'; -import { IViewDescriptorService } from 'vs/workbench/common/views'; +import { IViewDescriptorService, IViewsService } from 'vs/workbench/common/views'; export class SearchViewPaneContainer extends ViewPaneContainer { @@ -30,9 +30,10 @@ export class SearchViewPaneContainer extends ViewPaneContainer { @IThemeService themeService: IThemeService, @IContextMenuService contextMenuService: IContextMenuService, @IExtensionService extensionService: IExtensionService, - @IViewDescriptorService viewDescriptorService: IViewDescriptorService + @IViewDescriptorService viewDescriptorService: IViewDescriptorService, + @IViewsService viewsService: IViewsService ) { - super(VIEWLET_ID, `${VIEWLET_ID}.state`, { mergeViewWithContainerWhenSingleView: true, donotShowContainerTitleWhenMergedWithContainer: true }, instantiationService, configurationService, layoutService, contextMenuService, telemetryService, extensionService, themeService, storageService, contextService, viewDescriptorService); + super(VIEWLET_ID, `${VIEWLET_ID}.state`, { mergeViewWithContainerWhenSingleView: true, donotShowContainerTitleWhenMergedWithContainer: true }, instantiationService, configurationService, layoutService, contextMenuService, telemetryService, extensionService, themeService, storageService, contextService, viewDescriptorService, viewsService); } getSearchView(): SearchView | undefined { diff --git a/src/vs/workbench/services/views/browser/viewDescriptorService.ts b/src/vs/workbench/services/views/browser/viewDescriptorService.ts index 72e60eb9db6..b865c810f26 100644 --- a/src/vs/workbench/services/views/browser/viewDescriptorService.ts +++ b/src/vs/workbench/services/views/browser/viewDescriptorService.ts @@ -10,13 +10,6 @@ import { IExtensionService } from 'vs/workbench/services/extensions/common/exten import { Registry } from 'vs/platform/registry/common/platform'; import { toDisposable, DisposableStore, Disposable, IDisposable } from 'vs/base/common/lifecycle'; import { ViewPaneContainer } from 'vs/workbench/browser/parts/views/viewPaneContainer'; -import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; -import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { IThemeService } from 'vs/platform/theme/common/themeService'; -import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { Event, Emitter } from 'vs/base/common/event'; @@ -279,10 +272,6 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor private onDidRegisterExtensions(): void { for (const [viewId, containerInfo] of this.cachedViewInfo.entries()) { - if (!containerInfo) { - continue; - } - const containerId = containerInfo.containerId; // check if cached view container is registered if (this.viewContainersRegistry.get(containerId)) { @@ -301,8 +290,9 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor // check if view has been registered to default location const viewContainer = this.viewsRegistry.getViewContainer(viewId); - if (viewContainer) { - this.addViews(viewContainer, [this.viewsRegistry.getView(viewId)!]); + const viewDescriptor = this.viewsRegistry.getView(viewId); + if (viewContainer && viewDescriptor) { + this.addViews(viewContainer, [viewDescriptor]); } } @@ -353,7 +343,7 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor return this.getOrRegisterViewDescriptorCollection(container); } - moveView(view: IViewDescriptor, location: ViewContainerLocation): void { + moveViewToLocation(view: IViewDescriptor, location: ViewContainerLocation): void { const previousContainer = this.getViewContainer(view.id); if (previousContainer && this.viewContainersRegistry.getViewContainerLocation(previousContainer) === location) { return; @@ -364,10 +354,10 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor container = this.registerViewContainerForSingleView(view, location); } - this.moveViews([view], container); + this.moveViewsToContainer([view], container); } - moveViews(views: IViewDescriptor[], viewContainer: ViewContainer): void { + moveViewsToContainer(views: IViewDescriptor[], viewContainer: ViewContainer): void { if (!views.length) { return; } @@ -385,26 +375,9 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor private registerViewContainerForSingleView(sourceView: IViewDescriptor, location: ViewContainerLocation): ViewContainer { const id = this.generateContainerIdFromSourceViewId(sourceView.id, location); - class MovedViewPanelViewPaneContainer extends ViewPaneContainer { - constructor( - @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService, - @ITelemetryService telemetryService: ITelemetryService, - @IWorkspaceContextService protected contextService: IWorkspaceContextService, - @IStorageService protected storageService: IStorageService, - @IConfigurationService configurationService: IConfigurationService, - @IInstantiationService protected instantiationService: IInstantiationService, - @IThemeService themeService: IThemeService, - @IContextMenuService contextMenuService: IContextMenuService, - @IExtensionService extensionService: IExtensionService, - @IViewDescriptorService viewDescriptorService: IViewDescriptorService - ) { - super(id, `${id}.state`, { mergeViewWithContainerWhenSingleView: true }, instantiationService, configurationService, layoutService, contextMenuService, telemetryService, extensionService, themeService, storageService, contextService, viewDescriptorService); - } - } - return this.viewContainersRegistry.registerViewContainer({ id, - ctorDescriptor: new SyncDescriptor(MovedViewPanelViewPaneContainer), + ctorDescriptor: new SyncDescriptor(ViewPaneContainer, [id, `${id}.state`, { mergeViewWithContainerWhenSingleView: true }]), name: sourceView.name, icon: sourceView.containerIcon, hideIfEmpty: true @@ -412,7 +385,16 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor } private getCachedViewPositions(): Map { - return new Map(JSON.parse(this.cachedViewPositionsValue)); + const result = new Map(JSON.parse(this.cachedViewPositionsValue)); + + // Sanitize cache + for (const [viewId, containerInfo] of result.entries()) { + if (!containerInfo) { + result.delete(viewId); + } + } + + return result; } private onDidStorageChange(e: IWorkspaceStorageChangeEvent): void { From 8bb42eaa297d5afbf1cfafeebafef791f67fe347 Mon Sep 17 00:00:00 2001 From: SteVen Batten Date: Wed, 22 Jan 2020 18:59:49 -0800 Subject: [PATCH 033/801] update menu entry logic --- .../workbench/browser/parts/compositeBar.ts | 10 ++++- .../browser/parts/compositeBarActions.ts | 7 +++ .../browser/parts/panel/panelPart.ts | 10 ++--- .../browser/parts/views/viewPaneContainer.ts | 44 +++++++++++-------- 4 files changed, 46 insertions(+), 25 deletions(-) diff --git a/src/vs/workbench/browser/parts/compositeBar.ts b/src/vs/workbench/browser/parts/compositeBar.ts index 70c951cac17..1f3521904e1 100644 --- a/src/vs/workbench/browser/parts/compositeBar.ts +++ b/src/vs/workbench/browser/parts/compositeBar.ts @@ -40,6 +40,7 @@ export interface ICompositeBarOptions { getCompositePinnedAction: (compositeId: string) => Action; getOnCompositeClickAction: (compositeId: string) => Action; getContextMenuActions: () => Action[]; + getContextMenuActionsForCompositeId?: (compositeId: string) => Action[]; openComposite: (compositeId: string) => Promise; getDefaultCompositeId: () => string; hidePart: () => void; @@ -100,7 +101,14 @@ export class CompositeBar extends Widget implements ICompositeBar { return this.compositeOverflowActionViewItem; } const item = this.model.findItem(action.id); - return item && this.instantiationService.createInstance(CompositeActionViewItem, action as ActivityAction, item.pinnedAction, () => this.getContextMenuActions() as Action[], this.options.colors, this.options.icon, this); + return item && this.instantiationService.createInstance( + CompositeActionViewItem, action as ActivityAction, item.pinnedAction, + (compositeId: string) => { return this.options.getContextMenuActionsForCompositeId === undefined ? [] : this.options.getContextMenuActionsForCompositeId(compositeId); }, + () => this.getContextMenuActions() as Action[], + this.options.colors, + this.options.icon, + this + ); }, orientation: this.options.orientation, ariaLabel: nls.localize('activityBarAriaLabel', "Active View Switcher"), diff --git a/src/vs/workbench/browser/parts/compositeBarActions.ts b/src/vs/workbench/browser/parts/compositeBarActions.ts index 1f35a097e1d..61ecc360ea5 100644 --- a/src/vs/workbench/browser/parts/compositeBarActions.ts +++ b/src/vs/workbench/browser/parts/compositeBarActions.ts @@ -463,6 +463,7 @@ export class CompositeActionViewItem extends ActivityActionViewItem { constructor( private compositeActivityAction: ActivityAction, private toggleCompositePinnedAction: Action, + private compositeMenuActionsProvider: (compositeId: string) => ReadonlyArray, private contextMenuActionsProvider: () => ReadonlyArray, colors: (theme: ITheme) => ICompositeBarColors, icon: boolean, @@ -596,6 +597,12 @@ export class CompositeActionViewItem extends ActivityActionViewItem { private showContextMenu(container: HTMLElement): void { const actions: Action[] = [this.toggleCompositePinnedAction]; + + const compositeSpecificActions = this.compositeMenuActionsProvider(this.activity.id); + if (compositeSpecificActions.length) { + actions.push(...compositeSpecificActions); + } + if ((this.compositeActivityAction.activity).extensionId) { actions.push(new Separator()); actions.push(CompositeActionViewItem.manageExtensionAction); diff --git a/src/vs/workbench/browser/parts/panel/panelPart.ts b/src/vs/workbench/browser/parts/panel/panelPart.ts index f5b71ab1813..2e72172e7bb 100644 --- a/src/vs/workbench/browser/parts/panel/panelPart.ts +++ b/src/vs/workbench/browser/parts/panel/panelPart.ts @@ -131,13 +131,13 @@ export class PanelPart extends CompositePart implements IPanelService { getCompositePinnedAction: (compositeId: string) => this.getCompositeActions(compositeId).pinnedAction, getOnCompositeClickAction: (compositeId: string) => this.instantiationService.createInstance(PanelActivityAction, assertIsDefined(this.getPanel(compositeId))), getContextMenuActions: () => [ - ...this.getContextMenuActions(), ...PositionPanelActionConfigs // show the contextual menu item if it is not in that position .filter(({ when }) => contextKeyService.contextMatchesRules(when)) .map(({ id, label }) => this.instantiationService.createInstance(SetPanelPositionAction, id, label)), this.instantiationService.createInstance(TogglePanelAction, TogglePanelAction.ID, localize('hidePanel', "Hide Panel")) ] as Action[], + getContextMenuActionsForCompositeId: (compositeId: string) => this.getContextMenuActionsForCompositeId(compositeId) as Action[], getDefaultCompositeId: () => this.panelRegistry.getDefaultPanelId(), hidePart: () => this.layoutService.setPanelHidden(true), compositeSize: 0, @@ -161,14 +161,14 @@ export class PanelPart extends CompositePart implements IPanelService { this.onDidRegisterPanels([...this.getPanels()]); } - private getContextMenuActions(): readonly IAction[] { - const activePanel = this.getActivePanel(); + private getContextMenuActionsForCompositeId(compositeId: string): readonly IAction[] { + const panel = this.getActivePanel(); - if (!activePanel) { + if (!panel || panel.getId() !== compositeId) { return []; } - return activePanel.getContextMenuActions(); + return panel.getContextMenuActions(); } private onDidRegisterPanels(panels: PanelDescriptor[]): void { diff --git a/src/vs/workbench/browser/parts/views/viewPaneContainer.ts b/src/vs/workbench/browser/parts/views/viewPaneContainer.ts index b3a3e019cd0..20441cdfd09 100644 --- a/src/vs/workbench/browser/parts/views/viewPaneContainer.ts +++ b/src/vs/workbench/browser/parts/views/viewPaneContainer.ts @@ -397,10 +397,13 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer { viewDescriptor = Registry.as(ViewsExtensions.ViewsRegistry).getView(viewId)!; } + const viewContainerRegistry = Registry.as(ViewsExtensions.ViewContainersRegistry); + const currentLocation = viewContainerRegistry.getViewContainerLocation(this.viewContainer); + const result: IAction[] = []; if (viewDescriptor) { - - if (!this.isViewMergedWithContainer()) { + // For now, restrict any additional actions to the sidebar only + if (!this.isViewMergedWithContainer() && currentLocation === ViewContainerLocation.Sidebar) { result.push({ id: `${viewDescriptor.id}.removeView`, label: nls.localize('hideView', "Hide"), @@ -410,28 +413,33 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer { } if (viewDescriptor.canMoveView) { + const newLocation = currentLocation === ViewContainerLocation.Panel ? ViewContainerLocation.Sidebar : ViewContainerLocation.Panel; result.push({ - id: `${viewDescriptor.id}.removeView`, - label: this.isViewMergedWithContainer() ? nls.localize('toggleSpecificViewLocation', "Toggle {0} View Location", viewDescriptor.name) : nls.localize('toggleViewLocation', "Toggle View Location"), + id: `${viewDescriptor.id}.moveView`, + label: newLocation === ViewContainerLocation.Sidebar ? nls.localize('moveViewToSidebar', "Move to Sidebar") : nls.localize('moveViewToPanel', "Move to Panel"), enabled: true, - run: () => this.moveView(viewDescriptor!) + run: () => this.moveView(viewDescriptor!, newLocation) }); } } - const viewToggleActions = this.viewsModel.viewDescriptors.map(viewDescriptor => ({ - id: `${viewDescriptor.id}.toggleVisibility`, - label: viewDescriptor.name, - checked: this.viewsModel.isVisible(viewDescriptor.id), - enabled: viewDescriptor.canToggleVisibility, - run: () => this.toggleViewVisibility(viewDescriptor.id) - })); + // For now, restrict any additional actions to the sidebar only + if (currentLocation === ViewContainerLocation.Sidebar) { + const viewToggleActions = this.viewsModel.viewDescriptors.map(viewDescriptor => ({ + id: `${viewDescriptor.id}.toggleVisibility`, + label: viewDescriptor.name, + checked: this.viewsModel.isVisible(viewDescriptor.id), + enabled: viewDescriptor.canToggleVisibility, + run: () => this.toggleViewVisibility(viewDescriptor.id) + })); - if (result.length && viewToggleActions.length) { - result.push(new Separator()); + if (result.length && viewToggleActions.length) { + result.push(new Separator()); + } + + result.push(...viewToggleActions); } - result.push(...viewToggleActions); return result; } @@ -673,10 +681,8 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer { this.viewsModel.setVisible(viewId, visible); } - protected moveView(viewDescriptor: IViewDescriptor): void { - const viewContainerRegistry = Registry.as(ViewsExtensions.ViewContainersRegistry); - const currentLocation = viewContainerRegistry.getViewContainerLocation(this.viewContainer); - this.viewDescriptorService.moveViewToLocation(viewDescriptor, currentLocation === ViewContainerLocation.Sidebar ? ViewContainerLocation.Panel : ViewContainerLocation.Sidebar); + protected moveView(viewDescriptor: IViewDescriptor, location: ViewContainerLocation): void { + this.viewDescriptorService.moveViewToLocation(viewDescriptor, location); this.viewsService.openView(viewDescriptor.id, true); } From ff6bf7f788fbb12d2c49d2de2cc29319d01e52f0 Mon Sep 17 00:00:00 2001 From: Teja Date: Thu, 23 Jan 2020 16:16:20 +0530 Subject: [PATCH 034/801] update calc node-sass throws a build error were unable to find `Calc` modifying to `calc` runs the build --- src/vs/editor/contrib/find/findWidget.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/editor/contrib/find/findWidget.css b/src/vs/editor/contrib/find/findWidget.css index 8e85d700b4e..4882113bec5 100644 --- a/src/vs/editor/contrib/find/findWidget.css +++ b/src/vs/editor/contrib/find/findWidget.css @@ -13,7 +13,7 @@ transition: transform 200ms linear; padding: 0 4px; box-sizing: border-box; - transform: translateY(Calc(-100% - 10px)); /* shadow (10px) */ + transform: translateY(calc(-100% - 10px)); /* shadow (10px) */ } .monaco-editor .find-widget textarea { From 5ec65bdc87fea3b2a71fa823144dc5df3aaa7c8a Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Thu, 23 Jan 2020 12:14:27 +0100 Subject: [PATCH 035/801] backup - remove discardAllBackups() method This method is actually deleting the entire backup home folder for the workspace and it is possible that this could lead to race conditions when a backup is running at the same time. --- .../backup/electron-browser/backupTracker.ts | 19 +++-------- .../electron-browser/backupTracker.test.ts | 6 ++-- .../services/backup/common/backup.ts | 5 --- .../backup/common/backupFileService.ts | 23 ------------- .../backupFileService.test.ts | 34 ++----------------- .../workbench/test/workbenchTestServices.ts | 4 --- 6 files changed, 10 insertions(+), 81 deletions(-) diff --git a/src/vs/workbench/contrib/backup/electron-browser/backupTracker.ts b/src/vs/workbench/contrib/backup/electron-browser/backupTracker.ts index 087cae06b13..be8f29a2f77 100644 --- a/src/vs/workbench/contrib/backup/electron-browser/backupTracker.ts +++ b/src/vs/workbench/contrib/backup/electron-browser/backupTracker.ts @@ -182,15 +182,14 @@ export class NativeBackupTracker extends BackupTracker implements IWorkbenchCont return true; // veto (save failed or was canceled) } - return this.noVeto(dirtyCount === workingCopies.length ? true /* all */ : workingCopies); // no veto (dirty saved) + return this.noVeto(workingCopies); // no veto (dirty saved) } // Don't Save else if (confirm === ConfirmResult.DONT_SAVE) { - const dirtyCount = this.workingCopyService.dirtyCount; await this.doRevertAllBeforeShutdown(workingCopies); - return this.noVeto(dirtyCount === workingCopies.length ? true /* all */ : workingCopies); // no veto (dirty reverted) + return this.noVeto(workingCopies); // no veto (dirty reverted) } // Cancel @@ -243,7 +242,7 @@ export class NativeBackupTracker extends BackupTracker implements IWorkbenchCont } } - private noVeto(backupsToDiscardOrAll: IWorkingCopy[] | boolean): boolean | Promise { + private noVeto(backupsToDiscard: IWorkingCopy[]): boolean | Promise { if (this.lifecycleService.phase < LifecyclePhase.Restored) { return false; // if editors have not restored, we are not up to speed with backups and thus should not discard them } @@ -252,16 +251,6 @@ export class NativeBackupTracker extends BackupTracker implements IWorkbenchCont return false; // extension development does not track any backups } - if (backupsToDiscardOrAll === true) { - // discard all backups - return this.backupFileService.discardAllBackups().then(() => false, () => false); - } - - if (Array.isArray(backupsToDiscardOrAll)) { - // otherwise, discard individually - return Promise.all(backupsToDiscardOrAll.map(workingCopy => this.backupFileService.discardBackup(workingCopy.resource))).then(() => false, () => false); - } - - return false; + return Promise.all(backupsToDiscard.map(workingCopy => this.backupFileService.discardBackup(workingCopy.resource))).then(() => false, () => false); } } diff --git a/src/vs/workbench/contrib/backup/test/electron-browser/backupTracker.test.ts b/src/vs/workbench/contrib/backup/test/electron-browser/backupTracker.test.ts index d0d0b3b89f0..4ae7f1e7421 100644 --- a/src/vs/workbench/contrib/backup/test/electron-browser/backupTracker.test.ts +++ b/src/vs/workbench/contrib/backup/test/electron-browser/backupTracker.test.ts @@ -279,11 +279,11 @@ suite('BackupTracker', () => { let veto = event.value; if (typeof veto === 'boolean') { - assert.ok(accessor.backupFileService.didDiscardAllBackups); + assert.ok(accessor.backupFileService.discardedBackups.length > 0); assert.ok(!veto); } else { veto = await veto; - assert.ok(accessor.backupFileService.didDiscardAllBackups); + assert.ok(accessor.backupFileService.discardedBackups.length > 0); assert.ok(!veto); } @@ -452,7 +452,7 @@ suite('BackupTracker', () => { accessor.lifecycleService.fireWillShutdown(event); const veto = await (>event.value); - assert.ok(!accessor.backupFileService.didDiscardAllBackups); // When hot exit is set, backups should never be cleaned since the confirm result is cancel + assert.equal(accessor.backupFileService.discardedBackups.length, 0); // When hot exit is set, backups should never be cleaned since the confirm result is cancel assert.equal(veto, shouldVeto); part.dispose(); diff --git a/src/vs/workbench/services/backup/common/backup.ts b/src/vs/workbench/services/backup/common/backup.ts index 25fae6a15ca..66750a668b8 100644 --- a/src/vs/workbench/services/backup/common/backup.ts +++ b/src/vs/workbench/services/backup/common/backup.ts @@ -68,9 +68,4 @@ export interface IBackupFileService { * @param resource The resource whose backup is being discarded discard to back up. */ discardBackup(resource: URI): Promise; - - /** - * Discards all backups that exist. - */ - discardAllBackups(): Promise; } diff --git a/src/vs/workbench/services/backup/common/backupFileService.ts b/src/vs/workbench/services/backup/common/backupFileService.ts index d4c3416bfc8..0b5cf5a1d8a 100644 --- a/src/vs/workbench/services/backup/common/backupFileService.ts +++ b/src/vs/workbench/services/backup/common/backupFileService.ts @@ -163,10 +163,6 @@ export class BackupFileService implements IBackupFileService { return this.impl.discardBackup(resource); } - discardAllBackups(): Promise { - return this.impl.discardAllBackups(); - } - getBackups(): Promise { return this.impl.getBackups(); } @@ -277,21 +273,6 @@ class BackupFileServiceImpl extends Disposable implements IBackupFileService { }); } - async discardAllBackups(): Promise { - - // Discard each backup and clear model - // We go through the doDiscardBackup() - // method to benefit from the IO queue - const model = await this.ready; - await Promise.all(model.get().map(backupResource => this.doDiscardBackup(backupResource))); - model.clear(); - - // Delete the backup home for this workspace - // It will automatically be populated again - // once another backup is made - await this.deleteIgnoreFileNotFound(this.backupWorkspacePath); - } - private async deleteIgnoreFileNotFound(resource: URI): Promise { try { await this.fileService.del(resource, { recursive: true }); @@ -443,10 +424,6 @@ export class InMemoryBackupFileService implements IBackupFileService { this.backups.delete(this.toBackupResource(resource).toString()); } - async discardAllBackups(): Promise { - this.backups.clear(); - } - toBackupResource(resource: URI): URI { return URI.file(join(resource.scheme, this.hashPath(resource))); } diff --git a/src/vs/workbench/services/backup/test/electron-browser/backupFileService.test.ts b/src/vs/workbench/services/backup/test/electron-browser/backupFileService.test.ts index dadf089a070..ac020313462 100644 --- a/src/vs/workbench/services/backup/test/electron-browser/backupFileService.test.ts +++ b/src/vs/workbench/services/backup/test/electron-browser/backupFileService.test.ts @@ -42,7 +42,6 @@ const barFile = URI.file(platform.isWindows ? 'c:\\Bar' : '/Bar'); const fooBarFile = URI.file(platform.isWindows ? 'c:\\Foo Bar' : '/Foo Bar'); const untitledFile = URI.from({ scheme: Schemas.untitled, path: 'Untitled-1' }); const fooBackupPath = path.join(workspaceBackupPath, 'file', hashPath(fooFile)); -const barBackupPath = path.join(workspaceBackupPath, 'file', hashPath(barFile)); const untitledBackupPath = path.join(workspaceBackupPath, 'untitled', hashPath(untitledFile)); class TestBackupEnvironmentService extends NativeWorkbenchEnvironmentService { @@ -58,6 +57,7 @@ export class NodeTestBackupFileService extends BackupFileService { private backupResourceJoiners: Function[]; private discardBackupJoiners: Function[]; + discardedBackups: URI[]; constructor(workspaceBackupPath: string) { const environmentService = new TestBackupEnvironmentService(workspaceBackupPath); @@ -71,7 +71,7 @@ export class NodeTestBackupFileService extends BackupFileService { this.fileService = fileService; this.backupResourceJoiners = []; this.discardBackupJoiners = []; - this.didDiscardAllBackups = false; + this.discardedBackups = []; } joinBackupResource(): Promise { @@ -92,19 +92,12 @@ export class NodeTestBackupFileService extends BackupFileService { async discardBackup(resource: URI): Promise { await super.discardBackup(resource); + this.discardedBackups.push(resource); while (this.discardBackupJoiners.length) { this.discardBackupJoiners.pop()!(); } } - - didDiscardAllBackups: boolean; - - discardAllBackups(): Promise { - this.didDiscardAllBackups = true; - - return super.discardAllBackups(); - } } suite('BackupFileService', () => { @@ -282,27 +275,6 @@ suite('BackupFileService', () => { }); }); - suite('discardAllBackups', () => { - test('text file', async () => { - await service.backup(fooFile, createTextBufferFactory('test').create(DefaultEndOfLine.LF).createSnapshot(false)); - assert.equal(fs.readdirSync(path.join(workspaceBackupPath, 'file')).length, 1); - await service.backup(barFile, createTextBufferFactory('test').create(DefaultEndOfLine.LF).createSnapshot(false)); - assert.equal(fs.readdirSync(path.join(workspaceBackupPath, 'file')).length, 2); - await service.discardAllBackups(); - assert.equal(fs.existsSync(fooBackupPath), false); - assert.equal(fs.existsSync(barBackupPath), false); - assert.equal(fs.existsSync(path.join(workspaceBackupPath, 'file')), false); - }); - - test('untitled file', async () => { - await service.backup(untitledFile, createTextBufferFactory('test').create(DefaultEndOfLine.LF).createSnapshot(false)); - assert.equal(fs.readdirSync(path.join(workspaceBackupPath, 'untitled')).length, 1); - await service.discardAllBackups(); - assert.equal(fs.existsSync(untitledBackupPath), false); - assert.equal(fs.existsSync(path.join(workspaceBackupPath, 'untitled')), false); - }); - }); - suite('getBackups', () => { test('("file") - text file', async () => { await service.backup(fooFile, createTextBufferFactory('test').create(DefaultEndOfLine.LF).createSnapshot(false)); diff --git a/src/vs/workbench/test/workbenchTestServices.ts b/src/vs/workbench/test/workbenchTestServices.ts index cabc8dfa39e..bab9fb6d2c9 100644 --- a/src/vs/workbench/test/workbenchTestServices.ts +++ b/src/vs/workbench/test/workbenchTestServices.ts @@ -1209,10 +1209,6 @@ export class TestBackupFileService implements IBackupFileService { discardBackup(_resource: URI): Promise { return Promise.resolve(); } - - discardAllBackups(): Promise { - return Promise.resolve(); - } } export class TestCodeEditorService implements ICodeEditorService { From 3172af8aef96276479bdab0a8bca6ef5c1806fda Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Thu, 23 Jan 2020 12:17:41 +0100 Subject: [PATCH 036/801] bulk - tweak rename input padding --- src/vs/editor/contrib/rename/renameInputField.css | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/vs/editor/contrib/rename/renameInputField.css b/src/vs/editor/contrib/rename/renameInputField.css index f8473863b15..99757bf38a4 100644 --- a/src/vs/editor/contrib/rename/renameInputField.css +++ b/src/vs/editor/contrib/rename/renameInputField.css @@ -9,12 +9,12 @@ } .monaco-editor .rename-box.preview { - padding: 4px; + padding: 3px 3px 0 3px; } .monaco-editor .rename-box .rename-input { - padding: 4px; - width: calc(100% - 8px); + padding: 3px; + width: calc(100% - 6px); } .monaco-editor .rename-box .rename-label { From a3771b4f05d63824059d6c9a350ff5b88ba5d691 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Thu, 23 Jan 2020 12:49:01 +0100 Subject: [PATCH 037/801] bulk: add tests --- .../electron-brower/bulkEditPreview.test.ts | 115 ++++++++++++++++++ 1 file changed, 115 insertions(+) create mode 100644 src/vs/workbench/contrib/bulkEdit/test/electron-brower/bulkEditPreview.test.ts diff --git a/src/vs/workbench/contrib/bulkEdit/test/electron-brower/bulkEditPreview.test.ts b/src/vs/workbench/contrib/bulkEdit/test/electron-brower/bulkEditPreview.test.ts new file mode 100644 index 00000000000..d29dca35c51 --- /dev/null +++ b/src/vs/workbench/contrib/bulkEdit/test/electron-brower/bulkEditPreview.test.ts @@ -0,0 +1,115 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as assert from 'assert'; +import { Event } from 'vs/base/common/event'; +import { IFileService } from 'vs/platform/files/common/files'; +import { mock } from 'vs/workbench/test/electron-browser/api/mock'; +import { InstantiationService } from 'vs/platform/instantiation/common/instantiationService'; +import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { IModelService } from 'vs/editor/common/services/modelService'; +import type { WorkspaceEdit } from 'vs/editor/common/modes'; +import { URI } from 'vs/base/common/uri'; +import { BulkFileOperations } from 'vs/workbench/contrib/bulkEdit/browser/bulkEditPreview'; + + +suite('BulkEditPreview', function () { + + + let instaService: IInstantiationService; + + setup(function () { + + const fileService: IFileService = new class extends mock() { + onFileChanges = Event.None; + async exists() { + return true; + } + }; + + const modelService: IModelService = new class extends mock() { + getModel() { + return null; + } + getModels() { + return []; + } + }; + + instaService = new InstantiationService(new ServiceCollection( + [IFileService, fileService], + [IModelService, modelService], + )); + }); + + test('one needsConfirmation unchecks all of file', async function () { + + const edit: WorkspaceEdit = { + edits: [ + { newUri: URI.parse('some:///uri1'), metadata: { label: 'cat1', needsConfirmation: true } }, + { oldUri: URI.parse('some:///uri1'), newUri: URI.parse('some:///uri2'), metadata: { label: 'cat2', needsConfirmation: false } }, + ] + }; + + const ops = await instaService.invokeFunction(BulkFileOperations.create, edit); + assert.equal(ops.fileOperations.length, 1); + assert.equal(ops.fileOperations[0].isChecked(), false); + }); + + test('has categories', async function () { + + const edit: WorkspaceEdit = { + edits: [ + { newUri: URI.parse('some:///uri1'), metadata: { label: 'uri1', needsConfirmation: true } }, + { newUri: URI.parse('some:///uri2'), metadata: { label: 'uri2', needsConfirmation: false } } + ] + }; + + const ops = await instaService.invokeFunction(BulkFileOperations.create, edit); + assert.equal(ops.categories.length, 2); + assert.equal(ops.categories[0].metadata.label, 'uri1'); // unconfirmed! + assert.equal(ops.categories[1].metadata.label, 'uri2'); + }); + + test('has not categories', async function () { + + const edit: WorkspaceEdit = { + edits: [ + { newUri: URI.parse('some:///uri1'), metadata: { label: 'uri1', needsConfirmation: true } }, + { newUri: URI.parse('some:///uri2'), metadata: { label: 'uri1', needsConfirmation: false } } + ] + }; + + const ops = await instaService.invokeFunction(BulkFileOperations.create, edit); + assert.equal(ops.categories.length, 1); + assert.equal(ops.categories[0].metadata.label, 'uri1'); // unconfirmed! + assert.equal(ops.categories[0].metadata.label, 'uri1'); + }); + + test('update file from categories', async function () { + + const edit: WorkspaceEdit = { + edits: [ + { newUri: URI.parse('some:///uri1'), metadata: { label: 'cat1', needsConfirmation: true } }, + { newUri: URI.parse('some:///uri1'), metadata: { label: 'cat2', needsConfirmation: true } } + ] + }; + + const ops = await instaService.invokeFunction(BulkFileOperations.create, edit); + assert.equal(ops.categories.length, 2); + + const [first, second] = ops.categories; + assert.equal(first.fileOperations.length, 1); + assert.equal(second.fileOperations.length, 1); + + assert.equal(first.fileOperations[0].isChecked(), false); + assert.equal(second.fileOperations[0].isChecked(), false); + + first.fileOperations[0].updateChecked(true); + assert.equal(first.fileOperations[0].isChecked(), true); + assert.equal(first.fileOperations[0].isChecked(), true); + }); +}); From 419360d3ba2ec5616b3b22296e3be1bf01bf3c79 Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Thu, 23 Jan 2020 12:50:58 +0100 Subject: [PATCH 038/801] Make show task act as a toggle Fixes #84795 --- .../tasks/browser/terminalTaskSystem.ts | 29 ++++++++++++++++--- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts b/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts index 9815f49fdf4..c018ba09d3d 100644 --- a/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts +++ b/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts @@ -27,7 +27,7 @@ import Constants from 'vs/workbench/contrib/markers/browser/constants'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IConfigurationResolverService } from 'vs/workbench/services/configurationResolver/common/configurationResolver'; -import { IShellLaunchConfig } from 'vs/workbench/contrib/terminal/common/terminal'; +import { IShellLaunchConfig, TERMINAL_PANEL_ID } from 'vs/workbench/contrib/terminal/common/terminal'; import { ITerminalService, ITerminalInstanceService, ITerminalInstance } from 'vs/workbench/contrib/terminal/browser/terminal'; import { IOutputService } from 'vs/workbench/contrib/output/common/output'; import { StartStopProblemCollector, WatchingProblemCollector, ProblemCollectorEventKind, ProblemHandlingStrategy } from 'vs/workbench/contrib/tasks/common/problemCollectors'; @@ -161,6 +161,8 @@ export class TerminalTaskSystem implements ITaskSystem { // Should always be set in run private currentTask!: VerifiedTask; private isRerun: boolean = false; + private previousPanelId: string | undefined; + private previousTerminalInstance: ITerminalInstance | undefined; private readonly _onDidStateChange: Emitter; @@ -259,9 +261,28 @@ export class TerminalTaskSystem implements ITaskSystem { if (!terminalData) { return false; } - this.terminalService.setActiveInstance(terminalData.terminal); - if (CustomTask.is(task) || ContributedTask.is(task)) { - this.terminalService.showPanel(task.command.presentation!.focus); + const activeTerminalInstance = this.terminalService.getActiveInstance(); + const isPanelShowingTerminal = this.panelService.getActivePanel()?.getId() === TERMINAL_PANEL_ID; + if (isPanelShowingTerminal && (activeTerminalInstance === terminalData.terminal)) { + if (this.previousPanelId) { + if (this.previousTerminalInstance) { + this.terminalService.setActiveInstance(this.previousTerminalInstance); + } + this.panelService.openPanel(this.previousPanelId); + } else { + this.panelService.hideActivePanel(); + } + this.previousPanelId = undefined; + this.previousTerminalInstance = undefined; + } else { + this.previousPanelId = this.panelService.getActivePanel()?.getId(); + if (this.previousPanelId === TERMINAL_PANEL_ID) { + this.previousTerminalInstance = this.terminalService.getActiveInstance() ?? undefined; + } + this.terminalService.setActiveInstance(terminalData.terminal); + if (CustomTask.is(task) || ContributedTask.is(task)) { + this.terminalService.showPanel(task.command.presentation!.focus); + } } return true; } From 2175d4c0dd2921004566bc63156372ae173f7a8d Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Thu, 23 Jan 2020 13:47:26 +0100 Subject: [PATCH 039/801] - Introduce menu id to contribute view title context menu actions - Use this menu id to get context menu action for a view --- src/vs/platform/actions/common/actions.ts | 1 + .../parts/activitybar/activitybarPart.ts | 1 + .../workbench/browser/parts/compositeBar.ts | 4 +- .../browser/parts/compositeBarActions.ts | 8 +- .../browser/parts/panel/panelPart.ts | 22 ++- .../browser/parts/views/viewMenuActions.ts | 71 +++++++++ .../browser/parts/views/viewPaneContainer.ts | 138 +++++------------- src/vs/workbench/browser/parts/views/views.ts | 85 ++++++----- src/vs/workbench/common/views.ts | 2 + .../views/browser/viewDescriptorService.ts | 31 +++- 10 files changed, 210 insertions(+), 153 deletions(-) create mode 100644 src/vs/workbench/browser/parts/views/viewMenuActions.ts diff --git a/src/vs/platform/actions/common/actions.ts b/src/vs/platform/actions/common/actions.ts index f654a5b9a90..c4798aeb187 100644 --- a/src/vs/platform/actions/common/actions.ts +++ b/src/vs/platform/actions/common/actions.ts @@ -104,6 +104,7 @@ export const enum MenuId { TunnelTitle, ViewItemContext, ViewTitle, + ViewTitleContext, CommentThreadTitle, CommentThreadActions, CommentTitle, diff --git a/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts b/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts index 610109d40a4..67876a096ae 100644 --- a/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts +++ b/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts @@ -125,6 +125,7 @@ export class ActivitybarPart extends Part implements IActivityBarService { actions.push(this.instantiationService.createInstance(ToggleActivityBarVisibilityAction, ToggleActivityBarVisibilityAction.ID, nls.localize('hideActivitBar', "Hide Activity Bar"))); return actions; }, + getContextMenuActionsForComposite: () => [], getDefaultCompositeId: () => this.viewletService.getDefaultViewletId(), hidePart: () => this.layoutService.setSideBarHidden(true), compositeSize: 50, diff --git a/src/vs/workbench/browser/parts/compositeBar.ts b/src/vs/workbench/browser/parts/compositeBar.ts index 1f3521904e1..b7e3949d961 100644 --- a/src/vs/workbench/browser/parts/compositeBar.ts +++ b/src/vs/workbench/browser/parts/compositeBar.ts @@ -40,7 +40,7 @@ export interface ICompositeBarOptions { getCompositePinnedAction: (compositeId: string) => Action; getOnCompositeClickAction: (compositeId: string) => Action; getContextMenuActions: () => Action[]; - getContextMenuActionsForCompositeId?: (compositeId: string) => Action[]; + getContextMenuActionsForComposite: (compositeId: string) => Action[]; openComposite: (compositeId: string) => Promise; getDefaultCompositeId: () => string; hidePart: () => void; @@ -103,7 +103,7 @@ export class CompositeBar extends Widget implements ICompositeBar { const item = this.model.findItem(action.id); return item && this.instantiationService.createInstance( CompositeActionViewItem, action as ActivityAction, item.pinnedAction, - (compositeId: string) => { return this.options.getContextMenuActionsForCompositeId === undefined ? [] : this.options.getContextMenuActionsForCompositeId(compositeId); }, + (compositeId: string) => this.options.getContextMenuActionsForComposite(compositeId), () => this.getContextMenuActions() as Action[], this.options.colors, this.options.icon, diff --git a/src/vs/workbench/browser/parts/compositeBarActions.ts b/src/vs/workbench/browser/parts/compositeBarActions.ts index 61ecc360ea5..7cce51bdb2e 100644 --- a/src/vs/workbench/browser/parts/compositeBarActions.ts +++ b/src/vs/workbench/browser/parts/compositeBarActions.ts @@ -463,7 +463,7 @@ export class CompositeActionViewItem extends ActivityActionViewItem { constructor( private compositeActivityAction: ActivityAction, private toggleCompositePinnedAction: Action, - private compositeMenuActionsProvider: (compositeId: string) => ReadonlyArray, + private compositeContextMenuActionsProvider: (compositeId: string) => ReadonlyArray, private contextMenuActionsProvider: () => ReadonlyArray, colors: (theme: ITheme) => ICompositeBarColors, icon: boolean, @@ -598,9 +598,9 @@ export class CompositeActionViewItem extends ActivityActionViewItem { private showContextMenu(container: HTMLElement): void { const actions: Action[] = [this.toggleCompositePinnedAction]; - const compositeSpecificActions = this.compositeMenuActionsProvider(this.activity.id); - if (compositeSpecificActions.length) { - actions.push(...compositeSpecificActions); + const compositeContextMenuActions = this.compositeContextMenuActionsProvider(this.activity.id); + if (compositeContextMenuActions.length) { + actions.push(...compositeContextMenuActions); } if ((this.compositeActivityAction.activity).extensionId) { diff --git a/src/vs/workbench/browser/parts/panel/panelPart.ts b/src/vs/workbench/browser/parts/panel/panelPart.ts index 2e72172e7bb..22723540bc2 100644 --- a/src/vs/workbench/browser/parts/panel/panelPart.ts +++ b/src/vs/workbench/browser/parts/panel/panelPart.ts @@ -34,6 +34,8 @@ import { isUndefinedOrNull, assertIsDefined } from 'vs/base/common/types'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { ViewContainer, IViewContainersRegistry, Extensions as ViewContainerExtensions, IViewDescriptorService, IViewDescriptorCollection } from 'vs/workbench/common/views'; +import { MenuId } from 'vs/platform/actions/common/actions'; +import { ViewMenuActions } from 'vs/workbench/browser/parts/views/viewMenuActions'; interface ICachedPanel { id: string; @@ -137,7 +139,7 @@ export class PanelPart extends CompositePart implements IPanelService { .map(({ id, label }) => this.instantiationService.createInstance(SetPanelPositionAction, id, label)), this.instantiationService.createInstance(TogglePanelAction, TogglePanelAction.ID, localize('hidePanel', "Hide Panel")) ] as Action[], - getContextMenuActionsForCompositeId: (compositeId: string) => this.getContextMenuActionsForCompositeId(compositeId) as Action[], + getContextMenuActionsForComposite: (compositeId: string) => this.getContextMenuActionsForComposite(compositeId) as Action[], getDefaultCompositeId: () => this.panelRegistry.getDefaultPanelId(), hidePart: () => this.layoutService.setPanelHidden(true), compositeSize: 0, @@ -161,14 +163,18 @@ export class PanelPart extends CompositePart implements IPanelService { this.onDidRegisterPanels([...this.getPanels()]); } - private getContextMenuActionsForCompositeId(compositeId: string): readonly IAction[] { - const panel = this.getActivePanel(); - - if (!panel || panel.getId() !== compositeId) { - return []; + private getContextMenuActionsForComposite(compositeId: string): readonly IAction[] { + const result: IAction[] = []; + const container = this.getViewContainer(compositeId); + if (container) { + const viewDescriptors = this.viewDescriptorService.getViewDescriptors(container); + if (viewDescriptors.allViewDescriptors.length === 1) { + const viewMenuActions = this.instantiationService.createInstance(ViewMenuActions, viewDescriptors.allViewDescriptors[0].id, MenuId.ViewTitle, MenuId.ViewTitleContext); + result.push(...viewMenuActions.getContextMenuActions()); + viewMenuActions.dispose(); + } } - - return panel.getContextMenuActions(); + return result; } private onDidRegisterPanels(panels: PanelDescriptor[]): void { diff --git a/src/vs/workbench/browser/parts/views/viewMenuActions.ts b/src/vs/workbench/browser/parts/views/viewMenuActions.ts new file mode 100644 index 00000000000..f55455a925e --- /dev/null +++ b/src/vs/workbench/browser/parts/views/viewMenuActions.ts @@ -0,0 +1,71 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IAction } from 'vs/base/common/actions'; +import { Disposable, MutableDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { Emitter, Event } from 'vs/base/common/event'; +import { MenuId, IMenuService } from 'vs/platform/actions/common/actions'; +import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { createAndFillInActionBarActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; + +export class ViewMenuActions extends Disposable { + + private primaryActions: IAction[] = []; + private readonly titleActionsDisposable = this._register(new MutableDisposable()); + private secondaryActions: IAction[] = []; + private contextMenuActions: IAction[] = []; + + private _onDidChangeTitle = this._register(new Emitter()); + readonly onDidChangeTitle: Event = this._onDidChangeTitle.event; + + constructor( + viewId: string, + menuId: MenuId, + contextMenuId: MenuId, + @IContextKeyService private readonly contextKeyService: IContextKeyService, + @IMenuService private readonly menuService: IMenuService, + ) { + super(); + + const scopedContextKeyService = this._register(this.contextKeyService.createScoped()); + scopedContextKeyService.createKey('view', viewId); + + const menu = this._register(this.menuService.createMenu(menuId, scopedContextKeyService)); + const updateActions = () => { + this.primaryActions = []; + this.secondaryActions = []; + this.titleActionsDisposable.value = createAndFillInActionBarActions(menu, undefined, { primary: this.primaryActions, secondary: this.secondaryActions }); + this._onDidChangeTitle.fire(); + }; + this._register(menu.onDidChange(updateActions)); + updateActions(); + + const contextMenu = this._register(this.menuService.createMenu(contextMenuId, scopedContextKeyService)); + const updateContextMenuActions = () => { + this.contextMenuActions = []; + this.titleActionsDisposable.value = createAndFillInActionBarActions(contextMenu, undefined, { primary: [], secondary: this.contextMenuActions }); + }; + this._register(contextMenu.onDidChange(updateContextMenuActions)); + updateContextMenuActions(); + + this._register(toDisposable(() => { + this.primaryActions = []; + this.secondaryActions = []; + this.contextMenuActions = []; + })); + } + + getPrimaryActions(): IAction[] { + return this.primaryActions; + } + + getSecondaryActions(): IAction[] { + return this.secondaryActions; + } + + getContextMenuActions(): IAction[] { + return this.contextMenuActions; + } +} diff --git a/src/vs/workbench/browser/parts/views/viewPaneContainer.ts b/src/vs/workbench/browser/parts/views/viewPaneContainer.ts index 20441cdfd09..cd546de9f62 100644 --- a/src/vs/workbench/browser/parts/views/viewPaneContainer.ts +++ b/src/vs/workbench/browser/parts/views/viewPaneContainer.ts @@ -10,7 +10,7 @@ import { ColorIdentifier } from 'vs/platform/theme/common/colorRegistry'; import { attachStyler, IColorMapping } from 'vs/platform/theme/common/styler'; import { SIDE_BAR_DRAG_AND_DROP_BACKGROUND, SIDE_BAR_SECTION_HEADER_FOREGROUND, SIDE_BAR_SECTION_HEADER_BACKGROUND, SIDE_BAR_SECTION_HEADER_BORDER } from 'vs/workbench/common/theme'; import { append, $, trackFocus, toggleClass, EventType, isAncestor, Dimension, addDisposableListener } from 'vs/base/browser/dom'; -import { IDisposable, combinedDisposable, dispose, toDisposable, Disposable, MutableDisposable } from 'vs/base/common/lifecycle'; +import { IDisposable, combinedDisposable, dispose, toDisposable } from 'vs/base/common/lifecycle'; import { firstIndex } from 'vs/base/common/arrays'; import { IAction, IActionRunner, ActionRunner } from 'vs/base/common/actions'; import { IActionViewItem, ActionsOrientation, Separator } from 'vs/base/browser/ui/actionbar/actionbar'; @@ -25,7 +25,7 @@ import { PaneView, IPaneViewOptions, IPaneOptions, Pane, DefaultPaneDndControlle import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; import { StandardMouseEvent } from 'vs/base/browser/mouseEvent'; -import { Extensions as ViewContainerExtensions, IView, FocusedViewContext, IViewContainersRegistry, IViewDescriptor, ViewContainer, IViewDescriptorService, Extensions as ViewsExtensions, ViewContainerLocation, IViewsService, IViewsRegistry } from 'vs/workbench/common/views'; +import { Extensions as ViewContainerExtensions, IView, FocusedViewContext, IViewContainersRegistry, IViewDescriptor, ViewContainer, IViewDescriptorService, ViewContainerLocation, IViewsService } from 'vs/workbench/common/views'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { assertIsDefined } from 'vs/base/common/types'; @@ -36,8 +36,9 @@ import { IExtensionService } from 'vs/workbench/services/extensions/common/exten import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { IViewPaneContainer } from 'vs/workbench/common/viewPaneContainer'; import { Component } from 'vs/workbench/common/component'; -import { IMenuService, MenuId, MenuItemAction } from 'vs/platform/actions/common/actions'; -import { createAndFillInActionBarActions, ContextAwareMenuEntryActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem'; +import { MenuId, MenuItemAction } from 'vs/platform/actions/common/actions'; +import { ContextAwareMenuEntryActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem'; +import { ViewMenuActions } from 'vs/workbench/browser/parts/views/viewMenuActions'; export interface IPaneColors extends IColorMapping { dropBackground?: ColorIdentifier; @@ -76,7 +77,7 @@ export abstract class ViewPane extends Pane implements IView { readonly id: string; title: string; - private readonly menuActions?: ViewMenuActions; + private readonly menuActions: ViewMenuActions; protected actionRunner?: IActionRunner; protected toolbar?: ToolBar; @@ -101,10 +102,8 @@ export abstract class ViewPane extends Pane implements IView { this.showActionsAlways = !!options.showActionsAlways; this.focusedViewContextKey = FocusedViewContext.bindTo(contextKeyService); - if (options.titleMenuId !== undefined) { - this.menuActions = this._register(instantiationService.createInstance(ViewMenuActions, this.id, options.titleMenuId)); - this._register(this.menuActions.onDidChangeTitle(() => this.updateActions())); - } + this.menuActions = this._register(instantiationService.createInstance(ViewMenuActions, this.id, options.titleMenuId || MenuId.ViewTitle, MenuId.ViewTitleContext)); + this._register(this.menuActions.onDidChangeTitle(() => this.updateActions())); } setVisible(visible: boolean): void { @@ -225,6 +224,10 @@ export abstract class ViewPane extends Pane implements IView { return this.menuActions ? this.menuActions.getSecondaryActions() : []; } + getContextMenuActions(): IAction[] { + return this.menuActions ? this.menuActions.getContextMenuActions() : []; + } + getActionViewItem(action: IAction): IActionViewItem | undefined { if (action instanceof MenuItemAction) { return this.instantiationService.createInstance(ContextAwareMenuEntryActionViewItem, action); @@ -391,55 +394,39 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer { } getContextMenuActions(viewDescriptor?: IViewDescriptor): IAction[] { - - if (this.isViewMergedWithContainer()) { - const viewId = this.panes[0].id; - viewDescriptor = Registry.as(ViewsExtensions.ViewsRegistry).getView(viewId)!; - } - - const viewContainerRegistry = Registry.as(ViewsExtensions.ViewContainersRegistry); - const currentLocation = viewContainerRegistry.getViewContainerLocation(this.viewContainer); - const result: IAction[] = []; + + if (!viewDescriptor && this.isViewMergedWithContainer()) { + viewDescriptor = this.viewDescriptorService.getViewDescriptor(this.panes[0].id) || undefined; + } + if (viewDescriptor) { - // For now, restrict any additional actions to the sidebar only - if (!this.isViewMergedWithContainer() && currentLocation === ViewContainerLocation.Sidebar) { - result.push({ - id: `${viewDescriptor.id}.removeView`, - label: nls.localize('hideView', "Hide"), - enabled: viewDescriptor.canToggleVisibility, - run: () => this.toggleViewVisibility(viewDescriptor!.id) - }); - } - - if (viewDescriptor.canMoveView) { - const newLocation = currentLocation === ViewContainerLocation.Panel ? ViewContainerLocation.Sidebar : ViewContainerLocation.Panel; - result.push({ - id: `${viewDescriptor.id}.moveView`, - label: newLocation === ViewContainerLocation.Sidebar ? nls.localize('moveViewToSidebar', "Move to Sidebar") : nls.localize('moveViewToPanel', "Move to Panel"), - enabled: true, - run: () => this.moveView(viewDescriptor!, newLocation) - }); - } - } - - // For now, restrict any additional actions to the sidebar only - if (currentLocation === ViewContainerLocation.Sidebar) { - const viewToggleActions = this.viewsModel.viewDescriptors.map(viewDescriptor => ({ - id: `${viewDescriptor.id}.toggleVisibility`, - label: viewDescriptor.name, - checked: this.viewsModel.isVisible(viewDescriptor.id), + result.push({ + id: `${viewDescriptor.id}.removeView`, + label: nls.localize('hideView', "Hide"), enabled: viewDescriptor.canToggleVisibility, - run: () => this.toggleViewVisibility(viewDescriptor.id) - })); - - if (result.length && viewToggleActions.length) { - result.push(new Separator()); + run: () => this.toggleViewVisibility(viewDescriptor!.id) + }); + const view = this.getView(viewDescriptor.id); + if (view) { + result.push(...view.getContextMenuActions()); } - - result.push(...viewToggleActions); } + const viewToggleActions = this.viewsModel.viewDescriptors.map(viewDescriptor => ({ + id: `${viewDescriptor.id}.toggleVisibility`, + label: viewDescriptor.name, + checked: this.viewsModel.isVisible(viewDescriptor.id), + enabled: viewDescriptor.canToggleVisibility, + run: () => this.toggleViewVisibility(viewDescriptor.id) + })); + + if (result.length && viewToggleActions.length) { + result.push(new Separator()); + } + + result.push(...viewToggleActions); + return result; } @@ -803,50 +790,3 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer { } } } - - -class ViewMenuActions extends Disposable { - - private primaryActions: IAction[] = []; - private readonly titleActionsDisposable = this._register(new MutableDisposable()); - private secondaryActions: IAction[] = []; - - private _onDidChangeTitle = this._register(new Emitter()); - readonly onDidChangeTitle: Event = this._onDidChangeTitle.event; - - constructor( - viewId: string, - menuId: MenuId, - @IContextKeyService private readonly contextKeyService: IContextKeyService, - @IMenuService private readonly menuService: IMenuService, - ) { - super(); - - const scopedContextKeyService = this._register(this.contextKeyService.createScoped()); - scopedContextKeyService.createKey('view', viewId); - - const menu = this._register(this.menuService.createMenu(menuId, scopedContextKeyService)); - const updateActions = () => { - this.primaryActions = []; - this.secondaryActions = []; - this.titleActionsDisposable.value = createAndFillInActionBarActions(menu, undefined, { primary: this.primaryActions, secondary: this.secondaryActions }); - this._onDidChangeTitle.fire(); - }; - - this._register(menu.onDidChange(updateActions)); - updateActions(); - - this._register(toDisposable(() => { - this.primaryActions = []; - this.secondaryActions = []; - })); - } - - getPrimaryActions(): IAction[] { - return this.primaryActions; - } - - getSecondaryActions(): IAction[] { - return this.secondaryActions; - } -} diff --git a/src/vs/workbench/browser/parts/views/views.ts b/src/vs/workbench/browser/parts/views/views.ts index c907735668b..ebce4a178e5 100644 --- a/src/vs/workbench/browser/parts/views/views.ts +++ b/src/vs/workbench/browser/parts/views/views.ts @@ -13,18 +13,16 @@ import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { Event, Emitter } from 'vs/base/common/event'; import { firstIndex, move } from 'vs/base/common/arrays'; import { isUndefinedOrNull, isUndefined } from 'vs/base/common/types'; -import { MenuId, MenuRegistry, ICommandAction } from 'vs/platform/actions/common/actions'; -import { CommandsRegistry } from 'vs/platform/commands/common/commands'; +import { MenuId, registerAction2, Action2 } from 'vs/platform/actions/common/actions'; import { localize } from 'vs/nls'; -import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; +import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { values } from 'vs/base/common/map'; import { IFileIconTheme, IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService'; import { toggleClass, addClass } from 'vs/base/browser/dom'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { IPaneComposite } from 'vs/workbench/common/panecomposite'; import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; - - +import type { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; export interface IViewState { visibleGlobal: boolean | undefined; @@ -472,14 +470,14 @@ export class ViewsService extends Disposable implements IViewsService { private onViewContainerRegistered(viewContainer: ViewContainer): void { const viewDescriptorCollection = this.viewDescriptorService.getViewDescriptors(viewContainer); - this.onViewsRegistered(viewDescriptorCollection.allViewDescriptors, viewContainer); + this.onViewsAdded(viewDescriptorCollection.allViewDescriptors, viewContainer); this._register(viewDescriptorCollection.onDidChangeViews(({ added, removed }) => { - this.onViewsRegistered(added, viewContainer); - this.onViewsDeregistered(removed); + this.onViewsAdded(added, viewContainer); + this.onViewsRemoved(removed); })); } - private onViewsRegistered(views: IViewDescriptor[], container: ViewContainer): void { + private onViewsAdded(views: IViewDescriptor[], container: ViewContainer): void { const location = this.viewContainersRegistry.getViewContainerLocation(container); if (location === undefined) { return; @@ -488,38 +486,57 @@ export class ViewsService extends Disposable implements IViewsService { const composite = this.getComposite(container.id, location); for (const viewDescriptor of views) { const disposables = new DisposableStore(); - const command: ICommandAction = { - id: viewDescriptor.focusCommand ? viewDescriptor.focusCommand.id : `${viewDescriptor.id}.focus`, - title: { original: `Focus on ${viewDescriptor.name} View`, value: localize('focus view', "Focus on {0} View", viewDescriptor.name) }, - category: composite ? composite.name : localize('view category', "View"), - }; - const when = ContextKeyExpr.has(`${viewDescriptor.id}.active`); - - disposables.add(CommandsRegistry.registerCommand(command.id, () => this.openView(viewDescriptor.id, true))); - - disposables.add(MenuRegistry.appendMenuItem(MenuId.CommandPalette, { - command, - when + disposables.add(registerAction2(class FocusViewAction extends Action2 { + constructor() { + super({ + id: viewDescriptor.focusCommand ? viewDescriptor.focusCommand.id : `${viewDescriptor.id}.focus`, + title: { original: `Focus on ${viewDescriptor.name} View`, value: localize('focus view', "Focus on {0} View", viewDescriptor.name) }, + category: composite ? composite.name : localize('view category', "View"), + menu: [{ + id: MenuId.CommandPalette, + }], + keybinding: { + when: ContextKeyExpr.has(`${viewDescriptor.id}.active`), + weight: KeybindingWeight.WorkbenchContrib, + primary: viewDescriptor.focusCommand?.keybindings?.primary, + secondary: viewDescriptor.focusCommand?.keybindings?.secondary, + linux: viewDescriptor.focusCommand?.keybindings?.linux, + mac: viewDescriptor.focusCommand?.keybindings?.mac, + win: viewDescriptor.focusCommand?.keybindings?.win + } + }); + } + run(accessor: ServicesAccessor): any { + accessor.get(IViewsService).openView(viewDescriptor.id, true); + } })); - if (viewDescriptor.focusCommand && viewDescriptor.focusCommand.keybindings) { - KeybindingsRegistry.registerKeybindingRule({ - id: command.id, - when, - weight: KeybindingWeight.WorkbenchContrib, - primary: viewDescriptor.focusCommand.keybindings.primary, - secondary: viewDescriptor.focusCommand.keybindings.secondary, - linux: viewDescriptor.focusCommand.keybindings.linux, - mac: viewDescriptor.focusCommand.keybindings.mac, - win: viewDescriptor.focusCommand.keybindings.win - }); - } + const newLocation = location === ViewContainerLocation.Panel ? ViewContainerLocation.Sidebar : ViewContainerLocation.Panel; + disposables.add(registerAction2(class MoveViewAction extends Action2 { + constructor() { + super({ + id: `${viewDescriptor.id}.moveView`, + title: { + original: newLocation === ViewContainerLocation.Sidebar ? 'Move to Sidebar' : 'Move to Panel', + value: newLocation === ViewContainerLocation.Sidebar ? localize('moveViewToSidebar', "Move to Sidebar") : localize('moveViewToPanel', "Move to Panel") + }, + menu: [{ + id: MenuId.ViewTitleContext, + when: ContextKeyExpr.and(ContextKeyExpr.equals('view', viewDescriptor.id), ContextKeyExpr.has(`${viewDescriptor.id}.canMove`)), + }], + }); + } + run(accessor: ServicesAccessor): any { + accessor.get(IViewDescriptorService).moveViewToLocation(viewDescriptor, newLocation); + accessor.get(IViewsService).openView(viewDescriptor.id); + } + })); this.viewDisposable.set(viewDescriptor, disposables); } } - private onViewsDeregistered(views: IViewDescriptor[]): void { + private onViewsRemoved(views: IViewDescriptor[]): void { for (const view of views) { const disposable = this.viewDisposable.get(view); if (disposable) { diff --git a/src/vs/workbench/common/views.ts b/src/vs/workbench/common/views.ts index bb4ae89c1a2..6978d67d497 100644 --- a/src/vs/workbench/common/views.ts +++ b/src/vs/workbench/common/views.ts @@ -371,6 +371,8 @@ export interface IViewDescriptorService { getViewDescriptors(container: ViewContainer): IViewDescriptorCollection; + getViewDescriptor(viewId: string): IViewDescriptor | null; + getViewContainer(viewId: string): ViewContainer | null; getDefaultContainer(viewId: string): ViewContainer | null; diff --git a/src/vs/workbench/services/views/browser/viewDescriptorService.ts b/src/vs/workbench/services/views/browser/viewDescriptorService.ts index b865c810f26..f3f1d758418 100644 --- a/src/vs/workbench/services/views/browser/viewDescriptorService.ts +++ b/src/vs/workbench/services/views/browser/viewDescriptorService.ts @@ -183,6 +183,7 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor private readonly viewDescriptorCollections: Map; private readonly activeViewContextKeys: Map>; + private readonly movableViewContextKeys: Map>; private readonly viewsRegistry: IViewsRegistry; private readonly viewContainersRegistry: IViewContainersRegistry; @@ -215,6 +216,7 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor this.viewDescriptorCollections = new Map(); this.activeViewContextKeys = new Map>(); + this.movableViewContextKeys = new Map>(); this.viewContainersRegistry = Registry.as(ViewExtensions.ViewContainersRegistry); this.viewsRegistry = Registry.as(ViewExtensions.ViewsRegistry); @@ -280,7 +282,7 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor // check if we should generate this container if (containerInfo.sourceViewId && containerInfo.location !== undefined) { - const sourceView = this.viewsRegistry.getView(containerInfo.sourceViewId); + const sourceView = this.getViewDescriptor(containerInfo.sourceViewId); if (sourceView) { this.registerViewContainerForSingleView(sourceView, containerInfo.location); @@ -290,7 +292,7 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor // check if view has been registered to default location const viewContainer = this.viewsRegistry.getViewContainer(viewId); - const viewDescriptor = this.viewsRegistry.getView(viewId); + const viewDescriptor = this.getViewDescriptor(viewId); if (viewContainer && viewDescriptor) { this.addViews(viewContainer, [viewDescriptor]); } @@ -306,12 +308,15 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor // Once they are grouped, try registering them which occurs // if the container has already been registered within this service this.registerGroupedViews(regroupedViews); + + views.forEach(viewDescriptor => this.getOrCreateMovableViewContextKey(viewDescriptor).set(!!viewDescriptor.canMoveView)); } private onDidDeregisterViews(views: IViewDescriptor[], viewContainer: ViewContainer): void { // When views are registered, we need to regroup them based on the cache const regroupedViews = this.regroupViews(viewContainer.id, views); this.deregisterGroupedViews(regroupedViews); + views.forEach(viewDescriptor => this.getOrCreateMovableViewContextKey(viewDescriptor).set(false)); } private regroupViews(containerId: string, views: IViewDescriptor[]): Map { @@ -328,6 +333,10 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor return ret; } + getViewDescriptor(viewId: string): IViewDescriptor | null { + return this.viewsRegistry.getView(viewId); + } + getViewContainer(viewId: string): ViewContainer | null { const containerId = this.cachedViewInfo.get(viewId)?.containerId; return containerId ? @@ -408,7 +417,7 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor const prevViewContainer = this.getViewContainer(viewId); const newViewContainer = this.viewContainersRegistry.get(newCachedPositions.get(viewId)!.containerId); if (prevViewContainer && newViewContainer && newViewContainer !== prevViewContainer) { - const viewDescriptor = this.viewsRegistry.getView(viewId); + const viewDescriptor = this.getViewDescriptor(viewId); if (viewDescriptor) { // We don't call move views to avoid sending intermediate // cached data to the window that gave us this information @@ -470,7 +479,7 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor continue; } - const viewDescriptor = this.viewsRegistry.getView(viewId); + const viewDescriptor = this.getViewDescriptor(viewId); if (viewDescriptor) { result.push(viewDescriptor); } @@ -498,6 +507,7 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor const viewsToRegister = this.getViewsByContainer(viewContainer); if (viewsToRegister.length) { this.addViews(viewContainer, viewsToRegister); + viewsToRegister.forEach(viewDescriptor => this.getOrCreateMovableViewContextKey(viewDescriptor).set(!!viewDescriptor.canMoveView)); } } @@ -522,8 +532,7 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor } private removeViews(container: ViewContainer, views: IViewDescriptor[]): void { - const viewDescriptorCollection = this.getViewDescriptors(container); - viewDescriptorCollection.removeViews(views); + this.getViewDescriptors(container).removeViews(views); } private getOrCreateActiveViewContextKey(viewDescriptor: IViewDescriptor): IContextKey { @@ -535,6 +544,16 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor } return contextKey; } + + private getOrCreateMovableViewContextKey(viewDescriptor: IViewDescriptor): IContextKey { + const movableViewContextKeyId = `${viewDescriptor.id}.canMove`; + let contextKey = this.movableViewContextKeys.get(movableViewContextKeyId); + if (!contextKey) { + contextKey = new RawContextKey(movableViewContextKeyId, false).bindTo(this.contextKeyService); + this.movableViewContextKeys.set(movableViewContextKeyId, contextKey); + } + return contextKey; + } } registerSingleton(IViewDescriptorService, ViewDescriptorService); From 34d739f40c5c8c2d1569e10047b8487f93bd1d7f Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Thu, 23 Jan 2020 13:58:53 +0100 Subject: [PATCH 040/801] :lipstick: --- src/vs/workbench/api/browser/viewsExtensionPoint.ts | 7 +++---- .../workbench/browser/parts/views/viewPaneContainer.ts | 10 ++-------- src/vs/workbench/browser/parts/views/viewsViewlet.ts | 7 +++---- src/vs/workbench/contrib/debug/browser/debugViewlet.ts | 7 +++---- .../contrib/extensions/browser/extensionsViewlet.ts | 8 +++----- .../workbench/contrib/files/browser/explorerViewlet.ts | 7 +++---- src/vs/workbench/contrib/remote/browser/remote.ts | 5 ++--- src/vs/workbench/contrib/scm/browser/scmViewlet.ts | 7 +++---- .../workbench/contrib/search/browser/searchViewlet.ts | 7 +++---- 9 files changed, 25 insertions(+), 40 deletions(-) diff --git a/src/vs/workbench/api/browser/viewsExtensionPoint.ts b/src/vs/workbench/api/browser/viewsExtensionPoint.ts index 2d80611c62c..e198a29483d 100644 --- a/src/vs/workbench/api/browser/viewsExtensionPoint.ts +++ b/src/vs/workbench/api/browser/viewsExtensionPoint.ts @@ -8,7 +8,7 @@ import { forEach } from 'vs/base/common/collections'; import { IJSONSchema } from 'vs/base/common/jsonSchema'; import * as resources from 'vs/base/common/resources'; import { ExtensionMessageCollector, ExtensionsRegistry, IExtensionPoint, IExtensionPointUser } from 'vs/workbench/services/extensions/common/extensionsRegistry'; -import { ViewContainer, IViewsRegistry, ITreeViewDescriptor, IViewContainersRegistry, Extensions as ViewContainerExtensions, TEST_VIEW_CONTAINER_ID, IViewDescriptor, ViewContainerLocation, IViewDescriptorService, IViewsService } from 'vs/workbench/common/views'; +import { ViewContainer, IViewsRegistry, ITreeViewDescriptor, IViewContainersRegistry, Extensions as ViewContainerExtensions, TEST_VIEW_CONTAINER_ID, IViewDescriptor, ViewContainerLocation, IViewDescriptorService } from 'vs/workbench/common/views'; import { CustomTreeViewPane, CustomTreeView } from 'vs/workbench/browser/parts/views/customView'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { coalesce, } from 'vs/base/common/arrays'; @@ -325,10 +325,9 @@ class ViewsExtensionHandler implements IWorkbenchContribution { @IThemeService themeService: IThemeService, @IContextMenuService contextMenuService: IContextMenuService, @IExtensionService extensionService: IExtensionService, - @IViewDescriptorService viewDescriptorService: IViewDescriptorService, - @IViewsService viewsService: IViewsService + @IViewDescriptorService viewDescriptorService: IViewDescriptorService ) { - super(id, `${id}.state`, { mergeViewWithContainerWhenSingleView: true }, instantiationService, configurationService, layoutService, contextMenuService, telemetryService, extensionService, themeService, storageService, contextService, viewDescriptorService, viewsService); + super(id, `${id}.state`, { mergeViewWithContainerWhenSingleView: true }, instantiationService, configurationService, layoutService, contextMenuService, telemetryService, extensionService, themeService, storageService, contextService, viewDescriptorService); } } diff --git a/src/vs/workbench/browser/parts/views/viewPaneContainer.ts b/src/vs/workbench/browser/parts/views/viewPaneContainer.ts index cd546de9f62..d139b8680f0 100644 --- a/src/vs/workbench/browser/parts/views/viewPaneContainer.ts +++ b/src/vs/workbench/browser/parts/views/viewPaneContainer.ts @@ -25,7 +25,7 @@ import { PaneView, IPaneViewOptions, IPaneOptions, Pane, DefaultPaneDndControlle import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; import { StandardMouseEvent } from 'vs/base/browser/mouseEvent'; -import { Extensions as ViewContainerExtensions, IView, FocusedViewContext, IViewContainersRegistry, IViewDescriptor, ViewContainer, IViewDescriptorService, ViewContainerLocation, IViewsService } from 'vs/workbench/common/views'; +import { Extensions as ViewContainerExtensions, IView, FocusedViewContext, IViewContainersRegistry, IViewDescriptor, ViewContainer, IViewDescriptorService } from 'vs/workbench/common/views'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { assertIsDefined } from 'vs/base/common/types'; @@ -311,8 +311,7 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer { @IThemeService protected themeService: IThemeService, @IStorageService protected storageService: IStorageService, @IWorkspaceContextService protected contextService: IWorkspaceContextService, - @IViewDescriptorService protected viewDescriptorService: IViewDescriptorService, - @IViewsService protected viewsService: IViewsService + @IViewDescriptorService protected viewDescriptorService: IViewDescriptorService ) { super(id, themeService, storageService); @@ -668,11 +667,6 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer { this.viewsModel.setVisible(viewId, visible); } - protected moveView(viewDescriptor: IViewDescriptor, location: ViewContainerLocation): void { - this.viewDescriptorService.moveViewToLocation(viewDescriptor, location); - this.viewsService.openView(viewDescriptor.id, true); - } - private addPane(pane: ViewPane, size: number, index = this.paneItems.length - 1): void { const onDidFocus = pane.onDidFocus(() => this.lastFocusedPane = pane); const onDidChangeTitleArea = pane.onDidChangeTitleArea(() => { diff --git a/src/vs/workbench/browser/parts/views/viewsViewlet.ts b/src/vs/workbench/browser/parts/views/viewsViewlet.ts index a1fb3581236..9bd58721fe8 100644 --- a/src/vs/workbench/browser/parts/views/viewsViewlet.ts +++ b/src/vs/workbench/browser/parts/views/viewsViewlet.ts @@ -8,7 +8,7 @@ import { IAction } from 'vs/base/common/actions'; import { Separator } from 'vs/base/browser/ui/actionbar/actionbar'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; -import { IViewDescriptor, IViewDescriptorService, IViewsService } from 'vs/workbench/common/views'; +import { IViewDescriptor, IViewDescriptorService } from 'vs/workbench/common/views'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; @@ -44,11 +44,10 @@ export abstract class FilterViewPaneContainer extends ViewPaneContainer { @IContextMenuService contextMenuService: IContextMenuService, @IExtensionService extensionService: IExtensionService, @IWorkspaceContextService contextService: IWorkspaceContextService, - @IViewDescriptorService viewDescriptorService: IViewDescriptorService, - @IViewsService viewsService: IViewsService + @IViewDescriptorService viewDescriptorService: IViewDescriptorService ) { - super(viewletId, `${viewletId}.state`, { mergeViewWithContainerWhenSingleView: false }, instantiationService, configurationService, layoutService, contextMenuService, telemetryService, extensionService, themeService, storageService, contextService, viewDescriptorService, viewsService); + super(viewletId, `${viewletId}.state`, { mergeViewWithContainerWhenSingleView: false }, instantiationService, configurationService, layoutService, contextMenuService, telemetryService, extensionService, themeService, storageService, contextService, viewDescriptorService); this._register(onDidChangeFilterValue(newFilterValue => { this.filterValue = newFilterValue; this.onFilterChanged(newFilterValue); diff --git a/src/vs/workbench/contrib/debug/browser/debugViewlet.ts b/src/vs/workbench/contrib/debug/browser/debugViewlet.ts index c775ca71f69..7011327d8da 100644 --- a/src/vs/workbench/contrib/debug/browser/debugViewlet.ts +++ b/src/vs/workbench/contrib/debug/browser/debugViewlet.ts @@ -33,7 +33,7 @@ import { INotificationService } from 'vs/platform/notification/common/notificati import { TogglePanelAction } from 'vs/workbench/browser/panel'; import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; import { StartView } from 'vs/workbench/contrib/debug/browser/startView'; -import { IViewDescriptorService, IViewsService } from 'vs/workbench/common/views'; +import { IViewDescriptorService } from 'vs/workbench/common/views'; export class DebugViewPaneContainer extends ViewPaneContainer { @@ -61,10 +61,9 @@ export class DebugViewPaneContainer extends ViewPaneContainer { @IMenuService private readonly menuService: IMenuService, @IContextKeyService private readonly contextKeyService: IContextKeyService, @INotificationService private readonly notificationService: INotificationService, - @IViewDescriptorService viewDescriptorService: IViewDescriptorService, - @IViewsService viewsService: IViewsService + @IViewDescriptorService viewDescriptorService: IViewDescriptorService ) { - super(VIEWLET_ID, `${VIEWLET_ID}.state`, { mergeViewWithContainerWhenSingleView: true }, instantiationService, configurationService, layoutService, contextMenuService, telemetryService, extensionService, themeService, storageService, contextService, viewDescriptorService, viewsService); + super(VIEWLET_ID, `${VIEWLET_ID}.state`, { mergeViewWithContainerWhenSingleView: true }, instantiationService, configurationService, layoutService, contextMenuService, telemetryService, extensionService, themeService, storageService, contextService, viewDescriptorService); this._register(this.debugService.onDidChangeState(state => this.onDebugServiceStateChange(state))); this._register(this.debugService.onDidNewSession(() => this.updateToolBar())); diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts b/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts index bb179927b0f..f0741bcf4f5 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts @@ -35,7 +35,7 @@ import Severity from 'vs/base/common/severity'; import { IActivityService, NumberBadge } from 'vs/workbench/services/activity/common/activity'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { IViewsRegistry, IViewDescriptor, Extensions, ViewContainer, IViewContainersRegistry, IViewDescriptorService, IViewsService } from 'vs/workbench/common/views'; +import { IViewsRegistry, IViewDescriptor, Extensions, ViewContainer, IViewContainersRegistry, IViewDescriptorService } from 'vs/workbench/common/views'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; import { IContextKeyService, ContextKeyExpr, RawContextKey, IContextKey } from 'vs/platform/contextkey/common/contextkey'; @@ -356,11 +356,9 @@ export class ExtensionsViewPaneContainer extends ViewPaneContainer implements IE @IContextKeyService contextKeyService: IContextKeyService, @IContextMenuService contextMenuService: IContextMenuService, @IExtensionService extensionService: IExtensionService, - @IViewDescriptorService viewDescriptorService: IViewDescriptorService, - @IViewsService viewsService: IViewsService - + @IViewDescriptorService viewDescriptorService: IViewDescriptorService ) { - super(VIEWLET_ID, `${VIEWLET_ID}.state`, { mergeViewWithContainerWhenSingleView: true }, instantiationService, configurationService, layoutService, contextMenuService, telemetryService, extensionService, themeService, storageService, contextService, viewDescriptorService, viewsService); + super(VIEWLET_ID, `${VIEWLET_ID}.state`, { mergeViewWithContainerWhenSingleView: true }, instantiationService, configurationService, layoutService, contextMenuService, telemetryService, extensionService, themeService, storageService, contextService, viewDescriptorService); this.searchDelayer = new Delayer(500); this.nonEmptyWorkspaceContextKey = NonEmptyWorkspaceContext.bindTo(contextKeyService); diff --git a/src/vs/workbench/contrib/files/browser/explorerViewlet.ts b/src/vs/workbench/contrib/files/browser/explorerViewlet.ts index a6066e3843a..cc45efefec9 100644 --- a/src/vs/workbench/contrib/files/browser/explorerViewlet.ts +++ b/src/vs/workbench/contrib/files/browser/explorerViewlet.ts @@ -20,7 +20,7 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey'; import { IThemeService } from 'vs/platform/theme/common/themeService'; -import { IViewsRegistry, IViewDescriptor, Extensions, ViewContainer, IViewContainersRegistry, ViewContainerLocation, IViewDescriptorService, IViewsService } from 'vs/workbench/common/views'; +import { IViewsRegistry, IViewDescriptor, Extensions, ViewContainer, IViewContainersRegistry, ViewContainerLocation, IViewDescriptorService } from 'vs/workbench/common/views'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { Disposable } from 'vs/base/common/lifecycle'; import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; @@ -164,11 +164,10 @@ export class ExplorerViewPaneContainer extends ViewPaneContainer { @IThemeService themeService: IThemeService, @IContextMenuService contextMenuService: IContextMenuService, @IExtensionService extensionService: IExtensionService, - @IViewDescriptorService viewDescriptorService: IViewDescriptorService, - @IViewsService viewsService: IViewsService + @IViewDescriptorService viewDescriptorService: IViewDescriptorService ) { - super(VIEWLET_ID, ExplorerViewPaneContainer.EXPLORER_VIEWS_STATE, { mergeViewWithContainerWhenSingleView: true }, instantiationService, configurationService, layoutService, contextMenuService, telemetryService, extensionService, themeService, storageService, contextService, viewDescriptorService, viewsService); + super(VIEWLET_ID, ExplorerViewPaneContainer.EXPLORER_VIEWS_STATE, { mergeViewWithContainerWhenSingleView: true }, instantiationService, configurationService, layoutService, contextMenuService, telemetryService, extensionService, themeService, storageService, contextService, viewDescriptorService); this.viewletVisibleContextKey = ExplorerViewletVisibleContext.bindTo(contextKeyService); diff --git a/src/vs/workbench/contrib/remote/browser/remote.ts b/src/vs/workbench/contrib/remote/browser/remote.ts index cd2f14e7fdd..2b48dd6a82f 100644 --- a/src/vs/workbench/contrib/remote/browser/remote.ts +++ b/src/vs/workbench/contrib/remote/browser/remote.ts @@ -19,7 +19,7 @@ import { IExtensionService } from 'vs/workbench/services/extensions/common/exten import { FilterViewPaneContainer } from 'vs/workbench/browser/parts/views/viewsViewlet'; import { VIEWLET_ID } from 'vs/workbench/contrib/remote/common/remote.contribution'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; -import { IViewDescriptor, IViewsRegistry, Extensions, ViewContainerLocation, IViewContainersRegistry, IViewDescriptorService, IViewsService } from 'vs/workbench/common/views'; +import { IViewDescriptor, IViewsRegistry, Extensions, ViewContainerLocation, IViewContainersRegistry, IViewDescriptorService } from 'vs/workbench/common/views'; import { Registry } from 'vs/platform/registry/common/platform'; import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { IOpenerService } from 'vs/platform/opener/common/opener'; @@ -443,9 +443,8 @@ export class RemoteViewPaneContainer extends FilterViewPaneContainer implements @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService, @IContextKeyService private readonly contextKeyService: IContextKeyService, @IViewDescriptorService viewDescriptorService: IViewDescriptorService, - @IViewsService viewsService: IViewsService ) { - super(VIEWLET_ID, remoteExplorerService.onDidChangeTargetType, configurationService, layoutService, telemetryService, storageService, instantiationService, themeService, contextMenuService, extensionService, contextService, viewDescriptorService, viewsService); + super(VIEWLET_ID, remoteExplorerService.onDidChangeTargetType, configurationService, layoutService, telemetryService, storageService, instantiationService, themeService, contextMenuService, extensionService, contextService, viewDescriptorService); this.addConstantViewDescriptors([this.helpPanelDescriptor]); remoteHelpExtPoint.setHandler((extensions) => { let helpInformation: HelpInformation[] = []; diff --git a/src/vs/workbench/contrib/scm/browser/scmViewlet.ts b/src/vs/workbench/contrib/scm/browser/scmViewlet.ts index ec7f450bc18..cb21f9aa0b9 100644 --- a/src/vs/workbench/contrib/scm/browser/scmViewlet.ts +++ b/src/vs/workbench/contrib/scm/browser/scmViewlet.ts @@ -25,7 +25,7 @@ import { INotificationService } from 'vs/platform/notification/common/notificati import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; -import { IViewsRegistry, Extensions, IViewDescriptorService, IViewsService } from 'vs/workbench/common/views'; +import { IViewsRegistry, Extensions, IViewDescriptorService } from 'vs/workbench/common/views'; import { Registry } from 'vs/platform/registry/common/platform'; import { nextTick } from 'vs/base/common/process'; import { RepositoryPane, RepositoryViewDescriptor } from 'vs/workbench/contrib/scm/browser/repositoryPane'; @@ -98,10 +98,9 @@ export class SCMViewPaneContainer extends ViewPaneContainer implements IViewMode @IExtensionService extensionService: IExtensionService, @IWorkspaceContextService protected contextService: IWorkspaceContextService, @IContextKeyService contextKeyService: IContextKeyService, - @IViewDescriptorService viewDescriptorService: IViewDescriptorService, - @IViewsService viewsService: IViewsService + @IViewDescriptorService viewDescriptorService: IViewDescriptorService ) { - super(VIEWLET_ID, SCMViewPaneContainer.STATE_KEY, { mergeViewWithContainerWhenSingleView: true }, instantiationService, configurationService, layoutService, contextMenuService, telemetryService, extensionService, themeService, storageService, contextService, viewDescriptorService, viewsService); + super(VIEWLET_ID, SCMViewPaneContainer.STATE_KEY, { mergeViewWithContainerWhenSingleView: true }, instantiationService, configurationService, layoutService, contextMenuService, telemetryService, extensionService, themeService, storageService, contextService, viewDescriptorService); this.menus = instantiationService.createInstance(SCMMenus, undefined); this._register(this.menus.onDidChangeTitle(this.updateTitleArea, this)); diff --git a/src/vs/workbench/contrib/search/browser/searchViewlet.ts b/src/vs/workbench/contrib/search/browser/searchViewlet.ts index 0bfa4f2f10f..2754b71d7b8 100644 --- a/src/vs/workbench/contrib/search/browser/searchViewlet.ts +++ b/src/vs/workbench/contrib/search/browser/searchViewlet.ts @@ -15,7 +15,7 @@ import { IExtensionService } from 'vs/workbench/services/extensions/common/exten import { VIEWLET_ID, VIEW_ID } from 'vs/workbench/services/search/common/search'; import { SearchView } from 'vs/workbench/contrib/search/browser/searchView'; import { ViewPaneContainer } from 'vs/workbench/browser/parts/views/viewPaneContainer'; -import { IViewDescriptorService, IViewsService } from 'vs/workbench/common/views'; +import { IViewDescriptorService } from 'vs/workbench/common/views'; export class SearchViewPaneContainer extends ViewPaneContainer { @@ -30,10 +30,9 @@ export class SearchViewPaneContainer extends ViewPaneContainer { @IThemeService themeService: IThemeService, @IContextMenuService contextMenuService: IContextMenuService, @IExtensionService extensionService: IExtensionService, - @IViewDescriptorService viewDescriptorService: IViewDescriptorService, - @IViewsService viewsService: IViewsService + @IViewDescriptorService viewDescriptorService: IViewDescriptorService ) { - super(VIEWLET_ID, `${VIEWLET_ID}.state`, { mergeViewWithContainerWhenSingleView: true, donotShowContainerTitleWhenMergedWithContainer: true }, instantiationService, configurationService, layoutService, contextMenuService, telemetryService, extensionService, themeService, storageService, contextService, viewDescriptorService, viewsService); + super(VIEWLET_ID, `${VIEWLET_ID}.state`, { mergeViewWithContainerWhenSingleView: true, donotShowContainerTitleWhenMergedWithContainer: true }, instantiationService, configurationService, layoutService, contextMenuService, telemetryService, extensionService, themeService, storageService, contextService, viewDescriptorService); } getSearchView(): SearchView | undefined { From dd3b65f955cced205dc91efdb4fa8a44fcf375ed Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Thu, 23 Jan 2020 15:06:52 +0100 Subject: [PATCH 041/801] Only show 'task already running' message when task is visible If the task is running but not visible, show it Fixes #84794 --- .../tasks/browser/abstractTaskService.ts | 29 +++++++++---------- .../tasks/browser/terminalTaskSystem.ts | 23 +++++++-------- .../contrib/tasks/common/taskSystem.ts | 1 + .../contrib/tasks/node/processTaskSystem.ts | 4 +++ 4 files changed, 30 insertions(+), 27 deletions(-) diff --git a/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts b/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts index 5fe66a52075..1dc520e61d6 100644 --- a/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts +++ b/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts @@ -1260,23 +1260,22 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer if (executeResult.kind === TaskExecuteKind.Active) { let active = executeResult.active; if (active && active.same) { - let message; - if (active.background) { - message = nls.localize('TaskSystem.activeSame.background', 'The task \'{0}\' is already active and in background mode.', executeResult.task.getQualifiedLabel()); + if (this._taskSystem?.isTaskVisible(executeResult.task)) { + const message = nls.localize('TaskSystem.activeSame.noBackground', 'The task \'{0}\' is already active.', executeResult.task.getQualifiedLabel()); + this.notificationService.prompt(Severity.Info, message, + [{ + label: nls.localize('terminateTask', "Terminate Task"), + run: () => this.terminate(executeResult.task) + }, + { + label: nls.localize('restartTask', "Restart Task"), + run: () => this.restart(executeResult.task) + }], + { sticky: true } + ); } else { - message = nls.localize('TaskSystem.activeSame.noBackground', 'The task \'{0}\' is already active.', executeResult.task.getQualifiedLabel()); + this._taskSystem?.revealTask(executeResult.task); } - this.notificationService.prompt(Severity.Info, message, - [{ - label: nls.localize('terminateTask', "Terminate Task"), - run: () => this.terminate(executeResult.task) - }, - { - label: nls.localize('restartTask', "Restart Task"), - run: () => this.restart(executeResult.task) - }], - { sticky: true } - ); } else { throw new TaskError(Severity.Warning, nls.localize('TaskSystem.active', 'There is already a task running. Terminate it first before executing another task.'), TaskErrors.RunningTask); } diff --git a/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts b/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts index c018ba09d3d..1f857cc1ef5 100644 --- a/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts +++ b/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts @@ -208,16 +208,6 @@ export class TerminalTaskSystem implements ITaskSystem { this.currentTask = new VerifiedTask(task, resolver, trigger); let terminalData = this.activeTasks[task.getMapKey()]; if (terminalData && terminalData.promise) { - let reveal = RevealKind.Always; - let focus = false; - if (CustomTask.is(task) || ContributedTask.is(task)) { - reveal = task.command.presentation!.reveal; - focus = task.command.presentation!.focus; - } - if (reveal === RevealKind.Always || focus) { - this.terminalService.setActiveInstance(terminalData.terminal); - this.terminalService.showPanel(focus); - } this.lastTask = this.currentTask; return { kind: TaskExecuteKind.Active, task, active: { same: true, background: task.configurationProperties.isBackground! }, promise: terminalData.promise }; } @@ -256,14 +246,23 @@ export class TerminalTaskSystem implements ITaskSystem { } } - public revealTask(task: Task): boolean { + public isTaskVisible(task: Task): boolean { let terminalData = this.activeTasks[task.getMapKey()]; if (!terminalData) { return false; } const activeTerminalInstance = this.terminalService.getActiveInstance(); const isPanelShowingTerminal = this.panelService.getActivePanel()?.getId() === TERMINAL_PANEL_ID; - if (isPanelShowingTerminal && (activeTerminalInstance === terminalData.terminal)) { + return isPanelShowingTerminal && (activeTerminalInstance?.id === terminalData.terminal.id); + } + + + public revealTask(task: Task): boolean { + let terminalData = this.activeTasks[task.getMapKey()]; + if (!terminalData) { + return false; + } + if (this.isTaskVisible(task)) { if (this.previousPanelId) { if (this.previousTerminalInstance) { this.terminalService.setActiveInstance(this.previousTerminalInstance); diff --git a/src/vs/workbench/contrib/tasks/common/taskSystem.ts b/src/vs/workbench/contrib/tasks/common/taskSystem.ts index 887c3f288bf..c6d2c0bc03e 100644 --- a/src/vs/workbench/contrib/tasks/common/taskSystem.ts +++ b/src/vs/workbench/contrib/tasks/common/taskSystem.ts @@ -139,4 +139,5 @@ export interface ITaskSystem { terminateAll(): Promise; revealTask(task: Task): boolean; customExecutionComplete(task: Task, result: number): Promise; + isTaskVisible(task: Task): boolean; } diff --git a/src/vs/workbench/contrib/tasks/node/processTaskSystem.ts b/src/vs/workbench/contrib/tasks/node/processTaskSystem.ts index 648fa8617f9..73d947db159 100644 --- a/src/vs/workbench/contrib/tasks/node/processTaskSystem.ts +++ b/src/vs/workbench/contrib/tasks/node/processTaskSystem.ts @@ -82,6 +82,10 @@ export class ProcessTaskSystem implements ITaskSystem { return !!this.childProcess; } + public isTaskVisible(): boolean { + return true; + } + public getActiveTasks(): Task[] { let result: Task[] = []; if (this.activeTask) { From 5d85d2eeefc631c685cd94773df90fd7a2bf4f22 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Thu, 23 Jan 2020 15:29:39 +0100 Subject: [PATCH 042/801] suggest - don't send default ranges for each item --- .../api/browser/mainThreadLanguageFeatures.ts | 13 ++- .../workbench/api/common/extHost.protocol.ts | 12 ++- .../api/common/extHostLanguageFeatures.ts | 89 ++++++++++--------- 3 files changed, 64 insertions(+), 50 deletions(-) diff --git a/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts b/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts index fd435ad50fc..810e81e66ec 100644 --- a/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts +++ b/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts @@ -11,7 +11,7 @@ import * as search from 'vs/workbench/contrib/search/common/search'; import { CancellationToken } from 'vs/base/common/cancellation'; import { Position as EditorPosition } from 'vs/editor/common/core/position'; import { Range as EditorRange, IRange } from 'vs/editor/common/core/range'; -import { ExtHostContext, MainThreadLanguageFeaturesShape, ExtHostLanguageFeaturesShape, MainContext, IExtHostContext, ILanguageConfigurationDto, IRegExpDto, IIndentationRuleDto, IOnEnterRuleDto, ILocationDto, IWorkspaceSymbolDto, reviveWorkspaceEditDto, IDocumentFilterDto, IDefinitionLinkDto, ISignatureHelpProviderMetadataDto, ILinkDto, ICallHierarchyItemDto, ISuggestDataDto, ICodeActionDto, ISuggestDataDtoField } from '../common/extHost.protocol'; +import { ExtHostContext, MainThreadLanguageFeaturesShape, ExtHostLanguageFeaturesShape, MainContext, IExtHostContext, ILanguageConfigurationDto, IRegExpDto, IIndentationRuleDto, IOnEnterRuleDto, ILocationDto, IWorkspaceSymbolDto, reviveWorkspaceEditDto, IDocumentFilterDto, IDefinitionLinkDto, ISignatureHelpProviderMetadataDto, ILinkDto, ICallHierarchyItemDto, ISuggestDataDto, ICodeActionDto, ISuggestDataDtoField, ISuggestResultDtoField } from '../common/extHost.protocol'; import { LanguageConfigurationRegistry } from 'vs/editor/common/modes/languageConfigurationRegistry'; import { LanguageConfiguration, IndentationRule, OnEnterRule } from 'vs/editor/common/modes/languageConfiguration'; import { IModeService } from 'vs/editor/common/services/modeService'; @@ -368,10 +368,15 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha if (!result) { return result; } + return { - suggestions: result.b.map(d => MainThreadLanguageFeatures._inflateSuggestDto(result.a, d)), - incomplete: result.c, - dispose: () => typeof result.x === 'number' && this._proxy.$releaseCompletionItems(handle, result.x) + suggestions: result[ISuggestResultDtoField.completions].map(d => MainThreadLanguageFeatures._inflateSuggestDto(result[ISuggestResultDtoField.defaultRanges], d)), + incomplete: result[ISuggestResultDtoField.isIncomplete] || false, + dispose: () => { + if (typeof result.x === 'number') { + this._proxy.$releaseCompletionItems(handle, result.x); + } + } }; }); } diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 9809bbf4ca2..bd26f4a7aea 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -1030,11 +1030,17 @@ export interface ISuggestDataDto { x?: ChainedCacheId; } +export const enum ISuggestResultDtoField { + defaultRanges = 'a', + completions = 'b', + isIncomplete = 'c' +} + export interface ISuggestResultDto { + [ISuggestResultDtoField.defaultRanges]: { insert: IRange, replace: IRange; }; + [ISuggestResultDtoField.completions]: ISuggestDataDto[]; + [ISuggestResultDtoField.isIncomplete]: undefined | true; x?: number; - a: { insert: IRange, replace: IRange; }; - b: ISuggestDataDto[]; - c?: true; } export interface ISignatureHelpDto { diff --git a/src/vs/workbench/api/common/extHostLanguageFeatures.ts b/src/vs/workbench/api/common/extHostLanguageFeatures.ts index 2abddf839ee..66b8926ba3f 100644 --- a/src/vs/workbench/api/common/extHostLanguageFeatures.ts +++ b/src/vs/workbench/api/common/extHostLanguageFeatures.ts @@ -819,19 +819,20 @@ class SuggestAdapter { const disposables = new DisposableStore(); this._disposables.set(pid, disposables); + const completions: extHostProtocol.ISuggestDataDto[] = []; const result: extHostProtocol.ISuggestResultDto = { x: pid, - b: [], - a: { replace: typeConvert.Range.from(replaceRange), insert: typeConvert.Range.from(insertRange) }, - c: list.isIncomplete || undefined + [extHostProtocol.ISuggestResultDtoField.completions]: completions, + [extHostProtocol.ISuggestResultDtoField.defaultRanges]: { replace: typeConvert.Range.from(replaceRange), insert: typeConvert.Range.from(insertRange) }, + [extHostProtocol.ISuggestResultDtoField.isIncomplete]: list.isIncomplete || undefined }; for (let i = 0; i < list.items.length; i++) { - const suggestion = this._convertCompletionItem(list.items[i], pos, [pid, i]); - // check for bad completion item - // for the converter did warn - if (suggestion) { - result.b.push(suggestion); + const item = list.items[i]; + // check for bad completion item first + if (this._validateCompletionItem(item, pos)) { + const dto = this._convertCompletionItem(item, [pid, i], insertRange, replaceRange); + completions.push(dto); } } @@ -850,12 +851,13 @@ class SuggestAdapter { return Promise.resolve(undefined); } + const pos = typeConvert.Position.to(position); const _mustNotChange = SuggestAdapter._mustNotChangeHash(item); const _mayNotChange = SuggestAdapter._mayNotChangeHash(item); return asPromise(() => this._provider.resolveCompletionItem!(item, token)).then(resolvedItem => { - if (!resolvedItem) { + if (!resolvedItem || !this._validateCompletionItem(resolvedItem, pos)) { return undefined; } @@ -885,8 +887,7 @@ class SuggestAdapter { this._didWarnShould = true; } - const pos = typeConvert.Position.to(position); - return this._convertCompletionItem(resolvedItem, pos, id); + return this._convertCompletionItem(resolvedItem, id); }); } @@ -896,11 +897,7 @@ class SuggestAdapter { this._cache.delete(id); } - private _convertCompletionItem(item: vscode.CompletionItem, position: vscode.Position, id: extHostProtocol.ChainedCacheId): extHostProtocol.ISuggestDataDto | undefined { - if (typeof item.label !== 'string' || item.label.length === 0) { - this._logService.warn('INVALID text edit -> must have at least a label'); - return undefined; - } + private _convertCompletionItem(item: vscode.CompletionItem, id: extHostProtocol.ChainedCacheId, defaultInsertRange?: vscode.Range, defaultReplaceRange?: vscode.Range): extHostProtocol.ISuggestDataDto { const disposables = this._disposables.get(id[0]); if (!disposables) { @@ -928,9 +925,7 @@ class SuggestAdapter { // 'insertText'-logic if (item.textEdit) { - this._apiDeprecation.report('CompletionItem.textEdit', this._extension, - `Use 'CompletionItem.insertText' and 'CompletionItem.range' instead.`); - + this._apiDeprecation.report('CompletionItem.textEdit', this._extension, `Use 'CompletionItem.insertText' and 'CompletionItem.range' instead.`); result[extHostProtocol.ISuggestDataDtoField.insertText] = item.textEdit.newText; } else if (typeof item.insertText === 'string') { @@ -949,36 +944,44 @@ class SuggestAdapter { range = item.range; } - if (range) { - if (Range.isRange(range)) { - if (!SuggestAdapter._isValidRangeForCompletion(range, position)) { - this._logService.trace('INVALID range -> must be single line and on the same line'); - return undefined; - } - result[extHostProtocol.ISuggestDataDtoField.range] = typeConvert.Range.from(range); + if (Range.isRange(range)) { + // "old" range + result[extHostProtocol.ISuggestDataDtoField.range] = typeConvert.Range.from(range); - } else { - if ( - !SuggestAdapter._isValidRangeForCompletion(range.inserting, position) - || !SuggestAdapter._isValidRangeForCompletion(range.replacing, position) - || !range.inserting.start.isEqual(range.replacing.start) - || !range.replacing.contains(range.inserting) - ) { - this._logService.trace('INVALID range -> must be single line, on the same line, insert range must be a prefix of replace range'); - return undefined; - } - result[extHostProtocol.ISuggestDataDtoField.range] = { - insert: typeConvert.Range.from(range.inserting), - replace: typeConvert.Range.from(range.replacing) - }; - } + } else if (range && !defaultInsertRange?.isEqual(range.inserting) && !defaultReplaceRange?.isEqual(range.replacing)) { + // ONLY send range when it's different from the default ranges (safe bandwidth) + result[extHostProtocol.ISuggestDataDtoField.range] = { + insert: typeConvert.Range.from(range.inserting), + replace: typeConvert.Range.from(range.replacing) + }; } return result; } - private static _isValidRangeForCompletion(range: vscode.Range, position: vscode.Position): boolean { - return range.isSingleLine || range.start.line === position.line; + private _validateCompletionItem(item: vscode.CompletionItem, position: vscode.Position): boolean { + if (typeof item.label !== 'string' || item.label.length === 0) { + this._logService.warn('INVALID text edit -> must have at least a label'); + return false; + } + + if (Range.isRange(item.range)) { + if (!item.range.isSingleLine || item.range.start.line !== position.line) { + this._logService.trace('INVALID range -> must be single line and on the same line'); + return false; + } + + } else if (item.range) { + if (!item.range.inserting.isSingleLine || item.range.inserting.start.line !== position.line + || !item.range.replacing.isSingleLine || item.range.replacing.start.line !== position.line + || !item.range.inserting.start.isEqual(item.range.replacing.start) + || !item.range.replacing.contains(item.range.inserting) + ) { + this._logService.trace('INVALID range -> must be single line, on the same line, insert range must be a prefix of replace range'); + return false; + } + } + return true; } private static _mustNotChangeHash(item: vscode.CompletionItem) { From d7890fa51fdd3cafa06325c9739608a29cba677c Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Thu, 23 Jan 2020 15:51:27 +0100 Subject: [PATCH 043/801] Fixes #53947: Add "Developer: Restart Extension Host" --- .../electron-browser/extensionService.ts | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/src/vs/workbench/services/extensions/electron-browser/extensionService.ts b/src/vs/workbench/services/extensions/electron-browser/extensionService.ts index 2c2d10a1823..8e2bf659aeb 100644 --- a/src/vs/workbench/services/extensions/electron-browser/extensionService.ts +++ b/src/vs/workbench/services/extensions/electron-browser/extensionService.ts @@ -39,6 +39,10 @@ import { IStaticExtensionsService } from 'vs/workbench/services/extensions/commo import { IElectronService } from 'vs/platform/electron/node/electron'; import { IElectronEnvironmentService } from 'vs/workbench/services/electron/electron-browser/electronEnvironmentService'; import { IRemoteExplorerService } from 'vs/workbench/services/remote/common/remoteExplorerService'; +import { Action } from 'vs/base/common/actions'; +import { SyncActionDescriptor } from 'vs/platform/actions/common/actions'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { Extensions as ActionExtensions, IWorkbenchActionRegistry } from 'vs/workbench/common/actions'; class DeltaExtensionsQueueItem { constructor( @@ -600,3 +604,24 @@ function _removeSet(arr: IExtensionDescription[], toRemove: IExtensionDescriptio } registerSingleton(IExtensionService, ExtensionService); + +class RestartExtensionHostAction extends Action { + + public static readonly ID = 'workbench.action.restartExtensionHost'; + public static readonly LABEL = nls.localize('restartExtensionHost', "Developer: Restart Extension Host"); + + constructor( + id: string, + label: string, + @IExtensionService private readonly _extensionService: IExtensionService + ) { + super(id, label); + } + + public async run() { + this._extensionService.restartExtensionHost(); + } +} + +const registry = Registry.as(ActionExtensions.WorkbenchActions); +registry.registerWorkbenchAction(SyncActionDescriptor.create(RestartExtensionHostAction, RestartExtensionHostAction.ID, RestartExtensionHostAction.LABEL), 'Developer: Restart Extension Host'); From e09632f8bf63eec8f8d58ed876b1c1426a11ee72 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Thu, 23 Jan 2020 15:59:45 +0100 Subject: [PATCH 044/801] fix tests --- .../test/electron-browser/api/extHostLanguageFeatures.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/test/electron-browser/api/extHostLanguageFeatures.test.ts b/src/vs/workbench/test/electron-browser/api/extHostLanguageFeatures.test.ts index 8f526bedbb2..f00d6eec6c6 100644 --- a/src/vs/workbench/test/electron-browser/api/extHostLanguageFeatures.test.ts +++ b/src/vs/workbench/test/electron-browser/api/extHostLanguageFeatures.test.ts @@ -879,7 +879,7 @@ suite('ExtHostLanguageFeatures', function () { await rpcProtocol.sync(); const value = await provideSuggestionItems(model, new EditorPosition(1, 1), new CompletionOptions(undefined, new Set().add(modes.CompletionItemKind.Snippet))); - assert.equal(value[0].container.incomplete, undefined); + assert.equal(value[0].container.incomplete, false); }); test('Suggest, CompletionList', async () => { From 368a7b92e4ad083c7bfb5be3d22b812df4a8f1aa Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Thu, 23 Jan 2020 16:11:55 +0100 Subject: [PATCH 045/801] Fix remote explorer viewlet actions --- src/vs/workbench/browser/parts/views/viewsViewlet.ts | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/src/vs/workbench/browser/parts/views/viewsViewlet.ts b/src/vs/workbench/browser/parts/views/viewsViewlet.ts index 9bd58721fe8..304a78c4383 100644 --- a/src/vs/workbench/browser/parts/views/viewsViewlet.ts +++ b/src/vs/workbench/browser/parts/views/viewsViewlet.ts @@ -89,8 +89,7 @@ export abstract class FilterViewPaneContainer extends ViewPaneContainer { } getContextMenuActions(): IAction[] { - const result: IAction[] = []; - let viewToggleActions: IAction[] = Array.from(this.constantViewDescriptors.values()).map(viewDescriptor => ({ + const result: IAction[] = Array.from(this.constantViewDescriptors.values()).map(viewDescriptor => ({ id: `${viewDescriptor.id}.toggleVisibility`, label: viewDescriptor.name, checked: this.viewsModel.isVisible(viewDescriptor.id), @@ -98,13 +97,6 @@ export abstract class FilterViewPaneContainer extends ViewPaneContainer { run: () => this.toggleViewVisibility(viewDescriptor.id) })); - result.push(...viewToggleActions); - const parentActions = super.getContextMenuActions(); - if (viewToggleActions.length && parentActions.length) { - result.push(new Separator()); - } - - result.push(...parentActions); return result; } From 3efd575c64786100f67d80a29783715ccb3a9f18 Mon Sep 17 00:00:00 2001 From: isidor Date: Thu, 23 Jan 2020 16:22:02 +0100 Subject: [PATCH 046/801] If a screen reader is attached and the default value is not set we shuold automatically increase the page size to 1000 for a better experience fixes #85833 --- .../browser/controller/textAreaHandler.ts | 23 +++++++++++++------ 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/src/vs/editor/browser/controller/textAreaHandler.ts b/src/vs/editor/browser/controller/textAreaHandler.ts index 4a776034d2a..d641968b4b0 100644 --- a/src/vs/editor/browser/controller/textAreaHandler.ts +++ b/src/vs/editor/browser/controller/textAreaHandler.ts @@ -17,7 +17,7 @@ import { ViewController } from 'vs/editor/browser/view/viewController'; import { PartFingerprint, PartFingerprints, ViewPart } from 'vs/editor/browser/view/viewPart'; import { LineNumbersOverlay } from 'vs/editor/browser/viewParts/lineNumbers/lineNumbers'; import { Margin } from 'vs/editor/browser/viewParts/margin/margin'; -import { RenderLineNumbersType, EditorOption, IComputedEditorOptions } from 'vs/editor/common/config/editorOptions'; +import { RenderLineNumbersType, EditorOption, IComputedEditorOptions, EditorOptions } from 'vs/editor/common/config/editorOptions'; import { BareFontInfo } from 'vs/editor/common/config/fontInfo'; import { WordCharacterClass, getMapForWordSeparators } from 'vs/editor/common/controller/wordCharacterClassifier'; import { Position } from 'vs/editor/common/core/position'; @@ -62,8 +62,8 @@ export class TextAreaHandler extends ViewPart { private _scrollLeft: number; private _scrollTop: number; - private _accessibilitySupport: AccessibilitySupport; - private _accessibilityPageSize: number; + private _accessibilitySupport!: AccessibilitySupport; + private _accessibilityPageSize!: number; private _contentLeft: number; private _contentWidth: number; private _contentHeight: number; @@ -100,8 +100,7 @@ export class TextAreaHandler extends ViewPart { const options = this._context.configuration.options; const layoutInfo = options.get(EditorOption.layoutInfo); - this._accessibilitySupport = options.get(EditorOption.accessibilitySupport); - this._accessibilityPageSize = options.get(EditorOption.accessibilityPageSize); + this._setAccessibilityOptions(options); this._contentLeft = layoutInfo.contentLeft; this._contentWidth = layoutInfo.contentWidth; this._contentHeight = layoutInfo.height; @@ -346,14 +345,24 @@ export class TextAreaHandler extends ViewPart { return options.get(EditorOption.ariaLabel); } + private _setAccessibilityOptions(options: IComputedEditorOptions): void { + this._accessibilitySupport = options.get(EditorOption.accessibilitySupport); + const accessibilityPageSize = options.get(EditorOption.accessibilityPageSize); + if (this._accessibilitySupport === AccessibilitySupport.Enabled && accessibilityPageSize === EditorOptions.accessibilityPageSize.defaultValue) { + // If a screen reader is attached and the default value is not set we shuold automatically increase the page size to 1000 for a better experience + this._accessibilityPageSize = 1000; + } else { + this._accessibilityPageSize = accessibilityPageSize; + } + } + // --- begin event handlers public onConfigurationChanged(e: viewEvents.ViewConfigurationChangedEvent): boolean { const options = this._context.configuration.options; const layoutInfo = options.get(EditorOption.layoutInfo); - this._accessibilitySupport = options.get(EditorOption.accessibilitySupport); - this._accessibilityPageSize = options.get(EditorOption.accessibilityPageSize); + this._setAccessibilityOptions(options); this._contentLeft = layoutInfo.contentLeft; this._contentWidth = layoutInfo.contentWidth; this._contentHeight = layoutInfo.height; From 15d85ec41dd558454df07d0d4085ec823d377567 Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Thu, 23 Jan 2020 16:35:18 +0100 Subject: [PATCH 047/801] Remove unused reference in viewsViewlet.ts --- src/vs/workbench/browser/parts/views/viewsViewlet.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/vs/workbench/browser/parts/views/viewsViewlet.ts b/src/vs/workbench/browser/parts/views/viewsViewlet.ts index 304a78c4383..3ededdd5bf7 100644 --- a/src/vs/workbench/browser/parts/views/viewsViewlet.ts +++ b/src/vs/workbench/browser/parts/views/viewsViewlet.ts @@ -5,7 +5,6 @@ import * as DOM from 'vs/base/browser/dom'; import { IAction } from 'vs/base/common/actions'; -import { Separator } from 'vs/base/browser/ui/actionbar/actionbar'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IViewDescriptor, IViewDescriptorService } from 'vs/workbench/common/views'; From fc282975fea046a88907fa38a03c0b7d3e07d9d2 Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Thu, 23 Jan 2020 16:39:35 +0100 Subject: [PATCH 048/801] Stop sequential dependent tasks if one is canceled Fixes #84789 --- .../workbench/contrib/tasks/browser/terminalTaskSystem.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts b/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts index 1f857cc1ef5..9a17b4e2cf9 100644 --- a/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts +++ b/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts @@ -377,7 +377,13 @@ export class TerminalTaskSystem implements ITaskSystem { promise = this.executeTask(dependencyTask, resolver, trigger, alreadyResolved); } if (task.configurationProperties.dependsOrder === DependsOrder.sequence) { - promise = Promise.resolve(await promise); + const promiseResult = await promise; + if (promiseResult.exitCode === 0) { + promise = Promise.resolve(promiseResult); + } else { + promise = Promise.reject(promiseResult); + break; + } } promises.push(promise); } else { From ee22a386faab5148d274bdf9c284ad0c69125bc4 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Thu, 23 Jan 2020 16:48:40 +0100 Subject: [PATCH 049/801] fix #86692 --- .../services/markerDecorationsServiceImpl.ts | 23 ++++++++++++------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/src/vs/editor/common/services/markerDecorationsServiceImpl.ts b/src/vs/editor/common/services/markerDecorationsServiceImpl.ts index d37fbe83d1e..78820460000 100644 --- a/src/vs/editor/common/services/markerDecorationsServiceImpl.ts +++ b/src/vs/editor/common/services/markerDecorationsServiceImpl.ts @@ -147,12 +147,10 @@ export class MarkerDecorationsService extends Disposable implements IMarkerDecor let ret = Range.lift(rawMarker); - if (rawMarker.severity === MarkerSeverity.Hint) { - if (!rawMarker.tags || rawMarker.tags.indexOf(MarkerTag.Unnecessary) === -1) { - // * never render hints on multiple lines - // * make enough space for three dots - ret = ret.setEndPosition(ret.startLineNumber, ret.startColumn + 2); - } + if (rawMarker.severity === MarkerSeverity.Hint && !this._hasMarkerTag(rawMarker, MarkerTag.Unnecessary) && !this._hasMarkerTag(rawMarker, MarkerTag.Deprecated)) { + // * never render hints on multiple lines + // * make enough space for three dots + ret = ret.setEndPosition(ret.startLineNumber, ret.startColumn + 2); } ret = model.validateRange(ret); @@ -188,7 +186,7 @@ export class MarkerDecorationsService extends Disposable implements IMarkerDecor private _createDecorationOption(marker: IMarker): IModelDecorationOptions { - let className: string; + let className: string | undefined; let color: ThemeColor | undefined = undefined; let zIndex: number; let inlineClassName: string | undefined = undefined; @@ -196,7 +194,9 @@ export class MarkerDecorationsService extends Disposable implements IMarkerDecor switch (marker.severity) { case MarkerSeverity.Hint: - if (marker.tags && marker.tags.indexOf(MarkerTag.Unnecessary) >= 0) { + if (this._hasMarkerTag(marker, MarkerTag.Deprecated)) { + className = undefined; + } else if (this._hasMarkerTag(marker, MarkerTag.Unnecessary)) { className = ClassName.EditorUnnecessaryDecoration; } else { className = ClassName.EditorHintDecoration; @@ -251,4 +251,11 @@ export class MarkerDecorationsService extends Disposable implements IMarkerDecor inlineClassName, }; } + + private _hasMarkerTag(marker: IMarker, tag: MarkerTag): boolean { + if (marker.tags) { + return marker.tags.indexOf(tag) >= 0; + } + return false; + } } From 79f83f0033fc090ccbf9c8e8372e2175378ad1eb Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Thu, 23 Jan 2020 17:02:01 +0100 Subject: [PATCH 050/801] Fix #89046 --- src/vs/platform/environment/common/environment.ts | 1 + src/vs/platform/environment/node/environmentService.ts | 7 +++++-- .../platform/userDataSync/common/abstractSynchronizer.ts | 2 +- .../workbench/contrib/userDataSync/browser/userDataSync.ts | 2 +- .../services/environment/browser/environmentService.ts | 7 +++++-- 5 files changed, 13 insertions(+), 6 deletions(-) diff --git a/src/vs/platform/environment/common/environment.ts b/src/vs/platform/environment/common/environment.ts index c3a94942b82..abd1e33b185 100644 --- a/src/vs/platform/environment/common/environment.ts +++ b/src/vs/platform/environment/common/environment.ts @@ -128,6 +128,7 @@ export interface IEnvironmentService extends IUserHomeProvider { // sync resources userDataSyncLogResource: URI; + userDataSyncHome: URI; settingsSyncPreviewResource: URI; keybindingsSyncPreviewResource: URI; diff --git a/src/vs/platform/environment/node/environmentService.ts b/src/vs/platform/environment/node/environmentService.ts index 99cab4bba27..0428e1e888a 100644 --- a/src/vs/platform/environment/node/environmentService.ts +++ b/src/vs/platform/environment/node/environmentService.ts @@ -112,10 +112,13 @@ export class EnvironmentService implements IEnvironmentService { get settingsResource(): URI { return resources.joinPath(this.userRoamingDataHome, 'settings.json'); } @memoize - get settingsSyncPreviewResource(): URI { return resources.joinPath(this.userRoamingDataHome, '.settings.json'); } + get userDataSyncHome(): URI { return resources.joinPath(this.userRoamingDataHome, '.sync'); } @memoize - get keybindingsSyncPreviewResource(): URI { return resources.joinPath(this.userRoamingDataHome, '.keybindings.json'); } + get settingsSyncPreviewResource(): URI { return resources.joinPath(this.userDataSyncHome, 'settings.json'); } + + @memoize + get keybindingsSyncPreviewResource(): URI { return resources.joinPath(this.userDataSyncHome, 'keybindings.json'); } @memoize get userDataSyncLogResource(): URI { return URI.file(path.join(this.logsPath, 'userDataSync.log')); } diff --git a/src/vs/platform/userDataSync/common/abstractSynchronizer.ts b/src/vs/platform/userDataSync/common/abstractSynchronizer.ts index 5fa56da9cb7..ded00afd7ce 100644 --- a/src/vs/platform/userDataSync/common/abstractSynchronizer.ts +++ b/src/vs/platform/userDataSync/common/abstractSynchronizer.ts @@ -24,7 +24,7 @@ export abstract class AbstractSynchroniser extends Disposable { @IEnvironmentService environmentService: IEnvironmentService ) { super(); - this.syncFolder = joinPath(environmentService.userRoamingDataHome, '.sync', source); + this.syncFolder = joinPath(environmentService.userDataSyncHome, source); this.cleanUpDelayer = new ThrottledDelayer(50); } diff --git a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts index b7026db62a2..b928378375a 100644 --- a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts +++ b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts @@ -469,7 +469,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo previewResource = this.workbenchEnvironmentService.settingsSyncPreviewResource; label = localize('settings conflicts preview', "Settings Conflicts (Remote ↔ Local)"); } else if (this.userDataSyncService.conflictsSource === SyncSource.Keybindings) { - previewResource = this.workbenchEnvironmentService.keybindingsResource; + previewResource = this.workbenchEnvironmentService.keybindingsSyncPreviewResource; label = localize('keybindings conflicts preview', "Keybindings Conflicts (Remote ↔ Local)"); } if (previewResource) { diff --git a/src/vs/workbench/services/environment/browser/environmentService.ts b/src/vs/workbench/services/environment/browser/environmentService.ts index 604717c8faf..1bf4cfad2a3 100644 --- a/src/vs/workbench/services/environment/browser/environmentService.ts +++ b/src/vs/workbench/services/environment/browser/environmentService.ts @@ -137,10 +137,13 @@ export class BrowserWorkbenchEnvironmentService implements IWorkbenchEnvironment get argvResource(): URI { return joinPath(this.userRoamingDataHome, 'argv.json'); } @memoize - get settingsSyncPreviewResource(): URI { return joinPath(this.userRoamingDataHome, '.settings.json'); } + get userDataSyncHome(): URI { return joinPath(this.userRoamingDataHome, '.sync'); } @memoize - get keybindingsSyncPreviewResource(): URI { return joinPath(this.userRoamingDataHome, '.keybindings.json'); } + get settingsSyncPreviewResource(): URI { return joinPath(this.userDataSyncHome, 'settings.json'); } + + @memoize + get keybindingsSyncPreviewResource(): URI { return joinPath(this.userDataSyncHome, 'keybindings.json'); } @memoize get userDataSyncLogResource(): URI { return joinPath(this.options.logsPath, 'userDataSync.log'); } From 2b880f2b741bbf1171547c1adee0b5ac7576681b Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Thu, 23 Jan 2020 16:57:51 +0100 Subject: [PATCH 051/801] update typescript-vscode-sh-plugin --- extensions/typescript-language-features/package.json | 2 +- extensions/typescript-language-features/yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/extensions/typescript-language-features/package.json b/extensions/typescript-language-features/package.json index 105930e8fa0..d4a7d5eb4e8 100644 --- a/extensions/typescript-language-features/package.json +++ b/extensions/typescript-language-features/package.json @@ -19,7 +19,7 @@ "jsonc-parser": "^2.1.1", "rimraf": "^2.6.3", "semver": "5.5.1", - "typescript-vscode-sh-plugin": "^0.6.2", + "typescript-vscode-sh-plugin": "^0.6.3", "vscode-extension-telemetry": "0.1.1", "vscode-nls": "^4.0.0" }, diff --git a/extensions/typescript-language-features/yarn.lock b/extensions/typescript-language-features/yarn.lock index c0a75776a48..bb261f27279 100644 --- a/extensions/typescript-language-features/yarn.lock +++ b/extensions/typescript-language-features/yarn.lock @@ -626,10 +626,10 @@ tweetnacl@^0.14.3, tweetnacl@~0.14.0: resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64" integrity sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q= -typescript-vscode-sh-plugin@^0.6.2: - version "0.6.2" - resolved "https://registry.yarnpkg.com/typescript-vscode-sh-plugin/-/typescript-vscode-sh-plugin-0.6.2.tgz#fbbf6ca12f6fc29deba2abd153d15776eead08b0" - integrity sha512-I4O6rqtMIEK5/1rNgBBJ+ekqd/RdYD3Z9iwcXFeohWqY8VKAoS32Lq/I0tNLrnKHHxHGUxou37FJnlMZSU1GUQ== +typescript-vscode-sh-plugin@^0.6.3: + version "0.6.3" + resolved "https://registry.yarnpkg.com/typescript-vscode-sh-plugin/-/typescript-vscode-sh-plugin-0.6.3.tgz#24cb38259ed2698d99820af0a85be82d758f3d45" + integrity sha512-x+KscX/01UdQXRPAhkLNme8SJ2PGZYCYANQU5YmawAAVF1EZyuh2I0/wRJaf3i3w+Zy/In6MrPC4WYDfyWE8Yw== uri-js@^4.2.2: version "4.2.2" From c092ab5a5aa1ef3f4c93cde6bf2d2246686de5a8 Mon Sep 17 00:00:00 2001 From: Gabriel DeBacker Date: Thu, 23 Jan 2020 08:20:20 -0800 Subject: [PATCH 052/801] Remove rename of method arguments --- .../browser/extensions.contribution.ts | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts index cb6b40bc536..3a442314590 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts @@ -228,26 +228,26 @@ CommandsRegistry.registerCommand({ description: localize('workbench.extensions.installExtension.description', "Install the given extension"), args: [ { - name: localize('workbench.extensions.installExtension.extensionId.name', "Extension id or VSIX resource uri"), + name: localize('workbench.extensions.installExtension.args.name', "Extension id or VSIX resource uri"), schema: { 'type': ['object', 'string'] } } ] }, - handler: async (accessor, extensionId: string | UriComponents) => { + handler: async (accessor, arg: string | UriComponents) => { const extensionManagementService = accessor.get(IExtensionManagementService); const extensionGalleryService = accessor.get(IExtensionGalleryService); try { - if (typeof extensionId === 'string') { - const extension = await extensionGalleryService.getCompatibleExtension({ id: extensionId }); + if (typeof arg === 'string') { + const extension = await extensionGalleryService.getCompatibleExtension({ id: arg }); if (extension) { await extensionManagementService.installFromGallery(extension); } else { - throw new Error(localize('notFound', "Extension '{0}' not found.", extensionId)); + throw new Error(localize('notFound', "Extension '{0}' not found.", arg)); } } else { - const vsix = URI.revive(extensionId); + const vsix = URI.revive(arg); await extensionManagementService.install(vsix); } } catch (e) { @@ -263,22 +263,22 @@ CommandsRegistry.registerCommand({ description: localize('workbench.extensions.uninstallExtension.description', "Uninstall the given extension"), args: [ { - name: localize('workbench.extensions.uninstallExtension.extensionId.name', "Id of the extension to uninstall"), + name: localize('workbench.extensions.uninstallExtension.arg.name', "Id of the extension to uninstall"), schema: { 'type': 'string' } } ] }, - handler: async (accessor, extensionId: string) => { - if (!extensionId) { + handler: async (accessor, id: string) => { + if (!id) { throw new Error(localize('id required', "Extension id required.")); } const extensionManagementService = accessor.get(IExtensionManagementService); const installed = await extensionManagementService.getInstalled(ExtensionType.User); - const [extensionToUninstall] = installed.filter(e => areSameExtensions(e.identifier, { id: extensionId })); + const [extensionToUninstall] = installed.filter(e => areSameExtensions(e.identifier, { id })); if (!extensionToUninstall) { - throw new Error(localize('notInstalled', "Extension '{0}' is not installed. Make sure you use the full extension ID, including the publisher, e.g.: ms-vscode.csharp.", extensionId)); + throw new Error(localize('notInstalled', "Extension '{0}' is not installed. Make sure you use the full extension ID, including the publisher, e.g.: ms-vscode.csharp.", id)); } try { From 3756f10310386d7ff2819b3343898769abebafa3 Mon Sep 17 00:00:00 2001 From: Gabriel DeBacker Date: Thu, 23 Jan 2020 08:21:19 -0800 Subject: [PATCH 053/801] Remove 's' from args --- .../contrib/extensions/browser/extensions.contribution.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts index 3a442314590..2ec7478dfa0 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts @@ -228,7 +228,7 @@ CommandsRegistry.registerCommand({ description: localize('workbench.extensions.installExtension.description', "Install the given extension"), args: [ { - name: localize('workbench.extensions.installExtension.args.name', "Extension id or VSIX resource uri"), + name: localize('workbench.extensions.installExtension.arg.name', "Extension id or VSIX resource uri"), schema: { 'type': ['object', 'string'] } From dcb5998b75c845a2b3ce1fcb480eceec836c6608 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Thu, 23 Jan 2020 10:00:50 -0800 Subject: [PATCH 054/801] Fix resource scope access error logging (#89130) For #87768 It seems that this log message is meant to be fired when accessing a resource scoped config without a resource --- src/vs/workbench/api/common/extHostConfiguration.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/api/common/extHostConfiguration.ts b/src/vs/workbench/api/common/extHostConfiguration.ts index a8320d19b0e..b4aad76159e 100644 --- a/src/vs/workbench/api/common/extHostConfiguration.ts +++ b/src/vs/workbench/api/common/extHostConfiguration.ts @@ -301,7 +301,7 @@ export class ExtHostConfigProvider { const scope = OVERRIDE_PROPERTY_PATTERN.test(key) ? ConfigurationScope.RESOURCE : this._configurationScopes.get(key); const extensionIdText = extensionId ? `[${extensionId.value}] ` : ''; if (ConfigurationScope.RESOURCE === scope) { - if (overrides?.resource) { + if (typeof overrides?.resource === 'undefined') { this._logService.warn(`${extensionIdText}Accessing a resource scoped configuration without providing a resource is not expected. To get the effective value for '${key}', provide the URI of a resource or 'null' for any resource.`); } return; From ead3e2373df23df1bb4301937a5dcab345a34789 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Thu, 23 Jan 2020 10:01:06 -0800 Subject: [PATCH 055/801] Correctly convert `null` to a resource scope (#89131) For #87768 I believe that calling `vscode.workspace.getConfiguration('section', null)` is supposed to create a specific config accessor that works for any resource. Currently it behaves the same as if you omitted the second param --- src/vs/workbench/api/common/extHostConfiguration.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/vs/workbench/api/common/extHostConfiguration.ts b/src/vs/workbench/api/common/extHostConfiguration.ts index b4aad76159e..6042075bc00 100644 --- a/src/vs/workbench/api/common/extHostConfiguration.ts +++ b/src/vs/workbench/api/common/extHostConfiguration.ts @@ -85,6 +85,9 @@ function scopeToOverrides(scope: vscode.ConfigurationScope | undefined | null): if (isWorkspaceFolder(scope)) { return { resource: scope.uri }; } + if (scope === null) { + return { resource: null }; + } return undefined; } From 69f7b8d02798e1036424b128f08e640d06c20a6b Mon Sep 17 00:00:00 2001 From: Jackson Kearl Date: Thu, 23 Jan 2020 11:01:54 -0800 Subject: [PATCH 056/801] Refactoring editor creation logic --- .../contrib/search/browser/searchEditor.ts | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/vs/workbench/contrib/search/browser/searchEditor.ts b/src/vs/workbench/contrib/search/browser/searchEditor.ts index be086a6a095..2b4ad390d6c 100644 --- a/src/vs/workbench/contrib/search/browser/searchEditor.ts +++ b/src/vs/workbench/contrib/search/browser/searchEditor.ts @@ -87,9 +87,12 @@ export class SearchEditor extends BaseEditor { createEditor(parent: HTMLElement) { DOM.addClass(parent, 'search-editor'); - // Query - this.queryEditorContainer = DOM.append(parent, DOM.$('.query-container')); + this.createQueryEditor(parent); + this.createResultsEditor(parent); + } + private createQueryEditor(parent: HTMLElement) { + this.queryEditorContainer = DOM.append(parent, DOM.$('.query-container')); this.queryEditorWidget = this._register(this.instantiationService.createInstance(SearchWidget, this.queryEditorContainer, { _hideReplaceToggle: true, showContextToggle: true })); this._register(this.queryEditorWidget.onReplaceToggled(() => this.reLayout())); this._register(this.queryEditorWidget.onDidHeightChange(() => this.reLayout())); @@ -99,6 +102,7 @@ export class SearchEditor extends BaseEditor { // Includes/Excludes Dropdown this.includesExcludesContainer = DOM.append(this.queryEditorContainer, DOM.$('.includes-excludes')); + // // Toggle query details button this.toggleQueryDetailsButton = DOM.append(this.includesExcludesContainer, DOM.$('.expand.codicon.codicon-ellipsis', { tabindex: 0, role: 'button', title: localize('moreSearch', "Toggle Search Details") })); this._register(DOM.addDisposableListener(this.toggleQueryDetailsButton, DOM.EventType.CLICK, e => { @@ -107,7 +111,6 @@ export class SearchEditor extends BaseEditor { })); this._register(DOM.addDisposableListener(this.toggleQueryDetailsButton, DOM.EventType.KEY_UP, (e: KeyboardEvent) => { const event = new StandardKeyboardEvent(e); - if (event.equals(KeyCode.Enter) || event.equals(KeyCode.Space)) { DOM.EventHelper.stop(e); this.toggleIncludesExcludes(); @@ -115,11 +118,11 @@ export class SearchEditor extends BaseEditor { })); this._register(DOM.addDisposableListener(this.toggleQueryDetailsButton, DOM.EventType.KEY_DOWN, (e: KeyboardEvent) => { const event = new StandardKeyboardEvent(e); - if (event.equals(KeyMod.Shift | KeyCode.Tab)) { if (this.queryEditorWidget.isReplaceActive()) { this.queryEditorWidget.focusReplaceAllAction(); - } else { + } + else { this.queryEditorWidget.isReplaceShown() ? this.queryEditorWidget.replaceInput.focusOnPreserve() : this.queryEditorWidget.focusRegexAction(); } DOM.EventHelper.stop(e); @@ -127,11 +130,9 @@ export class SearchEditor extends BaseEditor { })); // // Includes - const folderIncludesList = DOM.append(this.includesExcludesContainer, DOM.$('.file-types.includes')); const filesToIncludeTitle = localize('searchScope.includes', "files to include"); DOM.append(folderIncludesList, DOM.$('h4', undefined, filesToIncludeTitle)); - this.inputPatternIncludes = this._register(this.instantiationService.createInstance(PatternInputWidget, folderIncludesList, this.contextViewService, { ariaLabel: localize('label.includes', 'Search Include Patterns'), })); @@ -141,15 +142,14 @@ export class SearchEditor extends BaseEditor { const excludesList = DOM.append(this.includesExcludesContainer, DOM.$('.file-types.excludes')); const excludesTitle = localize('searchScope.excludes', "files to exclude"); DOM.append(excludesList, DOM.$('h4', undefined, excludesTitle)); - this.inputPatternExcludes = this._register(this.instantiationService.createInstance(ExcludePatternInputWidget, excludesList, this.contextViewService, { ariaLabel: localize('label.excludes', 'Search Exclude Patterns'), })); - this.inputPatternExcludes.onSubmit(_triggeredOnType => this.runSearch()); this.inputPatternExcludes.onChangeIgnoreBox(() => this.runSearch()); + } - // Editor + private createResultsEditor(parent: HTMLElement) { const searchResultContainer = DOM.append(parent, DOM.$('.search-results')); const configuration: IEditorOptions = this.configurationService.getValue('editor', { overrideIdentifier: 'search-result' }); const options: ICodeEditorWidgetOptions = {}; From 72c34249169f4953c738b5795a2f5932688d9d32 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Thu, 23 Jan 2020 22:28:07 +0100 Subject: [PATCH 057/801] themes: normalize textmate theming rules. Fixes #89043 --- .../services/themes/common/colorThemeData.ts | 53 +++++++++++++++---- 1 file changed, 44 insertions(+), 9 deletions(-) diff --git a/src/vs/workbench/services/themes/common/colorThemeData.ts b/src/vs/workbench/services/themes/common/colorThemeData.ts index 623b89ddeeb..8dfd05c4a86 100644 --- a/src/vs/workbench/services/themes/common/colorThemeData.ts +++ b/src/vs/workbench/services/themes/common/colorThemeData.ts @@ -23,6 +23,7 @@ import { TokenStyle, TokenClassification, ProbeScope, TokenStylingRule, getToken import { MatcherWithPriority, Matcher, createMatchers } from 'vs/workbench/services/themes/common/textMateScopeMatcher'; import { IExtensionResourceLoaderService } from 'vs/workbench/services/extensionResourceLoader/common/extensionResourceLoader'; import { FontStyle, ColorId, MetadataConsts } from 'vs/editor/common/modes'; +import { CharCode } from 'vs/base/common/charCode'; let colorRegistry = Registry.as(ColorRegistryExtensions.ColorContribution); @@ -80,8 +81,8 @@ export class ColorThemeData implements IColorTheme { const background = this.getColor(editorBackground) || this.getDefault(editorBackground)!; result.push({ settings: { - foreground: Color.Format.CSS.formatHexA(foreground, true), - background: Color.Format.CSS.formatHexA(background, true) + foreground: normalizeColor(foreground), + background: normalizeColor(background) } }); @@ -92,7 +93,7 @@ export class ColorThemeData implements IColorTheme { if (rule.scope === 'token.info-token') { hasDefaultTokens = true; } - result.push(rule); + result.push({ scope: rule.scope, settings: { foreground: normalizeColor(rule.settings.foreground), background: normalizeColor(rule.settings.background), fontStyle: rule.settings.fontStyle } }); } } @@ -726,11 +727,11 @@ class TokenColorIndex { this._color2id = Object.create(null); } - public add(color: string | Color | undefined | null): number { - if (color === null || color === undefined) { + public add(color: string | Color | undefined): number { + color = normalizeColor(color); + if (color === undefined) { return 0; } - color = normalizeColorForIndex(color); let value = this._color2id[color]; if (value) { @@ -743,10 +744,10 @@ class TokenColorIndex { } public get(color: string | Color | undefined): number { + color = normalizeColor(color); if (color === undefined) { return 0; } - color = normalizeColorForIndex(color); let value = this._color2id[color]; if (value) { return value; @@ -761,11 +762,45 @@ class TokenColorIndex { } -function normalizeColorForIndex(color: string | Color): string { +function normalizeColor(color: string | Color | undefined | null): string | undefined { + if (!color) { + return undefined; + } if (typeof color !== 'string') { color = Color.Format.CSS.formatHexA(color, true); } - return color.toUpperCase(); + if (color.charCodeAt(0) !== CharCode.Hash) { + return undefined; + } + let result = [CharCode.Hash]; + const len = color.length; + for (let i = 1; i < len; i++) { + const upper = hexUpper(color.charCodeAt(i)); + if (!upper) { + return undefined; + } + result.push(upper); + if (len === 3 || len === 4) { + result.push(upper); + } + } + + if (result.length === 9 && result[7] === CharCode.F && result[8] === CharCode.F) { + result.length = 7; + } + if (result.length === 7) { + return String.fromCharCode(...result); + } + return undefined; +} + +function hexUpper(charCode: CharCode): number { + if (charCode >= CharCode.Digit0 && charCode <= CharCode.Digit9 || charCode >= CharCode.A && charCode <= CharCode.F) { + return charCode; + } else if (charCode >= CharCode.a && charCode <= CharCode.f) { + return charCode - CharCode.a + CharCode.A; + } + return 0; } function toMetadata(fontStyle: FontStyle, foreground: ColorId | number, background: ColorId | number) { From f76adb0d9cbae8ebc422693ac87d172e8833e43b Mon Sep 17 00:00:00 2001 From: Jackson Kearl Date: Thu, 23 Jan 2020 11:09:22 -0800 Subject: [PATCH 058/801] Remove extrenous hideHeader call --- src/vs/workbench/contrib/search/browser/searchEditor.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/search/browser/searchEditor.ts b/src/vs/workbench/contrib/search/browser/searchEditor.ts index 2b4ad390d6c..f32a5af1a4e 100644 --- a/src/vs/workbench/contrib/search/browser/searchEditor.ts +++ b/src/vs/workbench/contrib/search/browser/searchEditor.ts @@ -169,6 +169,8 @@ export class SearchEditor extends BaseEditor { } }); + + this._register(this.searchResultEditor.onKeyDown(e => e.keyCode === KeyCode.Escape && this.queryEditorWidget.searchInput.focus())); this._register(this.searchResultEditor.onDidChangeModel(() => this.hideHeader())); @@ -363,7 +365,6 @@ export class SearchEditor extends BaseEditor { const { model } = await newInput.reloadModel(); this.searchResultEditor.setModel(model); - this.hideHeader(); this.pauseSearching = true; const { query } = await newInput.reloadModel(); From 145ec39a8e71cdea7d2b9859efceb9e808919234 Mon Sep 17 00:00:00 2001 From: Jackson Kearl Date: Thu, 23 Jan 2020 14:05:16 -0800 Subject: [PATCH 059/801] Add telemetry events for search editors --- .../contrib/search/browser/searchActions.ts | 7 ++----- .../search/browser/searchEditorActions.ts | 21 ++++++++++++++++--- .../contrib/search/browser/searchView.ts | 4 +--- 3 files changed, 21 insertions(+), 11 deletions(-) diff --git a/src/vs/workbench/contrib/search/browser/searchActions.ts b/src/vs/workbench/contrib/search/browser/searchActions.ts index b0021171811..8105b8d43b7 100644 --- a/src/vs/workbench/contrib/search/browser/searchActions.ts +++ b/src/vs/workbench/contrib/search/browser/searchActions.ts @@ -556,7 +556,6 @@ export class OpenSearchEditorAction extends Action { static readonly LABEL = nls.localize('search.openNewEditor', "Open new Search Editor"); constructor(id: string, label: string, - @IEditorService private editorService: IEditorService, @IConfigurationService private configurationService: IConfigurationService, @IInstantiationService private readonly instantiationService: IInstantiationService, ) { @@ -573,7 +572,7 @@ export class OpenSearchEditorAction extends Action { async run() { if (this.configurationService.getValue('search').enableSearchEditorPreview) { - await openNewSearchEditor(this.editorService, this.instantiationService); + await this.instantiationService.invokeFunction(openNewSearchEditor); } } } @@ -586,8 +585,6 @@ export class OpenResultsInEditorAction extends Action { constructor(id: string, label: string, @IViewletService private viewletService: IViewletService, @IPanelService private panelService: IPanelService, - @ILabelService private labelService: ILabelService, - @IEditorService private editorService: IEditorService, @IConfigurationService private configurationService: IConfigurationService, @IInstantiationService private readonly instantiationService: IInstantiationService, ) { @@ -606,7 +603,7 @@ export class OpenResultsInEditorAction extends Action { async run() { const searchView = getSearchView(this.viewletService, this.panelService); if (searchView && this.configurationService.getValue('search').enableSearchEditorPreview) { - await createEditorFromSearchResult(searchView.searchResult, searchView.searchIncludePattern.getValue(), searchView.searchExcludePattern.getValue(), this.labelService, this.editorService, this.instantiationService); + await this.instantiationService.invokeFunction(createEditorFromSearchResult, searchView.searchResult, searchView.searchIncludePattern.getValue(), searchView.searchExcludePattern.getValue()); } } } diff --git a/src/vs/workbench/contrib/search/browser/searchEditorActions.ts b/src/vs/workbench/contrib/search/browser/searchEditorActions.ts index e5865ede77e..bcc164d2540 100644 --- a/src/vs/workbench/contrib/search/browser/searchEditorActions.ts +++ b/src/vs/workbench/contrib/search/browser/searchEditorActions.ts @@ -8,17 +8,22 @@ import { URI } from 'vs/base/common/uri'; import 'vs/css!./media/searchEditor'; import { isDiffEditor, ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { TrackedRangeStickiness } from 'vs/editor/common/model'; -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { ILabelService } from 'vs/platform/label/common/label'; import { SearchResult } from 'vs/workbench/contrib/search/common/searchModel'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { SearchEditor } from 'vs/workbench/contrib/search/browser/searchEditor'; import { getOrMakeSearchEditorInput, SearchEditorInput } from 'vs/workbench/contrib/search/browser/searchEditorInput'; import { serializeSearchResultForEditor, serializeSearchConfiguration } from 'vs/workbench/contrib/search/browser/searchEditorSerialization'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; export const openNewSearchEditor = - async (editorService: IEditorService, instantiationService: IInstantiationService) => { + async (accessor: ServicesAccessor) => { + const editorService = accessor.get(IEditorService); + const telemetryService = accessor.get(ITelemetryService); + const instantiationService = accessor.get(IInstantiationService); + const activeEditor = editorService.activeTextEditorWidget; let activeModel: ICodeEditor | undefined; let selected = ''; @@ -41,17 +46,27 @@ export const openNewSearchEditor = } } + telemetryService.publicLog2<{}, {}>('searchEditor/openNewSearchEditor'); + const input = instantiationService.invokeFunction(getOrMakeSearchEditorInput, { text: serializeSearchConfiguration({ query: selected }) }); await editorService.openEditor(input, { pinned: true }); }; export const createEditorFromSearchResult = - async (searchResult: SearchResult, rawIncludePattern: string, rawExcludePattern: string, labelService: ILabelService, editorService: IEditorService, instantiationService: IInstantiationService) => { + async (accessor: ServicesAccessor, searchResult: SearchResult, rawIncludePattern: string, rawExcludePattern: string) => { if (!searchResult.query) { console.error('Expected searchResult.query to be defined. Got', searchResult); return; } + const editorService = accessor.get(IEditorService); + const telemetryService = accessor.get(ITelemetryService); + const instantiationService = accessor.get(IInstantiationService); + const labelService = accessor.get(ILabelService); + + + telemetryService.publicLog2<{}, {}>('searchEditor/createEditorFromSearchResult'); + const labelFormatter = (uri: URI): string => labelService.getUriLabel(uri, { relative: true }); const { text, matchRanges } = serializeSearchResultForEditor(searchResult, rawIncludePattern, rawExcludePattern, 0, labelFormatter, true); diff --git a/src/vs/workbench/contrib/search/browser/searchView.ts b/src/vs/workbench/contrib/search/browser/searchView.ts index 0cf4ab44d70..e1b11eb5f29 100644 --- a/src/vs/workbench/contrib/search/browser/searchView.ts +++ b/src/vs/workbench/contrib/search/browser/searchView.ts @@ -65,7 +65,6 @@ import { MultiCursorSelectionController } from 'vs/editor/contrib/multicursor/mu import { Selection } from 'vs/editor/common/core/selection'; import { SIDE_BAR_BACKGROUND, PANEL_BACKGROUND } from 'vs/workbench/common/theme'; import { createEditorFromSearchResult } from 'vs/workbench/contrib/search/browser/searchEditorActions'; -import { ILabelService } from 'vs/platform/label/common/label'; import { Color, RGBA } from 'vs/base/common/color'; const $ = dom.$; @@ -171,7 +170,6 @@ export class SearchView extends ViewPane { @IAccessibilityService private readonly accessibilityService: IAccessibilityService, @IKeybindingService keybindingService: IKeybindingService, @IStorageService storageService: IStorageService, - @ILabelService private readonly labelService: ILabelService, @IOpenerService private readonly openerService: IOpenerService ) { super({ ...options, id: VIEW_ID, ariaHeaderLabel: nls.localize('searchView', "Search") }, keybindingService, contextMenuService, configurationService, contextKeyService, instantiationService); @@ -1559,7 +1557,7 @@ export class SearchView extends ViewPane { this.messageDisposables.push(dom.addDisposableListener(openInEditorLink, dom.EventType.CLICK, (e: MouseEvent) => { dom.EventHelper.stop(e, false); - createEditorFromSearchResult(this.searchResult, this.searchIncludePattern.getValue(), this.searchExcludePattern.getValue(), this.labelService, this.editorService, this.instantiationService); + this.instantiationService.invokeFunction(createEditorFromSearchResult, this.searchResult, this.searchIncludePattern.getValue(), this.searchExcludePattern.getValue()); })); } else { From b60f43d098246532fd05f94d5297366a8cc2e4d1 Mon Sep 17 00:00:00 2001 From: Jackson Kearl Date: Thu, 23 Jan 2020 14:14:34 -0800 Subject: [PATCH 060/801] Validate before deserialization --- .../contrib/search/browser/searchEditorInput.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/contrib/search/browser/searchEditorInput.ts b/src/vs/workbench/contrib/search/browser/searchEditorInput.ts index df93929877b..34faa8107c5 100644 --- a/src/vs/workbench/contrib/search/browser/searchEditorInput.ts +++ b/src/vs/workbench/contrib/search/browser/searchEditorInput.ts @@ -254,10 +254,12 @@ export class SearchEditorInputFactory implements IEditorInputFactory { deserialize(instantiationService: IInstantiationService, serializedEditorInput: string): SearchEditorInput | undefined { const { resource, dirty, config } = JSON.parse(serializedEditorInput); - - const input = instantiationService.invokeFunction(getOrMakeSearchEditorInput, { text: serializeSearchConfiguration(config), uri: URI.parse(resource) }); - input.setDirty(dirty); - return input; + if (config && (config.query !== undefined)) { + const input = instantiationService.invokeFunction(getOrMakeSearchEditorInput, { text: serializeSearchConfiguration(config), uri: URI.parse(resource) }); + input.setDirty(dirty); + return input; + } + return undefined; } } From f3dbcea32a6b52ce819dc863e778fb8517357f61 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Thu, 23 Jan 2020 16:14:27 -0800 Subject: [PATCH 061/801] Adds a backup method (#88948) Adds a backup method to the custom editor API proposal. This method allows custom editors to hook in to VS Code's hot exit behavior If `backup` is not implemented, VS Code will assume that the custom editor cannot be hot exited. When `backup` is implemented, VS Code will invoke the method after every edit (this is debounced). At this point, this extension should back up the current resource. The result is a promise indicating if the backup was successful or not VS Code will only hot exit if all backups were successful. --- src/vs/vscode.proposed.d.ts | 21 +++++ .../api/browser/mainThreadWebview.ts | 6 +- .../workbench/api/common/extHost.protocol.ts | 2 + src/vs/workbench/api/common/extHostWebview.ts | 12 +++ .../customEditor/common/customEditor.ts | 3 + .../customEditor/common/customEditorModel.ts | 80 +++++++++++++++++-- 6 files changed, 118 insertions(+), 6 deletions(-) diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index ec72e2adb43..33b1b497c8d 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -1240,6 +1240,27 @@ declare module 'vscode' { * @return Thenable signaling that the change has completed. */ undoEdits(resource: Uri, edits: readonly EditType[]): Thenable; + + /** + * Back up `resource` in its current state. + * + * Backups are used for hot exit and to prevent data loss. Your `backup` method should persist the resource in + * its current state, i.e. with the edits applied. Most commonly this means saving the resource to disk in + * the `ExtensionContext.storagePath`. When VS Code reloads and your custom editor is opened for a resource, + * your extension should first check to see if any backups exist for the resource. If there is a backup, your + * extension should load the file contents from there instead of from the resource in the workspace. + * + * `backup` is triggered whenever an edit it made. Calls to `backup` are debounced so that if multiple edits are + * made in quick succession, `backup` is only triggered after the last one. `backup` is not invoked when + * `auto save` is enabled (since auto save already persists resource ). + * + * @param resource The resource to back up. + * @param cancellation Token that signals the current backup since a new backup is coming in. It is up to your + * extension to decided how to respond to cancellation. If for example your extension is backing up a large file + * in an operation that takes time to complete, your extension may decide to finish the ongoing backup rather + * than cancelling it to ensure that VS Code has some valid backup. + */ + backup?(resource: Uri, cancellation: CancellationToken): Thenable; } export interface WebviewCustomEditorProvider { diff --git a/src/vs/workbench/api/browser/mainThreadWebview.ts b/src/vs/workbench/api/browser/mainThreadWebview.ts index 86057be7284..5726232e475 100644 --- a/src/vs/workbench/api/browser/mainThreadWebview.ts +++ b/src/vs/workbench/api/browser/mainThreadWebview.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { createCancelablePromise } from 'vs/base/common/async'; import { onUnexpectedError } from 'vs/base/common/errors'; import { Disposable, DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; import { Schemas } from 'vs/base/common/network'; @@ -355,7 +356,10 @@ export class MainThreadWebviews extends Disposable implements extHostProtocol.Ma }); if (capabilitiesSet.has(extHostProtocol.WebviewEditorCapabilities.SupportsHotExit)) { - // TODO: Hook up hot exit / backup logic + model.onBackup(() => { + return createCancelablePromise(token => + this._proxy.$backup(model.resource.toJSON(), viewType, token)); + }); } return model; diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index bd26f4a7aea..1a107144755 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -612,6 +612,8 @@ export interface ExtHostWebviewsShape { $onSave(resource: UriComponents, viewType: string): Promise; $onSaveAs(resource: UriComponents, viewType: string, targetResource: UriComponents): Promise; + + $backup(resource: UriComponents, viewType: string, cancellation: CancellationToken): Promise; } export interface MainThreadUrlsShape extends IDisposable { diff --git a/src/vs/workbench/api/common/extHostWebview.ts b/src/vs/workbench/api/common/extHostWebview.ts index aa41a27eadf..338232d2a77 100644 --- a/src/vs/workbench/api/common/extHostWebview.ts +++ b/src/vs/workbench/api/common/extHostWebview.ts @@ -18,6 +18,7 @@ import type * as vscode from 'vscode'; import { Cache } from './cache'; import { ExtHostWebviewsShape, IMainContext, MainContext, MainThreadWebviewsShape, WebviewEditorCapabilities, WebviewPanelHandle, WebviewPanelViewStateData } from './extHost.protocol'; import { Disposable as VSCodeDisposable } from './extHostTypes'; +import { CancellationToken } from 'vs/base/common/cancellation'; type IconPath = URI | { light: URI, dark: URI }; @@ -478,6 +479,14 @@ export class ExtHostWebviews implements ExtHostWebviewsShape { return provider?.editingDelegate?.saveAs(URI.revive(resource), URI.revive(targetResource)); } + async $backup(resource: UriComponents, viewType: string, cancellation: CancellationToken): Promise { + const provider = this.getEditorProvider(viewType); + if (!provider?.editingDelegate?.backup) { + return false; + } + return provider.editingDelegate.backup(URI.revive(resource), cancellation); + } + private getWebviewPanel(handle: WebviewPanelHandle): ExtHostWebviewEditor | undefined { return this._webviewPanels.get(handle); } @@ -491,6 +500,9 @@ export class ExtHostWebviews implements ExtHostWebviewsShape { if (capabilities.editingDelegate) { declaredCapabilites.push(WebviewEditorCapabilities.Editable); } + if (capabilities.editingDelegate?.backup) { + declaredCapabilites.push(WebviewEditorCapabilities.SupportsHotExit); + } return declaredCapabilites; } } diff --git a/src/vs/workbench/contrib/customEditor/common/customEditor.ts b/src/vs/workbench/contrib/customEditor/common/customEditor.ts index 1a5c109ea26..1b1aa52381b 100644 --- a/src/vs/workbench/contrib/customEditor/common/customEditor.ts +++ b/src/vs/workbench/contrib/customEditor/common/customEditor.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { distinct, find, mergeSort } from 'vs/base/common/arrays'; +import { CancelablePromise } from 'vs/base/common/async'; import { Event } from 'vs/base/common/event'; import * as glob from 'vs/base/common/glob'; import { basename } from 'vs/base/common/resources'; @@ -75,6 +76,8 @@ export interface ICustomEditorModel extends IWorkingCopy { readonly onWillSave: Event; readonly onWillSaveAs: Event; + onBackup(f: () => CancelablePromise): void; + undo(): void; redo(): void; revert(options?: IRevertOptions): Promise; diff --git a/src/vs/workbench/contrib/customEditor/common/customEditorModel.ts b/src/vs/workbench/contrib/customEditor/common/customEditorModel.ts index ff103cb3fdc..6448fbf2a63 100644 --- a/src/vs/workbench/contrib/customEditor/common/customEditorModel.ts +++ b/src/vs/workbench/contrib/customEditor/common/customEditorModel.ts @@ -3,25 +3,50 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { CancelablePromise } from 'vs/base/common/async'; import { Emitter, Event } from 'vs/base/common/event'; import { Disposable } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; -import { ICustomEditorModel, CustomEditorEdit, CustomEditorSaveAsEvent, CustomEditorSaveEvent } from 'vs/workbench/contrib/customEditor/common/customEditor'; -import { WorkingCopyCapabilities } from 'vs/workbench/services/workingCopy/common/workingCopyService'; -import { ISaveOptions, IRevertOptions } from 'vs/workbench/common/editor'; +import { IRevertOptions, ISaveOptions } from 'vs/workbench/common/editor'; +import { CustomEditorEdit, CustomEditorSaveAsEvent, CustomEditorSaveEvent, ICustomEditorModel } from 'vs/workbench/contrib/customEditor/common/customEditor'; +import { IWorkingCopyBackup, WorkingCopyCapabilities } from 'vs/workbench/services/workingCopy/common/workingCopyService'; import { ILabelService } from 'vs/platform/label/common/label'; import { basename } from 'vs/base/common/path'; +namespace HotExitState { + export const enum Type { + NotSupported, + Allowed, + NotAllowed, + Pending, + } + + export const NotSupported = Object.freeze({ type: Type.NotSupported } as const); + export const Allowed = Object.freeze({ type: Type.Allowed } as const); + export const NotAllowed = Object.freeze({ type: Type.NotAllowed } as const); + + export class Pending { + readonly type = Type.Pending; + + constructor( + public readonly operation: CancelablePromise, + ) { } + } + + export type State = typeof NotSupported | typeof Allowed | typeof NotAllowed | Pending; +} + export class CustomEditorModel extends Disposable implements ICustomEditorModel { private _currentEditIndex: number = -1; private _savePoint: number = -1; private readonly _edits: Array = []; + private _hotExitState: HotExitState.State = HotExitState.NotSupported; constructor( public readonly viewType: string, private readonly _resource: URI, - private readonly labelService: ILabelService + private readonly labelService: ILabelService, ) { super(); } @@ -72,7 +97,20 @@ export class CustomEditorModel extends Disposable implements ICustomEditorModel protected readonly _onWillSaveAs = this._register(new Emitter()); readonly onWillSaveAs = this._onWillSaveAs.event; - public pushEdit(edit: CustomEditorEdit, trigger: any): void { + private _onBackup: undefined | (() => CancelablePromise); + + public onBackup(f: () => CancelablePromise) { + if (this._onBackup) { + throw new Error('Backup already implemented'); + } + this._onBackup = f; + + if (this._hotExitState === HotExitState.NotSupported) { + this._hotExitState = this.isDirty() ? HotExitState.NotAllowed : HotExitState.Allowed; + } + } + + public pushEdit(edit: CustomEditorEdit, trigger: any) { this.spliceEdits(edit); this._currentEditIndex = this._edits.length - 1; @@ -196,4 +234,36 @@ export class CustomEditorModel extends Disposable implements ICustomEditorModel this.updateDirty(); this.updateContentChanged(); } + + public async backup(): Promise { + if (this._hotExitState === HotExitState.NotSupported) { + throw new Error('Not supported'); + } + + if (this._hotExitState.type === HotExitState.Type.Pending) { + this._hotExitState.operation.cancel(); + } + this._hotExitState = HotExitState.NotAllowed; + + const pendingState = new HotExitState.Pending(this._onBackup!()); + this._hotExitState = pendingState; + + try { + this._hotExitState = await pendingState.operation ? HotExitState.Allowed : HotExitState.NotAllowed; + } catch (e) { + // Make sure state has not changed in the meantime + if (this._hotExitState === pendingState) { + this._hotExitState = HotExitState.NotAllowed; + } + } + + if (this._hotExitState === HotExitState.Allowed) { + return { + meta: { + viewType: this.viewType, + } + }; + } + throw new Error('Cannot back up in this state'); + } } From cd2e132fbc9ad2829c5a4a07a6fb00d5e3203943 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Fri, 24 Jan 2020 07:20:53 +0100 Subject: [PATCH 062/801] tests - avoid tests that bring up webview for now (#88415) --- .../vscode-api-tests/src/singlefolder-tests/commands.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/commands.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/commands.test.ts index fba9348435c..739ce386371 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/commands.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/commands.test.ts @@ -105,7 +105,7 @@ suite('commands namespace tests', () => { }); test('api-command: vscode.open', function () { - let uri = Uri.parse(workspace.workspaceFolders![0].uri.toString() + '/image.png'); + let uri = Uri.parse(workspace.workspaceFolders![0].uri.toString() + '/far.js'); let a = commands.executeCommand('vscode.open', uri).then(() => assert.ok(true), () => assert.ok(false)); let b = commands.executeCommand('vscode.open', uri, ViewColumn.Two).then(() => assert.ok(true), () => assert.ok(false)); let c = commands.executeCommand('vscode.open').then(() => assert.ok(false), () => assert.ok(true)); From a9a5fd05b6e1ba863f612472b34cd2dcf6800c51 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Fri, 24 Jan 2020 07:27:27 +0100 Subject: [PATCH 063/801] untitled - change mode when found in paste data (#89159) * untitled - change mode when found in paste data * do not update mode for readonly editors --- .../browser/controller/textAreaHandler.ts | 14 ++++-- .../browser/controller/textAreaInput.ts | 5 +- src/vs/editor/browser/editorBrowser.ts | 10 +++- src/vs/editor/browser/view/viewController.ts | 6 +-- .../editor/browser/widget/codeEditorWidget.ts | 16 ++++--- src/vs/editor/common/viewModel/viewModel.ts | 2 +- .../editor/common/viewModel/viewModelImpl.ts | 34 ++++++++------ src/vs/editor/contrib/format/formatActions.ts | 2 +- .../editor/contrib/indentation/indentation.ts | 2 +- .../test/browser/controller/imeTester.ts | 3 +- src/vs/monaco.d.ts | 10 +++- .../parts/editor/textResourceEditor.ts | 46 ++++++++++++++++++- 12 files changed, 114 insertions(+), 36 deletions(-) diff --git a/src/vs/editor/browser/controller/textAreaHandler.ts b/src/vs/editor/browser/controller/textAreaHandler.ts index d641968b4b0..ea4e4f8930b 100644 --- a/src/vs/editor/browser/controller/textAreaHandler.ts +++ b/src/vs/editor/browser/controller/textAreaHandler.ts @@ -158,16 +158,22 @@ export class TextAreaHandler extends ViewPart { const text = (Array.isArray(rawTextToCopy) ? rawTextToCopy.join(newLineCharacter) : rawTextToCopy); let html: string | null | undefined = undefined; + let mode: string | null = null; if (generateHTML) { if (CopyOptions.forceCopyWithSyntaxHighlighting || (this._copyWithSyntaxHighlighting && text.length < 65536)) { - html = this._context.model.getHTMLToCopy(this._modelSelections, this._emptySelectionClipboard); + const richText = this._context.model.getRichTextToCopy(this._modelSelections, this._emptySelectionClipboard); + if (richText) { + html = richText.html; + mode = richText.mode; + } } } return { isFromEmptySelection, multicursorText, text, - html + html, + mode }; }, @@ -221,11 +227,13 @@ export class TextAreaHandler extends ViewPart { this._register(this._textAreaInput.onPaste((e: IPasteData) => { let pasteOnNewLine = false; let multicursorText: string[] | null = null; + let mode: string | null = null; if (e.metadata) { pasteOnNewLine = (this._emptySelectionClipboard && !!e.metadata.isFromEmptySelection); multicursorText = (typeof e.metadata.multicursorText !== 'undefined' ? e.metadata.multicursorText : null); + mode = e.metadata.mode; } - this._viewController.paste('keyboard', e.text, pasteOnNewLine, multicursorText); + this._viewController.paste('keyboard', e.text, pasteOnNewLine, multicursorText, mode); })); this._register(this._textAreaInput.onCut(() => { diff --git a/src/vs/editor/browser/controller/textAreaInput.ts b/src/vs/editor/browser/controller/textAreaInput.ts index 2e64c93ba59..8e80e794f8b 100644 --- a/src/vs/editor/browser/controller/textAreaInput.ts +++ b/src/vs/editor/browser/controller/textAreaInput.ts @@ -41,12 +41,14 @@ export interface ClipboardDataToCopy { multicursorText: string[] | null | undefined; text: string; html: string | null | undefined; + mode: string | null; } export interface ClipboardStoredMetadata { version: 1; isFromEmptySelection: boolean | undefined; multicursorText: string[] | null | undefined; + mode: string | null; } export interface ITextAreaInputHost { @@ -550,7 +552,8 @@ export class TextAreaInput extends Disposable { const storedMetadata: ClipboardStoredMetadata = { version: 1, isFromEmptySelection: dataToCopy.isFromEmptySelection, - multicursorText: dataToCopy.multicursorText + multicursorText: dataToCopy.multicursorText, + mode: dataToCopy.mode }; InMemoryClipboardMetadataManager.INSTANCE.set( // When writing "LINE\r\n" to the clipboard and then pasting, diff --git a/src/vs/editor/browser/editorBrowser.ts b/src/vs/editor/browser/editorBrowser.ts index ad5b0841bf2..85ee85e34f7 100644 --- a/src/vs/editor/browser/editorBrowser.ts +++ b/src/vs/editor/browser/editorBrowser.ts @@ -308,6 +308,14 @@ export interface IPartialEditorMouseEvent { readonly target: IMouseTarget | null; } +/** + * A paste event originating from the editor. + */ +export interface IPasteEvent { + readonly range: Range; + readonly mode: string | null; +} + /** * An overview ruler * @internal @@ -431,7 +439,7 @@ export interface ICodeEditor extends editorCommon.IEditor { * An event emitted when users paste text in the editor. * @event */ - onDidPaste(listener: (range: Range) => void): IDisposable; + onDidPaste(listener: (e: IPasteEvent) => void): IDisposable; /** * An event emitted on a "mouseup". * @event diff --git a/src/vs/editor/browser/view/viewController.ts b/src/vs/editor/browser/view/viewController.ts index 9057fdfbe22..d2eb4bdd1f1 100644 --- a/src/vs/editor/browser/view/viewController.ts +++ b/src/vs/editor/browser/view/viewController.ts @@ -37,7 +37,7 @@ export interface IMouseDispatchData { export interface ICommandDelegate { executeEditorCommand(editorCommand: CoreEditorCommand, args: any): void; - paste(source: string, text: string, pasteOnNewLine: boolean, multicursorText: string[] | null): void; + paste(source: string, text: string, pasteOnNewLine: boolean, multicursorText: string[] | null, mode: string | null): void; type(source: string, text: string): void; replacePreviousChar(source: string, text: string, replaceCharCnt: number): void; compositionStart(source: string): void; @@ -69,8 +69,8 @@ export class ViewController { this.commandDelegate.executeEditorCommand(editorCommand, args); } - public paste(source: string, text: string, pasteOnNewLine: boolean, multicursorText: string[] | null): void { - this.commandDelegate.paste(source, text, pasteOnNewLine, multicursorText); + public paste(source: string, text: string, pasteOnNewLine: boolean, multicursorText: string[] | null, mode: string | null): void { + this.commandDelegate.paste(source, text, pasteOnNewLine, multicursorText, mode); } public type(source: string, text: string): void { diff --git a/src/vs/editor/browser/widget/codeEditorWidget.ts b/src/vs/editor/browser/widget/codeEditorWidget.ts index d610127632d..2e2e8315efe 100644 --- a/src/vs/editor/browser/widget/codeEditorWidget.ts +++ b/src/vs/editor/browser/widget/codeEditorWidget.ts @@ -162,7 +162,7 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE private readonly _onDidCompositionEnd: Emitter = this._register(new Emitter()); public readonly onDidCompositionEnd = this._onDidCompositionEnd.event; - private readonly _onDidPaste: Emitter = this._register(new Emitter()); + private readonly _onDidPaste: Emitter = this._register(new Emitter()); public readonly onDidPaste = this._onDidPaste.event; private readonly _onMouseUp: Emitter = this._register(new Emitter()); @@ -950,7 +950,10 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE const endPosition = this._modelData.cursor.getSelection().getStartPosition(); if (source === 'keyboard') { this._onDidPaste.fire( - new Range(startPosition.lineNumber, startPosition.column, endPosition.lineNumber, endPosition.column) + { + range: new Range(startPosition.lineNumber, startPosition.column, endPosition.lineNumber, endPosition.column), + mode: payload.mode + } ); } return; @@ -1445,8 +1448,8 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE executeEditorCommand: (editorCommand: CoreEditorCommand, args: any): void => { editorCommand.runCoreEditorCommand(cursor, args); }, - paste: (source: string, text: string, pasteOnNewLine: boolean, multicursorText: string[] | null) => { - this.trigger(source, editorCommon.Handler.Paste, { text, pasteOnNewLine, multicursorText }); + paste: (source: string, text: string, pasteOnNewLine: boolean, multicursorText: string[] | null, mode: string | null) => { + this.trigger(source, editorCommon.Handler.Paste, { text, pasteOnNewLine, multicursorText, mode }); }, type: (source: string, text: string) => { this.trigger(source, editorCommon.Handler.Type, { text }); @@ -1469,11 +1472,12 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE executeEditorCommand: (editorCommand: CoreEditorCommand, args: any): void => { editorCommand.runCoreEditorCommand(cursor, args); }, - paste: (source: string, text: string, pasteOnNewLine: boolean, multicursorText: string[] | null) => { + paste: (source: string, text: string, pasteOnNewLine: boolean, multicursorText: string[] | null, mode: string | null) => { this._commandService.executeCommand(editorCommon.Handler.Paste, { text: text, pasteOnNewLine: pasteOnNewLine, - multicursorText: multicursorText + multicursorText: multicursorText, + mode }); }, type: (source: string, text: string) => { diff --git a/src/vs/editor/common/viewModel/viewModel.ts b/src/vs/editor/common/viewModel/viewModel.ts index c45f12b9530..145d770e7d6 100644 --- a/src/vs/editor/common/viewModel/viewModel.ts +++ b/src/vs/editor/common/viewModel/viewModel.ts @@ -139,7 +139,7 @@ export interface IViewModel { deduceModelPositionRelativeToViewPosition(viewAnchorPosition: Position, deltaOffset: number, lineFeedCnt: number): Position; getEOL(): string; getPlainTextToCopy(modelRanges: Range[], emptySelectionClipboard: boolean, forceCRLF: boolean): string | string[]; - getHTMLToCopy(modelRanges: Range[], emptySelectionClipboard: boolean): string | null; + getRichTextToCopy(modelRanges: Range[], emptySelectionClipboard: boolean): { html: string, mode: string } | null; } export class MinimapLinesRenderingData { diff --git a/src/vs/editor/common/viewModel/viewModelImpl.ts b/src/vs/editor/common/viewModel/viewModelImpl.ts index 0b35cb05ba5..43c39e824a3 100644 --- a/src/vs/editor/common/viewModel/viewModelImpl.ts +++ b/src/vs/editor/common/viewModel/viewModelImpl.ts @@ -717,8 +717,9 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel return result.length === 1 ? result[0] : result; } - public getHTMLToCopy(modelRanges: Range[], emptySelectionClipboard: boolean): string | null { - if (this.model.getLanguageIdentifier().id === LanguageId.PlainText) { + public getRichTextToCopy(modelRanges: Range[], emptySelectionClipboard: boolean): { html: string, mode: string } | null { + const languageId = this.model.getLanguageIdentifier(); + if (languageId.id === LanguageId.PlainText) { return null; } @@ -741,19 +742,22 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel const colorMap = this._getColorMap(); const fontFamily = fontInfo.fontFamily === EDITOR_FONT_DEFAULTS.fontFamily ? fontInfo.fontFamily : `'${fontInfo.fontFamily}', ${EDITOR_FONT_DEFAULTS.fontFamily}`; - return ( - `
` - + this._getHTMLToCopy(range, colorMap) - + '
' - ); + return { + mode: languageId.language, + html: ( + `
` + + this._getHTMLToCopy(range, colorMap) + + '
' + ) + }; } private _getHTMLToCopy(modelRange: Range, colorMap: string[]): string { diff --git a/src/vs/editor/contrib/format/formatActions.ts b/src/vs/editor/contrib/format/formatActions.ts index 61050ac167b..219308c8467 100644 --- a/src/vs/editor/contrib/format/formatActions.ts +++ b/src/vs/editor/contrib/format/formatActions.ts @@ -191,7 +191,7 @@ class FormatOnPaste implements IEditorContribution { return; } - this._callOnModel.add(this.editor.onDidPaste(range => this._trigger(range))); + this._callOnModel.add(this.editor.onDidPaste(({ range }) => this._trigger(range))); } private _trigger(range: Range): void { diff --git a/src/vs/editor/contrib/indentation/indentation.ts b/src/vs/editor/contrib/indentation/indentation.ts index e20fa833c5f..20280edd604 100644 --- a/src/vs/editor/contrib/indentation/indentation.ts +++ b/src/vs/editor/contrib/indentation/indentation.ts @@ -451,7 +451,7 @@ export class AutoIndentOnPaste implements IEditorContribution { return; } - this.callOnModel.add(this.editor.onDidPaste((range: Range) => { + this.callOnModel.add(this.editor.onDidPaste(({ range }) => { this.trigger(range); })); } diff --git a/src/vs/editor/test/browser/controller/imeTester.ts b/src/vs/editor/test/browser/controller/imeTester.ts index b6db5a6ec0e..1668233bf9d 100644 --- a/src/vs/editor/test/browser/controller/imeTester.ts +++ b/src/vs/editor/test/browser/controller/imeTester.ts @@ -91,7 +91,8 @@ function doCreateTest(description: string, inputStr: string, expectedStr: string isFromEmptySelection: false, multicursorText: null, text: '', - html: undefined + html: undefined, + mode: null }; }, getScreenReaderContent: (currentState: TextAreaState): TextAreaState => { diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index 3a978ec5dce..5537dd90508 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -4204,6 +4204,14 @@ declare namespace monaco.editor { readonly target: IMouseTarget | null; } + /** + * A paste event originating from the editor. + */ + export interface IPasteEvent { + readonly range: Range; + readonly mode: string | null; + } + /** * A rich code editor. */ @@ -4285,7 +4293,7 @@ declare namespace monaco.editor { * An event emitted when users paste text in the editor. * @event */ - onDidPaste(listener: (range: Range) => void): IDisposable; + onDidPaste(listener: (e: IPasteEvent) => void): IDisposable; /** * An event emitted on a "mouseup". * @event diff --git a/src/vs/workbench/browser/parts/editor/textResourceEditor.ts b/src/vs/workbench/browser/parts/editor/textResourceEditor.ts index 687cc45c7cd..b02453b85df 100644 --- a/src/vs/workbench/browser/parts/editor/textResourceEditor.ts +++ b/src/vs/workbench/browser/parts/editor/textResourceEditor.ts @@ -5,7 +5,7 @@ import * as nls from 'vs/nls'; import { assertIsDefined, isFunction } from 'vs/base/common/types'; -import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; +import { ICodeEditor, getCodeEditor, IPasteEvent } from 'vs/editor/browser/editorBrowser'; import { TextEditorOptions, EditorInput, EditorOptions } from 'vs/workbench/common/editor'; import { ResourceEditorInput } from 'vs/workbench/common/editor/resourceEditorInput'; import { BaseTextEditorModel } from 'vs/workbench/common/editor/textEditorModel'; @@ -21,6 +21,10 @@ import { ScrollType, IEditor } from 'vs/editor/common/editorCommon'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { CancellationToken } from 'vs/base/common/cancellation'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { IModelService } from 'vs/editor/common/services/modelService'; +import { IModeService } from 'vs/editor/common/services/modeService'; +import { PLAINTEXT_MODE_ID } from 'vs/editor/common/modes/modesRegistry'; +import { EditorOption, IEditorOptions } from 'vs/editor/common/config/editorOptions'; /** * An editor implementation that is capable of showing the contents of resource inputs. Uses @@ -187,8 +191,46 @@ export class TextResourceEditor extends AbstractTextResourceEditor { @ITextResourceConfigurationService textResourceConfigurationService: ITextResourceConfigurationService, @IThemeService themeService: IThemeService, @IEditorService editorService: IEditorService, - @IEditorGroupsService editorGroupService: IEditorGroupsService + @IEditorGroupsService editorGroupService: IEditorGroupsService, + @IModelService private readonly modelService: IModelService, + @IModeService private readonly modeService: IModeService ) { super(TextResourceEditor.ID, telemetryService, instantiationService, storageService, textResourceConfigurationService, themeService, editorGroupService, editorService); } + + protected createEditorControl(parent: HTMLElement, configuration: IEditorOptions): IEditor { + const control = super.createEditorControl(parent, configuration); + + // Install a listener for paste to update this editors + // language mode if the paste includes a specific mode + const codeEditor = getCodeEditor(control); + if (codeEditor) { + this._register(codeEditor.onDidPaste(e => this.onDidEditorPaste(e, codeEditor))); + } + + return control; + } + + private onDidEditorPaste(e: IPasteEvent, codeEditor: ICodeEditor): void { + if (!e.mode || e.mode === PLAINTEXT_MODE_ID) { + return; // require a specific mode + } + + if (codeEditor.getOption(EditorOption.readOnly)) { + return; // not for readonly editors + } + + const textModel = codeEditor.getModel(); + if (!textModel) { + return; // require a live model + } + + const currentMode = textModel.getModeId(); + if (currentMode !== PLAINTEXT_MODE_ID) { + return; // require current mode to be unspecific + } + + // Finally apply mode to model + this.modelService.setMode(textModel, this.modeService.create(e.mode)); + } } From c7cc30ca5aa4f64c1f7d85343594a7801029e8d4 Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Fri, 24 Jan 2020 08:06:32 +0100 Subject: [PATCH 064/801] Add editor.renderValidationDecorations (#89057) --- .../editor/browser/widget/codeEditorWidget.ts | 4 +- src/vs/editor/common/config/editorOptions.ts | 26 ++++++ .../common/standalone/standaloneEnums.ts | 73 ++++++++--------- .../common/viewModel/viewModelDecorations.ts | 4 +- .../editor/common/viewModel/viewModelImpl.ts | 4 +- src/vs/monaco.d.ts | 79 ++++++++++--------- 6 files changed, 112 insertions(+), 78 deletions(-) diff --git a/src/vs/editor/browser/widget/codeEditorWidget.ts b/src/vs/editor/browser/widget/codeEditorWidget.ts index 2e2e8315efe..69794a4f647 100644 --- a/src/vs/editor/browser/widget/codeEditorWidget.ts +++ b/src/vs/editor/browser/widget/codeEditorWidget.ts @@ -22,7 +22,7 @@ import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService import { ICommandDelegate } from 'vs/editor/browser/view/viewController'; import { IContentWidgetData, IOverlayWidgetData, View } from 'vs/editor/browser/view/viewImpl'; import { ViewOutgoingEvents } from 'vs/editor/browser/view/viewOutgoingEvents'; -import { ConfigurationChangedEvent, EditorLayoutInfo, IEditorOptions, EditorOption, IComputedEditorOptions, FindComputedEditorOptionValueById, IEditorConstructionOptions } from 'vs/editor/common/config/editorOptions'; +import { ConfigurationChangedEvent, EditorLayoutInfo, IEditorOptions, EditorOption, IComputedEditorOptions, FindComputedEditorOptionValueById, IEditorConstructionOptions, shouldRenderValidationDecorations } from 'vs/editor/common/config/editorOptions'; import { Cursor, CursorStateChangedEvent } from 'vs/editor/common/controller/cursor'; import { CursorColumns, ICursors } from 'vs/editor/common/controller/cursorCommon'; import { ICursorPositionChangedEvent, ICursorSelectionChangedEvent } from 'vs/editor/common/controller/cursorEvents'; @@ -1064,7 +1064,7 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE if (!this._modelData) { return null; } - return this._modelData.model.getLineDecorations(lineNumber, this._id, this._configuration.options.get(EditorOption.readOnly)); + return this._modelData.model.getLineDecorations(lineNumber, this._id, shouldRenderValidationDecorations(this._configuration.options)); } public deltaDecorations(oldDecorations: string[], newDecorations: IModelDeltaDecoration[]): string[] { diff --git a/src/vs/editor/common/config/editorOptions.ts b/src/vs/editor/common/config/editorOptions.ts index 9c457a596ad..bacc0dac28f 100644 --- a/src/vs/editor/common/config/editorOptions.ts +++ b/src/vs/editor/common/config/editorOptions.ts @@ -135,6 +135,11 @@ export interface IEditorOptions { * Defaults to false. */ readOnly?: boolean; + /** + * Should the editor render validation decorations. + * Defaults to editable. + */ + renderValidationDecorations?: 'editable' | 'on' | 'off'; /** * Control the behavior and rendering of the scrollbars. */ @@ -2334,6 +2339,21 @@ class EditorRenderLineNumbersOption extends BaseEditorOption { @@ -3168,6 +3188,7 @@ export const enum EditorOption { renderIndentGuides, renderFinalNewline, renderLineHighlight, + renderValidationDecorations, renderWhitespace, revealHorizontalRightPadding, roundedSelection, @@ -3586,6 +3607,11 @@ export const EditorOptions = { description: nls.localize('renderLineHighlight', "Controls how the editor should render the current line highlight.") } )), + renderValidationDecorations: register(new EditorStringEnumOption( + EditorOption.renderValidationDecorations, 'renderValidationDecorations', + 'editable' as 'editable' | 'on' | 'off', + ['editable', 'on', 'off'] as const + )), renderWhitespace: register(new EditorStringEnumOption( EditorOption.renderWhitespace, 'renderWhitespace', 'none' as 'none' | 'boundary' | 'selection' | 'all', diff --git a/src/vs/editor/common/standalone/standaloneEnums.ts b/src/vs/editor/common/standalone/standaloneEnums.ts index 012e50cdc4b..505779e7db8 100644 --- a/src/vs/editor/common/standalone/standaloneEnums.ts +++ b/src/vs/editor/common/standalone/standaloneEnums.ts @@ -238,42 +238,43 @@ export enum EditorOption { renderIndentGuides = 70, renderFinalNewline = 71, renderLineHighlight = 72, - renderWhitespace = 73, - revealHorizontalRightPadding = 74, - roundedSelection = 75, - rulers = 76, - scrollbar = 77, - scrollBeyondLastColumn = 78, - scrollBeyondLastLine = 79, - selectionClipboard = 80, - selectionHighlight = 81, - selectOnLineNumbers = 82, - semanticHighlighting = 83, - showFoldingControls = 84, - showUnused = 85, - snippetSuggestions = 86, - smoothScrolling = 87, - stopRenderingLineAfter = 88, - suggest = 89, - suggestFontSize = 90, - suggestLineHeight = 91, - suggestOnTriggerCharacters = 92, - suggestSelection = 93, - tabCompletion = 94, - useTabStops = 95, - wordSeparators = 96, - wordWrap = 97, - wordWrapBreakAfterCharacters = 98, - wordWrapBreakBeforeCharacters = 99, - wordWrapColumn = 100, - wordWrapMinified = 101, - wrappingIndent = 102, - wrappingAlgorithm = 103, - editorClassName = 104, - pixelRatio = 105, - tabFocusMode = 106, - layoutInfo = 107, - wrappingInfo = 108 + renderValidationDecorations = 73, + renderWhitespace = 74, + revealHorizontalRightPadding = 75, + roundedSelection = 76, + rulers = 77, + scrollbar = 78, + scrollBeyondLastColumn = 79, + scrollBeyondLastLine = 80, + selectionClipboard = 81, + selectionHighlight = 82, + selectOnLineNumbers = 83, + semanticHighlighting = 84, + showFoldingControls = 85, + showUnused = 86, + snippetSuggestions = 87, + smoothScrolling = 88, + stopRenderingLineAfter = 89, + suggest = 90, + suggestFontSize = 91, + suggestLineHeight = 92, + suggestOnTriggerCharacters = 93, + suggestSelection = 94, + tabCompletion = 95, + useTabStops = 96, + wordSeparators = 97, + wordWrap = 98, + wordWrapBreakAfterCharacters = 99, + wordWrapBreakBeforeCharacters = 100, + wordWrapColumn = 101, + wordWrapMinified = 102, + wrappingIndent = 103, + wrappingAlgorithm = 104, + editorClassName = 105, + pixelRatio = 106, + tabFocusMode = 107, + layoutInfo = 108, + wrappingInfo = 109 } /** diff --git a/src/vs/editor/common/viewModel/viewModelDecorations.ts b/src/vs/editor/common/viewModel/viewModelDecorations.ts index b74baa8dd1c..5dc3ff2e85e 100644 --- a/src/vs/editor/common/viewModel/viewModelDecorations.ts +++ b/src/vs/editor/common/viewModel/viewModelDecorations.ts @@ -10,7 +10,7 @@ import * as editorCommon from 'vs/editor/common/editorCommon'; import { IModelDecoration, ITextModel } from 'vs/editor/common/model'; import { IViewModelLinesCollection } from 'vs/editor/common/viewModel/splitLinesCollection'; import { ICoordinatesConverter, InlineDecoration, InlineDecorationType, ViewModelDecoration } from 'vs/editor/common/viewModel/viewModel'; -import { EditorOption } from 'vs/editor/common/config/editorOptions'; +import { shouldRenderValidationDecorations } from 'vs/editor/common/config/editorOptions'; export interface IDecorationsViewportData { /** @@ -104,7 +104,7 @@ export class ViewModelDecorations implements IDisposable { } private _getDecorationsViewportData(viewportRange: Range): IDecorationsViewportData { - const modelDecorations = this._linesCollection.getDecorationsInRange(viewportRange, this.editorId, this.configuration.options.get(EditorOption.readOnly)); + const modelDecorations = this._linesCollection.getDecorationsInRange(viewportRange, this.editorId, shouldRenderValidationDecorations(this.configuration.options)); const startLineNumber = viewportRange.startLineNumber; const endLineNumber = viewportRange.endLineNumber; diff --git a/src/vs/editor/common/viewModel/viewModelImpl.ts b/src/vs/editor/common/viewModel/viewModelImpl.ts index 43c39e824a3..9cfb2463bf1 100644 --- a/src/vs/editor/common/viewModel/viewModelImpl.ts +++ b/src/vs/editor/common/viewModel/viewModelImpl.ts @@ -6,7 +6,7 @@ import { Color } from 'vs/base/common/color'; import { IDisposable } from 'vs/base/common/lifecycle'; import * as strings from 'vs/base/common/strings'; -import { ConfigurationChangedEvent, EDITOR_FONT_DEFAULTS, EditorOption } from 'vs/editor/common/config/editorOptions'; +import { ConfigurationChangedEvent, EDITOR_FONT_DEFAULTS, EditorOption, shouldRenderValidationDecorations } from 'vs/editor/common/config/editorOptions'; import { IPosition, Position } from 'vs/editor/common/core/position'; import { IRange, Range } from 'vs/editor/common/core/range'; import { IConfiguration, IViewState } from 'vs/editor/common/editorCommon'; @@ -596,7 +596,7 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel } public getAllOverviewRulerDecorations(theme: ITheme): IOverviewRulerDecorations { - return this.lines.getAllOverviewRulerDecorations(this.editorId, this.configuration.options.get(EditorOption.readOnly), theme); + return this.lines.getAllOverviewRulerDecorations(this.editorId, shouldRenderValidationDecorations(this.configuration.options), theme); } public invalidateOverviewRulerColorCache(): void { diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index 5537dd90508..4412fce4a6f 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -2609,6 +2609,11 @@ declare namespace monaco.editor { * Defaults to false. */ readOnly?: boolean; + /** + * Should the editor render validation decorations. + * Defaults to editable. + */ + renderValidationDecorations?: 'editable' | 'on' | 'off'; /** * Control the behavior and rendering of the scrollbars. */ @@ -3753,42 +3758,43 @@ declare namespace monaco.editor { renderIndentGuides = 70, renderFinalNewline = 71, renderLineHighlight = 72, - renderWhitespace = 73, - revealHorizontalRightPadding = 74, - roundedSelection = 75, - rulers = 76, - scrollbar = 77, - scrollBeyondLastColumn = 78, - scrollBeyondLastLine = 79, - selectionClipboard = 80, - selectionHighlight = 81, - selectOnLineNumbers = 82, - semanticHighlighting = 83, - showFoldingControls = 84, - showUnused = 85, - snippetSuggestions = 86, - smoothScrolling = 87, - stopRenderingLineAfter = 88, - suggest = 89, - suggestFontSize = 90, - suggestLineHeight = 91, - suggestOnTriggerCharacters = 92, - suggestSelection = 93, - tabCompletion = 94, - useTabStops = 95, - wordSeparators = 96, - wordWrap = 97, - wordWrapBreakAfterCharacters = 98, - wordWrapBreakBeforeCharacters = 99, - wordWrapColumn = 100, - wordWrapMinified = 101, - wrappingIndent = 102, - wrappingAlgorithm = 103, - editorClassName = 104, - pixelRatio = 105, - tabFocusMode = 106, - layoutInfo = 107, - wrappingInfo = 108 + renderValidationDecorations = 73, + renderWhitespace = 74, + revealHorizontalRightPadding = 75, + roundedSelection = 76, + rulers = 77, + scrollbar = 78, + scrollBeyondLastColumn = 79, + scrollBeyondLastLine = 80, + selectionClipboard = 81, + selectionHighlight = 82, + selectOnLineNumbers = 83, + semanticHighlighting = 84, + showFoldingControls = 85, + showUnused = 86, + snippetSuggestions = 87, + smoothScrolling = 88, + stopRenderingLineAfter = 89, + suggest = 90, + suggestFontSize = 91, + suggestLineHeight = 92, + suggestOnTriggerCharacters = 93, + suggestSelection = 94, + tabCompletion = 95, + useTabStops = 96, + wordSeparators = 97, + wordWrap = 98, + wordWrapBreakAfterCharacters = 99, + wordWrapBreakBeforeCharacters = 100, + wordWrapColumn = 101, + wordWrapMinified = 102, + wrappingIndent = 103, + wrappingAlgorithm = 104, + editorClassName = 105, + pixelRatio = 106, + tabFocusMode = 107, + layoutInfo = 108, + wrappingInfo = 109 } export const EditorOptions: { acceptSuggestionOnCommitCharacter: IEditorOption; @@ -3864,6 +3870,7 @@ declare namespace monaco.editor { renderIndentGuides: IEditorOption; renderFinalNewline: IEditorOption; renderLineHighlight: IEditorOption; + renderValidationDecorations: IEditorOption; renderWhitespace: IEditorOption; revealHorizontalRightPadding: IEditorOption; roundedSelection: IEditorOption; From 2fc7c60b9545228f4a52bf47c81da2955523a87e Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Fri, 24 Jan 2020 08:27:57 +0100 Subject: [PATCH 065/801] Fix editor validation decorations (#89057) --- src/vs/editor/browser/widget/codeEditorWidget.ts | 4 ++-- src/vs/editor/common/config/editorOptions.ts | 6 +++--- src/vs/editor/common/viewModel/viewModelDecorations.ts | 4 ++-- src/vs/editor/common/viewModel/viewModelImpl.ts | 4 ++-- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/vs/editor/browser/widget/codeEditorWidget.ts b/src/vs/editor/browser/widget/codeEditorWidget.ts index 69794a4f647..f690983fb77 100644 --- a/src/vs/editor/browser/widget/codeEditorWidget.ts +++ b/src/vs/editor/browser/widget/codeEditorWidget.ts @@ -22,7 +22,7 @@ import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService import { ICommandDelegate } from 'vs/editor/browser/view/viewController'; import { IContentWidgetData, IOverlayWidgetData, View } from 'vs/editor/browser/view/viewImpl'; import { ViewOutgoingEvents } from 'vs/editor/browser/view/viewOutgoingEvents'; -import { ConfigurationChangedEvent, EditorLayoutInfo, IEditorOptions, EditorOption, IComputedEditorOptions, FindComputedEditorOptionValueById, IEditorConstructionOptions, shouldRenderValidationDecorations } from 'vs/editor/common/config/editorOptions'; +import { ConfigurationChangedEvent, EditorLayoutInfo, IEditorOptions, EditorOption, IComputedEditorOptions, FindComputedEditorOptionValueById, IEditorConstructionOptions, filterValidationDecorations } from 'vs/editor/common/config/editorOptions'; import { Cursor, CursorStateChangedEvent } from 'vs/editor/common/controller/cursor'; import { CursorColumns, ICursors } from 'vs/editor/common/controller/cursorCommon'; import { ICursorPositionChangedEvent, ICursorSelectionChangedEvent } from 'vs/editor/common/controller/cursorEvents'; @@ -1064,7 +1064,7 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE if (!this._modelData) { return null; } - return this._modelData.model.getLineDecorations(lineNumber, this._id, shouldRenderValidationDecorations(this._configuration.options)); + return this._modelData.model.getLineDecorations(lineNumber, this._id, filterValidationDecorations(this._configuration.options)); } public deltaDecorations(oldDecorations: string[], newDecorations: IModelDeltaDecoration[]): string[] { diff --git a/src/vs/editor/common/config/editorOptions.ts b/src/vs/editor/common/config/editorOptions.ts index bacc0dac28f..9de657dcd87 100644 --- a/src/vs/editor/common/config/editorOptions.ts +++ b/src/vs/editor/common/config/editorOptions.ts @@ -2344,12 +2344,12 @@ class EditorRenderLineNumbersOption extends BaseEditorOption Date: Fri, 24 Jan 2020 08:37:35 +0100 Subject: [PATCH 066/801] Let read-only editors show problems (fixes #89057) --- src/vs/workbench/browser/parts/editor/textEditor.ts | 5 ++++- src/vs/workbench/contrib/output/browser/logViewer.ts | 1 + src/vs/workbench/contrib/output/browser/outputPanel.ts | 1 + .../contrib/preferences/browser/preferencesEditor.ts | 1 + 4 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/browser/parts/editor/textEditor.ts b/src/vs/workbench/browser/parts/editor/textEditor.ts index 17aa4cb2db7..abcc7194afc 100644 --- a/src/vs/workbench/browser/parts/editor/textEditor.ts +++ b/src/vs/workbench/browser/parts/editor/textEditor.ts @@ -109,7 +109,10 @@ export abstract class BaseTextEditor extends BaseEditor implements ITextEditor { overviewRulerLanes: 3, lineNumbersMinChars: 3, fixedOverflowWidgets: true, - readOnly: this.input?.isReadonly() + readOnly: this.input?.isReadonly(), + // render problems even in readonly editors + // https://github.com/microsoft/vscode/issues/89057 + renderValidationDecorations: 'on' }; } diff --git a/src/vs/workbench/contrib/output/browser/logViewer.ts b/src/vs/workbench/contrib/output/browser/logViewer.ts index dbe437aff0c..33c74fdae1f 100644 --- a/src/vs/workbench/contrib/output/browser/logViewer.ts +++ b/src/vs/workbench/contrib/output/browser/logViewer.ts @@ -63,6 +63,7 @@ export class LogViewer extends AbstractTextResourceEditor { options.wordWrap = 'off'; // all log viewers do not wrap options.folding = false; options.scrollBeyondLastLine = false; + options.renderValidationDecorations = 'editable'; return options; } } diff --git a/src/vs/workbench/contrib/output/browser/outputPanel.ts b/src/vs/workbench/contrib/output/browser/outputPanel.ts index 8060591920f..378f789fc69 100644 --- a/src/vs/workbench/contrib/output/browser/outputPanel.ts +++ b/src/vs/workbench/contrib/output/browser/outputPanel.ts @@ -95,6 +95,7 @@ export class OutputPanel extends AbstractTextResourceEditor { options.scrollBeyondLastLine = false; options.renderLineHighlight = 'none'; options.minimap = { enabled: false }; + options.renderValidationDecorations = 'editable'; const outputConfig = this.configurationService.getValue('[Log]'); if (outputConfig) { diff --git a/src/vs/workbench/contrib/preferences/browser/preferencesEditor.ts b/src/vs/workbench/contrib/preferences/browser/preferencesEditor.ts index cda7c8dad3d..87b7abe01b3 100644 --- a/src/vs/workbench/contrib/preferences/browser/preferencesEditor.ts +++ b/src/vs/workbench/contrib/preferences/browser/preferencesEditor.ts @@ -1021,6 +1021,7 @@ export class DefaultPreferencesEditor extends BaseTextEditor { options.minimap = { enabled: false }; + options.renderValidationDecorations = 'editable'; } return options; } From 286db43065da2d4983ffd85740989956ba9e5b6b Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Fri, 24 Jan 2020 09:16:29 +0100 Subject: [PATCH 067/801] lifecycle - more details when unload fails --- .../electron-browser/lifecycleService.ts | 36 +++++++++++++++---- 1 file changed, 30 insertions(+), 6 deletions(-) diff --git a/src/vs/workbench/services/lifecycle/electron-browser/lifecycleService.ts b/src/vs/workbench/services/lifecycle/electron-browser/lifecycleService.ts index 8a859f0d230..ba08b6e7b79 100644 --- a/src/vs/workbench/services/lifecycle/electron-browser/lifecycleService.ts +++ b/src/vs/workbench/services/lifecycle/electron-browser/lifecycleService.ts @@ -13,6 +13,8 @@ import { INotificationService } from 'vs/platform/notification/common/notificati import { onUnexpectedError } from 'vs/base/common/errors'; import { AbstractLifecycleService } from 'vs/platform/lifecycle/common/lifecycleService'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import Severity from 'vs/base/common/severity'; +import { localize } from 'vs/nls'; export class NativeLifecycleService extends AbstractLifecycleService { @@ -107,10 +109,7 @@ export class NativeLifecycleService extends AbstractLifecycleService { reason }); - return handleVetos(vetos, err => { - this.notificationService.error(toErrorMessage(err)); - onUnexpectedError(err); - }); + return handleVetos(vetos, error => this.onShutdownError(reason, error)); } private async handleWillShutdown(reason: ShutdownReason): Promise { @@ -128,10 +127,35 @@ export class NativeLifecycleService extends AbstractLifecycleService { try { await Promise.all(joiners); } catch (error) { - this.notificationService.error(toErrorMessage(error)); - onUnexpectedError(error); + this.onShutdownError(reason, error); } } + + private onShutdownError(reason: ShutdownReason, error: Error): void { + let message: string; + switch (reason) { + case ShutdownReason.CLOSE: + message = localize('errorClose', "An unexpected error prevented the window from closing ({0}).", toErrorMessage(error)); + break; + case ShutdownReason.QUIT: + message = localize('errorQuit', "An unexpected error prevented the application from closing ({0}).", toErrorMessage(error)); + break; + case ShutdownReason.RELOAD: + message = localize('errorReload', "An unexpected error prevented the window from reloading ({0}).", toErrorMessage(error)); + break; + case ShutdownReason.LOAD: + message = localize('errorLoad', "An unexpected error prevented the window from changing it's workspace ({0}).", toErrorMessage(error)); + break; + } + + this.notificationService.notify({ + severity: Severity.Error, + message, + sticky: true + }); + + onUnexpectedError(error); + } } registerSingleton(ILifecycleService, NativeLifecycleService); From e4eec1db9c612bf44201f40085f0e624d3045c8a Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Fri, 24 Jan 2020 09:27:02 +0100 Subject: [PATCH 068/801] :lipstick: backup tracker --- .../backup/electron-browser/backupTracker.ts | 51 ++++++++++--------- 1 file changed, 28 insertions(+), 23 deletions(-) diff --git a/src/vs/workbench/contrib/backup/electron-browser/backupTracker.ts b/src/vs/workbench/contrib/backup/electron-browser/backupTracker.ts index be8f29a2f77..1aba5636ccb 100644 --- a/src/vs/workbench/contrib/backup/electron-browser/backupTracker.ts +++ b/src/vs/workbench/contrib/backup/electron-browser/backupTracker.ts @@ -44,29 +44,34 @@ export class NativeBackupTracker extends BackupTracker implements IWorkbenchCont // Dirty working copies need treatment on shutdown const dirtyWorkingCopies = this.workingCopyService.dirtyWorkingCopies; if (dirtyWorkingCopies.length) { - - // If auto save is enabled, save all non-untitled working copies - // and then check again for dirty copies - if (this.filesConfigurationService.getAutoSaveMode() !== AutoSaveMode.OFF) { - return this.doSaveAllBeforeShutdown(false /* not untitled */, SaveReason.AUTO).then(() => { - - // If we still have dirty working copies, we either have untitled ones or working copies that cannot be saved - const remainingDirtyWorkingCopies = this.workingCopyService.dirtyWorkingCopies; - if (remainingDirtyWorkingCopies.length) { - return this.handleDirtyBeforeShutdown(remainingDirtyWorkingCopies, reason); - } - - return false; // no veto (there are no remaining dirty working copies) - }); - } - - // Auto save is not enabled - return this.handleDirtyBeforeShutdown(dirtyWorkingCopies, reason); + return this.onBeforeShutdownWithDirty(reason, dirtyWorkingCopies); } return false; // no veto (no dirty working copies) } + protected async onBeforeShutdownWithDirty(reason: ShutdownReason, workingCopies: IWorkingCopy[]): Promise { + + // If auto save is enabled, save all non-untitled working copies + // and then check again for dirty copies + if (this.filesConfigurationService.getAutoSaveMode() !== AutoSaveMode.OFF) { + + // Save all files + await this.doSaveAllBeforeShutdown(false /* not untitled */, SaveReason.AUTO); + + // If we still have dirty working copies, we either have untitled ones or working copies that cannot be saved + const remainingDirtyWorkingCopies = this.workingCopyService.dirtyWorkingCopies; + if (remainingDirtyWorkingCopies.length) { + return this.handleDirtyBeforeShutdown(remainingDirtyWorkingCopies, reason); + } + + return false; // no veto (there are no remaining dirty working copies) + } + + // Auto save is not enabled + return this.handleDirtyBeforeShutdown(workingCopies, reason); + } + private async handleDirtyBeforeShutdown(workingCopies: IWorkingCopy[], reason: ShutdownReason): Promise { // Trigger backup if configured @@ -95,14 +100,14 @@ export class NativeBackupTracker extends BackupTracker implements IWorkbenchCont try { return await this.confirmBeforeShutdown(workingCopies.filter(workingCopy => backups.indexOf(workingCopy) === -1)); } catch (error) { - this.showErrorDialog(localize('backupTrackerConfirmFailed', "Editors that are dirty could not be saved or reverted."), error); + this.showErrorDialog(localize('backupTrackerConfirmFailed', "One or many editors that are dirty could not be saved or reverted."), error); return true; // veto (save or revert failed) } } private showErrorDialog(msg: string, error?: Error): void { - this.dialogService.show(Severity.Error, msg, [localize('ok', 'OK')], { detail: localize('backupErrorDetails', "Try saving the dirty editors first and then try again.") }); + this.dialogService.show(Severity.Error, msg, [localize('ok', 'OK')], { detail: localize('backupErrorDetails', "Try saving or reverting the dirty editors first and then try again.") }); this.logService.error(error ? `[backup tracker] ${msg}: ${error}` : `[backup tracker] ${msg}`); } @@ -174,10 +179,10 @@ export class NativeBackupTracker extends BackupTracker implements IWorkbenchCont // Save const confirm = await this.fileDialogService.showSaveConfirm(workingCopies.map(workingCopy => workingCopy.name)); if (confirm === ConfirmResult.SAVE) { - const dirtyCount = this.workingCopyService.dirtyCount; + const dirtyCountBeforeSave = this.workingCopyService.dirtyCount; await this.doSaveAllBeforeShutdown(workingCopies, SaveReason.EXPLICIT); - const savedWorkingCopies = dirtyCount - this.workingCopyService.dirtyCount; + const savedWorkingCopies = dirtyCountBeforeSave - this.workingCopyService.dirtyCount; if (savedWorkingCopies < workingCopies.length) { return true; // veto (save failed or was canceled) } @@ -224,7 +229,7 @@ export class NativeBackupTracker extends BackupTracker implements IWorkbenchCont } } - private async doRevertAllBeforeShutdown(workingCopies = this.workingCopyService.dirtyWorkingCopies): Promise { + private async doRevertAllBeforeShutdown(workingCopies: IWorkingCopy[]): Promise { // Soft revert is good enough on shutdown const revertOptions = { soft: true }; From 80e2ea28b68b768f49c55921da96063a12fc15da Mon Sep 17 00:00:00 2001 From: Joao Moreno Date: Fri, 24 Jan 2020 09:44:16 +0100 Subject: [PATCH 069/801] use macOS-latest hosted agent --- azure-pipelines.yml | 4 ++-- build/azure-pipelines/product-build.yml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index e52afca6028..b2f9a13c2b7 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -13,6 +13,6 @@ jobs: - job: macOS pool: - vmImage: macOS 10.13 + vmImage: macOS-latest steps: - - template: build/azure-pipelines/darwin/continuous-build-darwin.yml \ No newline at end of file + - template: build/azure-pipelines/darwin/continuous-build-darwin.yml diff --git a/build/azure-pipelines/product-build.yml b/build/azure-pipelines/product-build.yml index 2eedaf8dce5..a98b5f4f77e 100644 --- a/build/azure-pipelines/product-build.yml +++ b/build/azure-pipelines/product-build.yml @@ -102,7 +102,7 @@ jobs: - job: macOS condition: and(succeeded(), eq(variables['VSCODE_COMPILE_ONLY'], 'false'), eq(variables['VSCODE_BUILD_MACOS'], 'true')) pool: - vmImage: macOS 10.13 + vmImage: macOS-latest dependsOn: - Compile steps: From 38549a606cd46cd1030855ddd166b757e3e54c33 Mon Sep 17 00:00:00 2001 From: Pine Wu Date: Fri, 24 Jan 2020 10:30:19 +0100 Subject: [PATCH 070/801] Allow link on Diagnostic#code for #11847 Squashed commit of the following: commit 76689c23011bf960f5ba3e199659e8180455466d Author: Pine Wu Date: Fri Jan 24 10:19:44 2020 +0100 Clarify API commit 88d772d62da5d912b54285aa52dc6d6a108ca587 Author: Pine Wu Date: Fri Jan 24 10:17:28 2020 +0100 Tooltip commit 11ef8012ae3e560d6db6fff26ba3c258e7eae4a6 Author: Pine Wu Date: Thu Jan 23 16:50:28 2020 +0100 Hover to show link color and use cmd/alt click commit 38655a70b3eed56b99e8018fb5c79bd41b6c4f56 Author: Pine Wu Date: Thu Jan 23 15:21:13 2020 +0100 Add hack to always render underline a bit below commit 959f6b13bf81885cc370f6099e5123142089599e Author: Pine Wu Date: Thu Jan 23 11:32:37 2020 +0100 Fix compile error commit b5c50e872935e74503451bd2801227f82b8410f4 Author: Pine Wu Date: Thu Jan 23 11:19:52 2020 +0100 Bring code link everywhere for Diagnostics commit f88cd4cc4e2064fd96853373e26c12670161bf60 Author: Pine Wu Date: Wed Jan 22 17:24:54 2020 +0100 Add Diagnostic#code2 and render it in hover/inline/panel commit 2a13e3e4f62fd3707f2c03c1e814a8fdc557c367 Author: Pine Wu Date: Wed Jan 22 13:49:42 2020 +0100 WIP --- src/vs/editor/contrib/gotoError/gotoError.ts | 8 +- .../contrib/gotoError/gotoErrorWidget.ts | 100 +++++++++++++++-- .../gotoError/media/gotoErrorWidget.css | 19 +++- src/vs/editor/contrib/hover/hover.css | 17 +++ src/vs/editor/contrib/hover/hover.ts | 6 +- .../editor/contrib/hover/modesContentHover.ts | 90 ++++++++++++++- src/vs/monaco.d.ts | 10 +- src/vs/platform/markers/common/markers.ts | 10 +- src/vs/vscode.proposed.d.ts | 22 ++++ .../api/browser/mainThreadDiagnostics.ts | 3 + .../api/common/extHostTypeConverters.ts | 12 +- .../markers/browser/markersTreeViewer.ts | 104 ++++++++++++++++-- .../contrib/markers/browser/media/markers.css | 8 ++ 13 files changed, 372 insertions(+), 37 deletions(-) diff --git a/src/vs/editor/contrib/gotoError/gotoError.ts b/src/vs/editor/contrib/gotoError/gotoError.ts index 10f48cad8bb..80e2342e747 100644 --- a/src/vs/editor/contrib/gotoError/gotoError.ts +++ b/src/vs/editor/contrib/gotoError/gotoError.ts @@ -27,6 +27,8 @@ import { MenuRegistry, MenuId } from 'vs/platform/actions/common/actions'; import { Action } from 'vs/base/common/actions'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { isEqual } from 'vs/base/common/resources'; +import { IOpenerService } from 'vs/platform/opener/common/opener'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; class MarkerModel { @@ -209,7 +211,9 @@ export class MarkerController implements IEditorContribution { @IContextKeyService private readonly _contextKeyService: IContextKeyService, @IThemeService private readonly _themeService: IThemeService, @ICodeEditorService private readonly _editorService: ICodeEditorService, - @IKeybindingService private readonly _keybindingService: IKeybindingService + @IKeybindingService private readonly _keybindingService: IKeybindingService, + @IOpenerService private readonly _openerService: IOpenerService, + @IConfigurationService private readonly _configurationService: IConfigurationService ) { this._editor = editor; this._widgetVisible = CONTEXT_MARKERS_NAVIGATION_VISIBLE.bindTo(this._contextKeyService); @@ -243,7 +247,7 @@ export class MarkerController implements IEditorContribution { new Action(NextMarkerAction.ID, NextMarkerAction.LABEL + (nextMarkerKeybinding ? ` (${nextMarkerKeybinding.getLabel()})` : ''), 'show-next-problem codicon-chevron-down', this._model.canNavigate(), async () => { if (this._model) { this._model.move(true, true); } }), new Action(PrevMarkerAction.ID, PrevMarkerAction.LABEL + (prevMarkerKeybinding ? ` (${prevMarkerKeybinding.getLabel()})` : ''), 'show-previous-problem codicon-chevron-up', this._model.canNavigate(), async () => { if (this._model) { this._model.move(false, true); } }) ]; - this._widget = new MarkerNavigationWidget(this._editor, actions, this._themeService); + this._widget = new MarkerNavigationWidget(this._editor, actions, this._themeService, this._openerService, this._configurationService); this._widgetVisible.set(true); this._widget.onDidClose(() => this.closeMarkersNavigation(), this, this._disposeOnClose); diff --git a/src/vs/editor/contrib/gotoError/gotoErrorWidget.ts b/src/vs/editor/contrib/gotoError/gotoErrorWidget.ts index 0926872fc13..5190682a7c9 100644 --- a/src/vs/editor/contrib/gotoError/gotoErrorWidget.ts +++ b/src/vs/editor/contrib/gotoError/gotoErrorWidget.ts @@ -26,6 +26,11 @@ import { IAction } from 'vs/base/common/actions'; import { IActionBarOptions, ActionsOrientation } from 'vs/base/browser/ui/actionbar/actionbar'; import { SeverityIcon } from 'vs/platform/severityIcon/common/severityIcon'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; +import { IOpenerService } from 'vs/platform/opener/common/opener'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { OperatingSystem, OS } from 'vs/base/common/platform'; + +type ModifierKey = 'meta' | 'ctrl' | 'alt'; class MessageWidget { @@ -39,7 +44,16 @@ class MessageWidget { private readonly _relatedDiagnostics = new WeakMap(); private readonly _disposables: DisposableStore = new DisposableStore(); - constructor(parent: HTMLElement, editor: ICodeEditor, onRelatedInformation: (related: IRelatedInformation) => void) { + private _clickModifierKey: ModifierKey; + private _codeLink?: HTMLElement; + + constructor( + parent: HTMLElement, + editor: ICodeEditor, + onRelatedInformation: (related: IRelatedInformation) => void, + private readonly _openerService: IOpenerService, + private readonly _configurationService: IConfigurationService + ) { this._editor = editor; const domNode = document.createElement('div'); @@ -74,6 +88,16 @@ class MessageWidget { domNode.style.top = `-${e.scrollTop}px`; })); this._disposables.add(this._scrollable); + + this._clickModifierKey = this._getClickModifierKey(); + this._disposables.add(this._configurationService.onDidChangeConfiguration(e => { + if (e.affectsConfiguration('editor.multiCursorModifier')) { + this._clickModifierKey = this._getClickModifierKey(); + if (this._codeLink) { + this._codeLink.setAttribute('title', this._getCodelinkTooltip()); + } + } + })); } dispose(): void { @@ -81,12 +105,20 @@ class MessageWidget { } update({ source, message, relatedInformation, code }: IMarker): void { + let sourceAndCodeLength = (source?.length || 0) + '()'.length; + if (code) { + if (typeof code === 'string') { + sourceAndCodeLength += code.length; + } else { + sourceAndCodeLength += code.value.length; + } + } const lines = message.split(/\r\n|\r|\n/g); this._lines = lines.length; this._longestLineLength = 0; for (const line of lines) { - this._longestLineLength = Math.max(line.length, this._longestLineLength); + this._longestLineLength = Math.max(line.length + sourceAndCodeLength, this._longestLineLength); } dom.clearNode(this._messageBlock); @@ -111,10 +143,28 @@ class MessageWidget { detailsElement.appendChild(sourceElement); } if (code) { - const codeElement = document.createElement('span'); - codeElement.innerText = `(${code})`; - dom.addClass(codeElement, 'code'); - detailsElement.appendChild(codeElement); + if (typeof code === 'string') { + const codeElement = document.createElement('span'); + codeElement.innerText = `(${code})`; + dom.addClass(codeElement, 'code'); + detailsElement.appendChild(codeElement); + } else { + this._codeLink = dom.$('a.code-link'); + this._codeLink.setAttribute('title', this._getCodelinkTooltip()); + this._codeLink.setAttribute('href', `${code.link.toString()}`); + + this._codeLink.onclick = (e) => { + e.preventDefault(); + if ((this._clickModifierKey === 'meta' && e.metaKey) || (this._clickModifierKey === 'ctrl' && e.ctrlKey) || (this._clickModifierKey === 'alt' && e.altKey)) { + this._openerService.open(code.link); + e.stopPropagation(); + } + }; + + const codeElement = dom.append(this._codeLink, dom.$('span')); + codeElement.innerText = code.value; + detailsElement.appendChild(this._codeLink); + } } } @@ -161,6 +211,31 @@ class MessageWidget { getHeightInLines(): number { return Math.min(17, this._lines); } + + private _getClickModifierKey(): ModifierKey { + const value = this._configurationService.getValue<'ctrlCmd' | 'alt'>('editor.multiCursorModifier'); + if (value === 'ctrlCmd') { + return 'alt'; + } else { + if (OS === OperatingSystem.Macintosh) { + return 'meta'; + } else { + return 'ctrl'; + } + } + } + + private _getCodelinkTooltip(): string { + const tooltipLabel = nls.localize('links.navigate.follow', 'Follow link'); + const tooltipKeybinding = this._clickModifierKey === 'ctrl' + ? nls.localize('links.navigate.kb.meta', 'ctrl + click') + : + this._clickModifierKey === 'meta' + ? OS === OperatingSystem.Macintosh ? nls.localize('links.navigate.kb.meta.mac', 'cmd + click') : nls.localize('links.navigate.kb.meta', 'ctrl + click') + : OS === OperatingSystem.Macintosh ? nls.localize('links.navigate.kb.alt.mac', 'option + click') : nls.localize('links.navigate.kb.alt', 'alt + click'); + + return `${tooltipLabel} (${tooltipKeybinding})`; + } } export class MarkerNavigationWidget extends PeekViewWidget { @@ -180,7 +255,9 @@ export class MarkerNavigationWidget extends PeekViewWidget { constructor( editor: ICodeEditor, private readonly actions: ReadonlyArray, - private readonly _themeService: IThemeService + private readonly _themeService: IThemeService, + private readonly _openerService: IOpenerService, + private readonly _configurationService: IConfigurationService ) { super(editor, { showArrow: true, showFrame: true, isAccessible: true }); this._severity = MarkerSeverity.Warning; @@ -250,7 +327,7 @@ export class MarkerNavigationWidget extends PeekViewWidget { this._container = document.createElement('div'); container.appendChild(this._container); - this._message = new MessageWidget(this._container, this.editor, related => this._onDidSelectRelatedInformation.fire(related)); + this._message = new MessageWidget(this._container, this.editor, related => this._onDidSelectRelatedInformation.fire(related), this._openerService, this._configurationService); this._disposables.add(this._message); } @@ -329,8 +406,9 @@ export const editorMarkerNavigationInfo = registerColor('editorMarkerNavigationI export const editorMarkerNavigationBackground = registerColor('editorMarkerNavigation.background', { dark: '#2D2D30', light: Color.white, hc: '#0C141F' }, nls.localize('editorMarkerNavigationBackground', 'Editor marker navigation widget background.')); registerThemingParticipant((theme, collector) => { - const link = theme.getColor(textLinkForeground); - if (link) { - collector.addRule(`.monaco-editor .marker-widget a { color: ${link}; }`); + const linkFg = theme.getColor(textLinkForeground); + if (linkFg) { + collector.addRule(`.monaco-editor .marker-widget a { color: ${linkFg}; }`); + collector.addRule(`.monaco-editor .marker-widget a.code-link span:hover { color: ${linkFg}; }`); } }); diff --git a/src/vs/editor/contrib/gotoError/media/gotoErrorWidget.css b/src/vs/editor/contrib/gotoError/media/gotoErrorWidget.css index ee02594a087..c748c365bdc 100644 --- a/src/vs/editor/contrib/gotoError/media/gotoErrorWidget.css +++ b/src/vs/editor/contrib/gotoError/media/gotoErrorWidget.css @@ -45,10 +45,27 @@ } .monaco-editor .marker-widget .descriptioncontainer .message .source, -.monaco-editor .marker-widget .descriptioncontainer .message .code { +.monaco-editor .marker-widget .descriptioncontainer .message span.code { opacity: 0.6; } +.monaco-editor .marker-widget .descriptioncontainer .message a.code-link { + opacity: 0.6; + color: inherit; +} +.monaco-editor .marker-widget .descriptioncontainer .message a.code-link:before { + content: '('; +} +.monaco-editor .marker-widget .descriptioncontainer .message a.code-link:after { + content: ')'; +} +.monaco-editor .marker-widget .descriptioncontainer .message a.code-link > span { + text-decoration: underline; + /** Hack to force underline to show **/ + border-bottom: 1px solid transparent; + text-underline-position: under; +} + .monaco-editor .marker-widget .descriptioncontainer .filename { cursor: pointer; } diff --git a/src/vs/editor/contrib/hover/hover.css b/src/vs/editor/contrib/hover/hover.css index 19f7bcec136..8f35b9aec75 100644 --- a/src/vs/editor/contrib/hover/hover.css +++ b/src/vs/editor/contrib/hover/hover.css @@ -110,3 +110,20 @@ font-size: inherit; vertical-align: middle; } + +.monaco-editor-hover .hover-contents a.code-link:before { + content: '('; +} +.monaco-editor-hover .hover-contents a.code-link:after { + content: ')'; +} + +.monaco-editor-hover .hover-contents a.code-link { + color: inherit; +} +.monaco-editor-hover .hover-contents a.code-link > span { + text-decoration: underline; + /** Hack to force underline to show **/ + border-bottom: 1px solid transparent; + text-underline-position: under; +} diff --git a/src/vs/editor/contrib/hover/hover.ts b/src/vs/editor/contrib/hover/hover.ts index 3a0f4eea252..b77e2b0132a 100644 --- a/src/vs/editor/contrib/hover/hover.ts +++ b/src/vs/editor/contrib/hover/hover.ts @@ -27,6 +27,7 @@ import { IMarkerDecorationsService } from 'vs/editor/common/services/markersDeco import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { AccessibilitySupport } from 'vs/platform/accessibility/common/accessibility'; import { GotoDefinitionAtPositionEditorContribution } from 'vs/editor/contrib/gotoSymbol/link/goToDefinitionAtPosition'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; export class ModesHoverController implements IEditorContribution { @@ -66,7 +67,8 @@ export class ModesHoverController implements IEditorContribution { @IModeService private readonly _modeService: IModeService, @IMarkerDecorationsService private readonly _markerDecorationsService: IMarkerDecorationsService, @IKeybindingService private readonly _keybindingService: IKeybindingService, - @IThemeService private readonly _themeService: IThemeService + @IThemeService private readonly _themeService: IThemeService, + @IConfigurationService private readonly _configurationService: IConfigurationService ) { this._isMouseDown = false; this._hoverClicked = false; @@ -205,7 +207,7 @@ export class ModesHoverController implements IEditorContribution { } private _createHoverWidgets() { - this._contentWidget.value = new ModesContentHoverWidget(this._editor, this._markerDecorationsService, this._themeService, this._keybindingService, this._modeService, this._openerService); + this._contentWidget.value = new ModesContentHoverWidget(this._editor, this._markerDecorationsService, this._themeService, this._keybindingService, this._modeService, this._openerService, this._configurationService); this._glyphWidget.value = new ModesGlyphHoverWidget(this._editor, this._modeService, this._openerService); } diff --git a/src/vs/editor/contrib/hover/modesContentHover.ts b/src/vs/editor/contrib/hover/modesContentHover.ts index ef800dc9f59..c52183db542 100644 --- a/src/vs/editor/contrib/hover/modesContentHover.ts +++ b/src/vs/editor/contrib/hover/modesContentHover.ts @@ -22,7 +22,7 @@ import { getHover } from 'vs/editor/contrib/hover/getHover'; import { HoverOperation, HoverStartMode, IHoverComputer } from 'vs/editor/contrib/hover/hoverOperation'; import { ContentHoverWidget } from 'vs/editor/contrib/hover/hoverWidgets'; import { MarkdownRenderer } from 'vs/editor/contrib/markdown/markdownRenderer'; -import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; import { coalesce, isNonEmptyArray, asArray } from 'vs/base/common/arrays'; import { IMarker, IMarkerData, MarkerSeverity } from 'vs/platform/markers/common/markers'; import { basename } from 'vs/base/common/resources'; @@ -39,6 +39,9 @@ import { IModeService } from 'vs/editor/common/services/modeService'; import { IIdentifiedSingleEditOperation } from 'vs/editor/common/model'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; import { Constants } from 'vs/base/common/uint'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { OperatingSystem, OS } from 'vs/base/common/platform'; +import { textLinkForeground } from 'vs/platform/theme/common/colorRegistry'; const $ = dom.$; @@ -191,6 +194,8 @@ const markerCodeActionTrigger: CodeActionTrigger = { filter: { include: CodeActionKind.QuickFix } }; +type ModifierKey = 'meta' | 'ctrl' | 'alt'; + export class ModesContentHoverWidget extends ContentHoverWidget { static readonly ID = 'editor.contrib.modesContentHoverWidget'; @@ -204,6 +209,9 @@ export class ModesContentHoverWidget extends ContentHoverWidget { private _shouldFocus: boolean; private _colorPicker: ColorPickerWidget | null; + private _clickModifierKey: ModifierKey; + private _codeLink?: HTMLElement; + private readonly renderDisposable = this._register(new MutableDisposable()); constructor( @@ -213,6 +221,7 @@ export class ModesContentHoverWidget extends ContentHoverWidget { private readonly _keybindingService: IKeybindingService, private readonly _modeService: IModeService, private readonly _openerService: IOpenerService = NullOpenerService, + private readonly _configurationService: IConfigurationService ) { super(ModesContentHoverWidget.ID, editor); @@ -249,6 +258,16 @@ export class ModesContentHoverWidget extends ContentHoverWidget { this._renderMessages(this._lastRange, this._messages); } })); + + this._clickModifierKey = this._getClickModifierKey(); + this._register((this._configurationService.onDidChangeConfiguration(e => { + if (e.affectsConfiguration('editor.multiCursorModifier')) { + this._clickModifierKey = this._getClickModifierKey(); + if (this._codeLink) { + this._codeLink.setAttribute('title', this._getCodelinkTooltip()); + } + } + }))); } dispose(): void { @@ -500,10 +519,38 @@ export class ModesContentHoverWidget extends ContentHoverWidget { messageElement.innerText = message; if (source || code) { - const detailsElement = dom.append(markerElement, $('span')); - detailsElement.style.opacity = '0.6'; - detailsElement.style.paddingLeft = '6px'; - detailsElement.innerText = source && code ? `${source}(${code})` : source ? source : `(${code})`; + if (typeof code === 'string') { + const detailsElement = dom.append(markerElement, $('span')); + detailsElement.style.opacity = '0.6'; + detailsElement.style.paddingLeft = '6px'; + detailsElement.innerText = source && code ? `${source}(${code})` : source ? source : `(${code})`; + } else { + if (code) { + const sourceAndCodeElement = $('span'); + if (source) { + const sourceElement = dom.append(sourceAndCodeElement, $('span')); + sourceElement.innerText = source; + } + this._codeLink = dom.append(sourceAndCodeElement, $('a.code-link')); + this._codeLink.setAttribute('title', this._getCodelinkTooltip()); + this._codeLink.setAttribute('href', code.link.toString()); + + this._codeLink.onclick = (e) => { + e.preventDefault(); + if ((this._clickModifierKey === 'meta' && e.metaKey) || (this._clickModifierKey === 'ctrl' && e.ctrlKey) || (this._clickModifierKey === 'alt' && e.altKey)) { + this._openerService.open(code.link); + e.stopPropagation(); + } + }; + + const codeElement = dom.append(this._codeLink, $('span')); + codeElement.innerText = code.value; + + const detailsElement = dom.append(markerElement, sourceAndCodeElement); + detailsElement.style.opacity = '0.6'; + detailsElement.style.paddingLeft = '6px'; + } + } } if (isNonEmptyArray(relatedInformation)) { @@ -624,6 +671,31 @@ export class ModesContentHoverWidget extends ContentHoverWidget { private static readonly _DECORATION_OPTIONS = ModelDecorationOptions.register({ className: 'hoverHighlight' }); + + private _getClickModifierKey(): ModifierKey { + const value = this._configurationService.getValue<'ctrlCmd' | 'alt'>('editor.multiCursorModifier'); + if (value === 'ctrlCmd') { + return 'alt'; + } else { + if (OS === OperatingSystem.Macintosh) { + return 'meta'; + } else { + return 'ctrl'; + } + } + } + + private _getCodelinkTooltip(): string { + const tooltipLabel = nls.localize('links.navigate.follow', 'Follow link'); + const tooltipKeybinding = this._clickModifierKey === 'ctrl' + ? nls.localize('links.navigate.kb.meta', 'ctrl + click') + : + this._clickModifierKey === 'meta' + ? OS === OperatingSystem.Macintosh ? nls.localize('links.navigate.kb.meta.mac', 'cmd + click') : nls.localize('links.navigate.kb.meta', 'ctrl + click') + : OS === OperatingSystem.Macintosh ? nls.localize('links.navigate.kb.alt.mac', 'option + click') : nls.localize('links.navigate.kb.alt', 'alt + click'); + + return `${tooltipLabel} (${tooltipKeybinding})`; + } } function hoverContentsEquals(first: HoverPart[], second: HoverPart[]): boolean { @@ -648,3 +720,11 @@ function hoverContentsEquals(first: HoverPart[], second: HoverPart[]): boolean { } return true; } + +registerThemingParticipant((theme, collector) => { + const linkFg = theme.getColor(textLinkForeground); + if (linkFg) { + collector.addRule(`.monaco-editor-hover .hover-contents a.code-link span:hover { color: ${linkFg}; }`); + } +}); + diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index 4412fce4a6f..2e7725c31cc 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -1176,7 +1176,10 @@ declare namespace monaco.editor { owner: string; resource: Uri; severity: MarkerSeverity; - code?: string; + code?: string | { + value: string; + link: Uri; + }; message: string; source?: string; startLineNumber: number; @@ -1191,7 +1194,10 @@ declare namespace monaco.editor { * A structure defining a problem/warning/etc. */ export interface IMarkerData { - code?: string; + code?: string | { + value: string; + link: Uri; + }; severity: MarkerSeverity; message: string; source?: string; diff --git a/src/vs/platform/markers/common/markers.ts b/src/vs/platform/markers/common/markers.ts index 4b0f6095f1a..66cd4058541 100644 --- a/src/vs/platform/markers/common/markers.ts +++ b/src/vs/platform/markers/common/markers.ts @@ -87,7 +87,7 @@ export namespace MarkerSeverity { * A structure defining a problem/warning/etc. */ export interface IMarkerData { - code?: string; + code?: string | { value: string; link: URI }; severity: MarkerSeverity; message: string; source?: string; @@ -108,7 +108,7 @@ export interface IMarker { owner: string; resource: URI; severity: MarkerSeverity; - code?: string; + code?: string | { value: string; link: URI }; message: string; source?: string; startLineNumber: number; @@ -140,7 +140,11 @@ export namespace IMarkerData { result.push(emptyString); } if (markerData.code) { - result.push(markerData.code.replace('¦', '\¦')); + if (typeof markerData.code === 'string') { + result.push(markerData.code.replace('¦', '\¦')); + } else { + result.push(markerData.code.value.replace('¦', '\¦')); + } } else { result.push(emptyString); } diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index 33b1b497c8d..607cc0bcd83 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -1446,4 +1446,26 @@ declare module 'vscode' { } //#endregion + + //#region Diagnostic links https://github.com/microsoft/vscode/issues/11847 + + export interface Diagnostic { + /** + * Will be merged into `Diagnostic#code` + */ + code2?: { + /** + * A code or identifier for this diagnostic. + * Should be used for later processing, e.g. when providing [code actions](#CodeActionContext). + */ + value: string | number; + + /** + * A link to a URI with more information about the diagnostic error. + */ + link: Uri; + } + } + + //#endregion } diff --git a/src/vs/workbench/api/browser/mainThreadDiagnostics.ts b/src/vs/workbench/api/browser/mainThreadDiagnostics.ts index ca934673534..2727860a6cc 100644 --- a/src/vs/workbench/api/browser/mainThreadDiagnostics.ts +++ b/src/vs/workbench/api/browser/mainThreadDiagnostics.ts @@ -54,6 +54,9 @@ export class MainThreadDiagnostics implements MainThreadDiagnosticsShape { relatedInformation.resource = URI.revive(relatedInformation.resource); } } + if (marker.code && typeof marker.code !== 'string') { + marker.code.link = URI.revive(marker.code.link); + } } } this._markerService.changeOne(owner, URI.revive(uri), markers); diff --git a/src/vs/workbench/api/common/extHostTypeConverters.ts b/src/vs/workbench/api/common/extHostTypeConverters.ts index 6fbb9b7fb07..861af59f92a 100644 --- a/src/vs/workbench/api/common/extHostTypeConverters.ts +++ b/src/vs/workbench/api/common/extHostTypeConverters.ts @@ -127,11 +127,19 @@ export namespace DiagnosticTag { export namespace Diagnostic { export function from(value: vscode.Diagnostic): IMarkerData { + let code: string | { value: string; link: URI } | undefined = isString(value.code) || isNumber(value.code) ? String(value.code) : undefined; + if (value.code2) { + code = { + value: String(value.code2.value), + link: value.code2.link + }; + } + return { ...Range.from(value.range), message: value.message, source: value.source, - code: isString(value.code) || isNumber(value.code) ? String(value.code) : undefined, + code, severity: DiagnosticSeverity.from(value.severity), relatedInformation: value.relatedInformation && value.relatedInformation.map(DiagnosticRelatedInformation.from), tags: Array.isArray(value.tags) ? coalesce(value.tags.map(DiagnosticTag.from)) : undefined, @@ -141,7 +149,7 @@ export namespace Diagnostic { export function to(value: IMarkerData): vscode.Diagnostic { const res = new types.Diagnostic(Range.to(value), value.message, DiagnosticSeverity.to(value.severity)); res.source = value.source; - res.code = value.code; + res.code = isString(value.code) ? value.code : value.code?.value; res.relatedInformation = value.relatedInformation && value.relatedInformation.map(DiagnosticRelatedInformation.to); res.tags = value.tags && coalesce(value.tags.map(DiagnosticTag.to)); return res; diff --git a/src/vs/workbench/contrib/markers/browser/markersTreeViewer.ts b/src/vs/workbench/contrib/markers/browser/markersTreeViewer.ts index 3e5306bc9ed..f95d2c7873c 100644 --- a/src/vs/workbench/contrib/markers/browser/markersTreeViewer.ts +++ b/src/vs/workbench/contrib/markers/browser/markersTreeViewer.ts @@ -14,7 +14,7 @@ import { ResourceMarkers, Marker, RelatedInformation } from 'vs/workbench/contri import Messages from 'vs/workbench/contrib/markers/browser/messages'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { attachBadgeStyler } from 'vs/platform/theme/common/styler'; -import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; import { IDisposable, dispose, Disposable, toDisposable, DisposableStore } from 'vs/base/common/lifecycle'; import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; import { QuickFixAction, QuickFixActionViewItem } from 'vs/workbench/contrib/markers/browser/markersViewActions'; @@ -43,6 +43,10 @@ import { IEditorService, ACTIVE_GROUP } from 'vs/workbench/services/editor/commo import { applyCodeAction } from 'vs/editor/contrib/codeAction/codeActionCommands'; import { SeverityIcon } from 'vs/platform/severityIcon/common/severityIcon'; import { CodeActionTriggerType } from 'vs/editor/common/modes'; +import { IOpenerService } from 'vs/platform/opener/common/opener'; +import { textLinkForeground } from 'vs/platform/theme/common/colorRegistry'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { OS, OperatingSystem } from 'vs/base/common/platform'; export type TreeElement = ResourceMarkers | Marker | RelatedInformation; @@ -217,14 +221,16 @@ export class MarkerRenderer implements ITreeRenderer action.id === QuickFixAction.ID ? instantiationService.createInstance(QuickFixActionViewItem, action) : undefined + actionViewItemProvider: (action: QuickFixAction) => action.id === QuickFixAction.ID ? _instantiationService.createInstance(QuickFixActionViewItem, action) : undefined })); this.icon = dom.append(parent, dom.$('')); this.multilineActionbar = this._register(new ActionBar(dom.append(parent, dom.$('.multiline-actions')))); this.messageAndDetailsContainer = dom.append(parent, dom.$('.marker-message-details-container')); + + this._clickModifierKey = this._getClickModifierKey(); } render(element: Marker, filterData: MarkerFilterData | undefined): void { @@ -273,6 +288,15 @@ class MarkerWidget extends Disposable { this.renderMessageAndDetails(element, filterData); this.disposables.add(dom.addDisposableListener(this.parent, dom.EventType.MOUSE_OVER, () => this.markersViewModel.onMarkerMouseHover(element))); this.disposables.add(dom.addDisposableListener(this.parent, dom.EventType.MOUSE_LEAVE, () => this.markersViewModel.onMarkerMouseLeave(element))); + + this.disposables.add((this._configurationService.onDidChangeConfiguration(e => { + if (e.affectsConfiguration('editor.multiCursorModifier')) { + this._clickModifierKey = this._getClickModifierKey(); + if (this._codeLink) { + this._codeLink.setAttribute('title', this._getCodelinkTooltip()); + } + } + }))); } private renderQuickfixActionbar(marker: Marker): void { @@ -335,15 +359,63 @@ class MarkerWidget extends Disposable { source.set(marker.source, sourceMatches); if (marker.code) { - const code = new HighlightedLabel(dom.append(parent, dom.$('.marker-code')), false); - const codeMatches = filterData && filterData.codeMatches || []; - code.set(marker.code, codeMatches); + if (typeof marker.code === 'string') { + const code = new HighlightedLabel(dom.append(parent, dom.$('.marker-code')), false); + const codeMatches = filterData && filterData.codeMatches || []; + code.set(marker.code, codeMatches); + } else { + this._codeLink = dom.$('a.code-link'); + this._codeLink.setAttribute('title', this._getCodelinkTooltip()); + + const codeUri = marker.code.link; + const codeLink = codeUri.toString(); + + dom.append(parent, this._codeLink); + this._codeLink.setAttribute('href', codeLink); + + this._codeLink.onclick = (e) => { + e.preventDefault(); + if ((this._clickModifierKey === 'meta' && e.metaKey) || (this._clickModifierKey === 'ctrl' && e.ctrlKey) || (this._clickModifierKey === 'alt' && e.altKey)) { + this._openerService.open(codeUri); + e.stopPropagation(); + } + }; + + const code = new HighlightedLabel(dom.append(this._codeLink, dom.$('.marker-code')), false); + const codeMatches = filterData && filterData.codeMatches || []; + code.set(marker.code.value, codeMatches); + } } } const lnCol = dom.append(parent, dom.$('span.marker-line')); lnCol.textContent = Messages.MARKERS_PANEL_AT_LINE_COL_NUMBER(marker.startLineNumber, marker.startColumn); } + + private _getClickModifierKey(): ModifierKey { + const value = this._configurationService.getValue<'ctrlCmd' | 'alt'>('editor.multiCursorModifier'); + if (value === 'ctrlCmd') { + return 'alt'; + } else { + if (OS === OperatingSystem.Macintosh) { + return 'meta'; + } else { + return 'ctrl'; + } + } + } + + private _getCodelinkTooltip(): string { + const tooltipLabel = localize('links.navigate.follow', 'Follow link'); + const tooltipKeybinding = this._clickModifierKey === 'ctrl' + ? localize('links.navigate.kb.meta', 'ctrl + click') + : + this._clickModifierKey === 'meta' + ? OS === OperatingSystem.Macintosh ? localize('links.navigate.kb.meta.mac', 'cmd + click') : localize('links.navigate.kb.meta', 'ctrl + click') + : OS === OperatingSystem.Macintosh ? localize('links.navigate.kb.alt.mac', 'option + click') : localize('links.navigate.kb.alt', 'alt + click'); + + return `${tooltipLabel} (${tooltipKeybinding})`; + } } export class RelatedInformationRenderer implements ITreeRenderer { @@ -451,7 +523,14 @@ export class Filter implements ITreeFilter { lineMatches.push(FilterOptions._messageFilter(this.options.textFilter, line) || []); } const sourceMatches = marker.marker.source && FilterOptions._filter(this.options.textFilter, marker.marker.source); - const codeMatches = marker.marker.code && FilterOptions._filter(this.options.textFilter, marker.marker.code); + + let codeMatches: IMatch[] | null | undefined; + if (marker.marker.code) { + const codeText = typeof marker.marker.code === 'string' ? marker.marker.code : marker.marker.code.value; + codeMatches = FilterOptions._filter(this.options.textFilter, codeText); + } else { + codeMatches = undefined; + } if (sourceMatches || codeMatches || lineMatches.some(lineMatch => lineMatch.length > 0)) { return { visibility: true, data: { type: FilterDataType.Marker, lineMatches, sourceMatches: sourceMatches || [], codeMatches: codeMatches || [] } }; @@ -760,3 +839,10 @@ export class ResourceDragAndDrop implements ITreeDragAndDrop { drop(data: IDragAndDropData, targetElement: TreeElement, targetIndex: number, originalEvent: DragEvent): void { } } + +registerThemingParticipant((theme, collector) => { + const linkFg = theme.getColor(textLinkForeground); + if (linkFg) { + collector.addRule(`.markers-panel .markers-panel-container .tree-container .monaco-tl-contents .details-container a.code-link span:hover { color: ${linkFg}; }`); + } +}); diff --git a/src/vs/workbench/contrib/markers/browser/media/markers.css b/src/vs/workbench/contrib/markers/browser/media/markers.css index a7d18b76c71..f49bead0141 100644 --- a/src/vs/workbench/contrib/markers/browser/media/markers.css +++ b/src/vs/workbench/contrib/markers/browser/media/markers.css @@ -133,6 +133,14 @@ display: flex; } +.markers-panel .markers-panel-container .tree-container .monaco-tl-contents .details-container a.code-link { + color: inherit; +} +.markers-panel .markers-panel-container .tree-container .monaco-tl-contents .details-container a.code-link .monaco-highlighted-label { + text-decoration: underline; + text-underline-position: under; +} + .markers-panel .markers-panel-container .tree-container .monaco-tl-contents .marker-code:before { content: '('; } From 5d0bd4987cede428d7572ddaa25ebde5d9c7cbf1 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Fri, 24 Jan 2020 10:57:09 +0100 Subject: [PATCH 071/801] :lipstick: text files --- .../contrib/files/browser/fileActions.ts | 3 -- .../textfile/browser/textFileService.ts | 42 ++++++++++--------- .../services/textfile/common/textfiles.ts | 16 +++---- 3 files changed, 31 insertions(+), 30 deletions(-) diff --git a/src/vs/workbench/contrib/files/browser/fileActions.ts b/src/vs/workbench/contrib/files/browser/fileActions.ts index 0bf2c1e627a..b7034ea70a5 100644 --- a/src/vs/workbench/contrib/files/browser/fileActions.ts +++ b/src/vs/workbench/contrib/files/browser/fileActions.ts @@ -232,14 +232,11 @@ async function deleteFiles(workingCopyService: IWorkingCopyService, textFileServ }); } - - // Check for confirmation checkbox if (confirmation.confirmed && confirmation.checkboxChecked === true) { await configurationService.updateValue(CONFIRM_DELETE_SETTING_KEY, false, ConfigurationTarget.USER); } - // Check for confirmation if (!confirmation.confirmed) { return; diff --git a/src/vs/workbench/services/textfile/browser/textFileService.ts b/src/vs/workbench/services/textfile/browser/textFileService.ts index 355f94cb60d..3c6133cdb6b 100644 --- a/src/vs/workbench/services/textfile/browser/textFileService.ts +++ b/src/vs/workbench/services/textfile/browser/textFileService.ts @@ -87,7 +87,7 @@ export abstract class AbstractTextFileService extends Disposable implements ITex this.lifecycleService.onShutdown(this.dispose, this); } - //#region text file IO primitives (read, create, move, delete, update) + //#region text file read / write async read(resource: URI, options?: IReadTextFileOptions): Promise { const content = await this.fileService.readFile(resource, options); @@ -143,6 +143,14 @@ export abstract class AbstractTextFileService extends Disposable implements ITex } } + async write(resource: URI, value: string | ITextSnapshot, options?: IWriteTextFileOptions): Promise { + return this.fileService.writeFile(resource, toBufferOrReadable(value), options); + } + + //#endregion + + //#region text file IO primitives (create, move, copy, delete) + async create(resource: URI, value?: string | ITextSnapshot, options?: ICreateFileOptions): Promise { // before event @@ -169,24 +177,6 @@ export abstract class AbstractTextFileService extends Disposable implements ITex return this.fileService.createFile(resource, toBufferOrReadable(value), options); } - async write(resource: URI, value: string | ITextSnapshot, options?: IWriteTextFileOptions): Promise { - return this.fileService.writeFile(resource, toBufferOrReadable(value), options); - } - - async delete(resource: URI, options?: { useTrash?: boolean, recursive?: boolean }): Promise { - - // before event - await this._onWillRunOperation.fireAsync({ operation: FileOperation.DELETE, target: resource }, CancellationToken.None); - - const dirtyFiles = this.getDirtyFileModels().map(dirtyFileModel => dirtyFileModel.resource).filter(dirty => isEqualOrParent(dirty, resource)); - await this.doRevertFiles(dirtyFiles, { soft: true }); - - await this.fileService.del(resource, options); - - // after event - this._onDidRunOperation.fire(new FileOperationDidRunEvent(FileOperation.DELETE, resource)); - } - async move(source: URI, target: URI, overwrite?: boolean): Promise { return this.moveOrCopy(source, target, true, overwrite); } @@ -287,6 +277,20 @@ export abstract class AbstractTextFileService extends Disposable implements ITex return stat; } + async delete(resource: URI, options?: { useTrash?: boolean, recursive?: boolean }): Promise { + + // before event + await this._onWillRunOperation.fireAsync({ operation: FileOperation.DELETE, target: resource }, CancellationToken.None); + + const dirtyFiles = this.getDirtyFileModels().map(dirtyFileModel => dirtyFileModel.resource).filter(dirty => isEqualOrParent(dirty, resource)); + await this.doRevertFiles(dirtyFiles, { soft: true }); + + await this.fileService.del(resource, options); + + // after event + this._onDidRunOperation.fire(new FileOperationDidRunEvent(FileOperation.DELETE, resource)); + } + //#endregion //#region save diff --git a/src/vs/workbench/services/textfile/common/textfiles.ts b/src/vs/workbench/services/textfile/common/textfiles.ts index 07d83f290c5..373a388a77b 100644 --- a/src/vs/workbench/services/textfile/common/textfiles.ts +++ b/src/vs/workbench/services/textfile/common/textfiles.ts @@ -84,12 +84,6 @@ export interface ITextFileService extends IDisposable { */ revert(resource: URI, options?: IRevertOptions): Promise; - /** - * Create a file. If the file exists it will be overwritten with the contents if - * the options enable to overwrite. - */ - create(resource: URI, contents?: string | ITextSnapshot, options?: { overwrite?: boolean }): Promise; - /** * Read the contents of a file identified by the resource. */ @@ -106,9 +100,10 @@ export interface ITextFileService extends IDisposable { write(resource: URI, value: string | ITextSnapshot, options?: IWriteTextFileOptions): Promise; /** - * Delete a file. If the file is dirty, it will get reverted and then deleted from disk. + * Create a file. If the file exists it will be overwritten with the contents if + * the options enable to overwrite. */ - delete(resource: URI, options?: { useTrash?: boolean, recursive?: boolean }): Promise; + create(resource: URI, contents?: string | ITextSnapshot, options?: { overwrite?: boolean }): Promise; /** * Move a file. If the file is dirty, its contents will be preserved and restored. @@ -119,6 +114,11 @@ export interface ITextFileService extends IDisposable { * Copy a file. If the file is dirty, its contents will be preserved and restored. */ copy(source: URI, target: URI, overwrite?: boolean): Promise; + + /** + * Delete a file. If the file is dirty, it will get reverted and then deleted from disk. + */ + delete(resource: URI, options?: { useTrash?: boolean, recursive?: boolean }): Promise; } export interface FileOperationWillRunEvent extends IWaitUntil { From 4ba20708184180ff2a99c3e6982703da140699b1 Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Fri, 24 Jan 2020 11:17:54 +0100 Subject: [PATCH 072/801] Ensure that when there is a Windows drive letter as the whole path the simple file picker keeps the slash Part of https://github.com/microsoft/vscode-remote-release/issues/1596 --- .../workbench/services/dialogs/browser/simpleFileDialog.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/services/dialogs/browser/simpleFileDialog.ts b/src/vs/workbench/services/dialogs/browser/simpleFileDialog.ts index d8161e31d6e..04deebc2634 100644 --- a/src/vs/workbench/services/dialogs/browser/simpleFileDialog.ts +++ b/src/vs/workbench/services/dialogs/browser/simpleFileDialog.ts @@ -290,7 +290,12 @@ export class SimpleFileDialog { function doResolve(dialog: SimpleFileDialog, uri: URI | undefined) { if (uri) { - uri = resources.removeTrailingPathSeparator(uri); + // If the uri is only the drive letter, make sure it has the trailing path separator + if (/^[a-zA-Z]:(\/$|\\$|$)/.test(uri.fsPath)) { + uri = resources.addTrailingPathSeparator(uri); + } else { + uri = resources.removeTrailingPathSeparator(uri); + } } resolve(uri); dialog.contextKey.set(false); From 10cbb8533fd5a842409d328d95badd74181bedd3 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Fri, 24 Jan 2020 11:37:10 +0100 Subject: [PATCH 073/801] Explorer: copy() of dirty file reverts the source if dirty (fix #89217) --- .../textfile/browser/textFileService.ts | 15 +++---- .../textfile/test/textFileService.test.ts | 41 ++++++++++++++++--- .../workbench/test/workbenchTestServices.ts | 4 +- 3 files changed, 45 insertions(+), 15 deletions(-) diff --git a/src/vs/workbench/services/textfile/browser/textFileService.ts b/src/vs/workbench/services/textfile/browser/textFileService.ts index 3c6133cdb6b..1bc02959ca3 100644 --- a/src/vs/workbench/services/textfile/browser/textFileService.ts +++ b/src/vs/workbench/services/textfile/browser/textFileService.ts @@ -192,12 +192,12 @@ export abstract class AbstractTextFileService extends Disposable implements ITex // find all models that related to either source or target (can be many if resource is a folder) const sourceModels: ITextFileEditorModel[] = []; - const conflictingModels: ITextFileEditorModel[] = []; + const targetModels: ITextFileEditorModel[] = []; for (const model of this.getFileModels()) { const resource = model.resource; if (isEqualOrParent(resource, target, false /* do not ignorecase, see https://github.com/Microsoft/vscode/issues/56384 */)) { - conflictingModels.push(model); + targetModels.push(model); } if (isEqualOrParent(resource, source)) { @@ -232,10 +232,11 @@ export abstract class AbstractTextFileService extends Disposable implements ITex modelsToRestore.push(modelToRestore); } - // in order to move and copy, we need to soft revert all dirty models, - // both from the source as well as the target if any - const dirtyModels = [...sourceModels, ...conflictingModels].filter(model => model.isDirty()); - await this.doRevertFiles(dirtyModels.map(dirtyModel => dirtyModel.resource), { soft: true }); + // handle dirty models depending on the operation: + // - move: revert both source and target (if any) + // - copy: revert target (if any) + const dirtyModelsToRevert = (move ? [...sourceModels, ...targetModels] : [...targetModels]).filter(model => model.isDirty()); + await this.doRevertFiles(dirtyModelsToRevert.map(dirtyModel => dirtyModel.resource), { soft: true }); // now we can rename the source to target via file operation let stat: IFileStatWithMetadata; @@ -248,7 +249,7 @@ export abstract class AbstractTextFileService extends Disposable implements ITex } catch (error) { // in case of any error, ensure to set dirty flag back - dirtyModels.forEach(dirtyModel => dirtyModel.makeDirty()); + dirtyModelsToRevert.forEach(dirtyModel => dirtyModel.makeDirty()); throw error; } diff --git a/src/vs/workbench/services/textfile/test/textFileService.test.ts b/src/vs/workbench/services/textfile/test/textFileService.test.ts index c90fd6995ff..16f4b6e7f4f 100644 --- a/src/vs/workbench/services/textfile/test/textFileService.test.ts +++ b/src/vs/workbench/services/textfile/test/textFileService.test.ts @@ -5,7 +5,7 @@ import * as assert from 'assert'; import { URI } from 'vs/base/common/uri'; import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle'; -import { workbenchInstantiationService, TestLifecycleService, TestTextFileService, TestContextService, TestFileService, TestElectronService, TestFilesConfigurationService, TestFileDialogService } from 'vs/workbench/test/workbenchTestServices'; +import { workbenchInstantiationService, TestLifecycleService, TestContextService, TestFileService, TestElectronService, TestFilesConfigurationService, TestFileDialogService, TestTextFileService } 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'; @@ -126,6 +126,19 @@ suite('Files - TextFileService', () => { assert.ok(!accessor.textFileService.isDirty(model.resource)); }); + test('create', async function () { + model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file.txt'), 'utf8', undefined); + (accessor.textFileService.files).add(model.resource, model); + + await model.load(); + model!.textEditorModel!.setValue('foo'); + assert.ok(accessor.textFileService.isDirty(model.resource)); + + + await accessor.textFileService.create(model.resource, 'Foo'); + assert.ok(!accessor.textFileService.isDirty(model.resource)); + }); + test('delete - dirty file', async function () { model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file.txt'), 'utf8', undefined); (accessor.textFileService.files).add(model.resource, model); @@ -139,14 +152,22 @@ suite('Files - TextFileService', () => { }); test('move - dirty file', async function () { - await testMove(toResource.call(this, '/path/file.txt'), toResource.call(this, '/path/file_target.txt')); + await testMoveOrCopy(toResource.call(this, '/path/file.txt'), toResource.call(this, '/path/file_target.txt'), true); }); test('move - dirty file (target exists and is dirty)', async function () { - await testMove(toResource.call(this, '/path/file.txt'), toResource.call(this, '/path/file_target.txt'), true); + await testMoveOrCopy(toResource.call(this, '/path/file.txt'), toResource.call(this, '/path/file_target.txt'), true, true); }); - async function testMove(source: URI, target: URI, targetDirty?: boolean): Promise { + test('copy - dirty file', async function () { + await testMoveOrCopy(toResource.call(this, '/path/file.txt'), toResource.call(this, '/path/file_target.txt'), false); + }); + + test('copy - dirty file (target exists and is dirty)', async function () { + await testMoveOrCopy(toResource.call(this, '/path/file.txt'), toResource.call(this, '/path/file_target.txt'), false, true); + }); + + async function testMoveOrCopy(source: URI, target: URI, move: boolean, targetDirty?: boolean): Promise { let sourceModel: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, source, 'utf8', undefined); let targetModel: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, target, 'utf8', undefined); (accessor.textFileService.files).add(sourceModel.resource, sourceModel); @@ -162,11 +183,19 @@ suite('Files - TextFileService', () => { assert.ok(accessor.textFileService.isDirty(targetModel.resource)); } - await accessor.textFileService.move(sourceModel.resource, targetModel.resource, true); + if (move) { + await accessor.textFileService.move(sourceModel.resource, targetModel.resource, true); + } else { + await accessor.textFileService.copy(sourceModel.resource, targetModel.resource, true); + } assert.equal(targetModel.textEditorModel!.getValue(), 'foo'); - assert.ok(!accessor.textFileService.isDirty(sourceModel.resource)); + if (move) { + assert.ok(!accessor.textFileService.isDirty(sourceModel.resource)); + } else { + assert.ok(accessor.textFileService.isDirty(sourceModel.resource)); + } assert.ok(accessor.textFileService.isDirty(targetModel.resource)); sourceModel.dispose(); diff --git a/src/vs/workbench/test/workbenchTestServices.ts b/src/vs/workbench/test/workbenchTestServices.ts index bab9fb6d2c9..06ba8b40e29 100644 --- a/src/vs/workbench/test/workbenchTestServices.ts +++ b/src/vs/workbench/test/workbenchTestServices.ts @@ -1111,11 +1111,11 @@ export class TestFileService implements IFileService { } copy(_source: URI, _target: URI, _overwrite?: boolean): Promise { - throw new Error('not implemented'); + return Promise.resolve(null!); } createFile(_resource: URI, _content?: VSBuffer | VSBufferReadable, _options?: ICreateFileOptions): Promise { - throw new Error('not implemented'); + return Promise.resolve(null!); } createFolder(_resource: URI): Promise { From 4324cadcd048a4d60aeaf13041313c6c2dcf6cfb Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Fri, 24 Jan 2020 11:36:30 +0100 Subject: [PATCH 074/801] Revert "Ensure that when there is a Windows drive letter as the whole path the simple file picker keeps the slash" This reverts commit 4ba20708184180ff2a99c3e6982703da140699b1 in favor of a better solution to removeTrailingPathSeparator. --- .../workbench/services/dialogs/browser/simpleFileDialog.ts | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/vs/workbench/services/dialogs/browser/simpleFileDialog.ts b/src/vs/workbench/services/dialogs/browser/simpleFileDialog.ts index 04deebc2634..d8161e31d6e 100644 --- a/src/vs/workbench/services/dialogs/browser/simpleFileDialog.ts +++ b/src/vs/workbench/services/dialogs/browser/simpleFileDialog.ts @@ -290,12 +290,7 @@ export class SimpleFileDialog { function doResolve(dialog: SimpleFileDialog, uri: URI | undefined) { if (uri) { - // If the uri is only the drive letter, make sure it has the trailing path separator - if (/^[a-zA-Z]:(\/$|\\$|$)/.test(uri.fsPath)) { - uri = resources.addTrailingPathSeparator(uri); - } else { - uri = resources.removeTrailingPathSeparator(uri); - } + uri = resources.removeTrailingPathSeparator(uri); } resolve(uri); dialog.contextKey.set(false); From 27713adad54d7c090346e3478a76c90abb74d139 Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Fri, 24 Jan 2020 11:59:33 +0100 Subject: [PATCH 075/801] Fix ... when simple file picker is at drive root Fixes https://github.com/microsoft/vscode/issues/89209 --- .../workbench/services/dialogs/browser/simpleFileDialog.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/services/dialogs/browser/simpleFileDialog.ts b/src/vs/workbench/services/dialogs/browser/simpleFileDialog.ts index d8161e31d6e..e9e8c8fff36 100644 --- a/src/vs/workbench/services/dialogs/browser/simpleFileDialog.ts +++ b/src/vs/workbench/services/dialogs/browser/simpleFileDialog.ts @@ -857,8 +857,10 @@ export class SimpleFileDialog { } private createBackItem(currFolder: URI): FileQuickPickItem | null { - const parentFolder = resources.dirname(currFolder); - if (!resources.isEqual(currFolder, parentFolder, true)) { + const fileRepresentationCurr = this.currentFolder.with({ scheme: Schemas.file }); + const fileRepresentationParent = resources.dirname(fileRepresentationCurr); + if (!resources.isEqual(fileRepresentationCurr, fileRepresentationParent, true)) { + const parentFolder = resources.dirname(currFolder); return { label: '..', uri: resources.addTrailingPathSeparator(parentFolder, this.separator), isFolder: true }; } return null; From bfee2e5cdc6577a9600357e5e447facec553c210 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Fri, 24 Jan 2020 12:01:15 +0100 Subject: [PATCH 076/801] textfiles - add comment --- src/vs/workbench/services/textfile/browser/textFileService.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/vs/workbench/services/textfile/browser/textFileService.ts b/src/vs/workbench/services/textfile/browser/textFileService.ts index 1bc02959ca3..452b0d2b64b 100644 --- a/src/vs/workbench/services/textfile/browser/textFileService.ts +++ b/src/vs/workbench/services/textfile/browser/textFileService.ts @@ -283,9 +283,13 @@ export abstract class AbstractTextFileService extends Disposable implements ITex // before event await this._onWillRunOperation.fireAsync({ operation: FileOperation.DELETE, target: resource }, CancellationToken.None); + // Check for any existing dirty file model for the resource + // and do a soft revert before deleting to be able to close + // any opened editor with these files const dirtyFiles = this.getDirtyFileModels().map(dirtyFileModel => dirtyFileModel.resource).filter(dirty => isEqualOrParent(dirty, resource)); await this.doRevertFiles(dirtyFiles, { soft: true }); + // Now actually delete from disk await this.fileService.del(resource, options); // after event From 28bf0b1f56dc4530331edb2e6e8bbc3df192372e Mon Sep 17 00:00:00 2001 From: Konstantin Solomatov Date: Fri, 24 Jan 2020 03:02:19 -0800 Subject: [PATCH 077/801] Use leading flag set to true in debouncing events from extension trees (#88051) * Use leading flag set to true in debouncing events from extension trees * - remove the flag for debounce in extHostTreeView - refactor test extHostTreeView's test to test prod behavior - fix a small bug in debounce * Minor cleanup --- src/vs/base/common/event.ts | 1 + src/vs/base/test/common/event.test.ts | 14 + .../workbench/api/common/extHostTreeViews.ts | 2 +- .../api/extHostTreeViews.test.ts | 252 ++++++++++-------- 4 files changed, 158 insertions(+), 111 deletions(-) diff --git a/src/vs/base/common/event.ts b/src/vs/base/common/event.ts index f0492026239..49b7b449862 100644 --- a/src/vs/base/common/event.ts +++ b/src/vs/base/common/event.ts @@ -148,6 +148,7 @@ export namespace Event { if (leading && !handle) { emitter.fire(output); + output = undefined; } clearTimeout(handle); diff --git a/src/vs/base/test/common/event.test.ts b/src/vs/base/test/common/event.test.ts index 8c9d65ba20b..bb589ca7633 100644 --- a/src/vs/base/test/common/event.test.ts +++ b/src/vs/base/test/common/event.test.ts @@ -237,6 +237,20 @@ suite('Event', function () { assert.equal(calls, 2); }); + test('Debounce Event - leading reset', async function () { + const emitter = new Emitter(); + let debounced = Event.debounce(emitter.event, (l, e) => l ? l + 1 : 1, 0, /*leading=*/true); + + let calls: number[] = []; + debounced((e) => calls.push(e)); + + emitter.fire(1); + emitter.fire(1); + + await timeout(1); + assert.deepEqual(calls, [1, 1]); + }); + test('Emitter - In Order Delivery', function () { const a = new Emitter(); const listener2Events: string[] = []; diff --git a/src/vs/workbench/api/common/extHostTreeViews.ts b/src/vs/workbench/api/common/extHostTreeViews.ts index a17ec4889ca..f3b43d63036 100644 --- a/src/vs/workbench/api/common/extHostTreeViews.ts +++ b/src/vs/workbench/api/common/extHostTreeViews.ts @@ -237,7 +237,7 @@ class ExtHostTreeView extends Disposable { result.message = true; } return result; - }, 200)(({ message, elements }) => { + }, 200, true)(({ message, elements }) => { if (elements.length) { this.refreshQueue = this.refreshQueue.then(() => { const _promiseCallback = promiseCallback; diff --git a/src/vs/workbench/test/electron-browser/api/extHostTreeViews.test.ts b/src/vs/workbench/test/electron-browser/api/extHostTreeViews.test.ts index fb419d8e700..236d753dc54 100644 --- a/src/vs/workbench/test/electron-browser/api/extHostTreeViews.test.ts +++ b/src/vs/workbench/test/electron-browser/api/extHostTreeViews.test.ts @@ -18,6 +18,7 @@ import { mock } from 'vs/workbench/test/electron-browser/api/mock'; import { TreeItemCollapsibleState, ITreeItem } from 'vs/workbench/common/views'; import { NullLogService } from 'vs/platform/log/common/log'; import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; +import type { IDisposable } from 'vs/base/common/lifecycle'; suite('ExtHostTreeView', function () { @@ -29,7 +30,9 @@ suite('ExtHostTreeView', function () { } $refresh(viewId: string, itemsToRefresh: { [treeItemHandle: string]: ITreeItem }): Promise { - return Promise.resolve(null).then(() => this.onRefresh.fire(itemsToRefresh)); + return Promise.resolve(null).then(() => { + this.onRefresh.fire(itemsToRefresh); + }); } $reveal(): Promise { @@ -243,46 +246,62 @@ suite('ExtHostTreeView', function () { onDidChangeTreeNode.fire(getNode('bb')); }); - test('refresh parent and child node trigger refresh only on parent - scenario 1', function (done) { - target.onRefresh.event(actuals => { - assert.deepEqual(['0/0:b', '0/0:a/0:aa'], Object.keys(actuals)); - assert.deepEqual(removeUnsetKeys(actuals['0/0:b']), { - handle: '0/0:b', - label: { label: 'b' }, - collapsibleState: TreeItemCollapsibleState.Collapsed + async function runWithEventMerging(action: (resolve: () => void) => void) { + await new Promise((resolve) => { + let subscription: IDisposable | undefined = undefined; + subscription = target.onRefresh.event(() => { + subscription!.dispose(); + resolve(); }); - assert.deepEqual(removeUnsetKeys(actuals['0/0:a/0:aa']), { - handle: '0/0:a/0:aa', - parentHandle: '0/0:a', - label: { label: 'aa' }, - collapsibleState: TreeItemCollapsibleState.None - }); - done(); + onDidChangeTreeNode.fire(getNode('b')); + }); + await new Promise(action); + } + + test('refresh parent and child node trigger refresh only on parent - scenario 1', async () => { + return runWithEventMerging((resolve) => { + target.onRefresh.event(actuals => { + assert.deepEqual(['0/0:b', '0/0:a/0:aa'], Object.keys(actuals)); + assert.deepEqual(removeUnsetKeys(actuals['0/0:b']), { + handle: '0/0:b', + label: { label: 'b' }, + collapsibleState: TreeItemCollapsibleState.Collapsed + }); + assert.deepEqual(removeUnsetKeys(actuals['0/0:a/0:aa']), { + handle: '0/0:a/0:aa', + parentHandle: '0/0:a', + label: { label: 'aa' }, + collapsibleState: TreeItemCollapsibleState.None + }); + resolve(); + }); + onDidChangeTreeNode.fire(getNode('b')); + onDidChangeTreeNode.fire(getNode('aa')); + onDidChangeTreeNode.fire(getNode('bb')); }); - onDidChangeTreeNode.fire(getNode('b')); - onDidChangeTreeNode.fire(getNode('aa')); - onDidChangeTreeNode.fire(getNode('bb')); }); - test('refresh parent and child node trigger refresh only on parent - scenario 2', function (done) { - target.onRefresh.event(actuals => { - assert.deepEqual(['0/0:a/0:aa', '0/0:b'], Object.keys(actuals)); - assert.deepEqual(removeUnsetKeys(actuals['0/0:b']), { - handle: '0/0:b', - label: { label: 'b' }, - collapsibleState: TreeItemCollapsibleState.Collapsed + test('refresh parent and child node trigger refresh only on parent - scenario 2', async () => { + return runWithEventMerging((resolve) => { + target.onRefresh.event(actuals => { + assert.deepEqual(['0/0:a/0:aa', '0/0:b'], Object.keys(actuals)); + assert.deepEqual(removeUnsetKeys(actuals['0/0:b']), { + handle: '0/0:b', + label: { label: 'b' }, + collapsibleState: TreeItemCollapsibleState.Collapsed + }); + assert.deepEqual(removeUnsetKeys(actuals['0/0:a/0:aa']), { + handle: '0/0:a/0:aa', + parentHandle: '0/0:a', + label: { label: 'aa' }, + collapsibleState: TreeItemCollapsibleState.None + }); + resolve(); }); - assert.deepEqual(removeUnsetKeys(actuals['0/0:a/0:aa']), { - handle: '0/0:a/0:aa', - parentHandle: '0/0:a', - label: { label: 'aa' }, - collapsibleState: TreeItemCollapsibleState.None - }); - done(); + onDidChangeTreeNode.fire(getNode('bb')); + onDidChangeTreeNode.fire(getNode('aa')); + onDidChangeTreeNode.fire(getNode('b')); }); - onDidChangeTreeNode.fire(getNode('bb')); - onDidChangeTreeNode.fire(getNode('aa')); - onDidChangeTreeNode.fire(getNode('b')); }); test('refresh an element for label change', function (done) { @@ -299,63 +318,73 @@ suite('ExtHostTreeView', function () { onDidChangeTreeNode.fire(getNode('a')); }); - test('refresh calls are throttled on roots', function (done) { - target.onRefresh.event(actuals => { - assert.equal(undefined, actuals); - done(); + test('refresh calls are throttled on roots', () => { + return runWithEventMerging((resolve) => { + target.onRefresh.event(actuals => { + assert.equal(undefined, actuals); + resolve(); + }); + onDidChangeTreeNode.fire(undefined); + onDidChangeTreeNode.fire(undefined); + onDidChangeTreeNode.fire(undefined); + onDidChangeTreeNode.fire(undefined); }); - onDidChangeTreeNode.fire(undefined); - onDidChangeTreeNode.fire(undefined); - onDidChangeTreeNode.fire(undefined); - onDidChangeTreeNode.fire(undefined); }); - test('refresh calls are throttled on elements', function (done) { - target.onRefresh.event(actuals => { - assert.deepEqual(['0/0:a', '0/0:b'], Object.keys(actuals)); - done(); - }); + test('refresh calls are throttled on elements', () => { + return runWithEventMerging((resolve) => { + target.onRefresh.event(actuals => { + assert.deepEqual(['0/0:a', '0/0:b'], Object.keys(actuals)); + resolve(); + }); - onDidChangeTreeNode.fire(getNode('a')); - onDidChangeTreeNode.fire(getNode('b')); - onDidChangeTreeNode.fire(getNode('b')); - onDidChangeTreeNode.fire(getNode('a')); + onDidChangeTreeNode.fire(getNode('a')); + onDidChangeTreeNode.fire(getNode('b')); + onDidChangeTreeNode.fire(getNode('b')); + onDidChangeTreeNode.fire(getNode('a')); + }); }); - test('refresh calls are throttled on unknown elements', function (done) { - target.onRefresh.event(actuals => { - assert.deepEqual(['0/0:a', '0/0:b'], Object.keys(actuals)); - done(); - }); + test('refresh calls are throttled on unknown elements', () => { + return runWithEventMerging((resolve) => { + target.onRefresh.event(actuals => { + assert.deepEqual(['0/0:a', '0/0:b'], Object.keys(actuals)); + resolve(); + }); - onDidChangeTreeNode.fire(getNode('a')); - onDidChangeTreeNode.fire(getNode('b')); - onDidChangeTreeNode.fire(getNode('g')); - onDidChangeTreeNode.fire(getNode('a')); + onDidChangeTreeNode.fire(getNode('a')); + onDidChangeTreeNode.fire(getNode('b')); + onDidChangeTreeNode.fire(getNode('g')); + onDidChangeTreeNode.fire(getNode('a')); + }); }); - test('refresh calls are throttled on unknown elements and root', function (done) { - target.onRefresh.event(actuals => { - assert.equal(undefined, actuals); - done(); - }); + test('refresh calls are throttled on unknown elements and root', () => { + return runWithEventMerging((resolve) => { + target.onRefresh.event(actuals => { + assert.equal(undefined, actuals); + resolve(); + }); - onDidChangeTreeNode.fire(getNode('a')); - onDidChangeTreeNode.fire(getNode('b')); - onDidChangeTreeNode.fire(getNode('g')); - onDidChangeTreeNode.fire(undefined); + onDidChangeTreeNode.fire(getNode('a')); + onDidChangeTreeNode.fire(getNode('b')); + onDidChangeTreeNode.fire(getNode('g')); + onDidChangeTreeNode.fire(undefined); + }); }); - test('refresh calls are throttled on elements and root', function (done) { - target.onRefresh.event(actuals => { - assert.equal(undefined, actuals); - done(); - }); + test('refresh calls are throttled on elements and root', () => { + return runWithEventMerging((resolve) => { + target.onRefresh.event(actuals => { + assert.equal(undefined, actuals); + resolve(); + }); - onDidChangeTreeNode.fire(getNode('a')); - onDidChangeTreeNode.fire(getNode('b')); - onDidChangeTreeNode.fire(undefined); - onDidChangeTreeNode.fire(getNode('a')); + onDidChangeTreeNode.fire(getNode('a')); + onDidChangeTreeNode.fire(getNode('b')); + onDidChangeTreeNode.fire(undefined); + onDidChangeTreeNode.fire(getNode('a')); + }); }); test('generate unique handles from labels by escaping them', (done) => { @@ -552,37 +581,40 @@ suite('ExtHostTreeView', function () { const treeView = testObject.createTreeView('treeDataProvider', { treeDataProvider: aCompleteNodeTreeDataProvider() }, { enableProposedApi: true } as IExtensionDescription); return loadCompleteTree('treeDataProvider') .then(() => { - tree = { - 'a': { - 'aa': {}, - 'ac': {} - }, - 'b': { - 'ba': {}, - 'bb': {} - } - }; - onDidChangeTreeNode.fire(getNode('a')); - tree = { - 'a': { - 'aa': {}, - 'ac': {} - }, - 'b': { - 'ba': {}, - 'bc': {} - } - }; - onDidChangeTreeNode.fire(getNode('b')); - - return treeView.reveal({ key: 'bc' }) - .then(() => { - assert.ok(revealTarget.calledOnce); - assert.deepEqual('treeDataProvider', revealTarget.args[0][0]); - assert.deepEqual({ handle: '0/0:b/0:bc', label: { label: 'bc' }, collapsibleState: TreeItemCollapsibleState.None, parentHandle: '0/0:b' }, removeUnsetKeys(revealTarget.args[0][1])); - assert.deepEqual([{ handle: '0/0:b', label: { label: 'b' }, collapsibleState: TreeItemCollapsibleState.Collapsed }], (>revealTarget.args[0][2]).map(arg => removeUnsetKeys(arg))); - assert.deepEqual({ select: true, focus: false, expand: false }, revealTarget.args[0][3]); - }); + runWithEventMerging((resolve) => { + tree = { + 'a': { + 'aa': {}, + 'ac': {} + }, + 'b': { + 'ba': {}, + 'bb': {} + } + }; + onDidChangeTreeNode.fire(getNode('a')); + tree = { + 'a': { + 'aa': {}, + 'ac': {} + }, + 'b': { + 'ba': {}, + 'bc': {} + } + }; + onDidChangeTreeNode.fire(getNode('b')); + resolve(); + }).then(() => { + return treeView.reveal({ key: 'bc' }) + .then(() => { + assert.ok(revealTarget.calledOnce); + assert.deepEqual('treeDataProvider', revealTarget.args[0][0]); + assert.deepEqual({ handle: '0/0:b/0:bc', label: { label: 'bc' }, collapsibleState: TreeItemCollapsibleState.None, parentHandle: '0/0:b' }, removeUnsetKeys(revealTarget.args[0][1])); + assert.deepEqual([{ handle: '0/0:b', label: { label: 'b' }, collapsibleState: TreeItemCollapsibleState.Collapsed }], (>revealTarget.args[0][2]).map(arg => removeUnsetKeys(arg))); + assert.deepEqual({ select: true, focus: false, expand: false }, revealTarget.args[0][3]); + }); + }); }); }); From 61141737dd1a2d2d9e175451830ca6cc7b5f4c0f Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Fri, 24 Jan 2020 12:21:43 +0100 Subject: [PATCH 078/801] Fix #88324 --- .../userDataSync/common/extensionsSync.ts | 30 ++-- .../userDataSync/common/globalStateSync.ts | 28 ++-- .../userDataSync/common/keybindingsSync.ts | 129 ++++++++++-------- .../userDataSync/common/settingsSync.ts | 89 ++++++------ .../userDataSync/common/userDataAutoSync.ts | 12 +- .../userDataSync/common/userDataSync.ts | 9 +- .../userDataSync/common/userDataSyncIpc.ts | 10 +- .../common/userDataSyncService.ts | 44 +++++- .../userDataSync/browser/userDataSync.ts | 108 +++++---------- .../electron-browser/settingsSyncService.ts | 19 ++- .../electron-browser/userDataSyncService.ts | 17 ++- 11 files changed, 283 insertions(+), 212 deletions(-) diff --git a/src/vs/platform/userDataSync/common/extensionsSync.ts b/src/vs/platform/userDataSync/common/extensionsSync.ts index d00d62def18..d02dd14a69a 100644 --- a/src/vs/platform/userDataSync/common/extensionsSync.ts +++ b/src/vs/platform/userDataSync/common/extensionsSync.ts @@ -25,7 +25,7 @@ interface ISyncPreviewResult { readonly removed: IExtensionIdentifier[]; readonly updated: ISyncExtension[]; readonly remote: ISyncExtension[] | null; - readonly remoteUserData: IUserData | null; + readonly remoteUserData: IUserData; readonly skippedExtensions: ISyncExtension[]; } @@ -122,7 +122,8 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse const localExtensions = await this.getLocalExtensions(); const { added, removed, updated, remote } = merge(localExtensions, null, null, [], this.getIgnoredExtensions()); - await this.apply({ added, removed, updated, remote, remoteUserData: null, skippedExtensions: [] }); + const remoteUserData = await this.getRemoteUserData(); + await this.apply({ added, removed, updated, remote, remoteUserData, skippedExtensions: [] }, true); this.logService.info('Extensions: Finished pushing extensions.'); } finally { @@ -131,18 +132,18 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse } - async sync(): Promise { + async sync(): Promise { if (!this.configurationService.getValue('sync.enableExtensions')) { this.logService.trace('Extensions: Skipping synchronizing extensions as it is disabled.'); - return false; + return; } if (!this.extensionGalleryService.isEnabled()) { this.logService.trace('Extensions: Skipping synchronizing extensions as gallery is disabled.'); - return false; + return; } if (this.status !== SyncStatus.Idle) { this.logService.trace('Extensions: Skipping synchronizing extensions as it is running already.'); - return false; + return; } this.logService.trace('Extensions: Started synchronizing extensions...'); @@ -163,10 +164,17 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse this.logService.trace('Extensions: Finished synchronizing extensions.'); this.setStatus(SyncStatus.Idle); - return true; } - stop(): void { } + async stop(): Promise { } + + async restart(): Promise { + throw new Error('Extensions: Conflicts should not occur'); + } + + resolveConflicts(content: string): Promise { + throw new Error('Extensions: Conflicts should not occur'); + } async hasPreviouslySynced(): Promise { const lastSyncData = await this.getLastSyncUserData(); @@ -241,7 +249,7 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse return this.configurationService.getValue('sync.ignoredExtensions') || []; } - private async apply({ added, removed, updated, remote, remoteUserData, skippedExtensions }: ISyncPreviewResult): Promise { + private async apply({ added, removed, updated, remote, remoteUserData, skippedExtensions }: ISyncPreviewResult, forcePush?: boolean): Promise { if (!added.length && !removed.length && !updated.length && !remote) { this.logService.trace('Extensions: No changes found during synchronizing extensions.'); } @@ -254,10 +262,10 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse if (remote) { // update remote this.logService.info('Extensions: Updating remote extensions...'); - remoteUserData = await this.writeToRemote(remote, remoteUserData ? remoteUserData.ref : null); + remoteUserData = await this.writeToRemote(remote, forcePush ? null : remoteUserData.ref); } - if (remoteUserData?.content) { + if (remoteUserData.content) { // update last sync this.logService.info('Extensions: Updating last synchronised extensions...'); await this.updateLastSyncValue({ ...remoteUserData, skippedExtensions }); diff --git a/src/vs/platform/userDataSync/common/globalStateSync.ts b/src/vs/platform/userDataSync/common/globalStateSync.ts index cca41085228..f3275f87b1b 100644 --- a/src/vs/platform/userDataSync/common/globalStateSync.ts +++ b/src/vs/platform/userDataSync/common/globalStateSync.ts @@ -22,7 +22,7 @@ const argvProperties: string[] = ['locale']; interface ISyncPreviewResult { readonly local: IGlobalState | undefined; readonly remote: IGlobalState | undefined; - readonly remoteUserData: IUserData | null; + readonly remoteUserData: IUserData; } export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUserDataSynchroniser { @@ -102,7 +102,8 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs this.setStatus(SyncStatus.Syncing); const remote = await this.getLocalGlobalState(); - await this.apply({ local: undefined, remote, remoteUserData: null }); + const remoteUserData = await this.getRemoteUserData(); + await this.apply({ local: undefined, remote, remoteUserData }, true); this.logService.info('UI State: Finished pushing UI State.'); } finally { @@ -111,15 +112,15 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs } - async sync(): Promise { + async sync(): Promise { if (!this.configurationService.getValue('sync.enableUIState')) { this.logService.trace('UI State: Skipping synchronizing UI state as it is disabled.'); - return false; + return; } if (this.status !== SyncStatus.Idle) { this.logService.trace('UI State: Skipping synchronizing ui state as it is running already.'); - return false; + return; } this.logService.trace('UI State: Started synchronizing ui state...'); @@ -129,7 +130,6 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs const result = await this.getPreview(); await this.apply(result); this.logService.trace('UI State: Finished synchronizing ui state.'); - return true; } catch (e) { this.setStatus(SyncStatus.Idle); if (e instanceof UserDataSyncStoreError && e.code === UserDataSyncStoreErrorCode.Rejected) { @@ -143,7 +143,15 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs } } - stop(): void { } + async stop(): Promise { } + + async restart(): Promise { + throw new Error('UI State: Conflicts should not occur'); + } + + resolveConflicts(content: string): Promise { + throw new Error('UI State: Conflicts should not occur'); + } async hasPreviouslySynced(): Promise { const lastSyncData = await this.getLastSyncUserData(); @@ -191,7 +199,7 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs return { local, remote, remoteUserData }; } - private async apply({ local, remote, remoteUserData }: ISyncPreviewResult): Promise { + private async apply({ local, remote, remoteUserData }: ISyncPreviewResult, forcePush?: boolean): Promise { if (local) { // update local this.logService.info('UI State: Updating local ui state...'); @@ -201,10 +209,10 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs if (remote) { // update remote this.logService.info('UI State: Updating remote ui state...'); - remoteUserData = await this.writeToRemote(remote, remoteUserData ? remoteUserData.ref : null); + remoteUserData = await this.writeToRemote(remote, forcePush ? null : remoteUserData.ref); } - if (remoteUserData?.content) { + if (remoteUserData.content) { // update last sync this.logService.info('UI State: Updating last synchronised ui state...'); await this.updateLastSyncValue(remoteUserData); diff --git a/src/vs/platform/userDataSync/common/keybindingsSync.ts b/src/vs/platform/userDataSync/common/keybindingsSync.ts index aa60d2cec72..d4841150c24 100644 --- a/src/vs/platform/userDataSync/common/keybindingsSync.ts +++ b/src/vs/platform/userDataSync/common/keybindingsSync.ts @@ -31,7 +31,7 @@ interface ISyncContent { interface ISyncPreviewResult { readonly fileContent: IFileContent | null; - readonly remoteUserData: IUserData | null; + readonly remoteUserData: IUserData; readonly hasLocalChanged: boolean; readonly hasRemoteChanged: boolean; readonly hasConflicts: boolean; @@ -129,15 +129,16 @@ export class KeybindingsSynchroniser extends AbstractSynchroniser implements IUs const fileContent = await this.getLocalFileContent(); if (fileContent !== null) { + const remoteUserData = await this.getRemoteUserData(); await this.fileService.writeFile(this.environmentService.settingsSyncPreviewResource, fileContent.value); this.syncPreviewResultPromise = createCancelablePromise(() => Promise.resolve({ fileContent, hasConflicts: false, hasLocalChanged: false, hasRemoteChanged: true, - remoteUserData: null + remoteUserData })); - await this.apply(); + await this.apply(undefined, true); } // No local exists to push @@ -152,66 +153,57 @@ export class KeybindingsSynchroniser extends AbstractSynchroniser implements IUs } - async sync(_continue?: boolean): Promise { + async sync(): Promise { if (!this.configurationService.getValue('sync.enableKeybindings')) { this.logService.trace('Keybindings: Skipping synchronizing keybindings as it is disabled.'); - return false; - } - - if (_continue) { - this.logService.info('Keybindings: Resumed synchronizing keybindings'); - return this.continueSync(); + return; } if (this.status !== SyncStatus.Idle) { this.logService.trace('Keybindings: Skipping synchronizing keybindings as it is running already.'); - return false; + return; } this.logService.trace('Keybindings: Started synchronizing keybindings...'); this.setStatus(SyncStatus.Syncing); - try { - const result = await this.getPreview(); - if (result.hasConflicts) { - this.logService.info('Keybindings: Detected conflicts while synchronizing keybindings.'); - this.setStatus(SyncStatus.HasConflicts); - return false; - } - try { - await this.apply(); - this.logService.trace('Keybindings: Finished synchronizing keybindings...'); - return true; - } finally { - this.setStatus(SyncStatus.Idle); - } - } catch (e) { - this.syncPreviewResultPromise = null; - this.setStatus(SyncStatus.Idle); - if (e instanceof UserDataSyncStoreError && e.code === UserDataSyncStoreErrorCode.Rejected) { - // Rejected as there is a new remote version. Syncing again, - this.logService.info('Keybindings: Failed to synchronise keybindings as there is a new remote version available. Synchronizing again...'); - return this.sync(); - } - if (e instanceof FileSystemProviderError && e.code === FileSystemProviderErrorCode.FileExists) { - // Rejected as there is a new local version. Syncing again. - this.logService.info('Keybindings: Failed to synchronise keybindings as there is a new local version available. Synchronizing again...'); - return this.sync(); - } - throw e; - } + return this.doSync(); } - stop(): void { + async stop(): Promise { if (this.syncPreviewResultPromise) { this.syncPreviewResultPromise.cancel(); this.syncPreviewResultPromise = null; this.logService.info('Keybindings: Stopped synchronizing keybindings.'); } - this.fileService.del(this.environmentService.keybindingsSyncPreviewResource); + await this.fileService.del(this.environmentService.keybindingsSyncPreviewResource); this.setStatus(SyncStatus.Idle); } + async restart(): Promise { + if (this.status === SyncStatus.HasConflicts) { + this.syncPreviewResultPromise!.cancel(); + this.syncPreviewResultPromise = null; + await this.doSync(); + } + } + + async resolveConflicts(content: string): Promise { + if (this.status === SyncStatus.HasConflicts) { + try { + await this.apply(content, true); + this.setStatus(SyncStatus.Idle); + } catch (e) { + this.logService.error(e); + if ((e instanceof FileSystemProviderError && e.code === FileSystemProviderErrorCode.FileExists) || + (e instanceof FileOperationError && e.fileOperationResult === FileOperationResult.FILE_MODIFIED_SINCE)) { + throw new Error('Failed to resolve conflicts as there is a new local version available.'); + } + throw e; + } + } + } + async hasPreviouslySynced(): Promise { const lastSyncData = await this.getLastSyncUserData(); return !!lastSyncData; @@ -257,23 +249,51 @@ export class KeybindingsSynchroniser extends AbstractSynchroniser implements IUs } catch (e) { /* ignore */ } } - private async continueSync(): Promise { - if (this.status !== SyncStatus.HasConflicts) { - return false; + private async doSync(): Promise { + try { + const result = await this.getPreview(); + if (result.hasConflicts) { + this.logService.info('Keybindings: Detected conflicts while synchronizing keybindings.'); + this.setStatus(SyncStatus.HasConflicts); + return; + } + try { + await this.apply(); + this.logService.trace('Keybindings: Finished synchronizing keybindings...'); + } finally { + this.setStatus(SyncStatus.Idle); + } + } catch (e) { + this.syncPreviewResultPromise = null; + this.setStatus(SyncStatus.Idle); + if (e instanceof UserDataSyncStoreError && e.code === UserDataSyncStoreErrorCode.Rejected) { + // Rejected as there is a new remote version. Syncing again, + this.logService.info('Keybindings: Failed to synchronise keybindings as there is a new remote version available. Synchronizing again...'); + return this.sync(); + } + if ((e instanceof FileSystemProviderError && e.code === FileSystemProviderErrorCode.FileExists) || + (e instanceof FileOperationError && e.fileOperationResult === FileOperationResult.FILE_MODIFIED_SINCE)) { + // Rejected as there is a new local version. Syncing again. + this.logService.info('Keybindings: Failed to synchronise keybindings as there is a new local version available. Synchronizing again...'); + return this.sync(); + } + throw e; } - await this.apply(); - this.setStatus(SyncStatus.Idle); - return true; } - private async apply(): Promise { + private async apply(content?: string, forcePush?: boolean): Promise { if (!this.syncPreviewResultPromise) { return; } - if (await this.fileService.exists(this.environmentService.keybindingsSyncPreviewResource)) { - const keybindingsPreivew = await this.fileService.readFile(this.environmentService.keybindingsSyncPreviewResource); - const content = keybindingsPreivew.value.toString(); + if (content === undefined) { + if (await this.fileService.exists(this.environmentService.keybindingsSyncPreviewResource)) { + const keybindingsPreivew = await this.fileService.readFile(this.environmentService.keybindingsSyncPreviewResource); + content = keybindingsPreivew.value.toString(); + } + } + + if (content !== undefined) { if (this.hasErrors(content)) { const error = new Error(localize('errorInvalidKeybindings', "Unable to sync keybindings. Please resolve conflicts without any errors/warnings and try again.")); this.logService.error(error); @@ -290,9 +310,8 @@ export class KeybindingsSynchroniser extends AbstractSynchroniser implements IUs } if (hasRemoteChanged) { this.logService.info('Keybindings: Updating remote keybindings'); - let remoteContents = remoteUserData ? remoteUserData.content : (await this.getRemoteUserData()).content; - remoteContents = this.updateSyncContent(content, remoteContents); - const ref = await this.updateRemoteUserData(remoteContents, remoteUserData ? remoteUserData.ref : null); + const remoteContents = this.updateSyncContent(content, remoteUserData.content); + const ref = await this.updateRemoteUserData(remoteContents, forcePush ? null : remoteUserData.ref); remoteUserData = { ref, content: remoteContents }; } if (remoteUserData?.content) { diff --git a/src/vs/platform/userDataSync/common/settingsSync.ts b/src/vs/platform/userDataSync/common/settingsSync.ts index eeff808e36b..fc707cbc9b5 100644 --- a/src/vs/platform/userDataSync/common/settingsSync.ts +++ b/src/vs/platform/userDataSync/common/settingsSync.ts @@ -26,7 +26,7 @@ import { AbstractSynchroniser } from 'vs/platform/userDataSync/common/abstractSy interface ISyncPreviewResult { readonly fileContent: IFileContent | null; - readonly remoteUserData: IUserData | null; + readonly remoteUserData: IUserData; readonly hasLocalChanged: boolean; readonly hasRemoteChanged: boolean; readonly remoteContent: string | null; @@ -154,6 +154,7 @@ export class SettingsSynchroniser extends AbstractSynchroniser implements ISetti // Remove ignored settings const content = updateIgnoredSettings(fileContent.value.toString(), '{}', getIgnoredSettings(this.configurationService), formatUtils); await this.fileService.writeFile(this.environmentService.settingsSyncPreviewResource, VSBuffer.fromString(content)); + const remoteUserData = await this.getRemoteUserData(); this.syncPreviewResultPromise = createCancelablePromise(() => Promise.resolve({ conflictSettings: [], @@ -162,10 +163,10 @@ export class SettingsSynchroniser extends AbstractSynchroniser implements ISetti hasLocalChanged: false, hasRemoteChanged: true, remoteContent: content, - remoteUserData: null, + remoteUserData, })); - await this.apply(); + await this.apply(undefined, true); } // No local exists to push @@ -179,20 +180,15 @@ export class SettingsSynchroniser extends AbstractSynchroniser implements ISetti } } - async sync(_continue?: boolean): Promise { + async sync(): Promise { if (!this.configurationService.getValue('sync.enableSettings')) { this.logService.trace('Settings: Skipping synchronizing settings as it is disabled.'); - return false; - } - - if (_continue) { - this.logService.info('Settings: Resumed synchronizing settings'); - return this.continueSync(); + return; } if (this.status !== SyncStatus.Idle) { this.logService.trace('Settings: Skipping synchronizing settings as it is running already.'); - return false; + return; } this.logService.trace('Settings: Started synchronizing settings...'); @@ -200,13 +196,13 @@ export class SettingsSynchroniser extends AbstractSynchroniser implements ISetti return this.doSync([]); } - stop(): void { + async stop(): Promise { if (this.syncPreviewResultPromise) { this.syncPreviewResultPromise.cancel(); this.syncPreviewResultPromise = null; this.logService.info('Settings: Stopped synchronizing settings.'); } - this.fileService.del(this.environmentService.settingsSyncPreviewResource); + await this.fileService.del(this.environmentService.settingsSyncPreviewResource); this.setStatus(SyncStatus.Idle); } @@ -251,7 +247,31 @@ export class SettingsSynchroniser extends AbstractSynchroniser implements ISetti return content !== undefined ? content : null; } - async resolveConflicts(resolvedConflicts: { key: string, value: any | undefined }[]): Promise { + async restart(): Promise { + if (this.status === SyncStatus.HasConflicts) { + this.syncPreviewResultPromise!.cancel(); + this.syncPreviewResultPromise = null; + await this.doSync([]); + } + } + + async resolveConflicts(content: string): Promise { + if (this.status === SyncStatus.HasConflicts) { + try { + await this.apply(content, true); + this.setStatus(SyncStatus.Idle); + } catch (e) { + this.logService.error(e); + if ((e instanceof FileSystemProviderError && e.code === FileSystemProviderErrorCode.FileExists) || + (e instanceof FileOperationError && e.fileOperationResult === FileOperationResult.FILE_MODIFIED_SINCE)) { + throw new Error('New local version available.'); + } + throw e; + } + } + } + + async resolveSettingsConflicts(resolvedConflicts: { key: string, value: any | undefined }[]): Promise { if (this.status === SyncStatus.HasConflicts) { this.syncPreviewResultPromise!.cancel(); this.syncPreviewResultPromise = null; @@ -265,18 +285,17 @@ export class SettingsSynchroniser extends AbstractSynchroniser implements ISetti } catch (e) { /* ignore */ } } - private async doSync(resolvedConflicts: { key: string, value: any | undefined }[]): Promise { + private async doSync(resolvedConflicts: { key: string, value: any | undefined }[]): Promise { try { const result = await this.getPreview(resolvedConflicts); if (result.hasConflicts) { this.logService.info('Settings: Detected conflicts while synchronizing settings.'); this.setStatus(SyncStatus.HasConflicts); - return false; + return; } try { await this.apply(); this.logService.trace('Settings: Finished synchronizing settings.'); - return true; } finally { this.setStatus(SyncStatus.Idle); } @@ -288,7 +307,8 @@ export class SettingsSynchroniser extends AbstractSynchroniser implements ISetti this.logService.info('Settings: Failed to synchronise settings as there is a new remote version available. Synchronizing again...'); return this.sync(); } - if (e instanceof FileSystemProviderError && e.code === FileSystemProviderErrorCode.FileExists) { + if ((e instanceof FileSystemProviderError && e.code === FileSystemProviderErrorCode.FileExists) || + (e instanceof FileOperationError && e.fileOperationResult === FileOperationResult.FILE_MODIFIED_SINCE)) { // Rejected as there is a new local version. Syncing again. this.logService.info('Settings: Failed to synchronise settings as there is a new local version available. Synchronizing again...'); return this.sync(); @@ -297,27 +317,20 @@ export class SettingsSynchroniser extends AbstractSynchroniser implements ISetti } } - private async continueSync(): Promise { - if (this.status === SyncStatus.HasConflicts) { - try { - await this.apply(); - this.setStatus(SyncStatus.Idle); - } catch (e) { - this.logService.error(e); - throw e; - } - } - return true; - } - - private async apply(): Promise { + private async apply(content?: string, forcePush?: boolean): Promise { if (!this.syncPreviewResultPromise) { return; } - if (await this.fileService.exists(this.environmentService.settingsSyncPreviewResource)) { - const settingsPreivew = await this.fileService.readFile(this.environmentService.settingsSyncPreviewResource); - const content = settingsPreivew.value.toString(); + if (content === undefined) { + if (await this.fileService.exists(this.environmentService.settingsSyncPreviewResource)) { + const settingsPreivew = await this.fileService.readFile(this.environmentService.settingsSyncPreviewResource); + content = settingsPreivew.value.toString(); + } + } + + if (content !== undefined) { + if (this.hasErrors(content)) { const error = new Error(localize('errorInvalidSettings', "Unable to sync settings. Please resolve conflicts without any errors/warnings and try again.")); this.logService.error(error); @@ -334,12 +347,12 @@ export class SettingsSynchroniser extends AbstractSynchroniser implements ISetti } if (hasRemoteChanged) { const formatUtils = await this.getFormattingOptions(); - const remoteContent = remoteUserData ? updateIgnoredSettings(content, remoteUserData.content || '{}', getIgnoredSettings(this.configurationService, content), formatUtils) : content; + const remoteContent = updateIgnoredSettings(content, remoteUserData.content || '{}', getIgnoredSettings(this.configurationService, content), formatUtils); this.logService.info('Settings: Updating remote settings'); - const ref = await this.writeToRemote(remoteContent, remoteUserData ? remoteUserData.ref : null); + const ref = await this.writeToRemote(remoteContent, forcePush ? null : remoteUserData.ref); remoteUserData = { ref, content }; } - if (remoteUserData?.content) { + if (remoteUserData.content) { this.logService.info('Settings: Updating last synchronised sttings'); await this.updateLastSyncValue(remoteUserData); } diff --git a/src/vs/platform/userDataSync/common/userDataAutoSync.ts b/src/vs/platform/userDataSync/common/userDataAutoSync.ts index 308c505c74b..6f53447afb3 100644 --- a/src/vs/platform/userDataSync/common/userDataAutoSync.ts +++ b/src/vs/platform/userDataSync/common/userDataAutoSync.ts @@ -30,20 +30,20 @@ export class UserDataAutoSync extends Disposable implements IUserDataAutoSyncSer } private async updateEnablement(stopIfDisabled: boolean, auto: boolean): Promise { - const enabled = await this.isSyncEnabled(); + const enabled = await this.isAutoSyncEnabled(); if (this.enabled === enabled) { return; } this.enabled = enabled; if (this.enabled) { - this.logService.info('Syncing configuration started'); + this.logService.info('Auto sync started'); this.sync(true, auto); return; } else { if (stopIfDisabled) { this.userDataSyncService.stop(); - this.logService.info('Syncing configuration stopped.'); + this.logService.info('Auto sync stopped.'); } } @@ -59,6 +59,10 @@ export class UserDataAutoSync extends Disposable implements IUserDataAutoSyncSer await this.userDataSyncUtilService.updateConfigurationValue('sync.enable', false); return; } + if (this.userDataSyncService.status !== SyncStatus.Idle) { + this.logService.info('Skipped auto sync as sync is happening'); + return; + } } await this.userDataSyncService.sync(); } catch (e) { @@ -77,7 +81,7 @@ export class UserDataAutoSync extends Disposable implements IUserDataAutoSyncSer return !hasRemote && hasPreviouslySynced; } - private async isSyncEnabled(): Promise { + private async isAutoSyncEnabled(): Promise { return this.configurationService.getValue('sync.enable') && this.userDataSyncService.status !== SyncStatus.Uninitialized && !!(await this.userDataAuthTokenService.getToken()); diff --git a/src/vs/platform/userDataSync/common/userDataSync.ts b/src/vs/platform/userDataSync/common/userDataSync.ts index 090545a6b38..aeb1bd8425b 100644 --- a/src/vs/platform/userDataSync/common/userDataSync.ts +++ b/src/vs/platform/userDataSync/common/userDataSync.ts @@ -187,8 +187,9 @@ export interface ISynchroniser { readonly onDidChangeLocal: Event; pull(): Promise; push(): Promise; - sync(_continue?: boolean): Promise; - stop(): void; + sync(): Promise; + stop(): Promise; + restart(): Promise; hasPreviouslySynced(): Promise hasRemoteData(): Promise; hasLocalData(): Promise; @@ -198,6 +199,7 @@ export interface ISynchroniser { export interface IUserDataSynchroniser extends ISynchroniser { readonly source: SyncSource; getRemoteContent(): Promise; + resolveConflicts(content: string): Promise; } export const IUserDataSyncService = createDecorator('IUserDataSyncService'); @@ -209,6 +211,7 @@ export interface IUserDataSyncService extends ISynchroniser { resetLocal(): Promise; removeExtension(identifier: IExtensionIdentifier): Promise; getRemoteContent(source: SyncSource): Promise; + resolveConflictsAndContinueSync(content: string): Promise; } export const IUserDataAutoSyncService = createDecorator('IUserDataAutoSyncService'); @@ -250,7 +253,7 @@ export interface ISettingsSyncService extends IUserDataSynchroniser { _serviceBrand: any; readonly onDidChangeConflicts: Event; readonly conflicts: IConflictSetting[]; - resolveConflicts(resolvedConflicts: { key: string, value: any | undefined }[]): Promise; + resolveSettingsConflicts(resolvedConflicts: { key: string, value: any | undefined }[]): Promise; } export const CONTEXT_SYNC_STATE = new RawContextKey('syncStatus', SyncStatus.Uninitialized); diff --git a/src/vs/platform/userDataSync/common/userDataSyncIpc.ts b/src/vs/platform/userDataSync/common/userDataSyncIpc.ts index feaec1ba928..c6340f89049 100644 --- a/src/vs/platform/userDataSync/common/userDataSyncIpc.ts +++ b/src/vs/platform/userDataSync/common/userDataSyncIpc.ts @@ -24,13 +24,15 @@ export class UserDataSyncChannel implements IServerChannel { call(context: any, command: string, args?: any): Promise { switch (command) { - case 'sync': return this.service.sync(args[0]); + case 'sync': return this.service.sync(); + case 'resolveConflictsAndContinueSync': return this.service.resolveConflictsAndContinueSync(args[0]); case 'pull': return this.service.pull(); case 'push': return this.service.push(); case '_getInitialStatus': return Promise.resolve(this.service.status); case 'getConflictsSource': return Promise.resolve(this.service.conflictsSource); case 'removeExtension': return this.service.removeExtension(args[0]); case 'stop': this.service.stop(); return Promise.resolve(); + case 'restart': return this.service.restart().then(() => this.service.status); case 'reset': return this.service.reset(); case 'resetLocal': return this.service.resetLocal(); case 'hasPreviouslySynced': return this.service.hasPreviouslySynced(); @@ -58,9 +60,11 @@ export class SettingsSyncChannel implements IServerChannel { call(context: any, command: string, args?: any): Promise { switch (command) { - case 'sync': return this.service.sync(args[0]); + case 'sync': return this.service.sync(); + case 'resolveConflicts': return this.service.resolveConflicts(args[0]); case 'pull': return this.service.pull(); case 'push': return this.service.push(); + case 'restart': return this.service.restart().then(() => this.service.status); case '_getInitialStatus': return Promise.resolve(this.service.status); case '_getInitialConflicts': return Promise.resolve(this.service.conflicts); case 'stop': this.service.stop(); return Promise.resolve(); @@ -68,7 +72,7 @@ export class SettingsSyncChannel implements IServerChannel { case 'hasPreviouslySynced': return this.service.hasPreviouslySynced(); case 'hasRemoteData': return this.service.hasRemoteData(); case 'hasLocalData': return this.service.hasLocalData(); - case 'resolveConflicts': return this.service.resolveConflicts(args[0]); + case 'resolveSettingsConflicts': return this.service.resolveSettingsConflicts(args[0]); case 'getRemoteContent': return this.service.getRemoteContent(); } throw new Error('Invalid call'); diff --git a/src/vs/platform/userDataSync/common/userDataSyncService.ts b/src/vs/platform/userDataSync/common/userDataSyncService.ts index f3ac43ec28d..270bab9d5a2 100644 --- a/src/vs/platform/userDataSync/common/userDataSyncService.ts +++ b/src/vs/platform/userDataSync/common/userDataSyncService.ts @@ -13,6 +13,7 @@ import { IExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { KeybindingsSynchroniser } from 'vs/platform/userDataSync/common/keybindingsSync'; import { GlobalStateSynchroniser } from 'vs/platform/userDataSync/common/globalStateSync'; import { toErrorMessage } from 'vs/base/common/errorMessage'; +import { localize } from 'vs/nls'; export class UserDataSyncService extends Disposable implements IUserDataSyncService { @@ -88,31 +89,57 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ } } - async sync(_continue?: boolean): Promise { + async sync(): Promise { if (!this.userDataSyncStoreService.userDataSyncStore) { throw new Error('Not enabled'); } if (!(await this.userDataAuthTokenService.getToken())) { throw new Error('Not Authenticated. Please sign in to start sync.'); } + if (this.status === SyncStatus.HasConflicts) { + throw new Error(localize('resolve conflicts', "Please resolve conflicts before resuming sync.")); + } for (const synchroniser of this.synchronisers) { try { - if (!await synchroniser.sync(_continue)) { - return false; + await synchroniser.sync(); + // do not continue if synchroniser has conflicts + if (synchroniser.status === SyncStatus.HasConflicts) { + return; } } catch (e) { this.logService.error(`${this.getSyncSource(synchroniser)}: ${toErrorMessage(e)}`); } } - return true; } - stop(): void { + async resolveConflictsAndContinueSync(content: string): Promise { + const synchroniser = this.getSynchroniserInConflicts(); + if (!synchroniser) { + throw new Error(localize('no synchroniser with conflicts', "No conflicts detected.")); + } + await synchroniser.resolveConflicts(content); + if (synchroniser.status !== SyncStatus.HasConflicts) { + await this.sync(); + } + } + + async stop(): Promise { if (!this.userDataSyncStoreService.userDataSyncStore) { throw new Error('Not enabled'); } for (const synchroniser of this.synchronisers) { - synchroniser.stop(); + await synchroniser.stop(); + } + } + + async restart(): Promise { + const synchroniser = this.getSynchroniserInConflicts(); + if (!synchroniser) { + throw new Error(localize('no synchroniser with conflicts', "No conflicts detected.")); + } + await synchroniser.restart(); + if (synchroniser.status !== SyncStatus.HasConflicts) { + await this.sync(); } } @@ -254,6 +281,11 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ return synchroniser ? this.getSyncSource(synchroniser) : null; } + private getSynchroniserInConflicts(): IUserDataSynchroniser | null { + const synchroniser = this.synchronisers.filter(s => s.status === SyncStatus.HasConflicts)[0]; + return synchroniser || null; + } + private getSyncSource(synchroniser: ISynchroniser): SyncSource { if (synchroniser instanceof SettingsSynchroniser) { return SyncSource.Settings; diff --git a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts index b928378375a..5446d32464c 100644 --- a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts +++ b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts @@ -16,11 +16,9 @@ import { GLOBAL_ACTIVITY_ID } from 'vs/workbench/common/activity'; import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; import { URI } from 'vs/base/common/uri'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import { Event } from 'vs/base/common/event'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { isEqual } from 'vs/base/common/resources'; -import { IEditorInput } from 'vs/workbench/common/editor'; import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; import { isWeb } from 'vs/base/common/platform'; @@ -42,11 +40,10 @@ import type { ITextModel } from 'vs/editor/common/model'; import type { IEditorContribution } from 'vs/editor/common/editorCommon'; import type { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { FloatingClickWidget } from 'vs/workbench/browser/parts/editor/editorWidgets'; -import { IFileService } from 'vs/platform/files/common/files'; -import { VSBuffer } from 'vs/base/common/buffer'; import { registerEditorContribution } from 'vs/editor/browser/editorExtensions'; import { areSame } from 'vs/platform/userDataSync/common/settingsMerge'; import { getIgnoredSettings } from 'vs/platform/userDataSync/common/settingsSync'; +import type { IEditorInput } from 'vs/workbench/common/editor'; const enum AuthStatus { Initializing = 'Initializing', @@ -86,7 +83,6 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo @INotificationService private readonly notificationService: INotificationService, @IConfigurationService private readonly configurationService: IConfigurationService, @IEditorService private readonly editorService: IEditorService, - @ITextFileService private readonly textFileService: ITextFileService, @IWorkbenchEnvironmentService private readonly workbenchEnvironmentService: IWorkbenchEnvironmentService, @IDialogService private readonly dialogService: IDialogService, @IQuickInputService private readonly quickInputService: IQuickInputService, @@ -206,7 +202,8 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo } if (this.userDataSyncService.status === SyncStatus.HasConflicts) { - if (!this.conflictsWarningDisposable.value) { + const conflictsEditorInput = this.getConflictsEditorInput(this.userDataSyncService.conflictsSource!); + if (!conflictsEditorInput && !this.conflictsWarningDisposable.value) { const conflictsArea = getSyncAreaLabel(this.userDataSyncService.conflictsSource!); const handle = this.notificationService.prompt(Severity.Warning, localize('conflicts detected', "Unable to sync due to conflicts in {0}. Please resolve them to continue.", conflictsArea), [ @@ -223,10 +220,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo handle.onDidClose(() => this.conflictsWarningDisposable.clear()); } } else { - const previewEditorInput = this.getConflictsEditorInput(); - if (previewEditorInput) { - previewEditorInput.dispose(); - } + this.getAllConflictsEditorInputs().forEach(input => input.dispose()); this.conflictsWarningDisposable.clear(); } } @@ -435,31 +429,18 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo } } - private async continueSync(): Promise { - // Get the preview editor - const previewEditorInput = this.getConflictsEditorInput(); - // Save the preview - if (previewEditorInput && previewEditorInput.isDirty()) { - await this.textFileService.save(previewEditorInput.getResource()!); - } - try { - // Continue Sync - await this.userDataSyncService.sync(true); - } catch (error) { - this.notificationService.error(error); - return; - } - // Close the preview editor - if (previewEditorInput) { - previewEditorInput.dispose(); - } + private getConflictsEditorInput(source: SyncSource): IEditorInput | undefined { + const previewResource = source === SyncSource.Settings ? this.workbenchEnvironmentService.settingsSyncPreviewResource + : source === SyncSource.Keybindings ? this.workbenchEnvironmentService.keybindingsSyncPreviewResource + : null; + return previewResource ? this.editorService.editors.filter(input => input instanceof DiffEditorInput && isEqual(previewResource, input.master.getResource()))[0] : undefined; } - private getConflictsEditorInput(): IEditorInput | undefined { + private getAllConflictsEditorInputs(): IEditorInput[] { return this.editorService.editors.filter(input => { const resource = input instanceof DiffEditorInput ? input.master.getResource() : input.getResource(); return isEqual(resource, this.workbenchEnvironmentService.settingsSyncPreviewResource) || isEqual(resource, this.workbenchEnvironmentService.keybindingsSyncPreviewResource); - })[0]; + }); } private async handleConflicts(): Promise { @@ -480,7 +461,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo label, options: { preserveFocus: false, - pinned: false, + pinned: true, revealIfVisible: true, }, }); @@ -502,7 +483,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo || (source === SyncSource.Settings && !areSame(remoteModelContent, preivewContent, getIgnoredSettings(this.configurationService)))) { return; } - await this.userDataSyncService.sync(true); + await this.userDataSyncService.resolveConflictsAndContinueSync(preivewContent); }); } } @@ -597,16 +578,6 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo when: resolveConflictsWhenContext, }); - const continueSyncCommandId = 'workbench.userData.actions.continueSync'; - CommandsRegistry.registerCommand(continueSyncCommandId, () => this.continueSync()); - MenuRegistry.appendMenuItem(MenuId.CommandPalette, { - command: { - id: continueSyncCommandId, - title: localize('continue sync', "Sync: Continue Sync") - }, - when: ContextKeyExpr.and(CONTEXT_SYNC_STATE.isEqualTo(SyncStatus.HasConflicts)), - }); - const signOutMenuItem: IMenuItem = { group: '5_sync', command: { @@ -689,7 +660,6 @@ class AcceptChangesContribution extends Disposable implements IEditorContributio @IInstantiationService private readonly instantiationService: IInstantiationService, @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService, @IUserDataSyncService private readonly userDataSyncService: IUserDataSyncService, - @IFileService private readonly fileService: IFileService, @INotificationService private readonly notificationService: INotificationService, @IDialogService private readonly dialogService: IDialogService, @IConfigurationService private readonly configurationService: IConfigurationService, @@ -746,40 +716,33 @@ class AcceptChangesContribution extends Disposable implements IEditorContributio private createAcceptChangesWidgetRenderer(): void { if (!this.acceptChangesButton) { const replaceLabel = localize('accept remote', "Replace (Overwrite Local)"); - const acceptLabel = localize('accept local', "Accept"); - this.acceptChangesButton = this.instantiationService.createInstance(FloatingClickWidget, this.editor, getSyncSourceFromRemoteContentResource(this.editor.getModel()!.uri) !== undefined ? replaceLabel : acceptLabel, null); + const applyLabel = localize('accept local', "Apply"); + this.acceptChangesButton = this.instantiationService.createInstance(FloatingClickWidget, this.editor, getSyncSourceFromRemoteContentResource(this.editor.getModel()!.uri) !== undefined ? replaceLabel : applyLabel, null); this._register(this.acceptChangesButton.onClick(async () => { const model = this.editor.getModel(); if (model) { - try { - const syncSource = getSyncSourceFromRemoteContentResource(model.uri); - if (syncSource !== undefined) { - const syncAreaLabel = getSyncAreaLabel(syncSource); - const result = await this.dialogService.confirm({ - type: 'info', - title: localize('Sync overwrite local', "Sync: {0}", replaceLabel), - message: localize('confirm replace and overwrite local', "Would you like to replace Local {0} with Remote {1}?", syncAreaLabel, syncAreaLabel), - primaryButton: replaceLabel - }); - if (!result.confirmed) { - return; - } - if (syncSource === SyncSource.Settings) { - const remoteContent = await this.userDataSyncService.getRemoteContent(SyncSource.Settings); - if (remoteContent) { - await this.fileService.writeFile(this.environmentService.settingsSyncPreviewResource, VSBuffer.fromString(remoteContent)); - } - } - else if (syncSource === SyncSource.Keybindings) { - const remoteContent = await this.userDataSyncService.getRemoteContent(SyncSource.Keybindings); - if (remoteContent) { - await this.fileService.writeFile(this.environmentService.keybindingsSyncPreviewResource, VSBuffer.fromString(remoteContent)); - } - } + const conflictsSource = this.userDataSyncService.conflictsSource; + const syncSource = getSyncSourceFromRemoteContentResource(model.uri); + if (syncSource !== undefined) { + const syncAreaLabel = getSyncAreaLabel(syncSource); + const result = await this.dialogService.confirm({ + type: 'info', + title: localize('Sync overwrite local', "Sync: {0}", replaceLabel), + message: localize('confirm replace and overwrite local', "Would you like to replace Local {0} with Remote {1}?", syncAreaLabel, syncAreaLabel), + primaryButton: replaceLabel + }); + if (!result.confirmed) { + return; } - await this.userDataSyncService.sync(true); + } + try { + await this.userDataSyncService.resolveConflictsAndContinueSync(model.getValue()); } catch (e) { - this.notificationService.error(e); + this.userDataSyncService.restart().then(() => { + if (conflictsSource === this.userDataSyncService.conflictsSource) { + this.notificationService.warn(localize('update conflicts', "Could not resolve conflicts as there is new local version available. Please try again.")); + } + }); } } })); @@ -795,7 +758,6 @@ class AcceptChangesContribution extends Disposable implements IEditorContributio dispose(): void { this.disposeAcceptChangesWidgetRenderer(); - super.dispose(); } } diff --git a/src/vs/workbench/services/userDataSync/electron-browser/settingsSyncService.ts b/src/vs/workbench/services/userDataSync/electron-browser/settingsSyncService.ts index 44ae3903dd0..95b78e46807 100644 --- a/src/vs/workbench/services/userDataSync/electron-browser/settingsSyncService.ts +++ b/src/vs/workbench/services/userDataSync/electron-browser/settingsSyncService.ts @@ -55,12 +55,17 @@ export class SettingsSyncService extends Disposable implements ISettingsSyncServ return this.channel.call('push'); } - sync(_continue?: boolean): Promise { - return this.channel.call('sync', [_continue]); + sync(): Promise { + return this.channel.call('sync'); } - stop(): void { - this.channel.call('stop'); + stop(): Promise { + return this.channel.call('stop'); + } + + async restart(): Promise { + const status = await this.channel.call('restart'); + await this.updateStatus(status); } resetLocal(): Promise { @@ -79,7 +84,11 @@ export class SettingsSyncService extends Disposable implements ISettingsSyncServ return this.channel.call('hasLocalData'); } - resolveConflicts(conflicts: { key: string, value: any | undefined }[]): Promise { + resolveConflicts(content: string): Promise { + return this.channel.call('resolveConflicts', [content]); + } + + resolveSettingsConflicts(conflicts: { key: string, value: any | undefined }[]): Promise { return this.channel.call('resolveConflicts', [conflicts]); } diff --git a/src/vs/workbench/services/userDataSync/electron-browser/userDataSyncService.ts b/src/vs/workbench/services/userDataSync/electron-browser/userDataSyncService.ts index 2097c824bd8..c0fda78d2f9 100644 --- a/src/vs/workbench/services/userDataSync/electron-browser/userDataSyncService.ts +++ b/src/vs/workbench/services/userDataSync/electron-browser/userDataSyncService.ts @@ -46,8 +46,12 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ return this.channel.call('push'); } - sync(_continue?: boolean): Promise { - return this.channel.call('sync', [_continue]); + sync(): Promise { + return this.channel.call('sync'); + } + + resolveConflictsAndContinueSync(content: string): Promise { + return this.channel.call('resolveConflictsAndContinueSync', [content]); } reset(): Promise { @@ -58,8 +62,13 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ return this.channel.call('resetLocal'); } - stop(): void { - this.channel.call('stop'); + stop(): Promise { + return this.channel.call('stop'); + } + + async restart(): Promise { + const status = await this.channel.call('restart'); + await this.updateStatus(status); } hasPreviouslySynced(): Promise { From 7b315c80be399e5cdf67b63554816c56db20b922 Mon Sep 17 00:00:00 2001 From: Oliver Larsson Date: Fri, 24 Jan 2020 12:22:17 +0100 Subject: [PATCH 079/801] Friendly pickstring options (#89180) * Implement git.pruneOnFetch setting (#86813) * Revert "Implement git.pruneOnFetch setting (#86813)" This reverts commit 55fbb5347f2e75d0da62f1875dcc12acbc52e0a6. * Implemented pickString "friendly name" * Default pickString option fix * "name" field changed to "label" Co-authored-by: Alex Ross --- .../browser/configurationResolverService.ts | 27 +++++++++++++++---- .../common/configurationResolver.ts | 2 +- .../common/configurationResolverSchema.ts | 23 ++++++++++++++-- 3 files changed, 44 insertions(+), 8 deletions(-) diff --git a/src/vs/workbench/services/configurationResolver/browser/configurationResolverService.ts b/src/vs/workbench/services/configurationResolver/browser/configurationResolverService.ts index 220b9d988e9..92518fd5b76 100644 --- a/src/vs/workbench/services/configurationResolver/browser/configurationResolverService.ts +++ b/src/vs/workbench/services/configurationResolver/browser/configurationResolverService.ts @@ -268,22 +268,39 @@ export abstract class BaseConfigurationResolverService extends AbstractVariableR if (!Types.isString(info.description)) { missingAttribute('description'); } - if (!Types.isStringArray(info.options)) { + if (Types.isArray(info.options)) { + info.options.forEach(pickOption => { + if (!Types.isString(pickOption) && !Types.isString(pickOption.value)) { + missingAttribute('value'); + } + }); + } else { missingAttribute('options'); } const picks = new Array(); info.options.forEach(pickOption => { - const item: IQuickPickItem = { label: pickOption }; - if (pickOption === info.default) { + const value = Types.isString(pickOption) ? pickOption : pickOption.value; + const label = Types.isString(pickOption) ? undefined : pickOption.label; + + // If there is no label defined, use value as label + const item: IQuickPickItem = { + label: label ? label : value, + detail: label ? value : undefined + }; + + if (value === info.default) { item.description = nls.localize('inputVariable.defaultInputValue', "Default"); picks.unshift(item); } else { picks.push(item); } }); - const pickOptions: IPickOptions = { placeHolder: info.description }; + const pickOptions: IPickOptions = { placeHolder: info.description, matchOnDetail: true }; return this.quickInputService.pick(picks, pickOptions, undefined).then(resolvedInput => { - return resolvedInput ? resolvedInput.label : undefined; + if (resolvedInput) { + return Types.isString(resolvedInput.detail) ? resolvedInput.detail : resolvedInput.label; + } + return undefined; }); } diff --git a/src/vs/workbench/services/configurationResolver/common/configurationResolver.ts b/src/vs/workbench/services/configurationResolver/common/configurationResolver.ts index df0da9e454f..3c74d925a11 100644 --- a/src/vs/workbench/services/configurationResolver/common/configurationResolver.ts +++ b/src/vs/workbench/services/configurationResolver/common/configurationResolver.ts @@ -55,7 +55,7 @@ export interface PickStringInputInfo { id: string; type: 'pickString'; description: string; - options: string[]; + options: (string | { value: string, label?: string })[]; default?: string; } diff --git a/src/vs/workbench/services/configurationResolver/common/configurationResolverSchema.ts b/src/vs/workbench/services/configurationResolver/common/configurationResolverSchema.ts index 1fa8a4ca589..bc2ba69de5c 100644 --- a/src/vs/workbench/services/configurationResolver/common/configurationResolverSchema.ts +++ b/src/vs/workbench/services/configurationResolver/common/configurationResolverSchema.ts @@ -73,9 +73,28 @@ export const inputsSchema: IJSONSchema = { }, options: { type: 'array', - description: nls.localize('JsonSchema.input.options', 'An array of strings that defines the options for a quick pick.'), + description: nls.localize('JsonSchema.input.options', "An array of strings that defines the options for a quick pick."), items: { - type: 'string' + oneOf: [ + { + type: 'string' + }, + { + type: 'object', + required: ['value'], + additionalProperties: false, + properties: { + label: { + type: 'string', + description: nls.localize('JsonSchema.input.pickString.optionLabel', "Label for the option.") + }, + value: { + type: 'string', + description: nls.localize('JsonSchema.input.pickString.optionValue', "Value for the option.") + } + } + } + ] } } } From e57ba0c87098fb82c97f890ecb9f2c04a2a55311 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Fri, 24 Jan 2020 12:26:38 +0100 Subject: [PATCH 080/801] textfiles - simplify getAll() --- .../textfile/browser/textFileService.ts | 36 +++++++------------ .../common/textFileEditorModelManager.ts | 8 +---- .../services/textfile/common/textfiles.ts | 1 + .../test/textFileEditorModelManager.test.ts | 36 +++++++++---------- 4 files changed, 32 insertions(+), 49 deletions(-) diff --git a/src/vs/workbench/services/textfile/browser/textFileService.ts b/src/vs/workbench/services/textfile/browser/textFileService.ts index 452b0d2b64b..3090dcfe774 100644 --- a/src/vs/workbench/services/textfile/browser/textFileService.ts +++ b/src/vs/workbench/services/textfile/browser/textFileService.ts @@ -6,7 +6,7 @@ import * as nls from 'vs/nls'; import { URI } from 'vs/base/common/uri'; import { Emitter, AsyncEmitter } from 'vs/base/common/event'; -import { IResult, ITextFileOperationResult, ITextFileService, ITextFileStreamContent, ITextFileEditorModel, ITextFileContent, IResourceEncodings, IReadTextFileOptions, IWriteTextFileOptions, toBufferOrReadable, TextFileOperationError, TextFileOperationResult, FileOperationWillRunEvent, FileOperationDidRunEvent, ITextFileSaveOptions } from 'vs/workbench/services/textfile/common/textfiles'; +import { IResult, ITextFileOperationResult, ITextFileService, ITextFileStreamContent, ITextFileEditorModel, ITextFileContent, IResourceEncodings, IReadTextFileOptions, IWriteTextFileOptions, toBufferOrReadable, TextFileOperationError, TextFileOperationResult, FileOperationWillRunEvent, FileOperationDidRunEvent, ITextFileSaveOptions, ITextFileEditorModelManager } from 'vs/workbench/services/textfile/common/textfiles'; import { IRevertOptions, IEncodingSupport } from 'vs/workbench/common/editor'; import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle'; import { IFileService, FileOperationError, FileOperationResult, IFileStatWithMetadata, ICreateFileOptions, FileOperation } from 'vs/platform/files/common/files'; @@ -33,6 +33,7 @@ import { CancellationToken } from 'vs/base/common/cancellation'; import { ITextModelService, IResolvedTextEditorModel } from 'vs/editor/common/services/resolverService'; import { BaseTextEditorModel } from 'vs/workbench/common/editor/textEditorModel'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; +import { coalesce } from 'vs/base/common/arrays'; /** * The workbench file service implementation implements the raw file service spec and adds additional methods on top. @@ -51,16 +52,15 @@ export abstract class AbstractTextFileService extends Disposable implements ITex //#endregion - readonly files = this._register(this.instantiationService.createInstance(TextFileEditorModelManager)); + readonly files: ITextFileEditorModelManager = this._register(this.instantiationService.createInstance(TextFileEditorModelManager)); - private _untitled: IUntitledTextEditorModelManager; - get untitled(): IUntitledTextEditorModelManager { return this._untitled; } + readonly untitled: IUntitledTextEditorModelManager = this.untitledTextEditorService; abstract get encoding(): IResourceEncodings; constructor( @IFileService protected readonly fileService: IFileService, - @IUntitledTextEditorService untitledTextEditorService: IUntitledTextEditorService, + @IUntitledTextEditorService private untitledTextEditorService: IUntitledTextEditorService, @ILifecycleService protected readonly lifecycleService: ILifecycleService, @IInstantiationService protected readonly instantiationService: IInstantiationService, @IModelService private readonly modelService: IModelService, @@ -76,8 +76,6 @@ export abstract class AbstractTextFileService extends Disposable implements ITex ) { super(); - this._untitled = untitledTextEditorService; - this.registerListeners(); } @@ -350,15 +348,12 @@ export abstract class AbstractTextFileService extends Disposable implements ITex return this.fileDialogService.pickFileToSave(defaultUri, availableFileSystems); } - private getFileModels(resources?: URI | URI[]): ITextFileEditorModel[] { + private getFileModels(resources?: URI[]): ITextFileEditorModel[] { if (Array.isArray(resources)) { - const models: ITextFileEditorModel[] = []; - resources.forEach(resource => models.push(...this.getFileModels(resource))); - - return models; + return coalesce(resources.map(resource => this.files.get(resource))); } - return this.files.getAll(resources); + return this.files.getAll(); } private getDirtyFileModels(resources?: URI[]): ITextFileEditorModel[] { @@ -637,19 +632,12 @@ export abstract class AbstractTextFileService extends Disposable implements ITex //#region dirty isDirty(resource: URI): boolean { - - // Check for dirty untitled - if (resource.scheme === Schemas.untitled) { - const model = this.untitled.get(resource); - if (model) { - return model.isDirty(); - } - - return false; + const model = resource.scheme === Schemas.untitled ? this.untitled.get(resource) : this.files.get(resource); + if (model) { + return model.isDirty(); } - // Check for dirty file - return this.files.getAll(resource).some(model => model.isDirty()); + return false; } //#endregion diff --git a/src/vs/workbench/services/textfile/common/textFileEditorModelManager.ts b/src/vs/workbench/services/textfile/common/textFileEditorModelManager.ts index 89ac755f495..337797b264b 100644 --- a/src/vs/workbench/services/textfile/common/textFileEditorModelManager.ts +++ b/src/vs/workbench/services/textfile/common/textFileEditorModelManager.ts @@ -177,13 +177,7 @@ export class TextFileEditorModelManager extends Disposable implements ITextFileE } } - getAll(resource?: URI, filter?: (model: ITextFileEditorModel) => boolean): ITextFileEditorModel[] { - if (resource) { - const res = this.mapResourceToModel.get(resource); - - return res ? [res] : []; - } - + getAll(filter?: (model: ITextFileEditorModel) => boolean): ITextFileEditorModel[] { const res: ITextFileEditorModel[] = []; this.mapResourceToModel.forEach(model => { if (!filter || filter(model)) { diff --git a/src/vs/workbench/services/textfile/common/textfiles.ts b/src/vs/workbench/services/textfile/common/textfiles.ts index 373a388a77b..95394f9471c 100644 --- a/src/vs/workbench/services/textfile/common/textfiles.ts +++ b/src/vs/workbench/services/textfile/common/textfiles.ts @@ -356,6 +356,7 @@ export interface ITextFileEditorModelManager { readonly onDidChangeOrphaned: Event; get(resource: URI): ITextFileEditorModel | undefined; + getAll(): ITextFileEditorModel[]; resolve(resource: URI, options?: IModelLoadOrCreateOptions): Promise; diff --git a/src/vs/workbench/services/textfile/test/textFileEditorModelManager.test.ts b/src/vs/workbench/services/textfile/test/textFileEditorModelManager.test.ts index 6a089108b14..8d32fd9113d 100644 --- a/src/vs/workbench/services/textfile/test/textFileEditorModelManager.test.ts +++ b/src/vs/workbench/services/textfile/test/textFileEditorModelManager.test.ts @@ -50,37 +50,37 @@ suite('Files - TextFileEditorModelManager', () => { assert.ok(!manager.get(fileUpper)); - let result = manager.getAll(); - assert.strictEqual(3, result.length); + let results = manager.getAll(); + assert.strictEqual(3, results.length); - result = manager.getAll(URI.file('/yes')); - assert.strictEqual(0, result.length); + let result = manager.get(URI.file('/yes')); + assert.ok(!result); - result = manager.getAll(URI.file('/some/other.txt')); - assert.strictEqual(0, result.length); + result = manager.get(URI.file('/some/other.txt')); + assert.ok(!result); - result = manager.getAll(URI.file('/some/other.html')); - assert.strictEqual(1, result.length); + result = manager.get(URI.file('/some/other.html')); + assert.ok(result); - result = manager.getAll(fileUpper); - assert.strictEqual(0, result.length); + result = manager.get(fileUpper); + assert.ok(!result); manager.remove(URI.file('')); - result = manager.getAll(); - assert.strictEqual(3, result.length); + results = manager.getAll(); + assert.strictEqual(3, results.length); manager.remove(URI.file('/some/other.html')); - result = manager.getAll(); - assert.strictEqual(2, result.length); + results = manager.getAll(); + assert.strictEqual(2, results.length); manager.remove(fileUpper); - result = manager.getAll(); - assert.strictEqual(2, result.length); + results = manager.getAll(); + assert.strictEqual(2, results.length); manager.clear(); - result = manager.getAll(); - assert.strictEqual(0, result.length); + results = manager.getAll(); + assert.strictEqual(0, results.length); model1.dispose(); model2.dispose(); From 09b83d342f44687f46ef20ea9358c57c20904326 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Fri, 24 Jan 2020 09:48:59 +0100 Subject: [PATCH 081/801] bulk - better view model --- .../contrib/bulkEdit/browser/bulkEditPane.ts | 16 +++--- .../contrib/bulkEdit/browser/bulkEditTree.ts | 51 ++++++++++--------- 2 files changed, 35 insertions(+), 32 deletions(-) diff --git a/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPane.ts b/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPane.ts index f6475702aff..bd759b3c924 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPane.ts +++ b/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPane.ts @@ -6,7 +6,7 @@ import 'vs/css!./media/bulkEdit'; import { WorkbenchAsyncDataTree, TreeResourceNavigator, IOpenEvent } from 'vs/platform/list/browser/listService'; import { WorkspaceEdit } from 'vs/editor/common/modes'; -import { BulkEditElement, BulkEditDelegate, TextEditElementRenderer, FileElementRenderer, BulkEditDataSource, BulkEditIdentityProvider, FileElement, TextEditElement, BulkEditAccessibilityProvider, BulkEditAriaProvider, CategoryElementRenderer, BulkEditNaviLabelProvider } from 'vs/workbench/contrib/bulkEdit/browser/bulkEditTree'; +import { BulkEditElement, BulkEditDelegate, TextEditElementRenderer, FileElementRenderer, BulkEditDataSource, BulkEditIdentityProvider, FileElement, TextEditElement, BulkEditAccessibilityProvider, BulkEditAriaProvider, CategoryElementRenderer, BulkEditNaviLabelProvider, CategoryElement } from 'vs/workbench/contrib/bulkEdit/browser/bulkEditTree'; import { FuzzyScore } from 'vs/base/common/filters'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { registerThemingParticipant, ITheme, ICssStyleCollector } from 'vs/platform/theme/common/themeService'; @@ -14,7 +14,7 @@ import { diffInserted, diffRemoved } from 'vs/platform/theme/common/colorRegistr import { localize } from 'vs/nls'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { BulkEditPreviewProvider, BulkFileOperations, BulkFileOperationType, BulkCategory } from 'vs/workbench/contrib/bulkEdit/browser/bulkEditPreview'; +import { BulkEditPreviewProvider, BulkFileOperations, BulkFileOperationType } from 'vs/workbench/contrib/bulkEdit/browser/bulkEditPreview'; import { ILabelService } from 'vs/platform/label/common/label'; import { ITextModelService } from 'vs/editor/common/services/resolverService'; import { URI } from 'vs/base/common/uri'; @@ -206,7 +206,7 @@ export class BulkEditPane extends ViewPane { if (element instanceof FileElement) { await this._tree.expand(element, true); } - if (element instanceof BulkCategory) { + if (element instanceof CategoryElement) { await this._tree.expand(element, true); expand.push(...this._tree.getNode(element).children); } @@ -307,21 +307,21 @@ export class BulkEditPane extends ViewPane { let leftResource: URI | undefined; if (fileElement.edit.type & BulkFileOperationType.TextEdit) { try { - (await this._textModelService.createModelReference(fileElement.uri)).dispose(); - leftResource = fileElement.uri; + (await this._textModelService.createModelReference(fileElement.edit.uri)).dispose(); + leftResource = fileElement.edit.uri; } catch { leftResource = BulkEditPreviewProvider.emptyPreview; } } - const previewUri = BulkEditPreviewProvider.asPreviewUri(fileElement.uri); + const previewUri = BulkEditPreviewProvider.asPreviewUri(fileElement.edit.uri); if (leftResource) { // show diff editor this._editorService.openEditor({ leftResource, rightResource: previewUri, - label: localize('edt.title', "{0} (refactor preview)", basename(fileElement.uri)), + label: localize('edt.title', "{0} (refactor preview)", basename(fileElement.edit.uri)), options }); } else { @@ -336,7 +336,7 @@ export class BulkEditPane extends ViewPane { } this._editorService.openEditor({ - label: typeLabel && localize('edt.title2', "{0} ({1}, refactor preview)", basename(fileElement.uri), typeLabel), + label: typeLabel && localize('edt.title2', "{0} ({1}, refactor preview)", basename(fileElement.edit.uri), typeLabel), resource: previewUri, options }); diff --git a/src/vs/workbench/contrib/bulkEdit/browser/bulkEditTree.ts b/src/vs/workbench/contrib/bulkEdit/browser/bulkEditTree.ts index 247829c8d25..6ec03c716f5 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/bulkEditTree.ts +++ b/src/vs/workbench/contrib/bulkEdit/browser/bulkEditTree.ts @@ -7,7 +7,6 @@ import { IAsyncDataSource, ITreeRenderer, ITreeNode } from 'vs/base/browser/ui/t import { ITextModelService } from 'vs/editor/common/services/resolverService'; import { FuzzyScore, createMatches } from 'vs/base/common/filters'; import { IResourceLabel, ResourceLabels } from 'vs/workbench/browser/labels'; -import { URI } from 'vs/base/common/uri'; import { HighlightedLabel, IHighlight } from 'vs/base/browser/ui/highlightedlabel/highlightedLabel'; import { IIdentityProvider, IListVirtualDelegate, IKeyboardNavigationLabelProvider } from 'vs/base/browser/ui/list/list'; import { Range } from 'vs/editor/common/core/range'; @@ -27,16 +26,20 @@ import { ThemeIcon } from 'vs/platform/theme/common/themeService'; // --- VIEW MODEL -export class FileElement { - - readonly uri: URI; +export class CategoryElement { constructor( - readonly parent: BulkCategory | undefined, + readonly parent: BulkFileOperations, + readonly category: BulkCategory + ) { } +} + +export class FileElement { + + constructor( + readonly parent: CategoryElement | BulkFileOperations, readonly edit: BulkFileOperation - ) { - this.uri = edit.uri; - } + ) { } } export class TextEditElement { @@ -48,7 +51,7 @@ export class TextEditElement { ) { } } -export type BulkEditElement = BulkCategory | FileElement | TextEditElement; +export type BulkEditElement = CategoryElement | FileElement | TextEditElement; // --- DATA SOURCE @@ -73,13 +76,13 @@ export class BulkEditDataSource implements IAsyncDataSource file/text edits if (element instanceof BulkFileOperations) { return this.groupByFile - ? element.fileOperations.map(op => new FileElement(undefined, op)) - : element.categories; + ? element.fileOperations.map(op => new FileElement(element, op)) + : element.categories.map(cat => new CategoryElement(element, cat)); } // category - if (element instanceof BulkCategory) { - return element.fileOperations.map(op => new FileElement(element, op)); + if (element instanceof CategoryElement) { + return element.category.fileOperations.map(op => new FileElement(element, op)); } // file: text edit @@ -209,11 +212,11 @@ export class BulkEditIdentityProvider implements IIdentityProvider { +export class CategoryElementRenderer implements ITreeRenderer { static readonly id: string = 'CategoryElementRenderer'; @@ -258,12 +261,12 @@ export class CategoryElementRenderer implements ITreeRenderer, _index: number, template: CategoryElementTemplate): void { + renderElement(node: ITreeNode, _index: number, template: CategoryElementTemplate): void { template.icon.style.setProperty('--background-dark', null); template.icon.style.setProperty('--background-light', null); - const { metadata } = node.element; + const { metadata } = node.element.category; if (ThemeIcon.isThemeIcon(metadata.iconPath)) { // css const className = ThemeIcon.asClassName(metadata.iconPath); @@ -340,12 +343,12 @@ class FileElementTemplate { this._details.innerText = localize( 'detail.rename', "(renaming from {0})", - this._labelService.getUriLabel(element.uri, { relative: true }) + this._labelService.getUriLabel(element.edit.uri, { relative: true }) ); } else { // create, delete, edit: NAME - this._label.setFile(element.uri, { + this._label.setFile(element.edit.uri, { matches: createMatches(score), fileKind: FileKind.FILE, fileDecorations: { colors: true, badges: false }, @@ -489,9 +492,9 @@ export class BulkEditNaviLabelProvider implements IKeyboardNavigationLabelProvid getKeyboardNavigationLabel(element: BulkEditElement) { if (element instanceof FileElement) { - return basename(element.uri); - } else if (element instanceof BulkCategory) { - return element.metadata.label; + return basename(element.edit.uri); + } else if (element instanceof CategoryElement) { + return element.category.metadata.label; } return undefined; } From 680393541b8b1f9ecc235f4554f8846eb30ceeb7 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Fri, 24 Jan 2020 12:27:54 +0100 Subject: [PATCH 082/801] bulk: have UX and model checked states, propagate check states to child edits --- .../contrib/bulkEdit/browser/bulkEditPane.ts | 10 +- .../bulkEdit/browser/bulkEditPreview.ts | 182 ++++++++++-------- .../contrib/bulkEdit/browser/bulkEditTree.ts | 137 +++++++++++-- .../electron-brower/bulkEditPreview.test.ts | 29 +-- 4 files changed, 235 insertions(+), 123 deletions(-) diff --git a/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPane.ts b/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPane.ts index bd759b3c924..3ea4de16047 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPane.ts +++ b/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPane.ts @@ -183,7 +183,7 @@ export class BulkEditPane extends ViewPane { this._setTreeInput(input); // refresh when check state changes - this._sessionDisposables.add(input.onDidChangeCheckedState(() => { + this._sessionDisposables.add(input.checked.onDidChange(() => { this._tree.updateChildren(); })); }); @@ -238,10 +238,8 @@ export class BulkEditPane extends ViewPane { toggleChecked() { const [first] = this._tree.getFocus(); - if (first instanceof FileElement) { - first.edit.updateChecked(!first.edit.isChecked()); - } else if (first instanceof TextEditElement && first.parent.edit.isChecked()) { - first.edit.updateChecked(!first.edit.isChecked()); + if ((first instanceof FileElement || first instanceof TextEditElement) && !first.isDisabled()) { + first.setChecked(!first.isChecked()); } } @@ -277,7 +275,7 @@ export class BulkEditPane extends ViewPane { private _done(accept: boolean): void { if (this._currentResolve) { - this._currentResolve(accept ? this._currentInput?.asWorkspaceEdit() : undefined); + this._currentResolve(accept ? this._currentInput?.getWorkspaceEdit() : undefined); this._currentInput = undefined; } this._setState(State.Message); diff --git a/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPreview.ts b/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPreview.ts index 85abf8f21ff..49f72eddf3a 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPreview.ts +++ b/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPreview.ts @@ -8,7 +8,7 @@ import { URI } from 'vs/base/common/uri'; import { IModeService } from 'vs/editor/common/services/modeService'; import { IModelService } from 'vs/editor/common/services/modelService'; import { createTextBufferFactoryFromSnapshot } from 'vs/editor/common/model/textModel'; -import { WorkspaceEdit, TextEdit, WorkspaceTextEdit, WorkspaceFileEdit, WorkspaceEditMetadata } from 'vs/editor/common/modes'; +import { WorkspaceEdit, WorkspaceTextEdit, WorkspaceFileEdit, WorkspaceEditMetadata } from 'vs/editor/common/modes'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { mergeSort, coalesceInPlace } from 'vs/base/common/arrays'; import { Range } from 'vs/editor/common/core/range'; @@ -21,34 +21,35 @@ import { ConflictDetector } from 'vs/workbench/services/bulkEdit/browser/conflic import { values } from 'vs/base/common/map'; import { localize } from 'vs/nls'; -export class CheckedObject { +export class CheckedStates { - private _checked: boolean = true; + private readonly _states = new WeakMap(); - constructor(protected _emitter: Emitter) { } + private readonly _onDidChange = new Emitter(); + readonly onDidChange: Event = this._onDidChange.event; - updateChecked(checked: boolean) { - if (this._checked !== checked) { - this._checked = checked; - this._emitter.fire(this); - } + dispose(): void { + this._onDidChange.dispose(); } - isChecked(): boolean { - return this._checked; + isChecked(obj: T): boolean { + return this._states.get(obj) ?? false; + } + + updateChecked(obj: T, value: boolean): void { + if (this._states.get(obj) !== value) { + this._states.set(obj, value); + this._onDidChange.fire(obj); + } } } -export class BulkTextEdit extends CheckedObject { +export class BulkTextEdit { constructor( readonly parent: BulkFileOperation, - readonly textEdit: WorkspaceTextEdit, - emitter: Emitter - ) { - super(emitter); - this.updateChecked(!textEdit.metadata?.needsConfirmation); - } + readonly textEdit: WorkspaceTextEdit + ) { } } export const enum BulkFileOperationType { @@ -58,7 +59,7 @@ export const enum BulkFileOperationType { Rename = 8, } -export class BulkFileOperation extends CheckedObject { +export class BulkFileOperation { type: BulkFileOperationType = 0; textEdits: BulkTextEdit[] = []; @@ -68,24 +69,16 @@ export class BulkFileOperation extends CheckedObject { constructor( readonly uri: URI, readonly parent: BulkFileOperations - ) { - super(parent._onDidChangeCheckedState); - } + ) { } addEdit(index: number, type: BulkFileOperationType, edit: WorkspaceTextEdit | WorkspaceFileEdit, ) { this.type |= type; this.originalEdits.set(index, edit); if (WorkspaceTextEdit.is(edit)) { - this.textEdits.push(new BulkTextEdit(this, edit, this._emitter)); + this.textEdits.push(new BulkTextEdit(this, edit)); - } else { - if (type === BulkFileOperationType.Rename) { - this.newUri = edit.newUri; - } - // one needsConfirmation is enough to uncheck this item - if (this.isChecked() && edit.metadata?.needsConfirmation) { - this.updateChecked(false); - } + } else if (type === BulkFileOperationType.Rename) { + this.newUri = edit.newUri; } } } @@ -118,8 +111,7 @@ export class BulkFileOperations { return await result._init(); } - readonly _onDidChangeCheckedState = new Emitter(); - readonly onDidChangeCheckedState: Event = this._onDidChangeCheckedState.event; + readonly checked = new CheckedStates(); readonly fileOperations: BulkFileOperation[] = []; readonly categories: BulkCategory[] = []; @@ -131,23 +123,10 @@ export class BulkFileOperations { @IInstantiationService instaService: IInstantiationService, ) { this.conflicts = instaService.createInstance(ConflictDetector, _bulkEdit); - - // reflect checked-state for files in all categories the file occurs in - this._onDidChangeCheckedState.event(e => { - if (e instanceof BulkFileOperation && e.parent) { - for (let item of this.categories) { - for (let file of item.fileOperations) { - if (file.uri.toString() === e.uri.toString()) { - file.updateChecked(e.isChecked()); - } - } - } - } - }); } dispose(): void { - this._onDidChangeCheckedState.dispose(); + this.checked.dispose(); this.conflicts.dispose(); } @@ -163,6 +142,9 @@ export class BulkFileOperations { let uri: URI; let type: BulkFileOperationType; + // store inital checked state + this.checked.updateChecked(edit, !edit.metadata?.needsConfirmation); + if (WorkspaceTextEdit.is(edit)) { type = BulkFileOperationType.TextEdit; uri = edit.resource; @@ -234,43 +216,79 @@ export class BulkFileOperations { return this; } - asWorkspaceEdit(): WorkspaceEdit { + getWorkspaceEdit(): WorkspaceEdit { const result: WorkspaceEdit = { edits: [] }; let allAccepted = true; - for (let file of this.fileOperations) { - if (!file.isChecked()) { - allAccepted = false; + for (let i = 0; i < this._bulkEdit.edits.length; i++) { + const edit = this._bulkEdit.edits[i]; + if (this.checked.isChecked(edit)) { + result.edits[i] = edit; continue; } + allAccepted = false; + } - const keyOfEdit = (edit: TextEdit) => JSON.stringify(edit); - const checkedEdits = new Set(); + if (allAccepted) { + return this._bulkEdit; + } - for (let edit of file.textEdits) { - if (edit.isChecked()) { - checkedEdits.add(keyOfEdit(edit.textEdit.edit)); + // not all edits have been accepted + coalesceInPlace(result.edits); + return result; + } + + getFileEdits(uri: URI): IIdentifiedSingleEditOperation[] { + + for (let file of this.fileOperations) { + if (file.uri.toString() === uri.toString()) { + + const result: IIdentifiedSingleEditOperation[] = []; + let ignoreAll = false; + + file.originalEdits.forEach(edit => { + + if (WorkspaceTextEdit.is(edit)) { + if (this.checked.isChecked(edit)) { + result.push(EditOperation.replaceMove(Range.lift(edit.edit.range), edit.edit.text)); + } + + } else if (!this.checked.isChecked(edit)) { + // UNCHECKED WorkspaceFileEdit disables all text edits + ignoreAll = true; + } + }); + + if (ignoreAll) { + return []; } + + return mergeSort( + result, + (a, b) => Range.compareRangesUsingStarts(a.range, b.range) + ); } + } + return []; + } - file.originalEdits.forEach((value, idx) => { + getUriOfEdit(edit: WorkspaceFileEdit | WorkspaceTextEdit): URI { + if (WorkspaceTextEdit.is(edit)) { + return edit.resource; + } - if (WorkspaceTextEdit.is(value) && !checkedEdits.has(keyOfEdit(value.edit))) { - allAccepted = false; - return; + for (let file of this.fileOperations) { + let found = false; + file.originalEdits.forEach(value => { + if (!found && value === edit) { + found = true; } - - result.edits[idx] = value; - }); + if (found) { + return file.uri; + } } - if (!allAccepted) { - // only return a new edit when something has changed - coalesceInPlace(result.edits); - return result; - } - return this._bulkEdit; - + throw new Error('invalid edit'); } } @@ -308,30 +326,26 @@ export class BulkEditPreviewProvider implements ITextModelContentProvider { private async _init() { for (let operation of this._operations.fileOperations) { - await this._applyTextEditsToPreviewModel(operation); + await this._applyTextEditsToPreviewModel(operation.uri); } - this._disposables.add(this._operations.onDidChangeCheckedState(element => { - let operation = element instanceof BulkFileOperation ? element : element.parent; - this._applyTextEditsToPreviewModel(operation); + this._disposables.add(this._operations.checked.onDidChange(e => { + const uri = this._operations.getUriOfEdit(e); + this._applyTextEditsToPreviewModel(uri); })); } - private async _applyTextEditsToPreviewModel(operation: BulkFileOperation) { - const model = await this._getOrCreatePreviewModel(operation.uri); + private async _applyTextEditsToPreviewModel(uri: URI) { + const model = await this._getOrCreatePreviewModel(uri); // undo edits that have been done before let undoEdits = this._modelPreviewEdits.get(model.id); if (undoEdits) { model.applyEdits(undoEdits); } - // compute new edits - const newEdits = mergeSort( - operation.textEdits.filter(edit => edit.isChecked() && edit.parent.isChecked()).map(edit => EditOperation.replaceMove(Range.lift(edit.textEdit.edit.range), edit.textEdit.edit.text)), - (a, b) => Range.compareRangesUsingStarts(a.range, b.range) - ); - // apply edits and keep undo edits - undoEdits = model.applyEdits(newEdits); - this._modelPreviewEdits.set(model.id, undoEdits); + // apply new edits and keep (future) undo edits + const newEdits = this._operations.getFileEdits(uri); + const newUndoEdits = model.applyEdits(newEdits); + this._modelPreviewEdits.set(model.id, newUndoEdits); } private async _getOrCreatePreviewModel(uri: URI) { diff --git a/src/vs/workbench/contrib/bulkEdit/browser/bulkEditTree.ts b/src/vs/workbench/contrib/bulkEdit/browser/bulkEditTree.ts index 6ec03c716f5..4f75537a3da 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/bulkEditTree.ts +++ b/src/vs/workbench/contrib/bulkEdit/browser/bulkEditTree.ts @@ -23,9 +23,15 @@ import type { IAriaProvider } from 'vs/base/browser/ui/list/listView'; import { IconLabel } from 'vs/base/browser/ui/iconLabel/iconLabel'; import { basename } from 'vs/base/common/resources'; import { ThemeIcon } from 'vs/platform/theme/common/themeService'; +import { WorkspaceFileEdit } from 'vs/editor/common/modes'; // --- VIEW MODEL +export interface ICheckable { + isChecked(): boolean; + setChecked(value: boolean): void; +} + export class CategoryElement { constructor( @@ -34,21 +40,116 @@ export class CategoryElement { ) { } } -export class FileElement { +export class FileElement implements ICheckable { constructor( readonly parent: CategoryElement | BulkFileOperations, readonly edit: BulkFileOperation ) { } + + isChecked(): boolean { + let model = this.parent instanceof CategoryElement ? this.parent.parent : this.parent; + + let checked = true; + + // only text edit children -> reflect children state + if (this.edit.type === BulkFileOperationType.TextEdit) { + checked = !this.edit.textEdits.every(edit => !model.checked.isChecked(edit.textEdit)); + } + + // multiple file edits -> reflect single state + this.edit.originalEdits.forEach(edit => { + if (WorkspaceFileEdit.is(edit)) { + checked = checked && model.checked.isChecked(edit); + } + }); + + // multiple categories and text change -> read all elements + if (this.parent instanceof CategoryElement && this.edit.type === BulkFileOperationType.TextEdit) { + for (let category of model.categories) { + for (let file of category.fileOperations) { + if (file.uri.toString() === this.edit.uri.toString()) { + file.originalEdits.forEach(edit => { + if (WorkspaceFileEdit.is(edit)) { + checked = checked && model.checked.isChecked(edit); + } + }); + } + } + } + } + + return checked; + } + + setChecked(value: boolean): void { + let model = this.parent instanceof CategoryElement ? this.parent.parent : this.parent; + this.edit.originalEdits.forEach(edit => { + model.checked.updateChecked(edit, value); + }); + + // multiple categories and file change -> update all elements + if (this.parent instanceof CategoryElement && this.edit.type !== BulkFileOperationType.TextEdit) { + for (let category of model.categories) { + for (let file of category.fileOperations) { + if (file.uri.toString() === this.edit.uri.toString()) { + file.originalEdits.forEach(edit => { + model.checked.updateChecked(edit, value); + }); + } + } + } + } + } + + isDisabled(): boolean { + if (this.parent instanceof CategoryElement && this.edit.type === BulkFileOperationType.TextEdit) { + let model = this.parent.parent; + let checked = true; + for (let category of model.categories) { + for (let file of category.fileOperations) { + if (file.uri.toString() === this.edit.uri.toString()) { + file.originalEdits.forEach(edit => { + if (WorkspaceFileEdit.is(edit)) { + checked = checked && model.checked.isChecked(edit); + } + }); + } + } + } + return !checked; + } + return false; + } } -export class TextEditElement { +export class TextEditElement implements ICheckable { constructor( readonly parent: FileElement, readonly edit: BulkTextEdit, readonly prefix: string, readonly selecting: string, readonly inserting: string, readonly suffix: string ) { } + + isChecked(): boolean { + let model = this.parent.parent; + if (model instanceof CategoryElement) { + model = model.parent; + } + return model.checked.isChecked(this.edit.textEdit); + } + + setChecked(value: boolean): void { + let model = this.parent.parent; + if (model instanceof CategoryElement) { + model = model.parent; + } + return model.checked.updateChecked(this.edit.textEdit, value); + } + + isDisabled(): boolean { + return this.parent.isDisabled(); + } } export type BulkEditElement = CategoryElement | FileElement | TextEditElement; @@ -325,13 +426,12 @@ class FileElementTemplate { set(element: FileElement, score: FuzzyScore | undefined) { this._localDisposables.clear(); - this._localDisposables.add(dom.addDisposableListener(this._checkbox, 'change', (() => element.edit.updateChecked(this._checkbox.checked)))); - if (element.edit.type === BulkFileOperationType.TextEdit && element.edit.textEdits.every(edit => !edit.isChecked())) { - this._checkbox.checked = false; - } else { - this._checkbox.checked = element.edit.isChecked(); - } + this._checkbox.checked = element.isChecked(); + this._checkbox.disabled = element.isDisabled(); + this._localDisposables.add(dom.addDisposableListener(this._checkbox, 'change', () => { + element.setChecked(this._checkbox.checked); + })); if (element.edit.type & BulkFileOperationType.Rename && element.edit.newUri) { // rename: NEW NAME (old name) @@ -415,22 +515,19 @@ class TextEditElementTemplate { set(element: TextEditElement) { this._localDisposables.clear(); - this._localDisposables.add(dom.addDisposableListener(this._checkbox, 'change', () => { - if (element.parent.edit.isChecked()) { - element.edit.updateChecked(this._checkbox.checked); - } - })); - if (element.parent.edit.isChecked()) { - this._checkbox.checked = element.edit.isChecked(); - this._checkbox.disabled = false; + if (element.parent.isChecked()) { + this._checkbox.checked = element.isChecked(); + this._checkbox.disabled = element.isDisabled(); + this._localDisposables.add(dom.addDisposableListener(this._checkbox, 'change', e => { + element.setChecked(this._checkbox.checked); + e.preventDefault(); + })); } else { - this._checkbox.checked = element.edit.isChecked(); - this._checkbox.disabled = true; + this._checkbox.checked = element.isChecked(); + this._checkbox.disabled = element.isDisabled(); } - dom.toggleClass(this._checkbox, 'disabled', !element.edit.parent.isChecked()); - let value = ''; value += element.prefix; value += element.selecting; diff --git a/src/vs/workbench/contrib/bulkEdit/test/electron-brower/bulkEditPreview.test.ts b/src/vs/workbench/contrib/bulkEdit/test/electron-brower/bulkEditPreview.test.ts index d29dca35c51..9b2bc1df08c 100644 --- a/src/vs/workbench/contrib/bulkEdit/test/electron-brower/bulkEditPreview.test.ts +++ b/src/vs/workbench/contrib/bulkEdit/test/electron-brower/bulkEditPreview.test.ts @@ -14,6 +14,7 @@ import { IModelService } from 'vs/editor/common/services/modelService'; import type { WorkspaceEdit } from 'vs/editor/common/modes'; import { URI } from 'vs/base/common/uri'; import { BulkFileOperations } from 'vs/workbench/contrib/bulkEdit/browser/bulkEditPreview'; +import { Range } from 'vs/editor/common/core/range'; suite('BulkEditPreview', function () { @@ -56,7 +57,7 @@ suite('BulkEditPreview', function () { const ops = await instaService.invokeFunction(BulkFileOperations.create, edit); assert.equal(ops.fileOperations.length, 1); - assert.equal(ops.fileOperations[0].isChecked(), false); + assert.equal(ops.checked.isChecked(edit.edits[0]), false); }); test('has categories', async function () { @@ -89,27 +90,29 @@ suite('BulkEditPreview', function () { assert.equal(ops.categories[0].metadata.label, 'uri1'); }); - test('update file from categories', async function () { + test('category selection', async function () { const edit: WorkspaceEdit = { edits: [ - { newUri: URI.parse('some:///uri1'), metadata: { label: 'cat1', needsConfirmation: true } }, - { newUri: URI.parse('some:///uri1'), metadata: { label: 'cat2', needsConfirmation: true } } + { newUri: URI.parse('some:///uri1'), metadata: { label: 'C1', needsConfirmation: false } }, + { resource: URI.parse('some:///uri2'), edit: { text: 'foo', range: new Range(1, 1, 1, 1) }, metadata: { label: 'C2', needsConfirmation: false } } ] }; const ops = await instaService.invokeFunction(BulkFileOperations.create, edit); - assert.equal(ops.categories.length, 2); - const [first, second] = ops.categories; - assert.equal(first.fileOperations.length, 1); - assert.equal(second.fileOperations.length, 1); + assert.equal(ops.checked.isChecked(edit.edits[0]), true); + assert.equal(ops.checked.isChecked(edit.edits[1]), true); - assert.equal(first.fileOperations[0].isChecked(), false); - assert.equal(second.fileOperations[0].isChecked(), false); + assert.ok(edit === ops.getWorkspaceEdit()); - first.fileOperations[0].updateChecked(true); - assert.equal(first.fileOperations[0].isChecked(), true); - assert.equal(first.fileOperations[0].isChecked(), true); + // NOT taking to create, but the invalid text edit will + // go through + ops.checked.updateChecked(edit.edits[0], false); + const newEdit = ops.getWorkspaceEdit(); + assert.ok(edit !== newEdit); + + assert.equal(edit.edits.length, 2); + assert.equal(newEdit.edits.length, 1); }); }); From 8c8d0d6c26748a94e7553734e4525d0e07630694 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Fri, 24 Jan 2020 12:39:35 +0100 Subject: [PATCH 083/801] update references viewlet, https://github.com/microsoft/vscode/issues/89214 --- build/builtInExtensions.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/builtInExtensions.json b/build/builtInExtensions.json index 2cddf17db1c..6a9ef0b745e 100644 --- a/build/builtInExtensions.json +++ b/build/builtInExtensions.json @@ -31,7 +31,7 @@ }, { "name": "ms-vscode.references-view", - "version": "0.0.46", + "version": "0.0.47", "repo": "https://github.com/Microsoft/vscode-reference-view", "metadata": { "id": "dc489f46-520d-4556-ae85-1f9eab3c412d", From 365c034866bc5f7792710af3366063cbae1f88af Mon Sep 17 00:00:00 2001 From: isidor Date: Fri, 24 Jan 2020 12:54:09 +0100 Subject: [PATCH 084/801] fixes #89177 --- src/vs/workbench/contrib/debug/browser/debugService.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/debug/browser/debugService.ts b/src/vs/workbench/contrib/debug/browser/debugService.ts index 77fd7a3ab3b..4c94927538d 100644 --- a/src/vs/workbench/contrib/debug/browser/debugService.ts +++ b/src/vs/workbench/contrib/debug/browser/debugService.ts @@ -596,8 +596,9 @@ export class DebugService implements IDebugService { return Promise.resolve(TaskRunResult.Success); } - await this.taskRunner.runTask(session.root, session.configuration.postDebugTask); - return this.taskRunner.runTaskAndCheckErrors(session.root, session.configuration.preLaunchTask, (msg, actions) => this.showError(msg, actions)); + const root = session.root || this.contextService.getWorkspace(); + await this.taskRunner.runTask(root, session.configuration.postDebugTask); + return this.taskRunner.runTaskAndCheckErrors(root, session.configuration.preLaunchTask, (msg, actions) => this.showError(msg, actions)); }; const extensionDebugSession = getExtensionHostDebugSession(session); From c5c2551f7ca919d28d1a72a1e69ba92649b9cd95 Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Fri, 24 Jan 2020 12:48:35 +0100 Subject: [PATCH 085/801] Fixes #88913: Make sure to always read the languageId/tokenType metadata from a valid TM token index --- src/vs/editor/common/model/tokensStore.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/editor/common/model/tokensStore.ts b/src/vs/editor/common/model/tokensStore.ts index 359c755e057..d77bb8b7de6 100644 --- a/src/vs/editor/common/model/tokensStore.ts +++ b/src/vs/editor/common/model/tokensStore.ts @@ -804,7 +804,7 @@ export class TokensStore2 { aIndex++; } - const aMetadata = aTokens.getMetadata(aIndex - 1 > 0 ? aIndex - 1 : aIndex); + const aMetadata = aTokens.getMetadata(Math.min(Math.max(0, aIndex - 1), aLen - 1)); const languageId = TokenMetadata.getLanguageId(aMetadata); const tokenType = TokenMetadata.getTokenType(aMetadata); From a93ffac38a53800fc0ea5ccf35df9957db3b512b Mon Sep 17 00:00:00 2001 From: Christof Marti Date: Fri, 24 Jan 2020 14:51:53 +0100 Subject: [PATCH 086/801] Add forwardPorts to schema (microsoft/vscode-remote-release#1009) --- .../configuration-editing/schemas/devContainer.schema.json | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/extensions/configuration-editing/schemas/devContainer.schema.json b/extensions/configuration-editing/schemas/devContainer.schema.json index c9c34af63fa..292fe629937 100644 --- a/extensions/configuration-editing/schemas/devContainer.schema.json +++ b/extensions/configuration-editing/schemas/devContainer.schema.json @@ -24,6 +24,13 @@ "$ref": "vscode://schemas/settings/machine", "description": "Machine specific settings that should be copied into the container." }, + "forwardPorts": { + "type": "array", + "description": "Ports that are forwarded from the container to the local machine.", + "items": { + "type": "integer" + } + }, "remoteEnv": { "type": "object", "additionalProperties": { From 951202baa7fa5bad8099467ce9192b9038026fd8 Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Fri, 24 Jan 2020 15:58:15 +0100 Subject: [PATCH 087/801] Fixes #86358 --- src/vs/editor/common/modes/linkComputer.ts | 4 ++++ src/vs/editor/test/common/modes/linkComputer.test.ts | 7 +++++++ 2 files changed, 11 insertions(+) diff --git a/src/vs/editor/common/modes/linkComputer.ts b/src/vs/editor/common/modes/linkComputer.ts index 9d381736536..0b70ffe5ef4 100644 --- a/src/vs/editor/common/modes/linkComputer.ts +++ b/src/vs/editor/common/modes/linkComputer.ts @@ -268,6 +268,10 @@ export class LinkComputer { // `*` terminates a link if the link began with `*` chClass = (linkBeginChCode === CharCode.Asterisk) ? CharacterClass.ForceTermination : CharacterClass.None; break; + case CharCode.Pipe: + // `|` terminates a link if the link began with `|` + chClass = (linkBeginChCode === CharCode.Pipe) ? CharacterClass.ForceTermination : CharacterClass.None; + break; default: chClass = classifier.get(chCode); } diff --git a/src/vs/editor/test/common/modes/linkComputer.test.ts b/src/vs/editor/test/common/modes/linkComputer.test.ts index ebb5df027a5..1e07c4e310a 100644 --- a/src/vs/editor/test/common/modes/linkComputer.test.ts +++ b/src/vs/editor/test/common/modes/linkComputer.test.ts @@ -209,4 +209,11 @@ suite('Editor Modes - Link Computer', () => { ' https://portal.azure.com ' ); }); + + test('issue #86358: URL wrong recognition pattern', () => { + assertLink( + 'POST|https://portal.azure.com|2019-12-05|', + ' https://portal.azure.com ' + ); + }); }); From 1d93480508768e5789d7ac56ee2312af1bacd245 Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Fri, 24 Jan 2020 16:16:25 +0100 Subject: [PATCH 088/801] Fixes #88012: consider pairs like (r',') to be symmetric --- .../common/controller/cursorTypeOperations.ts | 9 +++- .../test/browser/controller/cursor.test.ts | 45 +++++++++++++++++++ 2 files changed, 52 insertions(+), 2 deletions(-) diff --git a/src/vs/editor/common/controller/cursorTypeOperations.ts b/src/vs/editor/common/controller/cursorTypeOperations.ts index aab378176b7..64db6112da9 100644 --- a/src/vs/editor/common/controller/cursorTypeOperations.ts +++ b/src/vs/editor/common/controller/cursorTypeOperations.ts @@ -492,15 +492,20 @@ export class TypeOperations { }); } + private static _autoClosingPairIsSymmetric(autoClosingPair: StandardAutoClosingPairConditional): boolean { + const { open, close } = autoClosingPair; + return (open.indexOf(close) >= 0 || close.indexOf(open) >= 0); + } + private static _isBeforeClosingBrace(config: CursorConfiguration, autoClosingPair: StandardAutoClosingPairConditional, characterAfter: string) { const otherAutoClosingPairs = config.autoClosingPairsClose2.get(characterAfter); if (!otherAutoClosingPairs) { return false; } - const thisBraceIsSymmetric = (autoClosingPair.open === autoClosingPair.close); + const thisBraceIsSymmetric = TypeOperations._autoClosingPairIsSymmetric(autoClosingPair); for (const otherAutoClosingPair of otherAutoClosingPairs) { - const otherBraceIsSymmetric = (otherAutoClosingPair.open === otherAutoClosingPair.close); + const otherBraceIsSymmetric = TypeOperations._autoClosingPairIsSymmetric(otherAutoClosingPair); if (!thisBraceIsSymmetric && otherBraceIsSymmetric) { continue; } diff --git a/src/vs/editor/test/browser/controller/cursor.test.ts b/src/vs/editor/test/browser/controller/cursor.test.ts index 4d490f7d2c1..c4b0df1d3cb 100644 --- a/src/vs/editor/test/browser/controller/cursor.test.ts +++ b/src/vs/editor/test/browser/controller/cursor.test.ts @@ -4901,6 +4901,51 @@ suite('autoClosingPairs', () => { mode.dispose(); }); + test('issue #85983 - editor.autoClosingBrackets: beforeWhitespace is incorrect for Python', () => { + const languageId = new LanguageIdentifier('pythonMode', 5); + class PythonMode extends MockMode { + constructor() { + super(languageId); + this._register(LanguageConfigurationRegistry.register(this.getLanguageIdentifier(), { + autoClosingPairs: [ + { open: '{', close: '}' }, + { open: '[', close: ']' }, + { open: '(', close: ')' }, + { open: '\"', close: '\"', notIn: ['string'] }, + { open: 'r\"', close: '\"', notIn: ['string', 'comment'] }, + { open: 'R\"', close: '\"', notIn: ['string', 'comment'] }, + { open: 'u\"', close: '\"', notIn: ['string', 'comment'] }, + { open: 'U\"', close: '\"', notIn: ['string', 'comment'] }, + { open: 'f\"', close: '\"', notIn: ['string', 'comment'] }, + { open: 'F\"', close: '\"', notIn: ['string', 'comment'] }, + { open: 'b\"', close: '\"', notIn: ['string', 'comment'] }, + { open: 'B\"', close: '\"', notIn: ['string', 'comment'] }, + { open: '\'', close: '\'', notIn: ['string', 'comment'] }, + { open: 'r\'', close: '\'', notIn: ['string', 'comment'] }, + { open: 'R\'', close: '\'', notIn: ['string', 'comment'] }, + { open: 'u\'', close: '\'', notIn: ['string', 'comment'] }, + { open: 'U\'', close: '\'', notIn: ['string', 'comment'] }, + { open: 'f\'', close: '\'', notIn: ['string', 'comment'] }, + { open: 'F\'', close: '\'', notIn: ['string', 'comment'] }, + { open: 'b\'', close: '\'', notIn: ['string', 'comment'] }, + { open: 'B\'', close: '\'', notIn: ['string', 'comment'] }, + { open: '`', close: '`', notIn: ['string'] } + ], + })); + } + } + const mode = new PythonMode(); + usingCursor({ + text: [ + 'foo\'hello\'' + ], + languageIdentifier: mode.getLanguageIdentifier() + }, (model, cursor) => { + assertType(model, cursor, 1, 4, '(', '(', `does not auto close @ (1, 4)`); + }); + mode.dispose(); + }); + test('issue #78975 - Parentheses swallowing does not work when parentheses are inserted by autocomplete', () => { let mode = new AutoClosingMode(); usingCursor({ From 7ad58a9bd727ddf77bce49bc2e3b33f52765156d Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Fri, 24 Jan 2020 07:39:34 -0800 Subject: [PATCH 089/801] extensions: allow built-in extensions on different qualities (#89199) Adds a new optional `forQualities` property to built-in extensions, and filters that as appropriate for different builds. --- build/builtInExtensions.json | 16 +++++++++++++ build/builtin/browser-main.js | 8 ++++++- build/lib/extensions.ts | 6 ++++- .../common/extensionManagementUtil.ts | 23 ++++++++++++++++++- .../node/extensionManagementService.ts | 9 ++++---- .../cachedExtensionScanner.ts | 9 ++++++-- 6 files changed, 61 insertions(+), 10 deletions(-) diff --git a/build/builtInExtensions.json b/build/builtInExtensions.json index 6a9ef0b745e..2db43516b84 100644 --- a/build/builtInExtensions.json +++ b/build/builtInExtensions.json @@ -43,5 +43,21 @@ }, "publisherDisplayName": "Microsoft" } + }, + { + "name": "ms-vscode.js-debug-nightly", + "version": "latest", + "forQualities": ["insider"], + "repo": "https://github.com/Microsoft/vscode-js-debug", + "metadata": { + "id": "7acbb4ce-c85a-49d4-8d95-a8054406ae97", + "publisherId": { + "publisherId": "5f5636e7-69ed-4afe-b5d6-8d231fb3d3ee", + "publisherName": "ms-vscode", + "displayName": "Microsoft", + "flags": "verified" + }, + "publisherDisplayName": "Microsoft" + } } ] diff --git a/build/builtin/browser-main.js b/build/builtin/browser-main.js index 60b30655c0c..a7618454656 100644 --- a/build/builtin/browser-main.js +++ b/build/builtin/browser-main.js @@ -10,6 +10,7 @@ const os = require('os'); const { remote } = require('electron'); const dialog = remote.dialog; +const productJsonPath = path.join(__dirname, '..', '..', 'product.json'); const builtInExtensionsPath = path.join(__dirname, '..', 'builtInExtensions.json'); const controlFilePath = path.join(os.homedir(), '.vscode-oss-dev', 'extensions', 'control.json'); @@ -51,6 +52,7 @@ function render(el, state) { } const ul = document.createElement('ul'); + const { quality } = readJson(productJsonPath); const { builtin, control } = state; for (const ext of builtin) { @@ -61,6 +63,10 @@ function render(el, state) { const name = document.createElement('code'); name.textContent = ext.name; + if (quality && ext.forQualities && !ext.forQualities.includes(quality)) { + name.textContent += ` (only on ${ext.forQualities.join(', ')})`; + } + li.appendChild(name); const form = document.createElement('form'); @@ -123,4 +129,4 @@ function main() { render(el, { builtin, control }); } -window.onload = main; \ No newline at end of file +window.onload = main; diff --git a/build/lib/extensions.ts b/build/lib/extensions.ts index 05bc7094846..9c90cb60c47 100644 --- a/build/lib/extensions.ts +++ b/build/lib/extensions.ts @@ -27,6 +27,7 @@ const util = require('./util'); const root = path.dirname(path.dirname(__dirname)); const commit = util.getVersion(root); const sourceMappingURLBase = `https://ticino.blob.core.windows.net/sourcemaps/${commit}`; +const product = require('../../product.json'); function fromLocal(extensionPath: string): Stream { const webpackFilename = path.join(extensionPath, 'extension.webpack.config.js'); @@ -219,16 +220,19 @@ const excludedExtensions = [ 'vscode-test-resolver', 'ms-vscode.node-debug', 'ms-vscode.node-debug2', + 'ms.vscode.js-debug-nightly' ]; interface IBuiltInExtension { name: string; version: string; repo: string; + forQualities?: ReadonlyArray; metadata: any; } -const builtInExtensions: IBuiltInExtension[] = require('../builtInExtensions.json'); +const builtInExtensions = (require('../builtInExtensions.json')) + .filter(({ forQualities }) => !product.quality || forQualities?.includes?.(product.quality) !== false); export function packageLocalExtensionsStream(): NodeJS.ReadWriteStream { const localExtensionDescriptions = (glob.sync('extensions/*/package.json')) diff --git a/src/vs/platform/extensionManagement/common/extensionManagementUtil.ts b/src/vs/platform/extensionManagement/common/extensionManagementUtil.ts index bad92e00651..71d3fb133f6 100644 --- a/src/vs/platform/extensionManagement/common/extensionManagementUtil.ts +++ b/src/vs/platform/extensionManagement/common/extensionManagementUtil.ts @@ -116,4 +116,25 @@ export function getMaliciousExtensionsSet(report: IReportedExtension[]): Set; + metadata: any; +} + +/** + * Parses the built-in extension JSON data and filters it down to the + * extensions built into this product quality. + */ +export function parseBuiltInExtensions(rawJson: string, productQuality: string | undefined) { + const parsed: IBuiltInExtension[] = JSON.parse(rawJson); + if (!productQuality) { + return parsed; + } + + return parsed.filter(ext => ext.forQualities?.indexOf?.(productQuality) !== -1); +} diff --git a/src/vs/platform/extensionManagement/node/extensionManagementService.ts b/src/vs/platform/extensionManagement/node/extensionManagementService.ts index 5bfc2bb66c1..5b056505913 100644 --- a/src/vs/platform/extensionManagement/node/extensionManagementService.ts +++ b/src/vs/platform/extensionManagement/node/extensionManagementService.ts @@ -21,7 +21,7 @@ import { INSTALL_ERROR_MALICIOUS, INSTALL_ERROR_INCOMPATIBLE } from 'vs/platform/extensionManagement/common/extensionManagement'; -import { areSameExtensions, getGalleryExtensionId, groupByExtension, getMaliciousExtensionsSet, getGalleryExtensionTelemetryData, getLocalExtensionTelemetryData, ExtensionIdentifierWithVersion } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; +import { areSameExtensions, getGalleryExtensionId, groupByExtension, getMaliciousExtensionsSet, getGalleryExtensionTelemetryData, getLocalExtensionTelemetryData, ExtensionIdentifierWithVersion, parseBuiltInExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { localizeManifest } from '../common/extensionNls'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { Limiter, createCancelablePromise, CancelablePromise, Queue } from 'vs/base/common/async'; @@ -45,6 +45,7 @@ import { CancellationToken } from 'vs/base/common/cancellation'; import { getPathFromAmdModule } from 'vs/base/common/amd'; import { getManifest } from 'vs/platform/extensionManagement/node/extensionManagementUtil'; import { IExtensionManifest, ExtensionType } from 'vs/platform/extensions/common/extensions'; +import { IProductService } from 'vs/platform/product/common/productService'; const ERROR_SCANNING_SYS_EXTENSIONS = 'scanningSystem'; const ERROR_SCANNING_USER_EXTENSIONS = 'scanningUser'; @@ -132,6 +133,7 @@ export class ExtensionManagementService extends Disposable implements IExtension @ILogService private readonly logService: ILogService, @optional(IDownloadService) private downloadService: IDownloadService, @ITelemetryService private readonly telemetryService: ITelemetryService, + @IProductService private readonly productService: IProductService, ) { super(); this.systemExtensionsPath = environmentService.builtinExtensionsPath; @@ -954,10 +956,7 @@ export class ExtensionManagementService extends Disposable implements IExtension private getDevSystemExtensionsList(): Promise { return pfs.readFile(this.devSystemExtensionsFilePath, 'utf8') - .then(raw => { - const parsed: { name: string }[] = JSON.parse(raw); - return parsed.map(({ name }) => name); - }); + .then(data => parseBuiltInExtensions(data, this.productService.quality).map(ext => ext.name)); } private toNonCancellablePromise(promise: Promise): Promise { diff --git a/src/vs/workbench/services/extensions/electron-browser/cachedExtensionScanner.ts b/src/vs/workbench/services/extensions/electron-browser/cachedExtensionScanner.ts index 866c955d5c7..83ace18fc92 100644 --- a/src/vs/workbench/services/extensions/electron-browser/cachedExtensionScanner.ts +++ b/src/vs/workbench/services/extensions/electron-browser/cachedExtensionScanner.ts @@ -22,6 +22,8 @@ import { INotificationService, Severity } from 'vs/platform/notification/common/ import { IHostService } from 'vs/workbench/services/host/browser/host'; import { ExtensionScanner, ExtensionScannerInput, IExtensionReference, IExtensionResolver, IRelaxedExtensionDescription } from 'vs/workbench/services/extensions/node/extensionPoints'; import { Translations, ILog } from 'vs/workbench/services/extensions/common/extensionPoints'; +import { IProductService } from 'vs/platform/product/common/productService'; +import { parseBuiltInExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; interface IExtensionCacheData { input: ExtensionScannerInput; @@ -56,6 +58,7 @@ export class CachedExtensionScanner { @IEnvironmentService private readonly _environmentService: IEnvironmentService, @IWorkbenchExtensionEnablementService private readonly _extensionEnablementService: IWorkbenchExtensionEnablementService, @IHostService private readonly _hostService: IHostService, + @IProductService private readonly _productService: IProductService, ) { this.scannedExtensions = new Promise((resolve, reject) => { this._scannedExtensionsResolve = resolve; @@ -78,7 +81,7 @@ export class CachedExtensionScanner { public async startScanningExtensions(log: ILog): Promise { try { const translations = await this.translationConfig; - const { system, user, development } = await CachedExtensionScanner._scanInstalledExtensions(this._hostService, this._notificationService, this._environmentService, this._extensionEnablementService, log, translations); + const { system, user, development } = await CachedExtensionScanner._scanInstalledExtensions(this._hostService, this._notificationService, this._environmentService, this._extensionEnablementService, this._productService, log, translations); let result = new Map(); system.forEach((systemExtension) => { @@ -238,6 +241,7 @@ export class CachedExtensionScanner { notificationService: INotificationService, environmentService: IEnvironmentService, extensionEnablementService: IWorkbenchExtensionEnablementService, + productService: IProductService, log: ILog, translations: Translations ): Promise<{ system: IExtensionDescription[], user: IExtensionDescription[], development: IExtensionDescription[] }> { @@ -261,7 +265,7 @@ export class CachedExtensionScanner { if (devMode) { const builtInExtensionsFilePath = path.normalize(path.join(getPathFromAmdModule(require, ''), '..', 'build', 'builtInExtensions.json')); const builtInExtensions = pfs.readFile(builtInExtensionsFilePath, 'utf8') - .then(raw => JSON.parse(raw)); + .then(raw => parseBuiltInExtensions(raw, productService.quality)); const controlFilePath = path.join(os.homedir(), '.vscode-oss-dev', 'extensions', 'control.json'); const controlFile = pfs.readFile(controlFilePath, 'utf8') @@ -321,6 +325,7 @@ interface IBuiltInExtension { name: string; version: string; repo: string; + forQualities?: ReadonlyArray; } interface IBuiltInExtensionControl { From aac1e3d12cdf6bcf0664073a0f301ca064c1e4cd Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Fri, 24 Jan 2020 17:18:51 +0100 Subject: [PATCH 090/801] fix default styles for class, enum, interface --- .../platform/theme/common/tokenClassificationRegistry.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/vs/platform/theme/common/tokenClassificationRegistry.ts b/src/vs/platform/theme/common/tokenClassificationRegistry.ts index 5d6af25757c..2c8860d6c69 100644 --- a/src/vs/platform/theme/common/tokenClassificationRegistry.ts +++ b/src/vs/platform/theme/common/tokenClassificationRegistry.ts @@ -376,11 +376,11 @@ function registerDefaultClassifications(): void { registerTokenType('namespace', nls.localize('namespace', "Style for namespaces."), [['entity.name.namespace']]); - registerTokenType('type', nls.localize('type', "Style for types."), [['entity.name.type'], ['entity.name.class'], ['support.type'], ['support.class']]); + registerTokenType('type', nls.localize('type', "Style for types."), [['entity.name.type'], ['support.type'], ['support.class']]); registerTokenType('struct', nls.localize('struct', "Style for structs."), [['storage.type.struct']], 'type'); - registerTokenType('class', nls.localize('class', "Style for classes."), [['entity.name.class']], 'type'); - registerTokenType('interface', nls.localize('interface', "Style for interfaces."), undefined, 'type'); - registerTokenType('enum', nls.localize('enum', "Style for enums."), undefined, 'type'); + registerTokenType('class', nls.localize('class', "Style for classes."), [['entity.name.type.class']], 'type'); + registerTokenType('interface', nls.localize('interface', "Style for interfaces."), [['entity.name.type.interface']], 'type'); + registerTokenType('enum', nls.localize('enum', "Style for enums."), [['entity.name.type.enum']], 'type'); registerTokenType('typeParameter', nls.localize('typeParameter', "Style for type parameters."), undefined, 'type'); registerTokenType('function', nls.localize('function', "Style for functions"), [['entity.name.function'], ['support.function']]); From 481923c44e094c07fbfecbec0efd01dc43cdeed8 Mon Sep 17 00:00:00 2001 From: SteVen Batten <6561887+sbatten@users.noreply.github.com> Date: Fri, 24 Jan 2020 08:23:34 -0800 Subject: [PATCH 091/801] migrate search to use new setting (#89190) * migrate search to use new setting * rename * add getViewLocation api to vds for search * remove unreferenced location option --- .../browser/parts/views/customView.ts | 5 +- .../browser/parts/views/viewPaneContainer.ts | 17 +- src/vs/workbench/browser/parts/views/views.ts | 22 +++ src/vs/workbench/common/panecomposite.ts | 3 +- src/vs/workbench/common/viewPaneContainer.ts | 16 -- src/vs/workbench/common/views.ts | 19 ++- .../contrib/bulkEdit/browser/bulkEditPane.ts | 4 +- .../contrib/debug/browser/breakpointsView.ts | 4 +- .../contrib/debug/browser/callStackView.ts | 4 +- .../debug/browser/loadedScriptsView.ts | 4 +- .../workbench/contrib/debug/browser/repl.ts | 5 +- .../contrib/debug/browser/startView.ts | 4 +- .../contrib/debug/browser/variablesView.ts | 4 +- .../debug/browser/watchExpressionsView.ts | 4 +- .../extensions/browser/extensionsViews.ts | 7 +- .../contrib/extensions/common/extensions.ts | 2 +- .../contrib/files/browser/views/emptyView.ts | 4 +- .../files/browser/views/explorerView.ts | 4 +- .../files/browser/views/openEditorsView.ts | 4 +- .../contrib/markers/browser/markersView.ts | 4 +- .../contrib/outline/browser/outlinePane.ts | 4 +- .../contrib/remote/browser/remote.ts | 3 +- .../contrib/remote/browser/tunnelView.ts | 5 +- .../workbench/contrib/scm/browser/mainPane.ts | 5 +- .../contrib/scm/browser/repositoryPane.ts | 5 +- .../search/browser/search.contribution.ts | 134 ++++++--------- .../contrib/search/browser/searchActions.ts | 156 +++++++----------- .../contrib/search/browser/searchPanel.ts | 70 -------- .../contrib/search/browser/searchView.ts | 27 +-- .../contrib/search/browser/searchWidget.ts | 5 +- .../contrib/search/common/constants.ts | 2 - .../progress/test/progressIndicator.test.ts | 3 +- .../views/browser/viewDescriptorService.ts | 25 +++ 33 files changed, 259 insertions(+), 325 deletions(-) delete mode 100644 src/vs/workbench/common/viewPaneContainer.ts delete mode 100644 src/vs/workbench/contrib/search/browser/searchPanel.ts diff --git a/src/vs/workbench/browser/parts/views/customView.ts b/src/vs/workbench/browser/parts/views/customView.ts index 5aec72c4959..eecea644351 100644 --- a/src/vs/workbench/browser/parts/views/customView.ts +++ b/src/vs/workbench/browser/parts/views/customView.ts @@ -13,7 +13,7 @@ import { IContextMenuService } from 'vs/platform/contextview/browser/contextView import { IMenuService, MenuId, MenuItemAction } from 'vs/platform/actions/common/actions'; import { ContextAwareMenuEntryActionViewItem, createAndFillInContextMenuActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; -import { ITreeView, ITreeItem, TreeItemCollapsibleState, ITreeViewDataProvider, TreeViewItemHandleArg, ITreeViewDescriptor, IViewsRegistry, ViewContainer, ITreeItemLabel, Extensions } from 'vs/workbench/common/views'; +import { ITreeView, ITreeItem, TreeItemCollapsibleState, ITreeViewDataProvider, TreeViewItemHandleArg, ITreeViewDescriptor, IViewsRegistry, ViewContainer, ITreeItemLabel, Extensions, IViewDescriptorService } from 'vs/workbench/common/views'; import { IViewletViewOptions } from 'vs/workbench/browser/parts/views/viewsViewlet'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { INotificationService } from 'vs/platform/notification/common/notification'; @@ -54,9 +54,10 @@ export class CustomTreeViewPane extends ViewPane { @IContextMenuService contextMenuService: IContextMenuService, @IConfigurationService configurationService: IConfigurationService, @IContextKeyService contextKeyService: IContextKeyService, + @IViewDescriptorService viewDescriptorService: IViewDescriptorService, @IInstantiationService instantiationService: IInstantiationService, ) { - super({ ...(options as IViewPaneOptions), ariaHeaderLabel: options.title, titleMenuId: MenuId.ViewTitle }, keybindingService, contextMenuService, configurationService, contextKeyService, instantiationService); + super({ ...(options as IViewPaneOptions), ariaHeaderLabel: options.title, titleMenuId: MenuId.ViewTitle }, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService); const { treeView } = (Registry.as(Extensions.ViewsRegistry).getView(options.id)); this.treeView = treeView; this._register(this.treeView.onDidChangeActions(() => this.updateActions(), this)); diff --git a/src/vs/workbench/browser/parts/views/viewPaneContainer.ts b/src/vs/workbench/browser/parts/views/viewPaneContainer.ts index d139b8680f0..0722ccb4bf7 100644 --- a/src/vs/workbench/browser/parts/views/viewPaneContainer.ts +++ b/src/vs/workbench/browser/parts/views/viewPaneContainer.ts @@ -8,7 +8,7 @@ import * as nls from 'vs/nls'; import { Event, Emitter } from 'vs/base/common/event'; import { ColorIdentifier } from 'vs/platform/theme/common/colorRegistry'; import { attachStyler, IColorMapping } from 'vs/platform/theme/common/styler'; -import { SIDE_BAR_DRAG_AND_DROP_BACKGROUND, SIDE_BAR_SECTION_HEADER_FOREGROUND, SIDE_BAR_SECTION_HEADER_BACKGROUND, SIDE_BAR_SECTION_HEADER_BORDER } from 'vs/workbench/common/theme'; +import { SIDE_BAR_DRAG_AND_DROP_BACKGROUND, SIDE_BAR_SECTION_HEADER_FOREGROUND, SIDE_BAR_SECTION_HEADER_BACKGROUND, SIDE_BAR_SECTION_HEADER_BORDER, PANEL_BACKGROUND, SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme'; import { append, $, trackFocus, toggleClass, EventType, isAncestor, Dimension, addDisposableListener } from 'vs/base/browser/dom'; import { IDisposable, combinedDisposable, dispose, toDisposable } from 'vs/base/common/lifecycle'; import { firstIndex } from 'vs/base/common/arrays'; @@ -25,7 +25,7 @@ import { PaneView, IPaneViewOptions, IPaneOptions, Pane, DefaultPaneDndControlle import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; import { StandardMouseEvent } from 'vs/base/browser/mouseEvent'; -import { Extensions as ViewContainerExtensions, IView, FocusedViewContext, IViewContainersRegistry, IViewDescriptor, ViewContainer, IViewDescriptorService } from 'vs/workbench/common/views'; +import { Extensions as ViewContainerExtensions, IView, FocusedViewContext, IViewContainersRegistry, IViewDescriptor, ViewContainer, IViewDescriptorService, ViewContainerLocation, IViewPaneContainer } from 'vs/workbench/common/views'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { assertIsDefined } from 'vs/base/common/types'; @@ -34,7 +34,6 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { IViewletViewOptions } from 'vs/workbench/browser/parts/views/viewsViewlet'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; -import { IViewPaneContainer } from 'vs/workbench/common/viewPaneContainer'; import { Component } from 'vs/workbench/common/component'; import { MenuId, MenuItemAction } from 'vs/platform/actions/common/actions'; import { ContextAwareMenuEntryActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem'; @@ -92,6 +91,7 @@ export abstract class ViewPane extends Pane implements IView { @IContextMenuService protected contextMenuService: IContextMenuService, @IConfigurationService protected readonly configurationService: IConfigurationService, @IContextKeyService contextKeyService: IContextKeyService, + @IViewDescriptorService private viewDescriptorService: IViewDescriptorService, @IInstantiationService protected instantiationService: IInstantiationService, ) { super(options); @@ -189,6 +189,14 @@ export abstract class ViewPane extends Pane implements IView { this._onDidChangeTitleArea.fire(); } + protected getProgressLocation(): string { + return this.viewDescriptorService.getViewContainer(this.id)!.id; + } + + protected getBackgroundColor(): string { + return this.viewDescriptorService.getViewLocation(this.id) === ViewContainerLocation.Panel ? PANEL_BACKGROUND : SIDE_BAR_BACKGROUND; + } + focus(): void { if (this.element) { this.element.focus(); @@ -602,13 +610,14 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer { protected onDidAddViews(added: IAddedViewDescriptorRef[]): ViewPane[] { const panesToAdd: { pane: ViewPane, size: number, index: number }[] = []; + for (const { viewDescriptor, collapsed, index, size } of added) { const pane = this.createView(viewDescriptor, { id: viewDescriptor.id, title: viewDescriptor.name, actionRunner: this.getActionRunner(), - expanded: !collapsed + expanded: !collapsed, }); pane.render(); diff --git a/src/vs/workbench/browser/parts/views/views.ts b/src/vs/workbench/browser/parts/views/views.ts index ebce4a178e5..09da4059856 100644 --- a/src/vs/workbench/browser/parts/views/views.ts +++ b/src/vs/workbench/browser/parts/views/views.ts @@ -23,6 +23,7 @@ import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { IPaneComposite } from 'vs/workbench/common/panecomposite'; import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; import type { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; +import { PaneComposite } from 'vs/workbench/browser/panecomposite'; export interface IViewState { visibleGlobal: boolean | undefined; @@ -565,6 +566,27 @@ export class ViewsService extends Disposable implements IViewsService { return undefined; } + getActiveViewWithId(id: string): IView | null { + const viewContainer = this.viewDescriptorService.getViewContainer(id); + if (viewContainer) { + const location = this.viewContainersRegistry.getViewContainerLocation(viewContainer); + + if (location === ViewContainerLocation.Sidebar) { + const activeViewlet = this.viewletService.getActiveViewlet(); + if (activeViewlet?.getId() === viewContainer.id) { + return activeViewlet.getViewPaneContainer().getView(id) ?? null; + } + } else if (location === ViewContainerLocation.Panel) { + const activePanel = this.panelService.getActivePanel(); + if (activePanel?.getId() === viewContainer.id && activePanel instanceof PaneComposite) { + return activePanel.getViewPaneContainer().getView(id) ?? null; + } + } + } + + return null; + } + async openView(id: string, focus: boolean): Promise { const viewContainer = this.viewDescriptorService.getViewContainer(id); if (viewContainer) { diff --git a/src/vs/workbench/common/panecomposite.ts b/src/vs/workbench/common/panecomposite.ts index 3c6de0c3a60..e33f240f147 100644 --- a/src/vs/workbench/common/panecomposite.ts +++ b/src/vs/workbench/common/panecomposite.ts @@ -3,9 +3,8 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IView } from 'vs/workbench/common/views'; +import { IView, IViewPaneContainer } from 'vs/workbench/common/views'; import { IComposite } from 'vs/workbench/common/composite'; -import { IViewPaneContainer } from 'vs/workbench/common/viewPaneContainer'; export interface IPaneComposite extends IComposite { openView(id: string, focus?: boolean): IView; diff --git a/src/vs/workbench/common/viewPaneContainer.ts b/src/vs/workbench/common/viewPaneContainer.ts deleted file mode 100644 index 0c8f841fd6e..00000000000 --- a/src/vs/workbench/common/viewPaneContainer.ts +++ /dev/null @@ -1,16 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { IAction, IActionViewItem } from 'vs/base/common/actions'; - -export interface IViewPaneContainer { - setVisible(visible: boolean): void; - isVisible(): boolean; - focus(): void; - getActions(): IAction[]; - getSecondaryActions(): IAction[]; - getActionViewItem(action: IAction): IActionViewItem | undefined; - saveState(): void; -} diff --git a/src/vs/workbench/common/views.ts b/src/vs/workbench/common/views.ts index 6978d67d497..5a8f3edbd07 100644 --- a/src/vs/workbench/common/views.ts +++ b/src/vs/workbench/common/views.ts @@ -15,10 +15,9 @@ import { ThemeIcon } from 'vs/platform/theme/common/themeService'; import { values, keys, getOrSet } from 'vs/base/common/map'; import { Registry } from 'vs/platform/registry/common/platform'; import { IKeybindings } from 'vs/platform/keybinding/common/keybindingsRegistry'; -import { IAction } from 'vs/base/common/actions'; +import { IAction, IActionViewItem } from 'vs/base/common/actions'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { flatten } from 'vs/base/common/arrays'; -import { IViewPaneContainer } from 'vs/workbench/common/viewPaneContainer'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; export const TEST_VIEW_CONTAINER_ID = 'workbench.view.extension.test'; @@ -357,6 +356,8 @@ export const IViewsService = createDecorator('viewsService'); export interface IViewsService { _serviceBrand: undefined; + getActiveViewWithId(id: string): IView | null; + openView(id: string, focus?: boolean): Promise; } @@ -375,6 +376,8 @@ export interface IViewDescriptorService { getViewContainer(viewId: string): ViewContainer | null; + getViewLocation(viewId: string): ViewContainerLocation | null; + getDefaultContainer(viewId: string): ViewContainer | null; } @@ -505,3 +508,15 @@ export interface IEditableData { startingValue?: string | null; onFinish: (value: string, success: boolean) => void; } + +export interface IViewPaneContainer { + setVisible(visible: boolean): void; + isVisible(): boolean; + focus(): void; + getActions(): IAction[]; + getSecondaryActions(): IAction[]; + getActionViewItem(action: IAction): IActionViewItem | undefined; + getView(viewId: string): IView | undefined; + saveState(): void; +} + diff --git a/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPane.ts b/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPane.ts index 3ea4de16047..7f98ad816fc 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPane.ts +++ b/src/vs/workbench/contrib/bulkEdit/browser/bulkEditPane.ts @@ -36,6 +36,7 @@ import { CancellationToken } from 'vs/base/common/cancellation'; import { ITextEditorOptions } from 'vs/platform/editor/common/editor'; import type { IAsyncDataTreeViewState } from 'vs/base/browser/ui/tree/asyncDataTree'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; +import { IViewDescriptorService } from 'vs/workbench/common/views'; const enum State { Data = 'data', @@ -76,13 +77,14 @@ export class BulkEditPane extends ViewPane { @IContextMenuService private readonly _contextMenuService: IContextMenuService, @IContextKeyService private readonly _contextKeyService: IContextKeyService, @IStorageService private readonly _storageService: IStorageService, + @IViewDescriptorService viewDescriptorService: IViewDescriptorService, @IKeybindingService keybindingService: IKeybindingService, @IContextMenuService contextMenuService: IContextMenuService, @IConfigurationService configurationService: IConfigurationService, ) { super( { ...options, titleMenuId: MenuId.BulkEditTitle }, - keybindingService, contextMenuService, configurationService, _contextKeyService, _instaService + keybindingService, contextMenuService, configurationService, _contextKeyService, viewDescriptorService, _instaService ); this.element.classList.add('bulk-edit-panel', 'show-file-icons'); diff --git a/src/vs/workbench/contrib/debug/browser/breakpointsView.ts b/src/vs/workbench/contrib/debug/browser/breakpointsView.ts index 1c2711791de..babb3753773 100644 --- a/src/vs/workbench/contrib/debug/browser/breakpointsView.ts +++ b/src/vs/workbench/contrib/debug/browser/breakpointsView.ts @@ -33,6 +33,7 @@ import { ILabelService } from 'vs/platform/label/common/label'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme'; import { Gesture } from 'vs/base/browser/touch'; +import { IViewDescriptorService } from 'vs/workbench/common/views'; const $ = dom.$; @@ -66,9 +67,10 @@ export class BreakpointsView extends ViewPane { @IEditorService private readonly editorService: IEditorService, @IContextViewService private readonly contextViewService: IContextViewService, @IConfigurationService configurationService: IConfigurationService, + @IViewDescriptorService viewDescriptorService: IViewDescriptorService, @IContextKeyService contextKeyService: IContextKeyService, ) { - super({ ...(options as IViewPaneOptions), ariaHeaderLabel: nls.localize('breakpointsSection', "Breakpoints Section") }, keybindingService, contextMenuService, configurationService, contextKeyService, instantiationService); + super({ ...(options as IViewPaneOptions), ariaHeaderLabel: nls.localize('breakpointsSection', "Breakpoints Section") }, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService); this.minimumBodySize = this.maximumBodySize = getExpandedBodySize(this.debugService.getModel()); this._register(this.debugService.getModel().onDidChangeBreakpoints(() => this.onBreakpointsChange())); diff --git a/src/vs/workbench/contrib/debug/browser/callStackView.ts b/src/vs/workbench/contrib/debug/browser/callStackView.ts index a8048eab4e7..cd760ff2dfe 100644 --- a/src/vs/workbench/contrib/debug/browser/callStackView.ts +++ b/src/vs/workbench/contrib/debug/browser/callStackView.ts @@ -35,6 +35,7 @@ import { STOP_ID, STOP_LABEL, DISCONNECT_ID, DISCONNECT_LABEL, RESTART_SESSION_I import { ICommandService } from 'vs/platform/commands/common/commands'; import { CollapseAction } from 'vs/workbench/browser/viewlet'; import { SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme'; +import { IViewDescriptorService } from 'vs/workbench/common/views'; const $ = dom.$; @@ -92,12 +93,13 @@ export class CallStackView extends ViewPane { @IDebugService private readonly debugService: IDebugService, @IKeybindingService keybindingService: IKeybindingService, @IInstantiationService instantiationService: IInstantiationService, + @IViewDescriptorService viewDescriptorService: IViewDescriptorService, @IEditorService private readonly editorService: IEditorService, @IConfigurationService configurationService: IConfigurationService, @IMenuService menuService: IMenuService, @IContextKeyService readonly contextKeyService: IContextKeyService, ) { - super({ ...(options as IViewPaneOptions), ariaHeaderLabel: nls.localize('callstackSection', "Call Stack Section") }, keybindingService, contextMenuService, configurationService, contextKeyService, instantiationService); + super({ ...(options as IViewPaneOptions), ariaHeaderLabel: nls.localize('callstackSection', "Call Stack Section") }, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService); this.callStackItemType = CONTEXT_CALLSTACK_ITEM_TYPE.bindTo(contextKeyService); this.contributedContextMenu = menuService.createMenu(MenuId.DebugCallStackContext, contextKeyService); diff --git a/src/vs/workbench/contrib/debug/browser/loadedScriptsView.ts b/src/vs/workbench/contrib/debug/browser/loadedScriptsView.ts index bbb86f09dbd..52009d76a1f 100644 --- a/src/vs/workbench/contrib/debug/browser/loadedScriptsView.ts +++ b/src/vs/workbench/contrib/debug/browser/loadedScriptsView.ts @@ -37,6 +37,7 @@ import { ILabelService } from 'vs/platform/label/common/label'; import { SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme'; import type { ICompressedTreeNode } from 'vs/base/browser/ui/tree/compressedObjectTreeModel'; import type { ICompressibleTreeRenderer } from 'vs/base/browser/ui/tree/objectTree'; +import { IViewDescriptorService } from 'vs/workbench/common/views'; const NEW_STYLE_COMPRESS = true; @@ -415,6 +416,7 @@ export class LoadedScriptsView extends ViewPane { @IContextMenuService contextMenuService: IContextMenuService, @IKeybindingService keybindingService: IKeybindingService, @IInstantiationService instantiationService: IInstantiationService, + @IViewDescriptorService viewDescriptorService: IViewDescriptorService, @IConfigurationService configurationService: IConfigurationService, @IEditorService private readonly editorService: IEditorService, @IContextKeyService readonly contextKeyService: IContextKeyService, @@ -423,7 +425,7 @@ export class LoadedScriptsView extends ViewPane { @IDebugService private readonly debugService: IDebugService, @ILabelService private readonly labelService: ILabelService ) { - super({ ...(options as IViewPaneOptions), ariaHeaderLabel: nls.localize('loadedScriptsSection', "Loaded Scripts Section") }, keybindingService, contextMenuService, configurationService, contextKeyService, instantiationService); + super({ ...(options as IViewPaneOptions), ariaHeaderLabel: nls.localize('loadedScriptsSection', "Loaded Scripts Section") }, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService); this.loadedScriptsItemType = CONTEXT_LOADED_SCRIPTS_ITEM_TYPE.bindTo(contextKeyService); } diff --git a/src/vs/workbench/contrib/debug/browser/repl.ts b/src/vs/workbench/contrib/debug/browser/repl.ts index d9381e21706..8a679da1859 100644 --- a/src/vs/workbench/contrib/debug/browser/repl.ts +++ b/src/vs/workbench/contrib/debug/browser/repl.ts @@ -55,7 +55,7 @@ import { ReplDelegate, ReplVariablesRenderer, ReplSimpleElementsRenderer, ReplEv import { localize } from 'vs/nls'; import { ViewPane, IViewPaneOptions } from 'vs/workbench/browser/parts/views/viewPaneContainer'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; -import { IViewsService } from 'vs/workbench/common/views'; +import { IViewsService, IViewDescriptorService } from 'vs/workbench/common/views'; const $ = dom.$; @@ -109,6 +109,7 @@ export class Repl extends ViewPane implements IPrivateReplService, IHistoryNavig @IModelService private readonly modelService: IModelService, @IContextKeyService private readonly contextKeyService: IContextKeyService, @ICodeEditorService codeEditorService: ICodeEditorService, + @IViewDescriptorService viewDescriptorService: IViewDescriptorService, @IContextMenuService contextMenuService: IContextMenuService, @IConfigurationService configurationService: IConfigurationService, @ITextResourcePropertiesService private readonly textResourcePropertiesService: ITextResourcePropertiesService, @@ -116,7 +117,7 @@ export class Repl extends ViewPane implements IPrivateReplService, IHistoryNavig @IEditorService private readonly editorService: IEditorService, @IKeybindingService keybindingService: IKeybindingService ) { - super({ ...(options as IViewPaneOptions), id: REPL_VIEW_ID, ariaHeaderLabel: localize('debugConsole', "Debug Console") }, keybindingService, contextMenuService, configurationService, contextKeyService, instantiationService); + super({ ...(options as IViewPaneOptions), id: REPL_VIEW_ID, ariaHeaderLabel: localize('debugConsole', "Debug Console") }, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService); this.history = new HistoryNavigator(JSON.parse(this.storageService.get(HISTORY_STORAGE_KEY, StorageScope.WORKSPACE, '[]')), 50); codeEditorService.registerDecorationType(DECORATION_KEY, {}); diff --git a/src/vs/workbench/contrib/debug/browser/startView.ts b/src/vs/workbench/contrib/debug/browser/startView.ts index cd6218472de..7897662627d 100644 --- a/src/vs/workbench/contrib/debug/browser/startView.ts +++ b/src/vs/workbench/contrib/debug/browser/startView.ts @@ -25,6 +25,7 @@ import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { KeyCode } from 'vs/base/common/keyCodes'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { IViewDescriptorService } from 'vs/workbench/common/views'; const $ = dom.$; interface DebugStartMetrics { @@ -73,9 +74,10 @@ export class StartView extends ViewPane { @IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService, @IFileDialogService private readonly dialogService: IFileDialogService, @IInstantiationService instantiationService: IInstantiationService, + @IViewDescriptorService viewDescriptorService: IViewDescriptorService, @ITelemetryService private readonly telemetryService: ITelemetryService ) { - super({ ...(options as IViewPaneOptions), ariaHeaderLabel: localize('debugStart', "Debug Start Section") }, keybindingService, contextMenuService, configurationService, contextKeyService, instantiationService); + super({ ...(options as IViewPaneOptions), ariaHeaderLabel: localize('debugStart', "Debug Start Section") }, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService); this._register(editorService.onDidActiveEditorChange(() => this.updateView())); this._register(this.debugService.getConfigurationManager().onDidRegisterDebugger(() => this.updateView())); } diff --git a/src/vs/workbench/contrib/debug/browser/variablesView.ts b/src/vs/workbench/contrib/debug/browser/variablesView.ts index 71b9e9f8d32..14b902b3248 100644 --- a/src/vs/workbench/contrib/debug/browser/variablesView.ts +++ b/src/vs/workbench/contrib/debug/browser/variablesView.ts @@ -31,6 +31,7 @@ import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { dispose } from 'vs/base/common/lifecycle'; import { SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme'; +import { IViewDescriptorService } from 'vs/workbench/common/views'; const $ = dom.$; let forgetScopes = true; @@ -51,10 +52,11 @@ export class VariablesView extends ViewPane { @IKeybindingService keybindingService: IKeybindingService, @IConfigurationService configurationService: IConfigurationService, @IInstantiationService instantiationService: IInstantiationService, + @IViewDescriptorService viewDescriptorService: IViewDescriptorService, @IClipboardService private readonly clipboardService: IClipboardService, @IContextKeyService contextKeyService: IContextKeyService ) { - super({ ...(options as IViewPaneOptions), ariaHeaderLabel: nls.localize('variablesSection', "Variables Section") }, keybindingService, contextMenuService, configurationService, contextKeyService, instantiationService); + super({ ...(options as IViewPaneOptions), ariaHeaderLabel: nls.localize('variablesSection', "Variables Section") }, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService); // Use scheduler to prevent unnecessary flashing this.onFocusStackFrameScheduler = new RunOnceScheduler(async () => { diff --git a/src/vs/workbench/contrib/debug/browser/watchExpressionsView.ts b/src/vs/workbench/contrib/debug/browser/watchExpressionsView.ts index e21d0c884a7..ffe3e2b7162 100644 --- a/src/vs/workbench/contrib/debug/browser/watchExpressionsView.ts +++ b/src/vs/workbench/contrib/debug/browser/watchExpressionsView.ts @@ -31,6 +31,7 @@ import { variableSetEmitter, VariablesRenderer } from 'vs/workbench/contrib/debu import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { dispose } from 'vs/base/common/lifecycle'; import { SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme'; +import { IViewDescriptorService } from 'vs/workbench/common/views'; const MAX_VALUE_RENDER_LENGTH_IN_VIEWLET = 1024; let ignoreVariableSetEmitter = false; @@ -48,10 +49,11 @@ export class WatchExpressionsView extends ViewPane { @IDebugService private readonly debugService: IDebugService, @IKeybindingService keybindingService: IKeybindingService, @IInstantiationService instantiationService: IInstantiationService, + @IViewDescriptorService viewDescriptorService: IViewDescriptorService, @IConfigurationService configurationService: IConfigurationService, @IContextKeyService contextKeyService: IContextKeyService, ) { - super({ ...(options as IViewPaneOptions), ariaHeaderLabel: nls.localize('watchExpressionsSection', "Watch Expressions Section") }, keybindingService, contextMenuService, configurationService, contextKeyService, instantiationService); + super({ ...(options as IViewPaneOptions), ariaHeaderLabel: nls.localize('watchExpressionsSection', "Watch Expressions Section") }, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService); this.onWatchExpressionsUpdatedScheduler = new RunOnceScheduler(() => { this.needsRefresh = false; diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts b/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts index 477532889fd..e4acd0afe27 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts @@ -49,6 +49,7 @@ import { SeverityIcon } from 'vs/platform/severityIcon/common/severityIcon'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme'; import { IMenuService } from 'vs/platform/actions/common/actions'; +import { IViewDescriptorService } from 'vs/workbench/common/views'; // Extensions that are automatically classified as Programming Language extensions, but should be Feature extensions const FORCE_FEATURE_EXTENSIONS = ['vscode.git', 'vscode.search-result']; @@ -108,9 +109,10 @@ export class ExtensionsListView extends ViewPane { @IExtensionManagementServerService protected readonly extensionManagementServerService: IExtensionManagementServerService, @IProductService protected readonly productService: IProductService, @IContextKeyService private readonly contextKeyService: IContextKeyService, + @IViewDescriptorService viewDescriptorService: IViewDescriptorService, @IMenuService private readonly menuService: IMenuService, ) { - super({ ...(options as IViewPaneOptions), ariaHeaderLabel: options.title, showActionsAlways: true }, keybindingService, contextMenuService, configurationService, contextKeyService, instantiationService); + super({ ...(options as IViewPaneOptions), ariaHeaderLabel: options.title, showActionsAlways: true }, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService); this.server = options.server; } @@ -866,6 +868,7 @@ export class ServerExtensionsView extends ExtensionsListView { @INotificationService notificationService: INotificationService, @IKeybindingService keybindingService: IKeybindingService, @IContextMenuService contextMenuService: IContextMenuService, + @IViewDescriptorService viewDescriptorService: IViewDescriptorService, @IInstantiationService instantiationService: IInstantiationService, @IThemeService themeService: IThemeService, @IExtensionService extensionService: IExtensionService, @@ -883,7 +886,7 @@ export class ServerExtensionsView extends ExtensionsListView { @IMenuService menuService: IMenuService, ) { options.server = server; - super(options, notificationService, keybindingService, contextMenuService, instantiationService, themeService, extensionService, extensionsWorkbenchService, editorService, tipsService, telemetryService, configurationService, contextService, experimentService, workbenchThemeService, extensionManagementServerService, productService, contextKeyService, menuService); + super(options, notificationService, keybindingService, contextMenuService, instantiationService, themeService, extensionService, extensionsWorkbenchService, editorService, tipsService, telemetryService, configurationService, contextService, experimentService, workbenchThemeService, extensionManagementServerService, productService, contextKeyService, viewDescriptorService, menuService); this._register(onDidChangeTitle(title => this.updateTitle(title))); } diff --git a/src/vs/workbench/contrib/extensions/common/extensions.ts b/src/vs/workbench/contrib/extensions/common/extensions.ts index d54dd9ac3f2..c3264b67f81 100644 --- a/src/vs/workbench/contrib/extensions/common/extensions.ts +++ b/src/vs/workbench/contrib/extensions/common/extensions.ts @@ -13,7 +13,7 @@ import { Disposable } from 'vs/base/common/lifecycle'; import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { IExtensionManifest, ExtensionType } from 'vs/platform/extensions/common/extensions'; import { URI } from 'vs/base/common/uri'; -import { IViewPaneContainer } from 'vs/workbench/common/viewPaneContainer'; +import { IViewPaneContainer } from 'vs/workbench/common/views'; export const VIEWLET_ID = 'workbench.view.extensions'; diff --git a/src/vs/workbench/contrib/files/browser/views/emptyView.ts b/src/vs/workbench/contrib/files/browser/views/emptyView.ts index 350b41d643c..a0880ae1d0e 100644 --- a/src/vs/workbench/contrib/files/browser/views/emptyView.ts +++ b/src/vs/workbench/contrib/files/browser/views/emptyView.ts @@ -25,6 +25,7 @@ import { ILabelService } from 'vs/platform/label/common/label'; import { Schemas } from 'vs/base/common/network'; import { isWeb } from 'vs/base/common/platform'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { IViewDescriptorService } from 'vs/workbench/common/views'; export class EmptyView extends ViewPane { @@ -37,6 +38,7 @@ export class EmptyView extends ViewPane { constructor( options: IViewletViewOptions, @IThemeService private readonly themeService: IThemeService, + @IViewDescriptorService viewDescriptorService: IViewDescriptorService, @IInstantiationService instantiationService: IInstantiationService, @IKeybindingService keybindingService: IKeybindingService, @IContextMenuService contextMenuService: IContextMenuService, @@ -46,7 +48,7 @@ export class EmptyView extends ViewPane { @ILabelService private labelService: ILabelService, @IContextKeyService contextKeyService: IContextKeyService ) { - super({ ...(options as IViewPaneOptions), ariaHeaderLabel: nls.localize('explorerSection', "Files Explorer Section") }, keybindingService, contextMenuService, configurationService, contextKeyService, instantiationService); + super({ ...(options as IViewPaneOptions), ariaHeaderLabel: nls.localize('explorerSection', "Files Explorer Section") }, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService); this._register(this.contextService.onDidChangeWorkbenchState(() => this.setLabels())); this._register(this.labelService.onDidChangeFormatters(() => this.setLabels())); } diff --git a/src/vs/workbench/contrib/files/browser/views/explorerView.ts b/src/vs/workbench/contrib/files/browser/views/explorerView.ts index c95939c1f74..059dfa3fa4a 100644 --- a/src/vs/workbench/contrib/files/browser/views/explorerView.ts +++ b/src/vs/workbench/contrib/files/browser/views/explorerView.ts @@ -55,6 +55,7 @@ import { attachStyler, IColorMapping } from 'vs/platform/theme/common/styler'; import { ColorValue, listDropBackground } from 'vs/platform/theme/common/colorRegistry'; import { Color } from 'vs/base/common/color'; import { SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme'; +import { IViewDescriptorService } from 'vs/workbench/common/views'; interface IExplorerViewColors extends IColorMapping { listDropBackground?: ColorValue | undefined; @@ -149,6 +150,7 @@ export class ExplorerView extends ViewPane { constructor( options: IViewPaneOptions, @IContextMenuService contextMenuService: IContextMenuService, + @IViewDescriptorService viewDescriptorService: IViewDescriptorService, @IInstantiationService instantiationService: IInstantiationService, @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, @IProgressService private readonly progressService: IProgressService, @@ -167,7 +169,7 @@ export class ExplorerView extends ViewPane { @IClipboardService private clipboardService: IClipboardService, @IFileService private readonly fileService: IFileService ) { - super({ ...(options as IViewPaneOptions), id: ExplorerView.ID, ariaHeaderLabel: nls.localize('explorerSection', "Files Explorer Section") }, keybindingService, contextMenuService, configurationService, contextKeyService, instantiationService); + super({ ...(options as IViewPaneOptions), id: ExplorerView.ID, ariaHeaderLabel: nls.localize('explorerSection', "Files Explorer Section") }, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService); this.resourceContext = instantiationService.createInstance(ResourceContextKey); this._register(this.resourceContext); diff --git a/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts b/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts index a5c809c541b..23e04225b58 100644 --- a/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts +++ b/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts @@ -44,6 +44,7 @@ import { isWeb } from 'vs/base/common/platform'; import { IWorkingCopyService, IWorkingCopy, WorkingCopyCapabilities } from 'vs/workbench/services/workingCopy/common/workingCopyService'; import { SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme'; import { AutoSaveMode, IFilesConfigurationService } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; +import { IViewDescriptorService } from 'vs/workbench/common/views'; const $ = dom.$; @@ -68,6 +69,7 @@ export class OpenEditorsView extends ViewPane { constructor( options: IViewletViewOptions, @IInstantiationService instantiationService: IInstantiationService, + @IViewDescriptorService viewDescriptorService: IViewDescriptorService, @IContextMenuService contextMenuService: IContextMenuService, @IEditorService private readonly editorService: IEditorService, @IEditorGroupsService private readonly editorGroupService: IEditorGroupsService, @@ -83,7 +85,7 @@ export class OpenEditorsView extends ViewPane { super({ ...(options as IViewPaneOptions), ariaHeaderLabel: nls.localize({ key: 'openEditosrSection', comment: ['Open is an adjective'] }, "Open Editors Section"), - }, keybindingService, contextMenuService, configurationService, contextKeyService, instantiationService); + }, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService); this.structuralRefreshDelay = 0; this.listRefreshScheduler = new RunOnceScheduler(() => { diff --git a/src/vs/workbench/contrib/markers/browser/markersView.ts b/src/vs/workbench/contrib/markers/browser/markersView.ts index c3c6d6ab777..95398693f7e 100644 --- a/src/vs/workbench/contrib/markers/browser/markersView.ts +++ b/src/vs/workbench/contrib/markers/browser/markersView.ts @@ -50,6 +50,7 @@ import { KeyCode } from 'vs/base/common/keyCodes'; import { editorLightBulbForeground, editorLightBulbAutoFixForeground } from 'vs/platform/theme/common/colorRegistry'; import { ViewPane, IViewPaneOptions } from 'vs/workbench/browser/parts/views/viewPaneContainer'; import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; +import { IViewDescriptorService } from 'vs/workbench/common/views'; export function getMarkersView(panelService: IPanelService): MarkersView | undefined { const activePanel = panelService.getActivePanel(); @@ -102,6 +103,7 @@ export class MarkersView extends ViewPane implements IMarkerFilterController { constructor( options: IViewPaneOptions, @IInstantiationService instantiationService: IInstantiationService, + @IViewDescriptorService viewDescriptorService: IViewDescriptorService, @IEditorService private readonly editorService: IEditorService, @IConfigurationService configurationService: IConfigurationService, @ITelemetryService private readonly telemetryService: ITelemetryService, @@ -113,7 +115,7 @@ export class MarkersView extends ViewPane implements IMarkerFilterController { @IKeybindingService keybindingService: IKeybindingService, @IStorageService storageService: IStorageService, ) { - super({ ...(options as IViewPaneOptions), id: Constants.MARKERS_VIEW_ID, ariaHeaderLabel: Messages.MARKERS_PANEL_TITLE_PROBLEMS }, keybindingService, contextMenuService, configurationService, contextKeyService, instantiationService); + super({ ...(options as IViewPaneOptions), id: Constants.MARKERS_VIEW_ID, ariaHeaderLabel: Messages.MARKERS_PANEL_TITLE_PROBLEMS }, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService); this.panelFoucusContextKey = Constants.MarkerPanelFocusContextKey.bindTo(contextKeyService); this.panelState = new Memento(Constants.MARKERS_PANEL_STORAGE_ID, storageService).getMemento(StorageScope.WORKSPACE); this.markersViewModel = this._register(instantiationService.createInstance(MarkersViewModel, this.panelState['multiline'])); diff --git a/src/vs/workbench/contrib/outline/browser/outlinePane.ts b/src/vs/workbench/contrib/outline/browser/outlinePane.ts index 598b4cab644..629a8001b02 100644 --- a/src/vs/workbench/contrib/outline/browser/outlinePane.ts +++ b/src/vs/workbench/contrib/outline/browser/outlinePane.ts @@ -48,6 +48,7 @@ import { IDataSource } from 'vs/base/browser/ui/tree/tree'; import { IMarkerDecorationsService } from 'vs/editor/common/services/markersDecorationService'; import { MarkerSeverity } from 'vs/platform/markers/common/markers'; import { SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme'; +import { IViewDescriptorService } from 'vs/workbench/common/views'; class RequestState { @@ -257,6 +258,7 @@ export class OutlinePane extends ViewPane { constructor( options: IViewletViewOptions, @IInstantiationService private readonly _instantiationService: IInstantiationService, + @IViewDescriptorService viewDescriptorService: IViewDescriptorService, @IThemeService private readonly _themeService: IThemeService, @IStorageService private readonly _storageService: IStorageService, @IEditorService private readonly _editorService: IEditorService, @@ -266,7 +268,7 @@ export class OutlinePane extends ViewPane { @IContextKeyService contextKeyService: IContextKeyService, @IContextMenuService contextMenuService: IContextMenuService, ) { - super(options, keybindingService, contextMenuService, _configurationService, contextKeyService, _instantiationService); + super(options, keybindingService, contextMenuService, _configurationService, contextKeyService, viewDescriptorService, _instantiationService); this._outlineViewState.restore(this._storageService); this._contextKeyFocused = OutlineViewFocused.bindTo(contextKeyService); this._contextKeyFiltered = OutlineViewFiltered.bindTo(contextKeyService); diff --git a/src/vs/workbench/contrib/remote/browser/remote.ts b/src/vs/workbench/contrib/remote/browser/remote.ts index 2b48dd6a82f..a6f5e8b4cae 100644 --- a/src/vs/workbench/contrib/remote/browser/remote.ts +++ b/src/vs/workbench/contrib/remote/browser/remote.ts @@ -367,13 +367,14 @@ class HelpPanel extends ViewPane { @IContextKeyService protected contextKeyService: IContextKeyService, @IConfigurationService protected configurationService: IConfigurationService, @IInstantiationService protected readonly instantiationService: IInstantiationService, + @IViewDescriptorService viewDescriptorService: IViewDescriptorService, @IOpenerService protected openerService: IOpenerService, @IQuickInputService protected quickInputService: IQuickInputService, @ICommandService protected commandService: ICommandService, @IRemoteExplorerService protected readonly remoteExplorerService: IRemoteExplorerService, @IWorkbenchEnvironmentService protected readonly workbenchEnvironmentService: IWorkbenchEnvironmentService ) { - super(options, keybindingService, contextMenuService, configurationService, contextKeyService, instantiationService); + super(options, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService); } protected renderBody(container: HTMLElement): void { diff --git a/src/vs/workbench/contrib/remote/browser/tunnelView.ts b/src/vs/workbench/contrib/remote/browser/tunnelView.ts index ce7da57ef73..f7424c1835a 100644 --- a/src/vs/workbench/contrib/remote/browser/tunnelView.ts +++ b/src/vs/workbench/contrib/remote/browser/tunnelView.ts @@ -6,7 +6,7 @@ import 'vs/css!./media/tunnelView'; import * as nls from 'vs/nls'; import * as dom from 'vs/base/browser/dom'; -import { IViewDescriptor, IEditableData, IViewsService } from 'vs/workbench/common/views'; +import { IViewDescriptor, IEditableData, IViewsService, IViewDescriptorService } from 'vs/workbench/common/views'; import { WorkbenchAsyncDataTree, TreeResourceNavigator } from 'vs/platform/list/browser/listService'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IContextMenuService, IContextViewService } from 'vs/platform/contextview/browser/contextView'; @@ -423,6 +423,7 @@ export class TunnelPanel extends ViewPane { @IContextKeyService protected contextKeyService: IContextKeyService, @IConfigurationService protected configurationService: IConfigurationService, @IInstantiationService protected readonly instantiationService: IInstantiationService, + @IViewDescriptorService viewDescriptorService: IViewDescriptorService, @IOpenerService protected openerService: IOpenerService, @IQuickInputService protected quickInputService: IQuickInputService, @ICommandService protected commandService: ICommandService, @@ -432,7 +433,7 @@ export class TunnelPanel extends ViewPane { @IThemeService private readonly themeService: IThemeService, @IRemoteExplorerService private readonly remoteExplorerService: IRemoteExplorerService ) { - super(options, keybindingService, contextMenuService, configurationService, contextKeyService, instantiationService); + super(options, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService); this.tunnelTypeContext = TunnelTypeContextKey.bindTo(contextKeyService); this.tunnelCloseableContext = TunnelCloseableContextKey.bindTo(contextKeyService); this.tunnelViewFocusContext = TunnelViewFocusContextKey.bindTo(contextKeyService); diff --git a/src/vs/workbench/contrib/scm/browser/mainPane.ts b/src/vs/workbench/contrib/scm/browser/mainPane.ts index 695e9325011..5e7d5ae926f 100644 --- a/src/vs/workbench/contrib/scm/browser/mainPane.ts +++ b/src/vs/workbench/contrib/scm/browser/mainPane.ts @@ -29,7 +29,7 @@ import { renderCodicons } from 'vs/base/common/codicons'; import { escape } from 'vs/base/common/strings'; import { WorkbenchList } from 'vs/platform/list/browser/listService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { IViewDescriptor } from 'vs/workbench/common/views'; +import { IViewDescriptor, IViewDescriptorService } from 'vs/workbench/common/views'; import { SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; @@ -184,11 +184,12 @@ export class MainPane extends ViewPane { @IContextMenuService protected contextMenuService: IContextMenuService, @ISCMService protected scmService: ISCMService, @IInstantiationService instantiationService: IInstantiationService, + @IViewDescriptorService viewDescriptorService: IViewDescriptorService, @IContextKeyService private readonly contextKeyService: IContextKeyService, @IMenuService private readonly menuService: IMenuService, @IConfigurationService configurationService: IConfigurationService ) { - super(options, keybindingService, contextMenuService, configurationService, contextKeyService, instantiationService); + super(options, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService); } protected renderBody(container: HTMLElement): void { diff --git a/src/vs/workbench/contrib/scm/browser/repositoryPane.ts b/src/vs/workbench/contrib/scm/browser/repositoryPane.ts index b443b239d66..81f88f32cd7 100644 --- a/src/vs/workbench/contrib/scm/browser/repositoryPane.ts +++ b/src/vs/workbench/contrib/scm/browser/repositoryPane.ts @@ -41,7 +41,7 @@ import { URI } from 'vs/base/common/uri'; import { FileKind } from 'vs/platform/files/common/files'; import { compareFileNames } from 'vs/base/common/comparers'; import { FuzzyScore, createMatches } from 'vs/base/common/filters'; -import { IViewDescriptor } from 'vs/workbench/common/views'; +import { IViewDescriptor, IViewDescriptorService } from 'vs/workbench/common/views'; import { localize } from 'vs/nls'; import { flatten, find } from 'vs/base/common/arrays'; import { memoize } from 'vs/base/common/decorators'; @@ -619,13 +619,14 @@ export class RepositoryPane extends ViewPane { @INotificationService private readonly notificationService: INotificationService, @IEditorService protected editorService: IEditorService, @IInstantiationService protected instantiationService: IInstantiationService, + @IViewDescriptorService viewDescriptorService: IViewDescriptorService, @IConfigurationService protected configurationService: IConfigurationService, @IContextKeyService contextKeyService: IContextKeyService, @IMenuService protected menuService: IMenuService, @IStorageService private storageService: IStorageService, @IModelService private modelService: IModelService, ) { - super(options, keybindingService, contextMenuService, configurationService, contextKeyService, instantiationService); + super(options, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService); this.menus = instantiationService.createInstance(SCMMenus, this.repository.provider); this._register(this.menus); diff --git a/src/vs/workbench/contrib/search/browser/search.contribution.ts b/src/vs/workbench/contrib/search/browser/search.contribution.ts index 30d72f81113..bbea9b6271b 100644 --- a/src/vs/workbench/contrib/search/browser/search.contribution.ts +++ b/src/vs/workbench/contrib/search/browser/search.contribution.ts @@ -17,7 +17,7 @@ import { ToggleCaseSensitiveKeybinding, ToggleRegexKeybinding, ToggleWholeWordKe import * as nls from 'vs/nls'; import { ICommandAction, MenuId, MenuRegistry, SyncActionDescriptor } from 'vs/platform/actions/common/actions'; import { CommandsRegistry, ICommandHandler } from 'vs/platform/commands/common/commands'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IConfigurationService, ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; import { ConfigurationScope, Extensions as ConfigurationExtensions, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { IFileService } from 'vs/platform/files/common/files'; @@ -28,28 +28,25 @@ import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; import { IListService, WorkbenchListFocusContextKey, WorkbenchObjectTree } from 'vs/platform/list/browser/listService'; import { IQuickOpenService } from 'vs/platform/quickOpen/common/quickOpen'; import { Registry } from 'vs/platform/registry/common/platform'; -import { Extensions as PanelExtensions, PanelDescriptor, PanelRegistry } from 'vs/workbench/browser/panel'; import { defaultQuickOpenContextKey } from 'vs/workbench/browser/parts/quickopen/quickopen'; import { Extensions as QuickOpenExtensions, IQuickOpenRegistry, QuickOpenHandlerDescriptor } from 'vs/workbench/browser/quickopen'; import { Extensions as ActionExtensions, IWorkbenchActionRegistry } from 'vs/workbench/common/actions'; import { Extensions as WorkbenchExtensions, IWorkbenchContribution, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions'; -import { Extensions as ViewExtensions, IViewsRegistry, IViewContainersRegistry, ViewContainerLocation } from 'vs/workbench/common/views'; +import { Extensions as ViewExtensions, IViewsRegistry, IViewContainersRegistry, ViewContainerLocation, IViewDescriptorService, IViewsService } from 'vs/workbench/common/views'; import { getMultiSelectedResources } from 'vs/workbench/contrib/files/browser/files'; import { ExplorerFolderContext, ExplorerRootContext, FilesExplorerFocusCondition, IExplorerService, VIEWLET_ID as VIEWLET_ID_FILES } from 'vs/workbench/contrib/files/common/files'; import { OpenAnythingHandler } from 'vs/workbench/contrib/search/browser/openAnythingHandler'; import { OpenSymbolHandler } from 'vs/workbench/contrib/search/browser/openSymbolHandler'; import { registerContributions as replaceContributions } from 'vs/workbench/contrib/search/browser/replaceContributions'; import { clearHistoryCommand, ClearSearchResultsAction, CloseReplaceAction, CollapseDeepestExpandedLevelAction, copyAllCommand, copyMatchCommand, copyPathCommand, FocusNextInputAction, FocusNextSearchResultAction, FocusPreviousInputAction, FocusPreviousSearchResultAction, focusSearchListCommand, getSearchView, openSearchView, OpenSearchViewletAction, RefreshAction, RemoveAction, ReplaceAction, ReplaceAllAction, ReplaceAllInFolderAction, ReplaceInFilesAction, toggleCaseSensitiveCommand, toggleRegexCommand, toggleWholeWordCommand, FindInFilesCommand, ToggleSearchOnTypeAction, OpenResultsInEditorAction, ExpandAllAction, OpenSearchEditorAction, toggleSearchEditorCaseSensitiveCommand, toggleSearchEditorWholeWordCommand, toggleSearchEditorRegexCommand, toggleSearchEditorContextLinesCommand } from 'vs/workbench/contrib/search/browser/searchActions'; -import { SearchPanel } from 'vs/workbench/contrib/search/browser/searchPanel'; -import { SearchView, SearchViewPosition } from 'vs/workbench/contrib/search/browser/searchView'; +import { SearchView } from 'vs/workbench/contrib/search/browser/searchView'; import { registerContributions as searchWidgetContributions } from 'vs/workbench/contrib/search/browser/searchWidget'; import * as Constants from 'vs/workbench/contrib/search/common/constants'; import { getWorkspaceSymbols } from 'vs/workbench/contrib/search/common/search'; import { ISearchHistoryService, SearchHistoryService } from 'vs/workbench/contrib/search/common/searchHistoryService'; import { FileMatchOrMatch, ISearchWorkbenchService, RenderableMatch, SearchWorkbenchService, FileMatch, Match, FolderMatch } from 'vs/workbench/contrib/search/common/searchModel'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; -import { ISearchConfiguration, ISearchConfigurationProperties, PANEL_ID, VIEWLET_ID, VIEW_ID, SearchSortOrder } from 'vs/workbench/services/search/common/search'; +import { VIEWLET_ID, VIEW_ID, SearchSortOrder } from 'vs/workbench/services/search/common/search'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { ExplorerViewPaneContainer } from 'vs/workbench/contrib/files/browser/explorerViewlet'; @@ -76,7 +73,7 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ when: Constants.SearchViewVisibleKey, primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_J, handler: accessor => { - const searchView = getSearchView(accessor.get(IViewletService), accessor.get(IPanelService)); + const searchView = getSearchView(accessor.get(IViewsService)); if (searchView) { searchView.toggleQueryDetails(); } @@ -89,7 +86,7 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ when: ContextKeyExpr.and(Constants.SearchViewVisibleKey, Constants.FirstMatchFocusKey), primary: KeyMod.CtrlCmd | KeyCode.UpArrow, handler: (accessor, args: any) => { - const searchView = getSearchView(accessor.get(IViewletService), accessor.get(IPanelService)); + const searchView = getSearchView(accessor.get(IViewsService)); if (searchView) { searchView.focusPreviousInputBox(); } @@ -105,7 +102,7 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ primary: KeyMod.WinCtrl | KeyCode.Enter }, handler: (accessor, args: any) => { - const searchView = getSearchView(accessor.get(IViewletService), accessor.get(IPanelService)); + const searchView = getSearchView(accessor.get(IViewsService)); if (searchView) { const tree: WorkbenchObjectTree = searchView.getControl(); searchView.open(tree.getFocus()[0], false, true, true); @@ -119,7 +116,7 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ when: ContextKeyExpr.and(Constants.SearchViewVisibleKey, WorkbenchListFocusContextKey), primary: KeyCode.Escape, handler: (accessor, args: any) => { - const searchView = getSearchView(accessor.get(IViewletService), accessor.get(IPanelService)); + const searchView = getSearchView(accessor.get(IViewsService)); if (searchView) { searchView.cancelSearch(); } @@ -135,7 +132,7 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ primary: KeyMod.CtrlCmd | KeyCode.Backspace, }, handler: (accessor, args: any) => { - const searchView = getSearchView(accessor.get(IViewletService), accessor.get(IPanelService)); + const searchView = getSearchView(accessor.get(IViewsService)); if (searchView) { const tree: WorkbenchObjectTree = searchView.getControl(); accessor.get(IInstantiationService).createInstance(RemoveAction, tree, tree.getFocus()[0]!).run(); @@ -149,7 +146,7 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ when: ContextKeyExpr.and(Constants.SearchViewVisibleKey, Constants.ReplaceActiveKey, Constants.MatchFocusKey), primary: KeyMod.Shift | KeyMod.CtrlCmd | KeyCode.KEY_1, handler: (accessor, args: any) => { - const searchView = getSearchView(accessor.get(IViewletService), accessor.get(IPanelService)); + const searchView = getSearchView(accessor.get(IViewsService)); if (searchView) { const tree: WorkbenchObjectTree = searchView.getControl(); accessor.get(IInstantiationService).createInstance(ReplaceAction, tree, tree.getFocus()[0] as Match, searchView).run(); @@ -164,7 +161,7 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ primary: KeyMod.Shift | KeyMod.CtrlCmd | KeyCode.KEY_1, secondary: [KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.Enter], handler: (accessor, args: any) => { - const searchView = getSearchView(accessor.get(IViewletService), accessor.get(IPanelService)); + const searchView = getSearchView(accessor.get(IViewsService)); if (searchView) { const tree: WorkbenchObjectTree = searchView.getControl(); accessor.get(IInstantiationService).createInstance(ReplaceAllAction, searchView, tree.getFocus()[0] as FileMatch).run(); @@ -179,7 +176,7 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ primary: KeyMod.Shift | KeyMod.CtrlCmd | KeyCode.KEY_1, secondary: [KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.Enter], handler: (accessor, args: any) => { - const searchView = getSearchView(accessor.get(IViewletService), accessor.get(IPanelService)); + const searchView = getSearchView(accessor.get(IViewsService)); if (searchView) { const tree: WorkbenchObjectTree = searchView.getControl(); accessor.get(IInstantiationService).createInstance(ReplaceAllInFolderAction, tree, tree.getFocus()[0] as FolderMatch).run(); @@ -362,31 +359,6 @@ const ClearSearchHistoryCommand: ICommandAction = { }; MenuRegistry.addCommand(ClearSearchHistoryCommand); -CommandsRegistry.registerCommand({ - id: Constants.ToggleSearchViewPositionCommandId, - handler: (accessor) => { - const configurationService = accessor.get(IConfigurationService); - const currentValue = configurationService.getValue('search').location; - const toggleValue = currentValue === 'sidebar' ? 'panel' : 'sidebar'; - - configurationService.updateValue('search.location', toggleValue); - } -}); - -const toggleSearchViewPositionLabel = nls.localize('toggleSearchViewPositionLabel', "Toggle Search View Position"); -const ToggleSearchViewPositionCommand: ICommandAction = { - id: Constants.ToggleSearchViewPositionCommandId, - title: toggleSearchViewPositionLabel, - category -}; -MenuRegistry.addCommand(ToggleSearchViewPositionCommand); -MenuRegistry.appendMenuItem(MenuId.SearchContext, { - command: ToggleSearchViewPositionCommand, - when: Constants.SearchViewVisibleKey, - group: 'search_9', - order: 1 -}); - CommandsRegistry.registerCommand({ id: Constants.FocusSearchListCommandID, handler: focusSearchListCommand @@ -402,13 +374,11 @@ MenuRegistry.addCommand(FocusSearchListCommand); const searchInFolderCommand: ICommandHandler = (accessor, resource?: URI) => { const listService = accessor.get(IListService); - const viewletService = accessor.get(IViewletService); - const panelService = accessor.get(IPanelService); const fileService = accessor.get(IFileService); - const configurationService = accessor.get(IConfigurationService); + const viewsService = accessor.get(IViewsService); const resources = getMultiSelectedResources(resource, listService, accessor.get(IEditorService), accessor.get(IExplorerService)); - return openSearchView(viewletService, panelService, configurationService, true).then(searchView => { + return openSearchView(viewsService, true).then(searchView => { if (resources && resources.length && searchView) { return fileService.resolveAll(resources.map(resource => ({ resource }))).then(results => { const folders: URI[] = []; @@ -454,7 +424,7 @@ const FIND_IN_WORKSPACE_ID = 'filesExplorer.findInWorkspace'; CommandsRegistry.registerCommand({ id: FIND_IN_WORKSPACE_ID, handler: (accessor) => { - return openSearchView(accessor.get(IViewletService), accessor.get(IPanelService), accessor.get(IConfigurationService), true).then(searchView => { + return openSearchView(accessor.get(IViewsService), true).then(searchView => { if (searchView) { searchView.searchInFolders(); } @@ -522,51 +492,43 @@ const viewContainer = Registry.as(ViewExtensions.ViewCo order: 1 }, ViewContainerLocation.Sidebar); -Registry.as(PanelExtensions.Panels).registerPanel(PanelDescriptor.create( - SearchPanel, - PANEL_ID, - nls.localize('name', "Search"), - 'search', - 10 -)); +const viewDescriptor = { id: VIEW_ID, name: nls.localize('search', "Search"), ctorDescriptor: new SyncDescriptor(SearchView), canToggleVisibility: false, canMoveView: true }; +// Register search default location to sidebar +Registry.as(ViewExtensions.ViewsRegistry).registerViews([viewDescriptor], viewContainer); + + +// Migrate search location setting to new model class RegisterSearchViewContribution implements IWorkbenchContribution { - constructor( - @IViewletService viewletService: IViewletService, - @IPanelService panelService: IPanelService, - @IConfigurationService configurationService: IConfigurationService + @IConfigurationService configurationService: IConfigurationService, + @IViewDescriptorService viewDescriptorService: IViewDescriptorService ) { - const viewsRegistry = Registry.as(ViewExtensions.ViewsRegistry); - const updateSearchViewLocation = (open: boolean) => { - const config = configurationService.getValue(); - if (config.search.location === 'panel') { - viewsRegistry.deregisterViews(viewsRegistry.getViews(viewContainer), viewContainer); - Registry.as(PanelExtensions.Panels).registerPanel(PanelDescriptor.create( - SearchPanel, - PANEL_ID, - nls.localize('name', "Search"), - 'search', - 10 - )); - if (open) { - panelService.openPanel(PANEL_ID); - } - } else { - Registry.as(PanelExtensions.Panels).deregisterPanel(PANEL_ID); - viewsRegistry.registerViews([{ id: VIEW_ID, name: nls.localize('search', "Search"), ctorDescriptor: new SyncDescriptor(SearchView, [SearchViewPosition.SideBar]), canToggleVisibility: false }], viewContainer); - if (open) { - viewletService.openViewlet(VIEWLET_ID); - } - } - }; - configurationService.onDidChangeConfiguration(e => { - if (e.affectsConfiguration('search.location')) { - updateSearchViewLocation(true); - } - }); + const data = configurationService.inspect('search.location'); - updateSearchViewLocation(false); + if (data.value === 'panel') { + viewDescriptorService.moveViewToLocation(viewDescriptor, ViewContainerLocation.Panel); + } + + if (data.userValue) { + configurationService.updateValue('search.location', undefined, ConfigurationTarget.USER); + } + + if (data.userLocalValue) { + configurationService.updateValue('search.location', undefined, ConfigurationTarget.USER_LOCAL); + } + + if (data.userRemoteValue) { + configurationService.updateValue('search.location', undefined, ConfigurationTarget.USER_REMOTE); + } + + if (data.workspaceFolderValue) { + configurationService.updateValue('search.location', undefined, ConfigurationTarget.WORKSPACE_FOLDER); + } + + if (data.workspaceValue) { + configurationService.updateValue('search.location', undefined, ConfigurationTarget.WORKSPACE); + } } } Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(RegisterSearchViewContribution, LifecyclePhase.Starting); @@ -664,7 +626,7 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ when: ContextKeyExpr.and(Constants.SearchViewVisibleKey, Constants.FileMatchOrMatchFocusKey), primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_L, handler: (accessor, args: any) => { - const searchView = getSearchView(accessor.get(IViewletService), accessor.get(IPanelService)); + const searchView = getSearchView(accessor.get(IViewsService)); if (searchView) { const tree: WorkbenchObjectTree = searchView.getControl(); searchView.openEditorWithMultiCursor(tree.getFocus()[0]); diff --git a/src/vs/workbench/contrib/search/browser/searchActions.ts b/src/vs/workbench/contrib/search/browser/searchActions.ts index 8105b8d43b7..06abc43baf2 100644 --- a/src/vs/workbench/contrib/search/browser/searchActions.ts +++ b/src/vs/workbench/contrib/search/browser/searchActions.ts @@ -22,19 +22,16 @@ import { IReplaceService } from 'vs/workbench/contrib/search/common/replace'; import { FolderMatch, FileMatch, FileMatchOrMatch, FolderMatchWithResource, Match, RenderableMatch, searchMatchComparer, SearchResult } from 'vs/workbench/contrib/search/common/searchModel'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; -import { ISearchConfiguration, VIEWLET_ID, PANEL_ID, ISearchConfigurationProperties } from 'vs/workbench/services/search/common/search'; +import { ISearchConfiguration, ISearchConfigurationProperties, VIEW_ID } from 'vs/workbench/services/search/common/search'; import { ISearchHistoryService } from 'vs/workbench/contrib/search/common/searchHistoryService'; -import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; -import { SearchViewPaneContainer } from 'vs/workbench/contrib/search/browser/searchViewlet'; -import { SearchPanel } from 'vs/workbench/contrib/search/browser/searchPanel'; import { ITreeNavigator } from 'vs/base/browser/ui/tree/tree'; import { createEditorFromSearchResult, openNewSearchEditor } from 'vs/workbench/contrib/search/browser/searchEditorActions'; import type { SearchEditor } from 'vs/workbench/contrib/search/browser/searchEditor'; import { SearchEditorInput } from 'vs/workbench/contrib/search/browser/searchEditorInput'; +import { IViewsService } from 'vs/workbench/common/views'; -export function isSearchViewFocused(viewletService: IViewletService, panelService: IPanelService): boolean { - const searchView = getSearchView(viewletService, panelService); +export function isSearchViewFocused(viewsService: IViewsService): boolean { + const searchView = getSearchView(viewsService); const activeElement = document.activeElement; return !!(searchView && activeElement && DOM.isAncestor(activeElement, searchView.getContainer())); } @@ -52,26 +49,12 @@ export function appendKeyBindingLabel(label: string, inputKeyBinding: number | R } } -export function openSearchView(viewletService: IViewletService, panelService: IPanelService, configurationService: IConfigurationService, focus?: boolean): Promise { - if (configurationService.getValue().search.location === 'panel') { - return Promise.resolve((panelService.openPanel(PANEL_ID, focus) as SearchPanel).getSearchView()); - } - - return viewletService.openViewlet(VIEWLET_ID, focus).then(viewlet => (viewlet?.getViewPaneContainer() as SearchViewPaneContainer).getSearchView() as SearchView); +export function openSearchView(viewsService: IViewsService, focus?: boolean): Promise { + return viewsService.openView(VIEW_ID, focus).then(view => (view as SearchView ?? undefined)); } -export function getSearchView(viewletService: IViewletService, panelService: IPanelService): SearchView | undefined { - const activeViewlet = viewletService.getActiveViewlet(); - if (activeViewlet && activeViewlet.getId() === VIEWLET_ID) { - return (activeViewlet.getViewPaneContainer() as SearchViewPaneContainer).getSearchView(); - } - - const activePanel = panelService.getActivePanel(); - if (activePanel && activePanel.getId() === PANEL_ID) { - return (activePanel as SearchPanel).getSearchView(); - } - - return undefined; +export function getSearchView(viewsService: IViewsService): SearchView | undefined { + return viewsService.getActiveViewWithId(VIEW_ID) as SearchView ?? undefined; } function doAppendKeyBindingLabel(label: string, keyBinding: ResolvedKeybinding | undefined): string { @@ -79,7 +62,7 @@ function doAppendKeyBindingLabel(label: string, keyBinding: ResolvedKeybinding | } export const toggleCaseSensitiveCommand = (accessor: ServicesAccessor) => { - const searchView = getSearchView(accessor.get(IViewletService), accessor.get(IPanelService)); + const searchView = getSearchView(accessor.get(IViewsService)); if (searchView) { searchView.toggleCaseSensitive(); } @@ -94,7 +77,7 @@ export const toggleSearchEditorCaseSensitiveCommand = (accessor: ServicesAccesso }; export const toggleWholeWordCommand = (accessor: ServicesAccessor) => { - const searchView = getSearchView(accessor.get(IViewletService), accessor.get(IPanelService)); + const searchView = getSearchView(accessor.get(IViewsService)); if (searchView) { searchView.toggleWholeWords(); } @@ -109,7 +92,7 @@ export const toggleSearchEditorWholeWordCommand = (accessor: ServicesAccessor) = }; export const toggleRegexCommand = (accessor: ServicesAccessor) => { - const searchView = getSearchView(accessor.get(IViewletService), accessor.get(IPanelService)); + const searchView = getSearchView(accessor.get(IViewsService)); if (searchView) { searchView.toggleRegex(); } @@ -136,8 +119,7 @@ export class FocusNextInputAction extends Action { static readonly ID = 'search.focus.nextInputBox'; constructor(id: string, label: string, - @IViewletService private readonly viewletService: IViewletService, - @IPanelService private readonly panelService: IPanelService, + @IViewsService private readonly viewsService: IViewsService, @IEditorService private readonly editorService: IEditorService, ) { super(id, label); @@ -150,7 +132,7 @@ export class FocusNextInputAction extends Action { (this.editorService.activeControl as SearchEditor).focusNextInput(); } - const searchView = getSearchView(this.viewletService, this.panelService); + const searchView = getSearchView(this.viewsService); if (searchView) { searchView.focusNextInputBox(); } @@ -162,8 +144,7 @@ export class FocusPreviousInputAction extends Action { static readonly ID = 'search.focus.previousInputBox'; constructor(id: string, label: string, - @IViewletService private readonly viewletService: IViewletService, - @IPanelService private readonly panelService: IPanelService, + @IViewsService private readonly viewsService: IViewsService, @IEditorService private readonly editorService: IEditorService, ) { super(id, label); @@ -176,7 +157,7 @@ export class FocusPreviousInputAction extends Action { (this.editorService.activeControl as SearchEditor).focusPrevInput(); } - const searchView = getSearchView(this.viewletService, this.panelService); + const searchView = getSearchView(this.viewsService); if (searchView) { searchView.focusPreviousInputBox(); } @@ -185,14 +166,14 @@ export class FocusPreviousInputAction extends Action { export abstract class FindOrReplaceInFilesAction extends Action { - constructor(id: string, label: string, protected viewletService: IViewletService, protected panelService: IPanelService, protected configurationService: IConfigurationService, + constructor(id: string, label: string, protected viewsService: IViewsService, private expandSearchReplaceWidget: boolean ) { super(id, label); } run(): Promise { - return openSearchView(this.viewletService, this.panelService, this.configurationService, false).then(openedView => { + return openSearchView(this.viewsService, false).then(openedView => { if (openedView) { const searchAndReplaceWidget = openedView.searchAndReplaceWidget; searchAndReplaceWidget.toggleReplace(this.expandSearchReplaceWidget); @@ -215,10 +196,8 @@ export interface IFindInFilesArgs { } export const FindInFilesCommand: ICommandHandler = (accessor, args: IFindInFilesArgs = {}) => { - const viewletService = accessor.get(IViewletService); - const panelService = accessor.get(IPanelService); - const configurationService = accessor.get(IConfigurationService); - openSearchView(viewletService, panelService, configurationService, false).then(openedView => { + const viewsService = accessor.get(IViewsService); + openSearchView(viewsService, false).then(openedView => { if (openedView) { const searchAndReplaceWidget = openedView.searchAndReplaceWidget; searchAndReplaceWidget.toggleReplace(typeof args.replace === 'string'); @@ -238,18 +217,15 @@ export class OpenSearchViewletAction extends FindOrReplaceInFilesAction { static readonly LABEL = nls.localize('showSearch', "Show Search"); constructor(id: string, label: string, - @IViewletService viewletService: IViewletService, - @IPanelService panelService: IPanelService, - @IEditorGroupsService private readonly editorGroupService: IEditorGroupsService, - @IConfigurationService configurationService: IConfigurationService - ) { - super(id, label, viewletService, panelService, configurationService, /*expandSearchReplaceWidget=*/false); + @IViewsService viewsService: IViewsService, + @IEditorGroupsService private readonly editorGroupService: IEditorGroupsService) { + super(id, label, viewsService, /*expandSearchReplaceWidget=*/false); } run(): Promise { // Pass focus to viewlet if not open or focused - if (this.otherViewletShowing() || !isSearchViewFocused(this.viewletService, this.panelService)) { + if (this.otherViewletShowing() || !isSearchViewFocused(this.viewsService)) { return super.run(); } @@ -260,7 +236,7 @@ export class OpenSearchViewletAction extends FindOrReplaceInFilesAction { } private otherViewletShowing(): boolean { - return !getSearchView(this.viewletService, this.panelService); + return !getSearchView(this.viewsService); } } @@ -270,25 +246,21 @@ export class ReplaceInFilesAction extends FindOrReplaceInFilesAction { static readonly LABEL = nls.localize('replaceInFiles', "Replace in Files"); constructor(id: string, label: string, - @IViewletService viewletService: IViewletService, - @IPanelService panelService: IPanelService, - @IConfigurationService configurationService: IConfigurationService - ) { - super(id, label, viewletService, panelService, configurationService, /*expandSearchReplaceWidget=*/true); + @IViewsService viewsService: IViewsService) { + super(id, label, viewsService, /*expandSearchReplaceWidget=*/true); } } export class CloseReplaceAction extends Action { constructor(id: string, label: string, - @IViewletService private readonly viewletService: IViewletService, - @IPanelService private readonly panelService: IPanelService + @IViewsService private readonly viewsService: IViewsService ) { super(id, label); } run(): Promise { - const searchView = getSearchView(this.viewletService, this.panelService); + const searchView = getSearchView(this.viewsService); if (searchView) { searchView.searchAndReplaceWidget.toggleReplace(false); searchView.searchAndReplaceWidget.focus(); @@ -327,14 +299,13 @@ export class RefreshAction extends Action { static LABEL: string = nls.localize('RefreshAction.label', "Refresh"); constructor(id: string, label: string, - @IViewletService private readonly viewletService: IViewletService, - @IPanelService private readonly panelService: IPanelService + @IViewsService private readonly viewsService: IViewsService ) { super(id, label, 'search-action codicon-refresh'); } get enabled(): boolean { - const searchView = getSearchView(this.viewletService, this.panelService); + const searchView = getSearchView(this.viewsService); return !!searchView && searchView.hasSearchPattern(); } @@ -343,7 +314,7 @@ export class RefreshAction extends Action { } run(): Promise { - const searchView = getSearchView(this.viewletService, this.panelService); + const searchView = getSearchView(this.viewsService); if (searchView) { searchView.onQueryChanged(false); } @@ -358,20 +329,19 @@ export class CollapseDeepestExpandedLevelAction extends Action { static LABEL: string = nls.localize('CollapseDeepestExpandedLevelAction.label', "Collapse All"); constructor(id: string, label: string, - @IViewletService private readonly viewletService: IViewletService, - @IPanelService private readonly panelService: IPanelService + @IViewsService private readonly viewsService: IViewsService ) { super(id, label, 'search-action codicon-collapse-all'); this.update(); } update(): void { - const searchView = getSearchView(this.viewletService, this.panelService); + const searchView = getSearchView(this.viewsService); this.enabled = !!searchView && searchView.hasSearchResults(); } run(): Promise { - const searchView = getSearchView(this.viewletService, this.panelService); + const searchView = getSearchView(this.viewsService); if (searchView) { const viewer = searchView.getControl(); @@ -415,20 +385,19 @@ export class ExpandAllAction extends Action { static LABEL: string = nls.localize('ExpandAllAction.label', "Expand All"); constructor(id: string, label: string, - @IViewletService private readonly viewletService: IViewletService, - @IPanelService private readonly panelService: IPanelService + @IViewsService private readonly viewsService: IViewsService ) { super(id, label, 'search-action codicon-expand-all'); this.update(); } update(): void { - const searchView = getSearchView(this.viewletService, this.panelService); + const searchView = getSearchView(this.viewsService); this.enabled = !!searchView && searchView.hasSearchResults(); } run(): Promise { - const searchView = getSearchView(this.viewletService, this.panelService); + const searchView = getSearchView(this.viewsService); if (searchView) { const viewer = searchView.getControl(); viewer.expandAll(); @@ -449,15 +418,14 @@ export class ToggleCollapseAndExpandAction extends Action { constructor(id: string, label: string, private collapseAction: CollapseDeepestExpandedLevelAction, private expandAction: ExpandAllAction, - @IViewletService private readonly viewletService: IViewletService, - @IPanelService private readonly panelService: IPanelService + @IViewsService private readonly viewsService: IViewsService ) { super(id, label, collapseAction.class); this.update(); } update(): void { - const searchView = getSearchView(this.viewletService, this.panelService); + const searchView = getSearchView(this.viewsService); this.enabled = !!searchView && searchView.hasSearchResults(); this.onTreeCollapseStateChange(); } @@ -475,7 +443,7 @@ export class ToggleCollapseAndExpandAction extends Action { } private isSomeCollapsible(): boolean { - const searchView = getSearchView(this.viewletService, this.panelService); + const searchView = getSearchView(this.viewsService); if (searchView) { const viewer = searchView.getControl(); const navigator = viewer.navigate(); @@ -501,20 +469,19 @@ export class ClearSearchResultsAction extends Action { static LABEL: string = nls.localize('ClearSearchResultsAction.label', "Clear Search Results"); constructor(id: string, label: string, - @IViewletService private readonly viewletService: IViewletService, - @IPanelService private readonly panelService: IPanelService + @IViewsService private readonly viewsService: IViewsService ) { super(id, label, 'search-action codicon-clear-all'); this.update(); } update(): void { - const searchView = getSearchView(this.viewletService, this.panelService); + const searchView = getSearchView(this.viewsService); this.enabled = !!searchView && (!searchView.allSearchFieldsClear() || searchView.hasSearchResults()); } run(): Promise { - const searchView = getSearchView(this.viewletService, this.panelService); + const searchView = getSearchView(this.viewsService); if (searchView) { searchView.clearSearchResults(); } @@ -528,20 +495,19 @@ export class CancelSearchAction extends Action { static LABEL: string = nls.localize('CancelSearchAction.label', "Cancel Search"); constructor(id: string, label: string, - @IViewletService private readonly viewletService: IViewletService, - @IPanelService private readonly panelService: IPanelService + @IViewsService private readonly viewsService: IViewsService ) { super(id, label, 'search-action codicon-search-stop'); this.update(); } update(): void { - const searchView = getSearchView(this.viewletService, this.panelService); + const searchView = getSearchView(this.viewsService); this.enabled = !!searchView && searchView.isSlowSearch(); } run(): Promise { - const searchView = getSearchView(this.viewletService, this.panelService); + const searchView = getSearchView(this.viewsService); if (searchView) { searchView.cancelSearch(); } @@ -583,8 +549,7 @@ export class OpenResultsInEditorAction extends Action { static readonly LABEL = nls.localize('search.openResultsInEditor', "Open Results in Editor"); constructor(id: string, label: string, - @IViewletService private viewletService: IViewletService, - @IPanelService private panelService: IPanelService, + @IViewsService private viewsService: IViewsService, @IConfigurationService private configurationService: IConfigurationService, @IInstantiationService private readonly instantiationService: IInstantiationService, ) { @@ -592,7 +557,7 @@ export class OpenResultsInEditorAction extends Action { } get enabled(): boolean { - const searchView = getSearchView(this.viewletService, this.panelService); + const searchView = getSearchView(this.viewsService); return !!searchView && searchView.hasSearchResults(); } @@ -601,7 +566,7 @@ export class OpenResultsInEditorAction extends Action { } async run() { - const searchView = getSearchView(this.viewletService, this.panelService); + const searchView = getSearchView(this.viewsService); if (searchView && this.configurationService.getValue('search').enableSearchEditorPreview) { await this.instantiationService.invokeFunction(createEditorFromSearchResult, searchView.searchResult, searchView.searchIncludePattern.getValue(), searchView.searchExcludePattern.getValue()); } @@ -614,15 +579,13 @@ export class FocusNextSearchResultAction extends Action { static readonly LABEL = nls.localize('FocusNextSearchResult.label', "Focus Next Search Result"); constructor(id: string, label: string, - @IViewletService private readonly viewletService: IViewletService, - @IPanelService private readonly panelService: IPanelService, - @IConfigurationService private readonly configurationService: IConfigurationService + @IViewsService private readonly viewsService: IViewsService ) { super(id, label); } run(): Promise { - return openSearchView(this.viewletService, this.panelService, this.configurationService).then(searchView => { + return openSearchView(this.viewsService).then(searchView => { if (searchView) { searchView.selectNextMatch(); } @@ -635,15 +598,13 @@ export class FocusPreviousSearchResultAction extends Action { static readonly LABEL = nls.localize('FocusPreviousSearchResult.label', "Focus Previous Search Result"); constructor(id: string, label: string, - @IViewletService private readonly viewletService: IViewletService, - @IPanelService private readonly panelService: IPanelService, - @IConfigurationService private readonly configurationService: IConfigurationService + @IViewsService private readonly viewsService: IViewsService ) { super(id, label); } run(): Promise { - return openSearchView(this.viewletService, this.panelService, this.configurationService).then(searchView => { + return openSearchView(this.viewsService).then(searchView => { if (searchView) { searchView.selectPreviousMatch(); } @@ -980,12 +941,11 @@ function allFolderMatchesToString(folderMatches: Array { - const viewletService = accessor.get(IViewletService); - const panelService = accessor.get(IPanelService); + const viewsService = accessor.get(IViewsService); const clipboardService = accessor.get(IClipboardService); const labelService = accessor.get(ILabelService); - const searchView = getSearchView(viewletService, panelService); + const searchView = getSearchView(viewsService); if (searchView) { const root = searchView.searchResult; @@ -1000,10 +960,8 @@ export const clearHistoryCommand: ICommandHandler = accessor => { }; export const focusSearchListCommand: ICommandHandler = accessor => { - const viewletService = accessor.get(IViewletService); - const panelService = accessor.get(IPanelService); - const configurationService = accessor.get(IConfigurationService); - openSearchView(viewletService, panelService, configurationService).then(searchView => { + const viewsService = accessor.get(IViewsService); + openSearchView(viewsService).then(searchView => { if (searchView) { searchView.moveFocusToResults(); } diff --git a/src/vs/workbench/contrib/search/browser/searchPanel.ts b/src/vs/workbench/contrib/search/browser/searchPanel.ts deleted file mode 100644 index 559f023dee7..00000000000 --- a/src/vs/workbench/contrib/search/browser/searchPanel.ts +++ /dev/null @@ -1,70 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import { IStorageService } from 'vs/platform/storage/common/storage'; -import { IThemeService } from 'vs/platform/theme/common/themeService'; -import { PANEL_ID } from 'vs/workbench/services/search/common/search'; -import { SearchView, SearchViewPosition } from 'vs/workbench/contrib/search/browser/searchView'; -import { Panel } from 'vs/workbench/browser/panel'; -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { localize } from 'vs/nls'; -import * as dom from 'vs/base/browser/dom'; -import { IAction } from 'vs/base/common/actions'; - -export class SearchPanel extends Panel { - - private readonly searchView: SearchView; - - constructor( - @ITelemetryService telemetryService: ITelemetryService, - @IThemeService themeService: IThemeService, - @IStorageService storageService: IStorageService, - @IInstantiationService instantiationService: IInstantiationService, - ) { - super(PANEL_ID, telemetryService, themeService, storageService); - this.searchView = this._register(instantiationService.createInstance(SearchView, SearchViewPosition.Panel, { id: PANEL_ID, title: localize('search', "Search"), actionRunner: this.getActionRunner() })); - this._register(this.searchView.onDidChangeTitleArea(() => this.updateTitleArea())); - this._register(this.onDidChangeVisibility(visible => this.searchView.setVisible(visible))); - } - - create(parent: HTMLElement): void { - dom.addClasses(parent, 'monaco-pane-view', 'search-panel'); - this.searchView.render(); - dom.append(parent, this.searchView.element); - this.searchView.setExpanded(true); - this.searchView.headerVisible = false; - } - - public getTitle(): string { - return this.searchView.title; - } - - public layout(dimension: dom.Dimension): void { - this.searchView.width = dimension.width; - this.searchView.layout(dimension.height); - } - - public focus(): void { - this.searchView.focus(); - } - - getActions(): IAction[] { - return this.searchView.getActions(); - } - - getSecondaryActions(): IAction[] { - return this.searchView.getSecondaryActions(); - } - - saveState(): void { - this.searchView.saveState(); - super.saveState(); - } - - getSearchView(): SearchView { - return this.searchView; - } -} diff --git a/src/vs/workbench/contrib/search/browser/searchView.ts b/src/vs/workbench/contrib/search/browser/searchView.ts index e1b11eb5f29..2e0b61c063b 100644 --- a/src/vs/workbench/contrib/search/browser/searchView.ts +++ b/src/vs/workbench/contrib/search/browser/searchView.ts @@ -34,7 +34,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { TreeResourceNavigator, WorkbenchObjectTree, getSelectionKeyboardEvent } from 'vs/platform/list/browser/listService'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { IProgressService, IProgressStep, IProgress } from 'vs/platform/progress/common/progress'; -import { IPatternInfo, ISearchComplete, ISearchConfiguration, ISearchConfigurationProperties, ITextQuery, VIEW_ID, VIEWLET_ID, SearchSortOrder, PANEL_ID } from 'vs/workbench/services/search/common/search'; +import { IPatternInfo, ISearchComplete, ISearchConfiguration, ISearchConfigurationProperties, ITextQuery, VIEW_ID, SearchSortOrder } from 'vs/workbench/services/search/common/search'; import { ISearchHistoryService, ISearchHistoryValues } from 'vs/workbench/contrib/search/common/searchHistoryService'; import { diffInserted, diffInsertedOutline, diffRemoved, diffRemovedOutline, editorFindMatchHighlight, editorFindMatchHighlightBorder, listActiveSelectionForeground, foreground } from 'vs/platform/theme/common/colorRegistry'; import { ICssStyleCollector, ITheme, IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; @@ -63,9 +63,9 @@ import { IStorageService, StorageScope } from 'vs/platform/storage/common/storag import { IOpenerService } from 'vs/platform/opener/common/opener'; import { MultiCursorSelectionController } from 'vs/editor/contrib/multicursor/multicursor'; import { Selection } from 'vs/editor/common/core/selection'; -import { SIDE_BAR_BACKGROUND, PANEL_BACKGROUND } from 'vs/workbench/common/theme'; import { createEditorFromSearchResult } from 'vs/workbench/contrib/search/browser/searchEditorActions'; import { Color, RGBA } from 'vs/base/common/color'; +import { IViewDescriptorService } from 'vs/workbench/common/views'; const $ = dom.$; @@ -147,7 +147,6 @@ export class SearchView extends ViewPane { private toggleCollapseStateDelayer: Delayer; constructor( - private position: SearchViewPosition, options: IViewPaneOptions, @IFileService private readonly fileService: IFileService, @IEditorService private readonly editorService: IEditorService, @@ -156,6 +155,7 @@ export class SearchView extends ViewPane { @IDialogService private readonly dialogService: IDialogService, @IContextViewService private readonly contextViewService: IContextViewService, @IInstantiationService instantiationService: IInstantiationService, + @IViewDescriptorService viewDescriptorService: IViewDescriptorService, @IConfigurationService configurationService: IConfigurationService, @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, @ISearchWorkbenchService private readonly searchWorkbenchService: ISearchWorkbenchService, @@ -170,9 +170,9 @@ export class SearchView extends ViewPane { @IAccessibilityService private readonly accessibilityService: IAccessibilityService, @IKeybindingService keybindingService: IKeybindingService, @IStorageService storageService: IStorageService, - @IOpenerService private readonly openerService: IOpenerService - ) { - super({ ...options, id: VIEW_ID, ariaHeaderLabel: nls.localize('searchView', "Search") }, keybindingService, contextMenuService, configurationService, contextKeyService, instantiationService); + @IOpenerService private readonly openerService: IOpenerService) { + + super({ ...options, id: VIEW_ID, ariaHeaderLabel: nls.localize('searchView', "Search") }, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService); this.viewletVisible = Constants.SearchViewVisibleKey.bindTo(contextKeyService); this.viewletFocused = Constants.SearchViewFocusedKey.bindTo(contextKeyService); @@ -460,7 +460,7 @@ export class SearchView extends ViewPane { this.refreshTree(); })); - this._register(this.searchWidget.onReplaceValueChanged((value) => { + this._register(this.searchWidget.onReplaceValueChanged(() => { this.viewModel.replaceString = this.searchWidget.getReplaceValue(); this.delayedRefresh.trigger(() => this.refreshTree()); })); @@ -593,7 +593,8 @@ export class SearchView extends ViewPane { let progressComplete: () => void; let progressReporter: IProgress; - this.progressService.withProgress({ location: this.position === SearchViewPosition.SideBar ? VIEWLET_ID : PANEL_ID, delay: 100, total: occurrences }, p => { + + this.progressService.withProgress({ location: this.getProgressLocation(), delay: 100, total: occurrences }, p => { progressReporter = p; return new Promise(resolve => progressComplete = resolve); @@ -724,7 +725,7 @@ export class SearchView extends ViewPane { dnd: this.instantiationService.createInstance(SearchDND), multipleSelectionSupport: false, overrideStyles: { - listBackground: this.position === SearchViewPosition.SideBar ? SIDE_BAR_BACKGROUND : PANEL_BACKGROUND + listBackground: this.getBackgroundColor() } })); this._register(this.tree.onContextMenu(e => this.onContextMenu(e))); @@ -759,7 +760,7 @@ export class SearchView extends ViewPane { } })); - this._register(this.tree.onDidBlur(e => { + this._register(this.tree.onDidBlur(() => { this.firstMatchFocused.reset(); this.fileMatchOrMatchFocused.reset(); this.fileMatchFocused.reset(); @@ -1339,13 +1340,13 @@ export class SearchView extends ViewPane { this.viewModel.cancelSearch(); this.currentSearchQ = this.currentSearchQ - .then(() => this.doSearch(query, options, excludePatternText, includePatternText, triggeredOnType)) + .then(() => this.doSearch(query, excludePatternText, includePatternText, triggeredOnType)) .then(() => undefined, () => undefined); } - private doSearch(query: ITextQuery, options: ITextQueryBuilderOptions, excludePatternText: string, includePatternText: string, triggeredOnType: boolean): Thenable { + private doSearch(query: ITextQuery, excludePatternText: string, includePatternText: string, triggeredOnType: boolean): Thenable { let progressComplete: () => void; - this.progressService.withProgress({ location: this.position === SearchViewPosition.SideBar ? VIEWLET_ID : PANEL_ID, delay: triggeredOnType ? 300 : 0 }, _progress => { + this.progressService.withProgress({ location: this.getProgressLocation(), delay: triggeredOnType ? 300 : 0 }, _progress => { return new Promise(resolve => progressComplete = resolve); }); diff --git a/src/vs/workbench/contrib/search/browser/searchWidget.ts b/src/vs/workbench/contrib/search/browser/searchWidget.ts index ad51875c1d4..df337113aa4 100644 --- a/src/vs/workbench/contrib/search/browser/searchWidget.ts +++ b/src/vs/workbench/contrib/search/browser/searchWidget.ts @@ -29,12 +29,11 @@ import { IThemeService } from 'vs/platform/theme/common/themeService'; import { ContextScopedFindInput, ContextScopedReplaceInput } from 'vs/platform/browser/contextScopedHistoryWidget'; import { appendKeyBindingLabel, isSearchViewFocused } from 'vs/workbench/contrib/search/browser/searchActions'; import * as Constants from 'vs/workbench/contrib/search/common/constants'; -import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; -import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { IEditorOptions } from 'vs/editor/common/config/editorOptions'; import { IAccessibilityService, AccessibilitySupport } from 'vs/platform/accessibility/common/accessibility'; import { isMacintosh } from 'vs/base/common/platform'; import { Checkbox } from 'vs/base/browser/ui/checkbox/checkbox'; +import { IViewsService } from 'vs/workbench/common/views'; /** Specified in searchview.css */ export const SingleLineInputHeight = 24; @@ -677,7 +676,7 @@ export function registerContributions() { when: ContextKeyExpr.and(Constants.SearchViewVisibleKey, Constants.ReplaceActiveKey, CONTEXT_FIND_WIDGET_NOT_VISIBLE), primary: KeyMod.Alt | KeyMod.CtrlCmd | KeyCode.Enter, handler: accessor => { - if (isSearchViewFocused(accessor.get(IViewletService), accessor.get(IPanelService))) { + if (isSearchViewFocused(accessor.get(IViewsService))) { ReplaceAllAction.INSTANCE.run(); } } diff --git a/src/vs/workbench/contrib/search/common/constants.ts b/src/vs/workbench/contrib/search/common/constants.ts index d56b5fe5265..64ac782a467 100644 --- a/src/vs/workbench/contrib/search/common/constants.ts +++ b/src/vs/workbench/contrib/search/common/constants.ts @@ -33,8 +33,6 @@ export const ToggleSearchEditorContextLinesCommandId = 'toggleSearchEditorContex export const AddCursorsAtSearchResults = 'addCursorsAtSearchResults'; export const RevealInSideBarForSearchResults = 'search.action.revealInSideBar'; -export const ToggleSearchViewPositionCommandId = 'search.action.toggleSearchViewPosition'; - export const SearchViewVisibleKey = new RawContextKey('searchViewletVisible', true); export const SearchViewFocusedKey = new RawContextKey('searchViewletFocus', false); export const InputBoxFocusedKey = new RawContextKey('inputBoxFocus', false); diff --git a/src/vs/workbench/services/progress/test/progressIndicator.test.ts b/src/vs/workbench/services/progress/test/progressIndicator.test.ts index 2573e8938ea..79f25c0b909 100644 --- a/src/vs/workbench/services/progress/test/progressIndicator.test.ts +++ b/src/vs/workbench/services/progress/test/progressIndicator.test.ts @@ -12,8 +12,7 @@ import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; import { IViewlet } from 'vs/workbench/common/viewlet'; import { TestViewletService, TestPanelService } from 'vs/workbench/test/workbenchTestServices'; import { Event } from 'vs/base/common/event'; -import { IViewPaneContainer } from 'vs/workbench/common/viewPaneContainer'; -import { IView } from 'vs/workbench/common/views'; +import { IView, IViewPaneContainer } from 'vs/workbench/common/views'; class TestViewlet implements IViewlet { diff --git a/src/vs/workbench/services/views/browser/viewDescriptorService.ts b/src/vs/workbench/services/views/browser/viewDescriptorService.ts index f3f1d758418..fb1d0b4e3bd 100644 --- a/src/vs/workbench/services/views/browser/viewDescriptorService.ts +++ b/src/vs/workbench/services/views/browser/viewDescriptorService.ts @@ -337,6 +337,24 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor return this.viewsRegistry.getView(viewId); } + getViewLocation(viewId: string): ViewContainerLocation | null { + const cachedInfo = this.cachedViewInfo.get(viewId); + + if (cachedInfo && cachedInfo.location) { + return cachedInfo.location; + } + + const container = cachedInfo?.containerId ? + this.viewContainersRegistry.get(cachedInfo.containerId) ?? null : + this.viewsRegistry.getViewContainer(viewId); + + if (!container) { + return null; + } + + return this.viewContainersRegistry.getViewContainerLocation(container) ?? null; + } + getViewContainer(viewId: string): ViewContainer | null { const containerId = this.cachedViewInfo.get(viewId)?.containerId; return containerId ? @@ -528,6 +546,13 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor } private addViews(container: ViewContainer, views: IViewDescriptor[]): void { + // Update in memory cache + const location = this.viewContainersRegistry.getViewContainerLocation(container); + const sourceViewId = this.generatedContainerSourceViewIds.get(container.id); + views.forEach(view => { + this.cachedViewInfo.set(view.id, { containerId: container.id, location, sourceViewId }); + }); + this.getViewDescriptors(container).addViews(views); } From 1c4e2291f8ffbd5b8e4483f190229a9a541cc161 Mon Sep 17 00:00:00 2001 From: SteVen Batten Date: Thu, 23 Jan 2020 18:14:49 -0800 Subject: [PATCH 092/801] custom views are tricky --- src/vs/platform/list/browser/listService.ts | 19 ++++++++++++++++--- .../api/browser/viewsExtensionPoint.ts | 4 +++- .../browser/parts/views/customView.ts | 12 ++++-------- src/vs/workbench/common/views.ts | 6 +++--- .../views/browser/viewDescriptorService.ts | 7 ++++++- 5 files changed, 32 insertions(+), 16 deletions(-) diff --git a/src/vs/platform/list/browser/listService.ts b/src/vs/platform/list/browser/listService.ts index ee29d02b4d0..c8a08f2f706 100644 --- a/src/vs/platform/list/browser/listService.ts +++ b/src/vs/platform/list/browser/listService.ts @@ -839,6 +839,11 @@ export class WorkbenchAsyncDataTree extends Async this.internals = new WorkbenchTreeInternals(this, treeOptions, getAutomaticKeyboardNavigation, options.overrideStyles, contextKeyService, listService, themeService, configurationService, accessibilityService); this.disposables.add(this.internals); } + + updateOptions(options: IWorkbenchAsyncDataTreeOptions = {}): void { + super.updateOptions(options); + this.internals.updateStyleOverrides(options.overrideStyles); + } } export interface IWorkbenchCompressibleAsyncDataTreeOptions extends ICompressibleAsyncDataTreeOptions { @@ -936,15 +941,16 @@ class WorkbenchTreeInternals { private hasMultiSelection: IContextKey; private _useAltAsMultipleSelectionModifier: boolean; private disposables: IDisposable[] = []; + private styler!: IDisposable; constructor( - tree: WorkbenchObjectTree | CompressibleObjectTree | WorkbenchDataTree | WorkbenchAsyncDataTree | WorkbenchCompressibleAsyncDataTree, + private tree: WorkbenchObjectTree | CompressibleObjectTree | WorkbenchDataTree | WorkbenchAsyncDataTree | WorkbenchCompressibleAsyncDataTree, options: IAbstractTreeOptions | IAsyncDataTreeOptions, getAutomaticKeyboardNavigation: () => boolean | undefined, overrideStyles: IColorMapping | undefined, @IContextKeyService contextKeyService: IContextKeyService, @IListService listService: IListService, - @IThemeService themeService: IThemeService, + @IThemeService private themeService: IThemeService, @IConfigurationService configurationService: IConfigurationService, @IAccessibilityService accessibilityService: IAccessibilityService, ) { @@ -970,10 +976,11 @@ class WorkbenchTreeInternals { }); }; + this.updateStyleOverrides(overrideStyles); + this.disposables.push( this.contextKeyService, (listService as ListService).register(tree), - overrideStyles ? attachListStyler(tree, themeService, overrideStyles) : Disposable.None, tree.onDidChangeSelection(() => { const selection = tree.getSelection(); const focus = tree.getFocus(); @@ -1023,8 +1030,14 @@ class WorkbenchTreeInternals { return this._useAltAsMultipleSelectionModifier; } + updateStyleOverrides(overrideStyles?: IColorMapping): void { + dispose(this.styler); + this.styler = overrideStyles ? attachListStyler(this.tree, this.themeService, overrideStyles) : Disposable.None; + } + dispose(): void { this.disposables = dispose(this.disposables); + this.styler = dispose(this.styler); } } diff --git a/src/vs/workbench/api/browser/viewsExtensionPoint.ts b/src/vs/workbench/api/browser/viewsExtensionPoint.ts index e198a29483d..d313f037550 100644 --- a/src/vs/workbench/api/browser/viewsExtensionPoint.ts +++ b/src/vs/workbench/api/browser/viewsExtensionPoint.ts @@ -37,6 +37,7 @@ import { IWorkbenchActionRegistry, Extensions as ActionExtensions } from 'vs/wor import { SyncActionDescriptor } from 'vs/platform/actions/common/actions'; import { ViewPaneContainer } from 'vs/workbench/browser/parts/views/viewPaneContainer'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; +// import { SIDE_BAR_BACKGROUND, PANEL_BACKGROUND } from 'vs/workbench/common/theme'; export interface IUserFriendlyViewsContainerDescriptor { id: string; @@ -421,8 +422,9 @@ class ViewsExtensionHandler implements IWorkbenchContribution { ctorDescriptor: new SyncDescriptor(CustomTreeViewPane), when: ContextKeyExpr.deserialize(item.when), canToggleVisibility: true, + canMoveView: true, + treeView: this.instantiationService.createInstance(CustomTreeView, item.id, item.name), collapsed: this.showCollapsed(container), - treeView: this.instantiationService.createInstance(CustomTreeView, item.id, item.name, container), order: order, extensionId: extension.description.identifier, originalContainerId: entry.key, diff --git a/src/vs/workbench/browser/parts/views/customView.ts b/src/vs/workbench/browser/parts/views/customView.ts index eecea644351..a814eb0a15f 100644 --- a/src/vs/workbench/browser/parts/views/customView.ts +++ b/src/vs/workbench/browser/parts/views/customView.ts @@ -13,7 +13,7 @@ import { IContextMenuService } from 'vs/platform/contextview/browser/contextView import { IMenuService, MenuId, MenuItemAction } from 'vs/platform/actions/common/actions'; import { ContextAwareMenuEntryActionViewItem, createAndFillInContextMenuActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; -import { ITreeView, ITreeItem, TreeItemCollapsibleState, ITreeViewDataProvider, TreeViewItemHandleArg, ITreeViewDescriptor, IViewsRegistry, ViewContainer, ITreeItemLabel, Extensions, IViewDescriptorService } from 'vs/workbench/common/views'; +import { ITreeView, ITreeItem, TreeItemCollapsibleState, ITreeViewDataProvider, TreeViewItemHandleArg, ITreeViewDescriptor, IViewsRegistry, ViewContainer, ITreeItemLabel, Extensions, IViewDescriptorService, IViewContainersRegistry, ViewContainerLocation } from 'vs/workbench/common/views'; import { IViewletViewOptions } from 'vs/workbench/browser/parts/views/viewsViewlet'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { INotificationService } from 'vs/platform/notification/common/notification'; @@ -41,7 +41,7 @@ import { ITreeRenderer, ITreeNode, IAsyncDataSource, ITreeContextMenuEvent } fro import { FuzzyScore, createMatches } from 'vs/base/common/filters'; import { CollapseAllAction } from 'vs/base/browser/ui/tree/treeDefaults'; import { isFalsyOrWhitespace } from 'vs/base/common/strings'; -import { SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme'; +import { SIDE_BAR_BACKGROUND, PANEL_BACKGROUND } from 'vs/workbench/common/theme'; export class CustomTreeViewPane extends ViewPane { @@ -154,7 +154,6 @@ export class CustomTreeView extends Disposable implements ITreeView { constructor( private id: string, private _title: string, - private viewContainer: ViewContainer, @IExtensionService private readonly extensionService: IExtensionService, @IWorkbenchThemeService private readonly themeService: IWorkbenchThemeService, @IInstantiationService private readonly instantiationService: IInstantiationService, @@ -163,6 +162,7 @@ export class CustomTreeView extends Disposable implements ITreeView { @IProgressService private readonly progressService: IProgressService, @IContextMenuService private readonly contextMenuService: IContextMenuService, @IKeybindingService private readonly keybindingService: IKeybindingService, + @IViewDescriptorService private readonly viewDescriptorService: IViewDescriptorService, @INotificationService private readonly notificationService: INotificationService ) { super(); @@ -174,11 +174,7 @@ export class CustomTreeView extends Disposable implements ITreeView { this.doRefresh([this.root]); /** soft refresh **/ } })); - this._register(Registry.as(Extensions.ViewsRegistry).onDidChangeContainer(({ views, from, to }) => { - if (from === this.viewContainer && views.some(v => v.id === this.id)) { - this.viewContainer = to; - } - })); + this.create(); } diff --git a/src/vs/workbench/common/views.ts b/src/vs/workbench/common/views.ts index 5a8f3edbd07..d5f1e856ea7 100644 --- a/src/vs/workbench/common/views.ts +++ b/src/vs/workbench/common/views.ts @@ -366,6 +366,8 @@ export interface IViewDescriptorService { _serviceBrand: undefined; + readonly onDidChangeContainer: Event<{ views: IViewDescriptor[], from: ViewContainer, to: ViewContainer }>; + moveViewToLocation(view: IViewDescriptor, location: ViewContainerLocation): void; moveViewsToContainer(views: IViewDescriptor[], viewContainer: ViewContainer): void; @@ -443,9 +445,7 @@ export interface IRevealOptions { } export interface ITreeViewDescriptor extends IViewDescriptor { - - readonly treeView: ITreeView; - + treeView: ITreeView; } export type TreeViewItemHandleArg = { diff --git a/src/vs/workbench/services/views/browser/viewDescriptorService.ts b/src/vs/workbench/services/views/browser/viewDescriptorService.ts index fb1d0b4e3bd..f4f0e77d123 100644 --- a/src/vs/workbench/services/views/browser/viewDescriptorService.ts +++ b/src/vs/workbench/services/views/browser/viewDescriptorService.ts @@ -181,6 +181,9 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor private static readonly CACHED_VIEW_POSITIONS = 'views.cachedViewPositions'; private static readonly COMMON_CONTAINER_ID_PREFIX = 'workbench.views.service'; + private readonly _onDidChangeContainer: Emitter<{ views: IViewDescriptor[], from: ViewContainer, to: ViewContainer }> = this._register(new Emitter<{ views: IViewDescriptor[], from: ViewContainer, to: ViewContainer }>()); + readonly onDidChangeContainer: Event<{ views: IViewDescriptor[], from: ViewContainer, to: ViewContainer }> = this._onDidChangeContainer.event; + private readonly viewDescriptorCollections: Map; private readonly activeViewContextKeys: Map>; private readonly movableViewContextKeys: Map>; @@ -230,7 +233,7 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor this._register(this.viewsRegistry.onViewsRegistered(({ views, viewContainer }) => this.onDidRegisterViews(views, viewContainer))); this._register(this.viewsRegistry.onViewsDeregistered(({ views, viewContainer }) => this.onDidDeregisterViews(views, viewContainer))); - this._register(this.viewsRegistry.onDidChangeContainer(({ views, from, to }) => { this.removeViews(from, views); this.addViews(to, views); })); + this._register(this.viewsRegistry.onDidChangeContainer(({ views, from, to }) => { this.removeViews(from, views); this.addViews(to, views); this._onDidChangeContainer.fire({ views, from, to }); })); this._register(this.viewContainersRegistry.onDidRegister(({ viewContainer }) => this.onDidRegisterViewContainer(viewContainer))); this._register(this.viewContainersRegistry.onDidDeregister(({ viewContainer }) => this.onDidDeregisterViewContainer(viewContainer))); @@ -357,6 +360,7 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor getViewContainer(viewId: string): ViewContainer | null { const containerId = this.cachedViewInfo.get(viewId)?.containerId; + return containerId ? this.viewContainersRegistry.get(containerId) ?? null : this.viewsRegistry.getViewContainer(viewId); @@ -395,6 +399,7 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor if (from && to && from !== to) { this.removeViews(from, views); this.addViews(to, views); + this._onDidChangeContainer.fire({ views, from, to }); this.saveViewPositionsToCache(); } } From 4234daa30475a857b9fca50465a3841dff79fe59 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Fri, 24 Jan 2020 09:12:27 -0800 Subject: [PATCH 093/801] Bump node2 --- build/builtInExtensions.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/builtInExtensions.json b/build/builtInExtensions.json index 2db43516b84..47480f23670 100644 --- a/build/builtInExtensions.json +++ b/build/builtInExtensions.json @@ -16,7 +16,7 @@ }, { "name": "ms-vscode.node-debug2", - "version": "1.42.0", + "version": "1.42.1", "repo": "https://github.com/Microsoft/vscode-node-debug2", "metadata": { "id": "36d19e17-7569-4841-a001-947eb18602b2", From 66d21376e3cb87451a37307bb4741193309e45b4 Mon Sep 17 00:00:00 2001 From: isidor Date: Fri, 24 Jan 2020 18:15:38 +0100 Subject: [PATCH 094/801] accessibility service cleanup --- src/vs/editor/browser/config/configuration.ts | 6 +-- .../standalone/browser/standaloneServices.ts | 4 +- .../common/abstractAccessibilityService.ts | 43 ----------------- .../accessibility/common/accessibility.ts | 4 +- .../common/accessibilityService.ts | 47 +++++++++++++------ src/vs/platform/list/browser/listService.ts | 8 ++-- .../browser/parts/editor/editorStatus.ts | 2 +- .../browser/parts/quickinput/quickInput.ts | 11 +---- .../browser/parts/titlebar/menubarControl.ts | 6 +-- .../contrib/search/browser/searchView.ts | 10 +--- .../contrib/search/browser/searchWidget.ts | 13 ++--- .../terminal/browser/terminalInstance.ts | 12 ++--- src/vs/workbench/electron-browser/window.ts | 2 +- .../node/accessibilityService.ts | 18 ++----- .../timer/electron-browser/timerService.ts | 4 +- .../workbench/test/workbenchTestServices.ts | 4 +- src/vs/workbench/workbench.web.main.ts | 4 +- 17 files changed, 67 insertions(+), 131 deletions(-) delete mode 100644 src/vs/platform/accessibility/common/abstractAccessibilityService.ts diff --git a/src/vs/editor/browser/config/configuration.ts b/src/vs/editor/browser/config/configuration.ts index 142b4b3eda1..8ba8c494b7a 100644 --- a/src/vs/editor/browser/config/configuration.ts +++ b/src/vs/editor/browser/config/configuration.ts @@ -14,7 +14,7 @@ import { CommonEditorConfiguration, IEnvConfiguration } from 'vs/editor/common/c import { EditorOption, IEditorConstructionOptions, EditorFontLigatures } from 'vs/editor/common/config/editorOptions'; import { BareFontInfo, FontInfo } from 'vs/editor/common/config/fontInfo'; import { IDimension } from 'vs/editor/common/editorCommon'; -import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; +import { IAccessibilityService, AccessibilitySupport } from 'vs/platform/accessibility/common/accessibility'; class CSSBasedConfigurationCache { @@ -338,7 +338,7 @@ export class Configuration extends CommonEditorConfiguration { } this._register(browser.onDidChangeZoomLevel(_ => this._recomputeOptions())); - this._register(this.accessibilityService.onDidChangeAccessibilitySupport(() => this._recomputeOptions())); + this._register(this.accessibilityService.onDidChangeScreenReaderOptimized(() => this._recomputeOptions())); this._recomputeOptions(); } @@ -379,7 +379,7 @@ export class Configuration extends CommonEditorConfiguration { emptySelectionClipboard: browser.isWebKit || browser.isFirefox, pixelRatio: browser.getPixelRatio(), zoomLevel: browser.getZoomLevel(), - accessibilitySupport: this.accessibilityService.getAccessibilitySupport() + accessibilitySupport: this.accessibilityService.isScreenReaderOptimized() ? AccessibilitySupport.Enabled : AccessibilitySupport.Disabled }; } diff --git a/src/vs/editor/standalone/browser/standaloneServices.ts b/src/vs/editor/standalone/browser/standaloneServices.ts index be125790fee..44581138ad5 100644 --- a/src/vs/editor/standalone/browser/standaloneServices.ts +++ b/src/vs/editor/standalone/browser/standaloneServices.ts @@ -45,9 +45,9 @@ import { MenuService } from 'vs/platform/actions/common/menuService'; import { IMarkerDecorationsService } from 'vs/editor/common/services/markersDecorationService'; import { MarkerDecorationsService } from 'vs/editor/common/services/markerDecorationsServiceImpl'; import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; -import { BrowserAccessibilityService } from 'vs/platform/accessibility/common/accessibilityService'; import { ILayoutService } from 'vs/platform/layout/browser/layoutService'; import { getSingletonServiceDescriptors } from 'vs/platform/instantiation/common/extensions'; +import { AccessibilityService } from 'vs/platform/accessibility/common/accessibilityService'; export interface IEditorOverrideServices { [index: string]: any; @@ -192,7 +192,7 @@ export class DynamicStandaloneServices extends Disposable { let contextKeyService = ensure(IContextKeyService, () => this._register(new ContextKeyService(configurationService))); - ensure(IAccessibilityService, () => new BrowserAccessibilityService(contextKeyService, configurationService)); + ensure(IAccessibilityService, () => new AccessibilityService(contextKeyService, configurationService)); ensure(IListService, () => new ListService(themeService)); diff --git a/src/vs/platform/accessibility/common/abstractAccessibilityService.ts b/src/vs/platform/accessibility/common/abstractAccessibilityService.ts deleted file mode 100644 index 814e9fd0bf0..00000000000 --- a/src/vs/platform/accessibility/common/abstractAccessibilityService.ts +++ /dev/null @@ -1,43 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { Disposable } from 'vs/base/common/lifecycle'; -import { IAccessibilityService, AccessibilitySupport, CONTEXT_ACCESSIBILITY_MODE_ENABLED } from 'vs/platform/accessibility/common/accessibility'; -import { Event, Emitter } from 'vs/base/common/event'; -import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; - -export abstract class AbstractAccessibilityService extends Disposable implements IAccessibilityService { - _serviceBrand: undefined; - - private _accessibilityModeEnabledContext: IContextKey; - protected readonly _onDidChangeAccessibilitySupport = new Emitter(); - readonly onDidChangeAccessibilitySupport: Event = this._onDidChangeAccessibilitySupport.event; - - constructor( - @IContextKeyService private readonly _contextKeyService: IContextKeyService, - @IConfigurationService private readonly _configurationService: IConfigurationService, - ) { - super(); - this._accessibilityModeEnabledContext = CONTEXT_ACCESSIBILITY_MODE_ENABLED.bindTo(this._contextKeyService); - this._register(this._configurationService.onDidChangeConfiguration(e => { - if (e.affectsConfiguration('editor.accessibilitySupport')) { - this._updateContextKey(); - } - })); - this._updateContextKey(); - this.onDidChangeAccessibilitySupport(() => this._updateContextKey()); - } - - abstract alwaysUnderlineAccessKeys(): Promise; - abstract getAccessibilitySupport(): AccessibilitySupport; - abstract setAccessibilitySupport(accessibilitySupport: AccessibilitySupport): void; - - private _updateContextKey(): void { - const detected = this.getAccessibilitySupport() === AccessibilitySupport.Enabled; - const config = this._configurationService.getValue('editor.accessibilitySupport'); - this._accessibilityModeEnabledContext.set(config === 'on' || (config === 'auto' && detected)); - } -} \ No newline at end of file diff --git a/src/vs/platform/accessibility/common/accessibility.ts b/src/vs/platform/accessibility/common/accessibility.ts index 825ff4e20ec..0fb23d2e7f0 100644 --- a/src/vs/platform/accessibility/common/accessibility.ts +++ b/src/vs/platform/accessibility/common/accessibility.ts @@ -12,10 +12,10 @@ export const IAccessibilityService = createDecorator('acc export interface IAccessibilityService { _serviceBrand: undefined; - readonly onDidChangeAccessibilitySupport: Event; + readonly onDidChangeScreenReaderOptimized: Event; alwaysUnderlineAccessKeys(): Promise; - getAccessibilitySupport(): AccessibilitySupport; + isScreenReaderOptimized(): boolean; setAccessibilitySupport(accessibilitySupport: AccessibilitySupport): void; } diff --git a/src/vs/platform/accessibility/common/accessibilityService.ts b/src/vs/platform/accessibility/common/accessibilityService.ts index 2f1d30ec3de..bbc79d2c54a 100644 --- a/src/vs/platform/accessibility/common/accessibilityService.ts +++ b/src/vs/platform/accessibility/common/accessibilityService.ts @@ -3,22 +3,43 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IAccessibilityService, AccessibilitySupport } from 'vs/platform/accessibility/common/accessibility'; -import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { Disposable } from 'vs/base/common/lifecycle'; +import { IAccessibilityService, AccessibilitySupport, CONTEXT_ACCESSIBILITY_MODE_ENABLED } from 'vs/platform/accessibility/common/accessibility'; +import { Event, Emitter } from 'vs/base/common/event'; +import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { AbstractAccessibilityService } from 'vs/platform/accessibility/common/abstractAccessibilityService'; - -export class BrowserAccessibilityService extends AbstractAccessibilityService implements IAccessibilityService { +export class AccessibilityService extends Disposable implements IAccessibilityService { _serviceBrand: undefined; - private _accessibilitySupport = AccessibilitySupport.Unknown; + private _accessibilityModeEnabledContext: IContextKey; + protected _accessibilitySupport = AccessibilitySupport.Unknown; + protected readonly _onDidChangeScreenReaderOptimized = new Emitter(); constructor( - @IContextKeyService readonly contextKeyService: IContextKeyService, - @IConfigurationService readonly configurationService: IConfigurationService, + @IContextKeyService private readonly _contextKeyService: IContextKeyService, + @IConfigurationService protected readonly _configurationService: IConfigurationService, ) { - super(contextKeyService, configurationService); + super(); + this._accessibilityModeEnabledContext = CONTEXT_ACCESSIBILITY_MODE_ENABLED.bindTo(this._contextKeyService); + const updateContextKey = () => this._accessibilityModeEnabledContext.set(this.isScreenReaderOptimized()); + this._register(this._configurationService.onDidChangeConfiguration(e => { + if (e.affectsConfiguration('editor.accessibilitySupport')) { + updateContextKey(); + this._onDidChangeScreenReaderOptimized.fire(); + } + })); + updateContextKey(); + this.onDidChangeScreenReaderOptimized(() => updateContextKey()); + } + + get onDidChangeScreenReaderOptimized(): Event { + return this._onDidChangeScreenReaderOptimized.event; + } + + isScreenReaderOptimized(): boolean { + const config = this._configurationService.getValue('editor.accessibilitySupport'); + return config === 'on' || (config === 'auto' && this._accessibilitySupport === AccessibilitySupport.Enabled); } alwaysUnderlineAccessKeys(): Promise { @@ -31,10 +52,6 @@ export class BrowserAccessibilityService extends AbstractAccessibilityService im } this._accessibilitySupport = accessibilitySupport; - this._onDidChangeAccessibilitySupport.fire(); + this._onDidChangeScreenReaderOptimized.fire(); } - - getAccessibilitySupport(): AccessibilitySupport { - return this._accessibilitySupport; - } -} \ No newline at end of file +} diff --git a/src/vs/platform/list/browser/listService.ts b/src/vs/platform/list/browser/listService.ts index ee29d02b4d0..1a87d9dcc92 100644 --- a/src/vs/platform/list/browser/listService.ts +++ b/src/vs/platform/list/browser/listService.ts @@ -30,7 +30,7 @@ import { ITreeEvent, ITreeRenderer, IAsyncDataSource, IDataSource, ITreeMouseEve import { AsyncDataTree, IAsyncDataTreeOptions, CompressibleAsyncDataTree, ITreeCompressionDelegate, ICompressibleAsyncDataTreeOptions } from 'vs/base/browser/ui/tree/asyncDataTree'; import { DataTree, IDataTreeOptions } from 'vs/base/browser/ui/tree/dataTree'; import { IKeyboardNavigationEventFilter, IAbstractTreeOptions, RenderIndentGuides } from 'vs/base/browser/ui/tree/abstractTree'; -import { IAccessibilityService, AccessibilitySupport } from 'vs/platform/accessibility/common/accessibility'; +import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; export type ListWidget = List | PagedList | ITree | ObjectTree | DataTree | AsyncDataTree; @@ -900,7 +900,7 @@ function workbenchTreeDataPreamble(keyboardNavigationSettingKey); const horizontalScrolling = typeof options.horizontalScrolling !== 'undefined' ? options.horizontalScrolling : getHorizontalScrollingSetting(configurationService); const openOnSingleClick = useSingleClickToOpen(configurationService); @@ -962,7 +962,7 @@ class WorkbenchTreeInternals { const interestingContextKeys = new Set(); interestingContextKeys.add(WorkbenchListAutomaticKeyboardNavigationKey); const updateKeyboardNavigation = () => { - const accessibilityOn = accessibilityService.getAccessibilitySupport() === AccessibilitySupport.Enabled; + const accessibilityOn = accessibilityService.isScreenReaderOptimized(); const keyboardNavigation = accessibilityOn ? 'simple' : configurationService.getValue(keyboardNavigationSettingKey); tree.updateOptions({ simpleKeyboardNavigation: keyboardNavigation === 'simple', @@ -1015,7 +1015,7 @@ class WorkbenchTreeInternals { tree.updateOptions({ automaticKeyboardNavigation: getAutomaticKeyboardNavigation() }); } }), - accessibilityService.onDidChangeAccessibilitySupport(() => updateKeyboardNavigation()) + accessibilityService.onDidChangeScreenReaderOptimized(() => updateKeyboardNavigation()) ); } diff --git a/src/vs/workbench/browser/parts/editor/editorStatus.ts b/src/vs/workbench/browser/parts/editor/editorStatus.ts index 7a281a4cbdc..19fe24f3c7b 100644 --- a/src/vs/workbench/browser/parts/editor/editorStatus.ts +++ b/src/vs/workbench/browser/parts/editor/editorStatus.ts @@ -709,7 +709,7 @@ export class EditorStatus extends Disposable implements IWorkbenchContribution { // We only support text based editors if (editorWidget) { - const screenReaderDetected = (this.accessibilityService.getAccessibilitySupport() === AccessibilitySupport.Enabled); + const screenReaderDetected = this.accessibilityService.isScreenReaderOptimized(); if (screenReaderDetected) { const screenReaderConfiguration = this.configurationService.getValue('editor').accessibilitySupport; if (screenReaderConfiguration === 'auto') { diff --git a/src/vs/workbench/browser/parts/quickinput/quickInput.ts b/src/vs/workbench/browser/parts/quickinput/quickInput.ts index ab197d73eda..8aefb32cf81 100644 --- a/src/vs/workbench/browser/parts/quickinput/quickInput.ts +++ b/src/vs/workbench/browser/parts/quickinput/quickInput.ts @@ -40,9 +40,8 @@ import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { equals } from 'vs/base/common/arrays'; import { TimeoutTimer } from 'vs/base/common/async'; import { getIconClass } from 'vs/workbench/browser/parts/quickinput/quickInputUtils'; -import { IEditorOptions } from 'vs/editor/common/config/editorOptions'; import { IStorageService } from 'vs/platform/storage/common/storage'; -import { IAccessibilityService, AccessibilitySupport } from 'vs/platform/accessibility/common/accessibility'; +import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { registerAndGetAmdImageURL } from 'vs/base/common/amd'; @@ -1209,7 +1208,7 @@ export class QuickInputService extends Component implements IQuickInputService { onDidTriggerButton: this.onDidTriggerButtonEmitter.event, ignoreFocusOut: false, keyMods: this.keyMods, - isScreenReaderOptimized: () => this.isScreenReaderOptimized(), + isScreenReaderOptimized: () => this.accessibilityService.isScreenReaderOptimized(), show: controller => this.show(controller), hide: () => this.hide(), setVisibilities: visibilities => this.setVisibilities(visibilities), @@ -1465,12 +1464,6 @@ export class QuickInputService extends Component implements IQuickInputService { } } - private isScreenReaderOptimized() { - const detected = this.accessibilityService.getAccessibilitySupport() === AccessibilitySupport.Enabled; - const config = this.configurationService.getValue('editor').accessibilitySupport; - return config === 'on' || (config === 'auto' && detected); - } - private setEnabled(enabled: boolean) { if (enabled !== this.enabled) { this.enabled = enabled; diff --git a/src/vs/workbench/browser/parts/titlebar/menubarControl.ts b/src/vs/workbench/browser/parts/titlebar/menubarControl.ts index 93958775e4f..76a3ea95bf2 100644 --- a/src/vs/workbench/browser/parts/titlebar/menubarControl.ts +++ b/src/vs/workbench/browser/parts/titlebar/menubarControl.ts @@ -31,7 +31,7 @@ import { SubmenuAction, Direction } from 'vs/base/browser/ui/menu/menu'; import { attachMenuStyler } from 'vs/platform/theme/common/styler'; import { assign } from 'vs/base/common/objects'; import { mnemonicMenuLabel, unmnemonicLabel } from 'vs/base/common/labels'; -import { IAccessibilityService, AccessibilitySupport } from 'vs/platform/accessibility/common/accessibility'; +import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; import { isFullscreen } from 'vs/base/browser/browser'; import { IHostService } from 'vs/workbench/services/host/browser/host'; @@ -249,10 +249,8 @@ export abstract class MenubarControl extends Disposable { const hasBeenNotified = this.storageService.getBoolean('menubar/accessibleMenubarNotified', StorageScope.GLOBAL, false); const usingCustomMenubar = getTitleBarStyle(this.configurationService, this.environmentService) === 'custom'; - const detected = this.accessibilityService.getAccessibilitySupport() === AccessibilitySupport.Enabled; - const config = this.configurationService.getValue('editor.accessibilitySupport'); - if (hasBeenNotified || usingCustomMenubar || !(config === 'on' || (config === 'auto' && detected))) { + if (hasBeenNotified || usingCustomMenubar || !this.accessibilityService.isScreenReaderOptimized()) { return; } diff --git a/src/vs/workbench/contrib/search/browser/searchView.ts b/src/vs/workbench/contrib/search/browser/searchView.ts index 2e0b61c063b..a0ba9324449 100644 --- a/src/vs/workbench/contrib/search/browser/searchView.ts +++ b/src/vs/workbench/contrib/search/browser/searchView.ts @@ -55,7 +55,7 @@ import { ACTIVE_GROUP, IEditorService, SIDE_GROUP } from 'vs/workbench/services/ import { IPreferencesService, ISettingsEditorOptions } from 'vs/workbench/services/preferences/common/preferences'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import { relativePath } from 'vs/base/common/resources'; -import { IAccessibilityService, AccessibilitySupport } from 'vs/platform/accessibility/common/accessibility'; +import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; import { ViewPane, IViewPaneOptions } from 'vs/workbench/browser/parts/views/viewPaneContainer'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { Memento, MementoObject } from 'vs/workbench/common/memento'; @@ -277,7 +277,7 @@ export class SearchView extends ViewPane { this._register(dom.addDisposableListener(this.toggleQueryDetailsButton, dom.EventType.CLICK, e => { dom.EventHelper.stop(e); - this.toggleQueryDetails(!this.isScreenReaderOptimized()); + this.toggleQueryDetails(!this.accessibilityService.isScreenReaderOptimized()); })); this._register(dom.addDisposableListener(this.toggleQueryDetailsButton, dom.EventType.KEY_UP, (e: KeyboardEvent) => { const event = new StandardKeyboardEvent(e); @@ -410,12 +410,6 @@ export class SearchView extends ViewPane { super.updateActions(); } - private isScreenReaderOptimized() { - const detected = this.accessibilityService.getAccessibilitySupport() === AccessibilitySupport.Enabled; - const config = this.configurationService.getValue('editor').accessibilitySupport; - return config === 'on' || (config === 'auto' && detected); - } - private createSearchWidget(container: HTMLElement): void { const contentPattern = this.viewletState['query.contentPattern'] || ''; const replaceText = this.viewletState['query.replaceText'] || ''; diff --git a/src/vs/workbench/contrib/search/browser/searchWidget.ts b/src/vs/workbench/contrib/search/browser/searchWidget.ts index df337113aa4..6de22f21f68 100644 --- a/src/vs/workbench/contrib/search/browser/searchWidget.ts +++ b/src/vs/workbench/contrib/search/browser/searchWidget.ts @@ -29,8 +29,7 @@ import { IThemeService } from 'vs/platform/theme/common/themeService'; import { ContextScopedFindInput, ContextScopedReplaceInput } from 'vs/platform/browser/contextScopedHistoryWidget'; import { appendKeyBindingLabel, isSearchViewFocused } from 'vs/workbench/contrib/search/browser/searchActions'; import * as Constants from 'vs/workbench/contrib/search/common/constants'; -import { IEditorOptions } from 'vs/editor/common/config/editorOptions'; -import { IAccessibilityService, AccessibilitySupport } from 'vs/platform/accessibility/common/accessibility'; +import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; import { isMacintosh } from 'vs/base/common/platform'; import { Checkbox } from 'vs/base/browser/ui/checkbox/checkbox'; import { IViewsService } from 'vs/workbench/common/views'; @@ -186,7 +185,7 @@ export class SearchWidget extends Widget { this.updateAccessibilitySupport(); } }); - this.accessibilityService.onDidChangeAccessibilitySupport(() => this.updateAccessibilitySupport()); + this.accessibilityService.onDidChangeScreenReaderOptimized(() => this.updateAccessibilitySupport()); this.updateAccessibilitySupport(); } @@ -292,14 +291,8 @@ export class SearchWidget extends Widget { this.renderReplaceInput(this.domNode, options); } - private isScreenReaderOptimized() { - const detected = this.accessibilityService.getAccessibilitySupport() === AccessibilitySupport.Enabled; - const config = this.configurationService.getValue('editor').accessibilitySupport; - return config === 'on' || (config === 'auto' && detected); - } - private updateAccessibilitySupport(): void { - this.searchInput.setFocusInputOnOptionClick(!this.isScreenReaderOptimized()); + this.searchInput.setFocusInputOnOptionClick(!this.accessibilityService.isScreenReaderOptimized()); } private renderToggleReplaceButton(parent: HTMLElement): void { diff --git a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts index a8f89e9ba30..aea2f505d7c 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts @@ -30,7 +30,7 @@ import { ansiColorIdentifiers, TERMINAL_BACKGROUND_COLOR, TERMINAL_CURSOR_BACKGR import { TerminalConfigHelper } from 'vs/workbench/contrib/terminal/browser/terminalConfigHelper'; import { TerminalLinkHandler } from 'vs/workbench/contrib/terminal/browser/terminalLinkHandler'; import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; -import { IAccessibilityService, AccessibilitySupport } from 'vs/platform/accessibility/common/accessibility'; +import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; import { ITerminalInstanceService, ITerminalInstance, TerminalShellType } from 'vs/workbench/contrib/terminal/browser/terminal'; import { TerminalProcessManager } from 'vs/workbench/contrib/terminal/browser/terminalProcessManager'; import { Terminal as XTermTerminal, IBuffer, ITerminalAddon } from 'xterm'; @@ -526,12 +526,6 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { return xterm; } - private _isScreenReaderOptimized(): boolean { - const detected = this._accessibilityService.getAccessibilitySupport() === AccessibilitySupport.Enabled; - const config = this._configurationService.getValue('editor.accessibilitySupport'); - return config === 'on' || (config === 'auto' && detected); - } - public reattachToElement(container: HTMLElement): void { if (!this._wrapperElement) { throw new Error('The terminal instance has not been attached to a container yet'); @@ -1008,7 +1002,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { // Create the process asynchronously to allow the terminal's container // to be created so dimensions are accurate setTimeout(() => { - this._processManager.createProcess(this._shellLaunchConfig, this._cols, this._rows, this._isScreenReaderOptimized()); + this._processManager.createProcess(this._shellLaunchConfig, this._cols, this._rows, this._accessibilityService.isScreenReaderOptimized()); }, 0); } @@ -1246,7 +1240,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { } public updateAccessibilitySupport(): void { - const isEnabled = this._isScreenReaderOptimized(); + const isEnabled = this._accessibilityService.isScreenReaderOptimized(); if (isEnabled) { this._navigationModeAddon = new NavigationModeAddon(this._terminalA11yTreeFocusContextKey); this._xterm!.loadAddon(this._navigationModeAddon); diff --git a/src/vs/workbench/electron-browser/window.ts b/src/vs/workbench/electron-browser/window.ts index 989c9733cf6..65d95f95ecd 100644 --- a/src/vs/workbench/electron-browser/window.ts +++ b/src/vs/workbench/electron-browser/window.ts @@ -213,7 +213,7 @@ export class ElectronWindow extends Disposable { KeyboardMapperFactory.INSTANCE._onKeyboardLayoutChanged(); }); - // keyboard layout changed event + // accessibility support changed event ipc.on('vscode:accessibilitySupportChanged', (event: IpcEvent, accessibilitySupportEnabled: boolean) => { this.accessibilityService.setAccessibilitySupport(accessibilitySupportEnabled ? AccessibilitySupport.Enabled : AccessibilitySupport.Disabled); }); diff --git a/src/vs/workbench/services/accessibility/node/accessibilityService.ts b/src/vs/workbench/services/accessibility/node/accessibilityService.ts index 1c698443b36..faedc3fd318 100644 --- a/src/vs/workbench/services/accessibility/node/accessibilityService.ts +++ b/src/vs/workbench/services/accessibility/node/accessibilityService.ts @@ -8,7 +8,7 @@ import { isWindows } from 'vs/base/common/platform'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { AbstractAccessibilityService } from 'vs/platform/accessibility/common/abstractAccessibilityService'; +import { AccessibilityService } from 'vs/platform/accessibility/common/accessibilityService'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; @@ -19,11 +19,10 @@ type AccessibilityMetricsClassification = { enabled: { classification: 'SystemMetaData', purpose: 'FeatureInsight' }; }; -export class AccessibilityService extends AbstractAccessibilityService implements IAccessibilityService { +export class NodeAccessibilityService extends AccessibilityService implements IAccessibilityService { _serviceBrand: undefined; - private _accessibilitySupport = AccessibilitySupport.Unknown; private didSendTelemetry = false; constructor( @@ -56,22 +55,13 @@ export class AccessibilityService extends AbstractAccessibilityService implement } setAccessibilitySupport(accessibilitySupport: AccessibilitySupport): void { - if (this._accessibilitySupport === accessibilitySupport) { - return; - } - - this._accessibilitySupport = accessibilitySupport; - this._onDidChangeAccessibilitySupport.fire(); + super.setAccessibilitySupport(accessibilitySupport); if (!this.didSendTelemetry && accessibilitySupport === AccessibilitySupport.Enabled) { this._telemetryService.publicLog2('accessibility', { enabled: true }); this.didSendTelemetry = true; } } - - getAccessibilitySupport(): AccessibilitySupport { - return this._accessibilitySupport; - } } -registerSingleton(IAccessibilityService, AccessibilityService, true); +registerSingleton(IAccessibilityService, NodeAccessibilityService, true); diff --git a/src/vs/workbench/services/timer/electron-browser/timerService.ts b/src/vs/workbench/services/timer/electron-browser/timerService.ts index 1f761754f54..f796d9d28b1 100644 --- a/src/vs/workbench/services/timer/electron-browser/timerService.ts +++ b/src/vs/workbench/services/timer/electron-browser/timerService.ts @@ -17,7 +17,7 @@ import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { IAccessibilityService, AccessibilitySupport } from 'vs/platform/accessibility/common/accessibility'; +import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; /* __GDPR__FRAGMENT__ "IMemoryInfo" : { @@ -414,7 +414,7 @@ class TimerService implements ITimerService { loadavg, initialStartup, isVMLikelyhood, - hasAccessibilitySupport: this._accessibilityService.getAccessibilitySupport() === AccessibilitySupport.Enabled, + hasAccessibilitySupport: this._accessibilityService.isScreenReaderOptimized(), emptyWorkbench: this._contextService.getWorkbenchState() === WorkbenchState.EMPTY }; } diff --git a/src/vs/workbench/test/workbenchTestServices.ts b/src/vs/workbench/test/workbenchTestServices.ts index 06ba8b40e29..bca2b4ab1ca 100644 --- a/src/vs/workbench/test/workbenchTestServices.ts +++ b/src/vs/workbench/test/workbenchTestServices.ts @@ -331,10 +331,10 @@ export class TestAccessibilityService implements IAccessibilityService { _serviceBrand: undefined; - onDidChangeAccessibilitySupport = Event.None; + onDidChangeScreenReaderOptimized = Event.None; + isScreenReaderOptimized(): boolean { return false; } alwaysUnderlineAccessKeys(): Promise { return Promise.resolve(false); } - getAccessibilitySupport(): AccessibilitySupport { return AccessibilitySupport.Unknown; } setAccessibilitySupport(accessibilitySupport: AccessibilitySupport): void { } } diff --git a/src/vs/workbench/workbench.web.main.ts b/src/vs/workbench/workbench.web.main.ts index fe9232cab52..0719b361e00 100644 --- a/src/vs/workbench/workbench.web.main.ts +++ b/src/vs/workbench/workbench.web.main.ts @@ -52,7 +52,6 @@ import 'vs/workbench/services/extensionResourceLoader/browser/extensionResourceL import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; -import { BrowserAccessibilityService } from 'vs/platform/accessibility/common/accessibilityService'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { ContextMenuService } from 'vs/platform/contextview/browser/contextMenuService'; import { IBackupFileService } from 'vs/workbench/services/backup/common/backup'; @@ -71,10 +70,11 @@ import { UserDataSyncService } from 'vs/platform/userDataSync/common/userDataSyn import { SettingsSynchroniser } from 'vs/platform/userDataSync/common/settingsSync'; import { UserDataAuthTokenService } from 'vs/platform/userDataSync/common/userDataAuthTokenService'; import { UserDataAutoSync } from 'vs/platform/userDataSync/common/userDataAutoSync'; +import { AccessibilityService } from 'vs/platform/accessibility/common/accessibilityService'; registerSingleton(IExtensionManagementService, ExtensionManagementService); registerSingleton(IBackupFileService, BackupFileService); -registerSingleton(IAccessibilityService, BrowserAccessibilityService, true); +registerSingleton(IAccessibilityService, AccessibilityService, true); registerSingleton(IContextMenuService, ContextMenuService); registerSingleton(ITunnelService, TunnelService, true); registerSingleton(ILoggerService, FileLoggerService); From 268f0a22a37376a260ce1bc4d69b5efc000b03cc Mon Sep 17 00:00:00 2001 From: SteVen Batten Date: Fri, 24 Jan 2020 09:37:52 -0800 Subject: [PATCH 095/801] add eventing to VDS to enable custom views --- .../api/browser/viewsExtensionPoint.ts | 1 - .../browser/parts/views/customView.ts | 21 +++++++++++++++---- src/vs/workbench/common/views.ts | 4 ++-- .../views/browser/viewDescriptorService.ts | 16 ++++++++++++++ 4 files changed, 35 insertions(+), 7 deletions(-) diff --git a/src/vs/workbench/api/browser/viewsExtensionPoint.ts b/src/vs/workbench/api/browser/viewsExtensionPoint.ts index d313f037550..e88f2c3df7c 100644 --- a/src/vs/workbench/api/browser/viewsExtensionPoint.ts +++ b/src/vs/workbench/api/browser/viewsExtensionPoint.ts @@ -37,7 +37,6 @@ import { IWorkbenchActionRegistry, Extensions as ActionExtensions } from 'vs/wor import { SyncActionDescriptor } from 'vs/platform/actions/common/actions'; import { ViewPaneContainer } from 'vs/workbench/browser/parts/views/viewPaneContainer'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; -// import { SIDE_BAR_BACKGROUND, PANEL_BACKGROUND } from 'vs/workbench/common/theme'; export interface IUserFriendlyViewsContainerDescriptor { id: string; diff --git a/src/vs/workbench/browser/parts/views/customView.ts b/src/vs/workbench/browser/parts/views/customView.ts index a814eb0a15f..c573df84e12 100644 --- a/src/vs/workbench/browser/parts/views/customView.ts +++ b/src/vs/workbench/browser/parts/views/customView.ts @@ -13,7 +13,7 @@ import { IContextMenuService } from 'vs/platform/contextview/browser/contextView import { IMenuService, MenuId, MenuItemAction } from 'vs/platform/actions/common/actions'; import { ContextAwareMenuEntryActionViewItem, createAndFillInContextMenuActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; -import { ITreeView, ITreeItem, TreeItemCollapsibleState, ITreeViewDataProvider, TreeViewItemHandleArg, ITreeViewDescriptor, IViewsRegistry, ViewContainer, ITreeItemLabel, Extensions, IViewDescriptorService, IViewContainersRegistry, ViewContainerLocation } from 'vs/workbench/common/views'; +import { ITreeView, ITreeItem, TreeItemCollapsibleState, ITreeViewDataProvider, TreeViewItemHandleArg, ITreeViewDescriptor, IViewsRegistry, ITreeItemLabel, Extensions, IViewDescriptorService, ViewContainer, ViewContainerLocation } from 'vs/workbench/common/views'; import { IViewletViewOptions } from 'vs/workbench/browser/parts/views/viewsViewlet'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { INotificationService } from 'vs/platform/notification/common/notification'; @@ -162,8 +162,8 @@ export class CustomTreeView extends Disposable implements ITreeView { @IProgressService private readonly progressService: IProgressService, @IContextMenuService private readonly contextMenuService: IContextMenuService, @IKeybindingService private readonly keybindingService: IKeybindingService, - @IViewDescriptorService private readonly viewDescriptorService: IViewDescriptorService, - @INotificationService private readonly notificationService: INotificationService + @INotificationService private readonly notificationService: INotificationService, + @IViewDescriptorService private readonly viewDescriptorService: IViewDescriptorService ) { super(); this.root = new Root(); @@ -174,10 +174,23 @@ export class CustomTreeView extends Disposable implements ITreeView { this.doRefresh([this.root]); /** soft refresh **/ } })); + this._register(this.viewDescriptorService.onDidChangeLocation(({ views, from, to }) => { + if (views.some(v => v.id === this.id)) { + this.tree?.updateOptions({ overrideStyles: { listBackground: this.viewLocation === ViewContainerLocation.Sidebar ? SIDE_BAR_BACKGROUND : PANEL_BACKGROUND } }); + } + })); this.create(); } + get viewContainer(): ViewContainer { + return this.viewDescriptorService.getViewContainer(this.id)!; + } + + get viewLocation(): ViewContainerLocation { + return this.viewDescriptorService.getViewLocation(this.id)!; + } + private _dataProvider: ITreeViewDataProvider | undefined; get dataProvider(): ITreeViewDataProvider | undefined { return this._dataProvider; @@ -356,7 +369,7 @@ export class CustomTreeView extends Disposable implements ITreeView { }, multipleSelectionSupport: this.canSelectMany, overrideStyles: { - listBackground: SIDE_BAR_BACKGROUND + listBackground: this.viewLocation === ViewContainerLocation.Sidebar ? SIDE_BAR_BACKGROUND : PANEL_BACKGROUND } }) as WorkbenchAsyncDataTree); aligner.tree = this.tree; diff --git a/src/vs/workbench/common/views.ts b/src/vs/workbench/common/views.ts index d5f1e856ea7..65cb05175d9 100644 --- a/src/vs/workbench/common/views.ts +++ b/src/vs/workbench/common/views.ts @@ -349,10 +349,8 @@ export interface IViewsViewlet extends IViewlet { } -export const IViewDescriptorService = createDecorator('viewDescriptorService'); export const IViewsService = createDecorator('viewsService'); - export interface IViewsService { _serviceBrand: undefined; @@ -361,12 +359,14 @@ export interface IViewsService { openView(id: string, focus?: boolean): Promise; } +export const IViewDescriptorService = createDecorator('viewDescriptorService'); export interface IViewDescriptorService { _serviceBrand: undefined; readonly onDidChangeContainer: Event<{ views: IViewDescriptor[], from: ViewContainer, to: ViewContainer }>; + readonly onDidChangeLocation: Event<{ views: IViewDescriptor[], from: ViewContainerLocation, to: ViewContainerLocation }>; moveViewToLocation(view: IViewDescriptor, location: ViewContainerLocation): void; diff --git a/src/vs/workbench/services/views/browser/viewDescriptorService.ts b/src/vs/workbench/services/views/browser/viewDescriptorService.ts index f4f0e77d123..b7a164dee5e 100644 --- a/src/vs/workbench/services/views/browser/viewDescriptorService.ts +++ b/src/vs/workbench/services/views/browser/viewDescriptorService.ts @@ -184,6 +184,9 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor private readonly _onDidChangeContainer: Emitter<{ views: IViewDescriptor[], from: ViewContainer, to: ViewContainer }> = this._register(new Emitter<{ views: IViewDescriptor[], from: ViewContainer, to: ViewContainer }>()); readonly onDidChangeContainer: Event<{ views: IViewDescriptor[], from: ViewContainer, to: ViewContainer }> = this._onDidChangeContainer.event; + private readonly _onDidChangeLocation: Emitter<{ views: IViewDescriptor[], from: ViewContainerLocation, to: ViewContainerLocation }> = this._register(new Emitter<{ views: IViewDescriptor[], from: ViewContainerLocation, to: ViewContainerLocation }>()); + readonly onDidChangeLocation: Event<{ views: IViewDescriptor[], from: ViewContainerLocation, to: ViewContainerLocation }> = this._onDidChangeLocation.event; + private readonly viewDescriptorCollections: Map; private readonly activeViewContextKeys: Map>; private readonly movableViewContextKeys: Map>; @@ -298,6 +301,11 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor const viewDescriptor = this.getViewDescriptor(viewId); if (viewContainer && viewDescriptor) { this.addViews(viewContainer, [viewDescriptor]); + + const newLocation = this.viewContainersRegistry.getViewContainerLocation(viewContainer)!; + if (containerInfo.location && containerInfo.location !== newLocation) { + this._onDidChangeLocation.fire({ views: [viewDescriptor], from: containerInfo.location, to: newLocation }); + } } } @@ -399,6 +407,14 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor if (from && to && from !== to) { this.removeViews(from, views); this.addViews(to, views); + + const oldLocation = this.viewContainersRegistry.getViewContainerLocation(from)!; + const newLocation = this.viewContainersRegistry.getViewContainerLocation(to)!; + + if (oldLocation !== newLocation) { + this._onDidChangeLocation.fire({ views, from: oldLocation, to: newLocation }); + } + this._onDidChangeContainer.fire({ views, from, to }); this.saveViewPositionsToCache(); } From 61a928c82466587ccdf85f4fa17c83caf51f4f7b Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Fri, 24 Jan 2020 09:45:25 -0800 Subject: [PATCH 096/801] extensions: use specific js-debug-nightly version to avoid downloading each time --- build/builtInExtensions.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/builtInExtensions.json b/build/builtInExtensions.json index 47480f23670..b718bdd60e5 100644 --- a/build/builtInExtensions.json +++ b/build/builtInExtensions.json @@ -46,7 +46,7 @@ }, { "name": "ms-vscode.js-debug-nightly", - "version": "latest", + "version": "2020.1.33183", "forQualities": ["insider"], "repo": "https://github.com/Microsoft/vscode-js-debug", "metadata": { From b6b604ab37518f9302516f70eaba611c323180f6 Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Fri, 24 Jan 2020 09:59:33 -0800 Subject: [PATCH 097/801] extensions: fix missing update to compiled extensions build file --- build/lib/extensions.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/build/lib/extensions.js b/build/lib/extensions.js index c4385655eb9..e45b0d4e35a 100644 --- a/build/lib/extensions.js +++ b/build/lib/extensions.js @@ -27,6 +27,7 @@ const util = require('./util'); const root = path.dirname(path.dirname(__dirname)); const commit = util.getVersion(root); const sourceMappingURLBase = `https://ticino.blob.core.windows.net/sourcemaps/${commit}`; +const product = require('../../product.json'); function fromLocal(extensionPath) { const webpackFilename = path.join(extensionPath, 'extension.webpack.config.js'); const input = fs.existsSync(webpackFilename) @@ -184,8 +185,10 @@ const excludedExtensions = [ 'vscode-test-resolver', 'ms-vscode.node-debug', 'ms-vscode.node-debug2', + 'ms.vscode.js-debug-nightly' ]; -const builtInExtensions = require('../builtInExtensions.json'); +const builtInExtensions = require('../builtInExtensions.json') + .filter(({ forQualities }) => { var _a; return !product.quality || ((_a = forQualities === null || forQualities === void 0 ? void 0 : forQualities.includes) === null || _a === void 0 ? void 0 : _a.call(forQualities, product.quality)) !== false; }); function packageLocalExtensionsStream() { const localExtensionDescriptions = glob.sync('extensions/*/package.json') .map(manifestPath => { From fe6465627533ff253aa75b98b967793e0205662b Mon Sep 17 00:00:00 2001 From: SteVen Batten Date: Fri, 24 Jan 2020 10:49:33 -0800 Subject: [PATCH 098/801] doing the same for outline --- src/vs/platform/list/browser/listService.ts | 5 +++++ .../workbench/browser/parts/views/viewPaneContainer.ts | 2 +- src/vs/workbench/contrib/outline/browser/outlinePane.ts | 9 +++++++-- 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/src/vs/platform/list/browser/listService.ts b/src/vs/platform/list/browser/listService.ts index 280d271e07a..29bcaa64d97 100644 --- a/src/vs/platform/list/browser/listService.ts +++ b/src/vs/platform/list/browser/listService.ts @@ -807,6 +807,11 @@ export class WorkbenchDataTree extends DataTree = {}): void { + super.updateOptions(options); + this.internals.updateStyleOverrides(options.overrideStyles); + } } export interface IWorkbenchAsyncDataTreeOptions extends IAsyncDataTreeOptions { diff --git a/src/vs/workbench/browser/parts/views/viewPaneContainer.ts b/src/vs/workbench/browser/parts/views/viewPaneContainer.ts index 0722ccb4bf7..cd41ae0249b 100644 --- a/src/vs/workbench/browser/parts/views/viewPaneContainer.ts +++ b/src/vs/workbench/browser/parts/views/viewPaneContainer.ts @@ -91,7 +91,7 @@ export abstract class ViewPane extends Pane implements IView { @IContextMenuService protected contextMenuService: IContextMenuService, @IConfigurationService protected readonly configurationService: IConfigurationService, @IContextKeyService contextKeyService: IContextKeyService, - @IViewDescriptorService private viewDescriptorService: IViewDescriptorService, + @IViewDescriptorService protected viewDescriptorService: IViewDescriptorService, @IInstantiationService protected instantiationService: IInstantiationService, ) { super(options); diff --git a/src/vs/workbench/contrib/outline/browser/outlinePane.ts b/src/vs/workbench/contrib/outline/browser/outlinePane.ts index 629a8001b02..f2b0d0af50c 100644 --- a/src/vs/workbench/contrib/outline/browser/outlinePane.ts +++ b/src/vs/workbench/contrib/outline/browser/outlinePane.ts @@ -47,7 +47,6 @@ import { basename } from 'vs/base/common/resources'; import { IDataSource } from 'vs/base/browser/ui/tree/tree'; import { IMarkerDecorationsService } from 'vs/editor/common/services/markersDecorationService'; import { MarkerSeverity } from 'vs/platform/markers/common/markers'; -import { SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme'; import { IViewDescriptorService } from 'vs/workbench/common/views'; class RequestState { @@ -335,13 +334,19 @@ export class OutlinePane extends ViewPane { keyboardNavigationLabelProvider: new OutlineNavigationLabelProvider(), hideTwistiesOfChildlessElements: true, overrideStyles: { - listBackground: SIDE_BAR_BACKGROUND + listBackground: this.getBackgroundColor() } } ); + this._disposables.push(this._tree); this._disposables.push(this._outlineViewState.onDidChange(this._onDidChangeUserState, this)); + this._disposables.push(this.viewDescriptorService.onDidChangeLocation(({ views, from, to }) => { + if (views.some(v => v.id === this.id)) { + this._tree.updateOptions({ overrideStyles: { listBackground: this.getBackgroundColor() } }); + } + })); // override the globally defined behaviour this._tree.updateOptions({ From acd1d19ec90186d4fe6e9534b329d19d1ee04e24 Mon Sep 17 00:00:00 2001 From: Pine Wu Date: Fri, 24 Jan 2020 20:58:36 +0100 Subject: [PATCH 099/801] Single click in hover/inline to go to link --- src/vs/editor/contrib/gotoError/gotoError.ts | 6 +-- .../contrib/gotoError/gotoErrorWidget.ts | 53 ++----------------- src/vs/editor/contrib/hover/hover.ts | 6 +-- .../editor/contrib/hover/modesContentHover.ts | 48 +---------------- 4 files changed, 10 insertions(+), 103 deletions(-) diff --git a/src/vs/editor/contrib/gotoError/gotoError.ts b/src/vs/editor/contrib/gotoError/gotoError.ts index 80e2342e747..5ac29409f1c 100644 --- a/src/vs/editor/contrib/gotoError/gotoError.ts +++ b/src/vs/editor/contrib/gotoError/gotoError.ts @@ -28,7 +28,6 @@ import { Action } from 'vs/base/common/actions'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { isEqual } from 'vs/base/common/resources'; import { IOpenerService } from 'vs/platform/opener/common/opener'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; class MarkerModel { @@ -212,8 +211,7 @@ export class MarkerController implements IEditorContribution { @IThemeService private readonly _themeService: IThemeService, @ICodeEditorService private readonly _editorService: ICodeEditorService, @IKeybindingService private readonly _keybindingService: IKeybindingService, - @IOpenerService private readonly _openerService: IOpenerService, - @IConfigurationService private readonly _configurationService: IConfigurationService + @IOpenerService private readonly _openerService: IOpenerService ) { this._editor = editor; this._widgetVisible = CONTEXT_MARKERS_NAVIGATION_VISIBLE.bindTo(this._contextKeyService); @@ -247,7 +245,7 @@ export class MarkerController implements IEditorContribution { new Action(NextMarkerAction.ID, NextMarkerAction.LABEL + (nextMarkerKeybinding ? ` (${nextMarkerKeybinding.getLabel()})` : ''), 'show-next-problem codicon-chevron-down', this._model.canNavigate(), async () => { if (this._model) { this._model.move(true, true); } }), new Action(PrevMarkerAction.ID, PrevMarkerAction.LABEL + (prevMarkerKeybinding ? ` (${prevMarkerKeybinding.getLabel()})` : ''), 'show-previous-problem codicon-chevron-up', this._model.canNavigate(), async () => { if (this._model) { this._model.move(false, true); } }) ]; - this._widget = new MarkerNavigationWidget(this._editor, actions, this._themeService, this._openerService, this._configurationService); + this._widget = new MarkerNavigationWidget(this._editor, actions, this._themeService, this._openerService); this._widgetVisible.set(true); this._widget.onDidClose(() => this.closeMarkersNavigation(), this, this._disposeOnClose); diff --git a/src/vs/editor/contrib/gotoError/gotoErrorWidget.ts b/src/vs/editor/contrib/gotoError/gotoErrorWidget.ts index 5190682a7c9..7cbc0b2e504 100644 --- a/src/vs/editor/contrib/gotoError/gotoErrorWidget.ts +++ b/src/vs/editor/contrib/gotoError/gotoErrorWidget.ts @@ -27,10 +27,6 @@ import { IActionBarOptions, ActionsOrientation } from 'vs/base/browser/ui/action import { SeverityIcon } from 'vs/platform/severityIcon/common/severityIcon'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; import { IOpenerService } from 'vs/platform/opener/common/opener'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { OperatingSystem, OS } from 'vs/base/common/platform'; - -type ModifierKey = 'meta' | 'ctrl' | 'alt'; class MessageWidget { @@ -44,7 +40,6 @@ class MessageWidget { private readonly _relatedDiagnostics = new WeakMap(); private readonly _disposables: DisposableStore = new DisposableStore(); - private _clickModifierKey: ModifierKey; private _codeLink?: HTMLElement; constructor( @@ -52,7 +47,6 @@ class MessageWidget { editor: ICodeEditor, onRelatedInformation: (related: IRelatedInformation) => void, private readonly _openerService: IOpenerService, - private readonly _configurationService: IConfigurationService ) { this._editor = editor; @@ -88,16 +82,6 @@ class MessageWidget { domNode.style.top = `-${e.scrollTop}px`; })); this._disposables.add(this._scrollable); - - this._clickModifierKey = this._getClickModifierKey(); - this._disposables.add(this._configurationService.onDidChangeConfiguration(e => { - if (e.affectsConfiguration('editor.multiCursorModifier')) { - this._clickModifierKey = this._getClickModifierKey(); - if (this._codeLink) { - this._codeLink.setAttribute('title', this._getCodelinkTooltip()); - } - } - })); } dispose(): void { @@ -150,15 +134,12 @@ class MessageWidget { detailsElement.appendChild(codeElement); } else { this._codeLink = dom.$('a.code-link'); - this._codeLink.setAttribute('title', this._getCodelinkTooltip()); this._codeLink.setAttribute('href', `${code.link.toString()}`); this._codeLink.onclick = (e) => { + this._openerService.open(code.link); e.preventDefault(); - if ((this._clickModifierKey === 'meta' && e.metaKey) || (this._clickModifierKey === 'ctrl' && e.ctrlKey) || (this._clickModifierKey === 'alt' && e.altKey)) { - this._openerService.open(code.link); - e.stopPropagation(); - } + e.stopPropagation(); }; const codeElement = dom.append(this._codeLink, dom.$('span')); @@ -211,31 +192,6 @@ class MessageWidget { getHeightInLines(): number { return Math.min(17, this._lines); } - - private _getClickModifierKey(): ModifierKey { - const value = this._configurationService.getValue<'ctrlCmd' | 'alt'>('editor.multiCursorModifier'); - if (value === 'ctrlCmd') { - return 'alt'; - } else { - if (OS === OperatingSystem.Macintosh) { - return 'meta'; - } else { - return 'ctrl'; - } - } - } - - private _getCodelinkTooltip(): string { - const tooltipLabel = nls.localize('links.navigate.follow', 'Follow link'); - const tooltipKeybinding = this._clickModifierKey === 'ctrl' - ? nls.localize('links.navigate.kb.meta', 'ctrl + click') - : - this._clickModifierKey === 'meta' - ? OS === OperatingSystem.Macintosh ? nls.localize('links.navigate.kb.meta.mac', 'cmd + click') : nls.localize('links.navigate.kb.meta', 'ctrl + click') - : OS === OperatingSystem.Macintosh ? nls.localize('links.navigate.kb.alt.mac', 'option + click') : nls.localize('links.navigate.kb.alt', 'alt + click'); - - return `${tooltipLabel} (${tooltipKeybinding})`; - } } export class MarkerNavigationWidget extends PeekViewWidget { @@ -256,8 +212,7 @@ export class MarkerNavigationWidget extends PeekViewWidget { editor: ICodeEditor, private readonly actions: ReadonlyArray, private readonly _themeService: IThemeService, - private readonly _openerService: IOpenerService, - private readonly _configurationService: IConfigurationService + private readonly _openerService: IOpenerService ) { super(editor, { showArrow: true, showFrame: true, isAccessible: true }); this._severity = MarkerSeverity.Warning; @@ -327,7 +282,7 @@ export class MarkerNavigationWidget extends PeekViewWidget { this._container = document.createElement('div'); container.appendChild(this._container); - this._message = new MessageWidget(this._container, this.editor, related => this._onDidSelectRelatedInformation.fire(related), this._openerService, this._configurationService); + this._message = new MessageWidget(this._container, this.editor, related => this._onDidSelectRelatedInformation.fire(related), this._openerService); this._disposables.add(this._message); } diff --git a/src/vs/editor/contrib/hover/hover.ts b/src/vs/editor/contrib/hover/hover.ts index b77e2b0132a..3a0f4eea252 100644 --- a/src/vs/editor/contrib/hover/hover.ts +++ b/src/vs/editor/contrib/hover/hover.ts @@ -27,7 +27,6 @@ import { IMarkerDecorationsService } from 'vs/editor/common/services/markersDeco import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { AccessibilitySupport } from 'vs/platform/accessibility/common/accessibility'; import { GotoDefinitionAtPositionEditorContribution } from 'vs/editor/contrib/gotoSymbol/link/goToDefinitionAtPosition'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; export class ModesHoverController implements IEditorContribution { @@ -67,8 +66,7 @@ export class ModesHoverController implements IEditorContribution { @IModeService private readonly _modeService: IModeService, @IMarkerDecorationsService private readonly _markerDecorationsService: IMarkerDecorationsService, @IKeybindingService private readonly _keybindingService: IKeybindingService, - @IThemeService private readonly _themeService: IThemeService, - @IConfigurationService private readonly _configurationService: IConfigurationService + @IThemeService private readonly _themeService: IThemeService ) { this._isMouseDown = false; this._hoverClicked = false; @@ -207,7 +205,7 @@ export class ModesHoverController implements IEditorContribution { } private _createHoverWidgets() { - this._contentWidget.value = new ModesContentHoverWidget(this._editor, this._markerDecorationsService, this._themeService, this._keybindingService, this._modeService, this._openerService, this._configurationService); + this._contentWidget.value = new ModesContentHoverWidget(this._editor, this._markerDecorationsService, this._themeService, this._keybindingService, this._modeService, this._openerService); this._glyphWidget.value = new ModesGlyphHoverWidget(this._editor, this._modeService, this._openerService); } diff --git a/src/vs/editor/contrib/hover/modesContentHover.ts b/src/vs/editor/contrib/hover/modesContentHover.ts index c52183db542..7a3ed0db358 100644 --- a/src/vs/editor/contrib/hover/modesContentHover.ts +++ b/src/vs/editor/contrib/hover/modesContentHover.ts @@ -39,8 +39,6 @@ import { IModeService } from 'vs/editor/common/services/modeService'; import { IIdentifiedSingleEditOperation } from 'vs/editor/common/model'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; import { Constants } from 'vs/base/common/uint'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { OperatingSystem, OS } from 'vs/base/common/platform'; import { textLinkForeground } from 'vs/platform/theme/common/colorRegistry'; const $ = dom.$; @@ -194,8 +192,6 @@ const markerCodeActionTrigger: CodeActionTrigger = { filter: { include: CodeActionKind.QuickFix } }; -type ModifierKey = 'meta' | 'ctrl' | 'alt'; - export class ModesContentHoverWidget extends ContentHoverWidget { static readonly ID = 'editor.contrib.modesContentHoverWidget'; @@ -209,7 +205,6 @@ export class ModesContentHoverWidget extends ContentHoverWidget { private _shouldFocus: boolean; private _colorPicker: ColorPickerWidget | null; - private _clickModifierKey: ModifierKey; private _codeLink?: HTMLElement; private readonly renderDisposable = this._register(new MutableDisposable()); @@ -221,7 +216,6 @@ export class ModesContentHoverWidget extends ContentHoverWidget { private readonly _keybindingService: IKeybindingService, private readonly _modeService: IModeService, private readonly _openerService: IOpenerService = NullOpenerService, - private readonly _configurationService: IConfigurationService ) { super(ModesContentHoverWidget.ID, editor); @@ -258,16 +252,6 @@ export class ModesContentHoverWidget extends ContentHoverWidget { this._renderMessages(this._lastRange, this._messages); } })); - - this._clickModifierKey = this._getClickModifierKey(); - this._register((this._configurationService.onDidChangeConfiguration(e => { - if (e.affectsConfiguration('editor.multiCursorModifier')) { - this._clickModifierKey = this._getClickModifierKey(); - if (this._codeLink) { - this._codeLink.setAttribute('title', this._getCodelinkTooltip()); - } - } - }))); } dispose(): void { @@ -532,15 +516,12 @@ export class ModesContentHoverWidget extends ContentHoverWidget { sourceElement.innerText = source; } this._codeLink = dom.append(sourceAndCodeElement, $('a.code-link')); - this._codeLink.setAttribute('title', this._getCodelinkTooltip()); this._codeLink.setAttribute('href', code.link.toString()); this._codeLink.onclick = (e) => { + this._openerService.open(code.link); e.preventDefault(); - if ((this._clickModifierKey === 'meta' && e.metaKey) || (this._clickModifierKey === 'ctrl' && e.ctrlKey) || (this._clickModifierKey === 'alt' && e.altKey)) { - this._openerService.open(code.link); - e.stopPropagation(); - } + e.stopPropagation(); }; const codeElement = dom.append(this._codeLink, $('span')); @@ -671,31 +652,6 @@ export class ModesContentHoverWidget extends ContentHoverWidget { private static readonly _DECORATION_OPTIONS = ModelDecorationOptions.register({ className: 'hoverHighlight' }); - - private _getClickModifierKey(): ModifierKey { - const value = this._configurationService.getValue<'ctrlCmd' | 'alt'>('editor.multiCursorModifier'); - if (value === 'ctrlCmd') { - return 'alt'; - } else { - if (OS === OperatingSystem.Macintosh) { - return 'meta'; - } else { - return 'ctrl'; - } - } - } - - private _getCodelinkTooltip(): string { - const tooltipLabel = nls.localize('links.navigate.follow', 'Follow link'); - const tooltipKeybinding = this._clickModifierKey === 'ctrl' - ? nls.localize('links.navigate.kb.meta', 'ctrl + click') - : - this._clickModifierKey === 'meta' - ? OS === OperatingSystem.Macintosh ? nls.localize('links.navigate.kb.meta.mac', 'cmd + click') : nls.localize('links.navigate.kb.meta', 'ctrl + click') - : OS === OperatingSystem.Macintosh ? nls.localize('links.navigate.kb.alt.mac', 'option + click') : nls.localize('links.navigate.kb.alt', 'alt + click'); - - return `${tooltipLabel} (${tooltipKeybinding})`; - } } function hoverContentsEquals(first: HoverPart[], second: HoverPart[]): boolean { From c7c619784abf7bea53cd8353f8c108ac89f7a495 Mon Sep 17 00:00:00 2001 From: Rachel Macfarlane Date: Thu, 23 Jan 2020 13:51:42 -0800 Subject: [PATCH 100/801] Move to v2 endpoint for account extension --- extensions/vscode-account/src/AADHelper.ts | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/extensions/vscode-account/src/AADHelper.ts b/extensions/vscode-account/src/AADHelper.ts index 26bca9f8614..3ebb909abd4 100644 --- a/extensions/vscode-account/src/AADHelper.ts +++ b/extensions/vscode-account/src/AADHelper.ts @@ -15,8 +15,8 @@ import { toBase64UrlEncoding } from './utils'; const redirectUrl = 'https://vscode-redirect.azurewebsites.net/'; const loginEndpointUrl = 'https://login.microsoftonline.com/'; const clientId = 'aebc6443-996d-45c2-90f0-388ff96faa56'; -const resourceId = 'https://management.core.windows.net/'; -const tenant = 'common'; +const scope = 'https://management.core.windows.net/.default offline_access'; +const tenant = 'organizations'; interface IToken { expiresIn: string; // How long access token is valid, in seconds @@ -40,7 +40,11 @@ export class AzureActiveDirectoryService { public async initialize(): Promise { const existingRefreshToken = await keychain.getToken(); if (existingRefreshToken) { - await this.refreshToken(existingRefreshToken); + try { + await this.refreshToken(existingRefreshToken); + } catch (e) { + await this.logout(); + } } this.pollForChange(); @@ -112,7 +116,7 @@ export class AzureActiveDirectoryService { const codeVerifier = toBase64UrlEncoding(crypto.randomBytes(32).toString('base64')); const codeChallenge = toBase64UrlEncoding(crypto.createHash('sha256').update(codeVerifier).digest('base64')); - const loginUrl = `${loginEndpointUrl}${tenant}/oauth2/authorize?response_type=code&response_mode=query&client_id=${encodeURIComponent(clientId)}&redirect_uri=${encodeURIComponent(redirectUrl)}&state=${state}&resource=${encodeURIComponent(resourceId)}&prompt=select_account&code_challenge_method=S256&code_challenge=${codeChallenge}`; + const loginUrl = `${loginEndpointUrl}${tenant}/oauth2/v2.0/authorize?response_type=code&response_mode=query&client_id=${encodeURIComponent(clientId)}&redirect_uri=${encodeURIComponent(redirectUrl)}&state=${state}&scope=${encodeURIComponent(scopes.join(' '))}&prompt=select_account&code_challenge_method=S256&code_challenge=${codeChallenge}`; await redirectReq.res.writeHead(302, { Location: loginUrl }); redirectReq.res.end(); @@ -169,12 +173,12 @@ export class AzureActiveDirectoryService { grant_type: 'authorization_code', code: code, client_id: clientId, - resource: resourceId, + scope: scope, code_verifier: codeVerifier, redirect_uri: redirectUrl }); - const tokenUrl = vscode.Uri.parse(`${loginEndpointUrl}${tenant}/oauth2/token`); + const tokenUrl = vscode.Uri.parse(`${loginEndpointUrl}${tenant}/oauth2/v2.0/token`); const post = https.request({ host: tokenUrl.authority, @@ -224,12 +228,12 @@ export class AzureActiveDirectoryService { refresh_token: refreshToken, client_id: clientId, grant_type: 'refresh_token', - resource: resourceId + scope: scope }); const post = https.request({ host: 'login.microsoftonline.com', - path: `/${tenant}/oauth2/token`, + path: `/${tenant}/oauth2/v2.0/token`, method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded', From 8c2a25968f24ac772e202d4a88ee7ef801205f6c Mon Sep 17 00:00:00 2001 From: Rachel Macfarlane Date: Fri, 24 Jan 2020 10:43:57 -0800 Subject: [PATCH 101/801] Account extension - support multiple sessions, passing scopes to login --- extensions/vscode-account/src/AADHelper.ts | 213 +++++++++++++----- extensions/vscode-account/src/extension.ts | 6 +- .../vscode-account/src/vscode.proposed.d.ts | 11 +- src/vs/vscode.proposed.d.ts | 3 +- .../api/browser/mainThreadAuthentication.ts | 4 +- .../workbench/api/common/extHost.protocol.ts | 4 +- .../api/common/extHostAuthentication.ts | 8 +- .../userDataSync/browser/userDataSync.ts | 2 +- .../browser/authenticationService.ts | 6 +- 9 files changed, 176 insertions(+), 81 deletions(-) diff --git a/extensions/vscode-account/src/AADHelper.ts b/extensions/vscode-account/src/AADHelper.ts index 3ebb909abd4..df6b4e5ebb0 100644 --- a/extensions/vscode-account/src/AADHelper.ts +++ b/extensions/vscode-account/src/AADHelper.ts @@ -15,53 +15,120 @@ import { toBase64UrlEncoding } from './utils'; const redirectUrl = 'https://vscode-redirect.azurewebsites.net/'; const loginEndpointUrl = 'https://login.microsoftonline.com/'; const clientId = 'aebc6443-996d-45c2-90f0-388ff96faa56'; -const scope = 'https://management.core.windows.net/.default offline_access'; const tenant = 'organizations'; interface IToken { expiresIn: string; // How long access token is valid, in seconds accessToken: string; refreshToken: string; + + displayName: string; + scope: string; + sessionId: string; // The account id + the scope } interface ITokenClaims { + tid: string; email?: string; unique_name?: string; oid?: string; altsecid?: string; + scp: string; +} + +interface IStoredSession { + id: string; + refreshToken: string; + scope: string; // Scopes are alphabetized and joined with a space } export const onDidChangeSessions = new vscode.EventEmitter(); export class AzureActiveDirectoryService { - private _token: IToken | undefined; - private _refreshTimeout: NodeJS.Timeout | undefined; + private _tokens: IToken[] = []; + private _refreshTimeouts: Map = new Map(); public async initialize(): Promise { - const existingRefreshToken = await keychain.getToken(); - if (existingRefreshToken) { + const storedData = await keychain.getToken(); + if (storedData) { try { - await this.refreshToken(existingRefreshToken); + const sessions = this.parseStoredData(storedData); + const refreshes = sessions.map(async session => { + try { + await this.refreshToken(session.refreshToken, session.scope); + } catch (e) { + await this.logout(session.id); + } + }); + + await Promise.all(refreshes); } catch (e) { - await this.logout(); + await this.clearSessions(); } } this.pollForChange(); } + private parseStoredData(data: string): IStoredSession[] { + return JSON.parse(data); + } + + private async storeTokenData(): Promise { + const serializedData: IStoredSession[] = this._tokens.map(token => { + return { + id: token.sessionId, + refreshToken: token.refreshToken, + scope: token.scope + }; + }); + + await keychain.setToken(JSON.stringify(serializedData)); + } + private pollForChange() { setTimeout(async () => { - const refreshToken = await keychain.getToken(); - // Another window has logged in, generate access token for this instance. - if (refreshToken && !this._token) { - await this.refreshToken(refreshToken); - onDidChangeSessions.fire(); + let didChange = false; + const storedData = await keychain.getToken(); + if (storedData) { + try { + const sessions = this.parseStoredData(storedData); + let promises = sessions.map(async session => { + const matchesExisting = this._tokens.some(token => token.scope === session.scope && token.sessionId === session.id); + if (!matchesExisting) { + try { + await this.refreshToken(session.refreshToken, session.scope); + didChange = true; + } catch (e) { + await this.logout(session.id); + } + } + }); + + promises = promises.concat(this._tokens.map(async token => { + const matchesExisting = sessions.some(session => token.scope === session.scope && token.sessionId === session.id); + if (!matchesExisting) { + await this.logout(token.sessionId); + didChange = true; + } + })); + + await Promise.all(promises); + } catch (e) { + Logger.error(e.message); + // if data is improperly formatted, remove all of it and send change event + this.clearSessions(); + didChange = true; + } + } else { + if (this._tokens.length) { + // Log out all + await this.clearSessions(); + didChange = true; + } } - // Another window has logged out - if (!refreshToken && this._token) { - await this.logout(); + if (didChange) { onDidChangeSessions.fire(); } @@ -69,28 +136,29 @@ export class AzureActiveDirectoryService { }, 1000 * 30); } - private tokenToAccount(token: IToken): vscode.Session { - const claims = this.getTokenClaims(token.accessToken); + private convertToSession(token: IToken): vscode.Session { return { - id: claims?.oid || claims?.altsecid || '', + id: token.sessionId, accessToken: token.accessToken, - displayName: claims?.email || claims?.unique_name || 'user@example.com' + displayName: token.displayName, + scopes: token.scope.split(' ') }; } - private getTokenClaims(accessToken: string): ITokenClaims | undefined { + private getTokenClaims(accessToken: string): ITokenClaims { try { return JSON.parse(Buffer.from(accessToken.split('.')[1], 'base64').toString()); } catch (e) { Logger.error(e.message); + throw new Error('Unable to read token claims'); } } get sessions(): vscode.Session[] { - return this._token ? [this.tokenToAccount(this._token)] : []; + return this._tokens.map(token => this.convertToSession(token)); } - public async login(): Promise { + public async login(scope: string): Promise { Logger.info('Logging in...'); const nonce = crypto.randomBytes(16).toString('base64'); const { server, redirectPromise, codePromise } = createServer(nonce); @@ -116,7 +184,7 @@ export class AzureActiveDirectoryService { const codeVerifier = toBase64UrlEncoding(crypto.randomBytes(32).toString('base64')); const codeChallenge = toBase64UrlEncoding(crypto.createHash('sha256').update(codeVerifier).digest('base64')); - const loginUrl = `${loginEndpointUrl}${tenant}/oauth2/v2.0/authorize?response_type=code&response_mode=query&client_id=${encodeURIComponent(clientId)}&redirect_uri=${encodeURIComponent(redirectUrl)}&state=${state}&scope=${encodeURIComponent(scopes.join(' '))}&prompt=select_account&code_challenge_method=S256&code_challenge=${codeChallenge}`; + const loginUrl = `${loginEndpointUrl}${tenant}/oauth2/v2.0/authorize?response_type=code&response_mode=query&client_id=${encodeURIComponent(clientId)}&redirect_uri=${encodeURIComponent(redirectUrl)}&state=${state}&scope=${encodeURIComponent(scope)}&prompt=select_account&code_challenge_method=S256&code_challenge=${codeChallenge}`; await redirectReq.res.writeHead(302, { Location: loginUrl }); redirectReq.res.end(); @@ -128,8 +196,8 @@ export class AzureActiveDirectoryService { if ('err' in codeRes) { throw codeRes.err; } - token = await this.exchangeCodeForToken(codeRes.code, codeVerifier); - this.setToken(token); + token = await this.exchangeCodeForToken(codeRes.code, codeVerifier, scope); + this.setToken(token, scope); Logger.info('Login successful'); res.writeHead(302, { Location: '/' }); res.end(); @@ -145,27 +213,46 @@ export class AzureActiveDirectoryService { } } - private async setToken(token: IToken): Promise { - this._token = token; - - if (this._refreshTimeout) { - clearTimeout(this._refreshTimeout); + private async setToken(token: IToken, scope: string): Promise { + const existingToken = this._tokens.findIndex(t => t.sessionId === token.sessionId); + if (existingToken) { + this._tokens.splice(existingToken, 1, token); + } else { + this._tokens.push(token); } - this._refreshTimeout = setTimeout(async () => { + const existingTimeout = this._refreshTimeouts.get(token.sessionId); + if (existingTimeout) { + clearTimeout(existingTimeout); + } + + this._refreshTimeouts.set(token.sessionId, setTimeout(async () => { try { - await this.refreshToken(token.refreshToken); + await this.refreshToken(token.refreshToken, scope); } catch (e) { - await this.logout(); + await this.logout(token.sessionId); } finally { onDidChangeSessions.fire(); } - }, 1000 * (parseInt(token.expiresIn) - 10)); + }, 1000 * (parseInt(token.expiresIn) - 10))); - await keychain.setToken(token.refreshToken); + this.storeTokenData(); } - private async exchangeCodeForToken(code: string, codeVerifier: string): Promise { + private getTokenFromResponse(buffer: Buffer[], scope: string): IToken { + const json = JSON.parse(Buffer.concat(buffer).toString()); + const claims = this.getTokenClaims(json.access_token); + return { + expiresIn: json.expires_in, + accessToken: json.access_token, + refreshToken: json.refresh_token, + scope, + sessionId: claims.tid + (claims.oid || claims.altsecid) + scope, + displayName: claims.email || claims.unique_name || 'user@example.com' + }; + } + + private async exchangeCodeForToken(code: string, codeVerifier: string, scope: string): Promise { return new Promise((resolve: (value: IToken) => void, reject) => { Logger.info('Exchanging login code for token'); try { @@ -195,12 +282,7 @@ export class AzureActiveDirectoryService { }); result.on('end', () => { if (result.statusCode === 200) { - const json = JSON.parse(Buffer.concat(buffer).toString()); - resolve({ - expiresIn: json.expires_in, - accessToken: json.access_token, - refreshToken: json.refresh_token - }); + resolve(this.getTokenFromResponse(buffer, scope)); } else { reject(new Error('Unable to login.')); } @@ -221,7 +303,7 @@ export class AzureActiveDirectoryService { }); } - private async refreshToken(refreshToken: string): Promise { + private async refreshToken(refreshToken: string, scope: string): Promise { return new Promise((resolve: (value: IToken) => void, reject) => { Logger.info('Refreshing token...'); const postData = querystring.stringify({ @@ -246,17 +328,11 @@ export class AzureActiveDirectoryService { }); result.on('end', async () => { if (result.statusCode === 200) { - const json = JSON.parse(Buffer.concat(buffer).toString()); - const token = { - expiresIn: json.expires_in, - accessToken: json.access_token, - refreshToken: json.refresh_token - }; - this.setToken(token); + const token = this.getTokenFromResponse(buffer, scope); + this.setToken(token, scope); Logger.info('Token refresh success'); resolve(token); } else { - await this.logout(); Logger.error('Refreshing token failed'); reject(new Error('Refreshing token failed.')); } @@ -273,12 +349,35 @@ export class AzureActiveDirectoryService { }); } - public async logout() { - Logger.info('Logging out'); - delete this._token; - await keychain.deleteToken(); - if (this._refreshTimeout) { - clearTimeout(this._refreshTimeout); + public async logout(sessionId: string) { + Logger.info(`Logging out of session '${sessionId}'`); + const tokenIndex = this._tokens.findIndex(token => token.sessionId === sessionId); + if (tokenIndex > -1) { + this._tokens.splice(tokenIndex, 1); + } + + if (this._tokens.length === 0) { + await keychain.deleteToken(); + } else { + this.storeTokenData(); + } + + const timeout = this._refreshTimeouts.get(sessionId); + if (timeout) { + clearTimeout(timeout); + this._refreshTimeouts.delete(sessionId); } } + + public async clearSessions() { + Logger.info('Logging out of all sessions'); + this._tokens = []; + await keychain.deleteToken(); + + this._refreshTimeouts.forEach(timeout => { + clearTimeout(timeout); + }); + + this._refreshTimeouts.clear(); + } } diff --git a/extensions/vscode-account/src/extension.ts b/extensions/vscode-account/src/extension.ts index 5e71ed3d97a..83988aa7c18 100644 --- a/extensions/vscode-account/src/extension.ts +++ b/extensions/vscode-account/src/extension.ts @@ -17,9 +17,9 @@ export async function activate(context: vscode.ExtensionContext) { displayName: 'Microsoft', onDidChangeSessions: onDidChangeSessions.event, getSessions: () => Promise.resolve(loginService.sessions), - login: async () => { + login: async (scopes: string[]) => { try { - await loginService.login(); + await loginService.login(scopes.sort().join(' ')); return loginService.sessions[0]!; } catch (e) { vscode.window.showErrorMessage(`Logging in failed: ${e}`); @@ -27,7 +27,7 @@ export async function activate(context: vscode.ExtensionContext) { } }, logout: async (id: string) => { - return loginService.logout(); + return loginService.logout(id); } }); diff --git a/extensions/vscode-account/src/vscode.proposed.d.ts b/extensions/vscode-account/src/vscode.proposed.d.ts index 6eca3720fb3..d6ad851d3dd 100644 --- a/extensions/vscode-account/src/vscode.proposed.d.ts +++ b/extensions/vscode-account/src/vscode.proposed.d.ts @@ -20,6 +20,7 @@ declare module 'vscode' { id: string; accessToken: string; displayName: string; + scopes: string[] } export interface AuthenticationProvider { @@ -35,7 +36,7 @@ declare module 'vscode' { /** * Prompts a user to login. */ - login(): Promise; + login(scopes: string[]): Promise; logout(sessionId: string): Promise; } @@ -48,13 +49,7 @@ declare module 'vscode' { export const onDidRegisterAuthenticationProvider: Event; export const onDidUnregisterAuthenticationProvider: Event; - /** - * Fires with the provider id that changed sessions. - */ - export const onDidChangeSessions: Event; - export function login(providerId: string): Promise; - export function logout(providerId: string, accountId: string): Promise; - export function getSessions(providerId: string): Promise | undefined>; + export const providers: ReadonlyArray; } // #region Ben - extension auth flow (desktop+web) diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index 607cc0bcd83..ff9d5389785 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -22,6 +22,7 @@ declare module 'vscode' { id: string; accessToken: string; displayName: string; + scopes: string[] } export interface AuthenticationProvider { @@ -37,7 +38,7 @@ declare module 'vscode' { /** * Prompts a user to login. */ - login(): Promise; + login(scopes: string[]): Promise; logout(sessionId: string): Promise; } diff --git a/src/vs/workbench/api/browser/mainThreadAuthentication.ts b/src/vs/workbench/api/browser/mainThreadAuthentication.ts index 979b50d5764..d2d98d8987b 100644 --- a/src/vs/workbench/api/browser/mainThreadAuthentication.ts +++ b/src/vs/workbench/api/browser/mainThreadAuthentication.ts @@ -24,8 +24,8 @@ export class MainThreadAuthenticationProvider { return this._proxy.$getSessions(this.id); } - login(): Promise { - return this._proxy.$login(this.id); + login(scopes: string[]): Promise { + return this._proxy.$login(this.id, scopes); } logout(accountId: string): Promise { diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 1a107144755..19564877215 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -908,8 +908,8 @@ export interface ExtHostLabelServiceShape { export interface ExtHostAuthenticationShape { $getSessions(id: string): Promise>; - $login(id: string): Promise; - $logout(id: string, accountId: string): Promise; + $login(id: string, scopes: string[]): Promise; + $logout(id: string, sessionId: string): Promise; } export interface ExtHostSearchShape { diff --git a/src/vs/workbench/api/common/extHostAuthentication.ts b/src/vs/workbench/api/common/extHostAuthentication.ts index c9cedd3af3d..c4518b0a407 100644 --- a/src/vs/workbench/api/common/extHostAuthentication.ts +++ b/src/vs/workbench/api/common/extHostAuthentication.ts @@ -37,13 +37,13 @@ export class AuthenticationProviderWrapper implements vscode.AuthenticationProvi return this._provider.getSessions(); } - async login(): Promise { + async login(scopes: string[]): Promise { const isAllowed = await this._proxy.$loginPrompt(this._provider.id, this.displayName, ExtensionIdentifier.toKey(this._requestingExtension.identifier), this._requestingExtension.displayName || this._requestingExtension.name); if (!isAllowed) { throw new Error('User did not consent to login.'); } - return this._provider.login(); + return this._provider.login(scopes); } logout(sessionId: string): Promise { @@ -93,10 +93,10 @@ export class ExtHostAuthentication implements ExtHostAuthenticationShape { }); } - $login(providerId: string): Promise { + $login(providerId: string, scopes: string[]): Promise { const authProvider = this._authenticationProviders.get(providerId); if (authProvider) { - return Promise.resolve(authProvider.login()); + return Promise.resolve(authProvider.login(scopes)); } throw new Error(`Unable to find authentication provider with handle: ${0}`); diff --git a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts index 5446d32464c..0612f07a58b 100644 --- a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts +++ b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts @@ -415,7 +415,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo private async signIn(): Promise { try { - this.activeAccount = await this.authenticationService.login(this.userDataSyncStore!.authenticationProviderId); + this.activeAccount = await this.authenticationService.login(this.userDataSyncStore!.authenticationProviderId, ['https://management.core.windows.net/.default', 'offline_access']); } catch (e) { this.notificationService.error(e); throw e; diff --git a/src/vs/workbench/services/authentication/browser/authenticationService.ts b/src/vs/workbench/services/authentication/browser/authenticationService.ts index e384482de42..57b75baa80b 100644 --- a/src/vs/workbench/services/authentication/browser/authenticationService.ts +++ b/src/vs/workbench/services/authentication/browser/authenticationService.ts @@ -25,7 +25,7 @@ export interface IAuthenticationService { readonly onDidChangeSessions: Event; getSessions(providerId: string): Promise | undefined>; getDisplayName(providerId: string): string; - login(providerId: string): Promise; + login(providerId: string, scopes: string[]): Promise; logout(providerId: string, accountId: string): Promise; } @@ -79,10 +79,10 @@ export class AuthenticationService extends Disposable implements IAuthentication return undefined; } - async login(id: string): Promise { + async login(id: string, scopes: string[]): Promise { const authProvider = this._authenticationProviders.get(id); if (authProvider) { - return authProvider.login(); + return authProvider.login(scopes); } else { throw new Error(`No authentication provider '${id}' is currently registered.`); } From ad063b9df97af1fb984d8140a92f37a2b121d976 Mon Sep 17 00:00:00 2001 From: Christof Marti Date: Fri, 24 Jan 2020 21:40:12 +0100 Subject: [PATCH 102/801] ThemeIcon support (fixes #72489) --- .../api/browser/mainThreadQuickOpen.ts | 25 +++++++++++----- .../workbench/api/common/extHost.protocol.ts | 4 ++- .../workbench/api/common/extHostQuickOpen.ts | 30 +++++++------------ 3 files changed, 30 insertions(+), 29 deletions(-) diff --git a/src/vs/workbench/api/browser/mainThreadQuickOpen.ts b/src/vs/workbench/api/browser/mainThreadQuickOpen.ts index 78f8439bf22..30e3d7bca23 100644 --- a/src/vs/workbench/api/browser/mainThreadQuickOpen.ts +++ b/src/vs/workbench/api/browser/mainThreadQuickOpen.ts @@ -8,6 +8,7 @@ import { ExtHostContext, MainThreadQuickOpenShape, ExtHostQuickOpenShape, Transf import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers'; import { URI } from 'vs/base/common/uri'; import { CancellationToken } from 'vs/base/common/cancellation'; +import { ThemeIcon } from 'vs/platform/theme/common/themeService'; interface QuickInputSession { input: IQuickInput; @@ -185,14 +186,22 @@ export class MainThreadQuickOpen implements MainThreadQuickOpenShape { return this._quickInputService.backButton; } const { iconPath, tooltip, handle } = button; - return { - iconPath: iconPath && { - dark: URI.revive(iconPath.dark), - light: iconPath.light && URI.revive(iconPath.light) - }, - tooltip, - handle - }; + if ('id' in iconPath) { + return { + iconClass: ThemeIcon.asClassName(iconPath), + tooltip, + handle + }; + } else { + return { + iconPath: { + dark: URI.revive(iconPath.dark), + light: iconPath.light && URI.revive(iconPath.light) + }, + tooltip, + handle + }; + } }); } else { (input as any)[param] = params[param]; diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 19564877215..045e8ce561c 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -439,8 +439,10 @@ export interface TransferQuickPickItems extends quickInput.IQuickPickItem { handle: number; } -export interface TransferQuickInputButton extends quickInput.IQuickInputButton { +export interface TransferQuickInputButton { handle: number; + iconPath: { dark: URI; light?: URI; } | { id: string; }; + tooltip?: string; } export type TransferQuickInput = TransferQuickPick | TransferInputBox; diff --git a/src/vs/workbench/api/common/extHostQuickOpen.ts b/src/vs/workbench/api/common/extHostQuickOpen.ts index aa41e81b660..fa1aca30a20 100644 --- a/src/vs/workbench/api/common/extHostQuickOpen.ts +++ b/src/vs/workbench/api/common/extHostQuickOpen.ts @@ -443,31 +443,21 @@ class ExtHostQuickInput implements QuickInput { } } -function getIconUris(iconPath: QuickInputButton['iconPath']): { dark: URI, light?: URI } | undefined { - const dark = getDarkIconUri(iconPath); - const light = getLightIconUri(iconPath); - if (!light && !dark) { - return undefined; +function getIconUris(iconPath: QuickInputButton['iconPath']): { dark: URI, light?: URI } | { id: string } { + if (iconPath instanceof ThemeIcon) { + return { id: iconPath.id }; } - return { dark: (dark || light)!, light }; + const dark = getDarkIconUri(iconPath as any); + const light = getLightIconUri(iconPath as any); + return { dark, light }; } -function getLightIconUri(iconPath: QuickInputButton['iconPath']) { - if (iconPath && !(iconPath instanceof ThemeIcon)) { - if (typeof iconPath === 'string' - || URI.isUri(iconPath)) { - return getIconUri(iconPath); - } - return getIconUri((iconPath as any).light); - } - return undefined; +function getLightIconUri(iconPath: string | URI | { light: URI; dark: URI; }) { + return getIconUri(typeof iconPath === 'object' && 'light' in iconPath ? iconPath.light : iconPath); } -function getDarkIconUri(iconPath: QuickInputButton['iconPath']) { - if (iconPath && !(iconPath instanceof ThemeIcon) && (iconPath as any).dark) { - return getIconUri((iconPath as any).dark); - } - return undefined; +function getDarkIconUri(iconPath: string | URI | { light: URI; dark: URI; }) { + return getIconUri(typeof iconPath === 'object' && 'dark' in iconPath ? iconPath.dark : iconPath); } function getIconUri(iconPath: string | URI) { From a27c9fc6b18f5235aa0a457f5deae69e77499e47 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Fri, 24 Jan 2020 21:47:00 +0100 Subject: [PATCH 103/801] Fix #85211 --- .../userDataSync/common/extensionsSync.ts | 8 ++-- .../userDataSync/common/globalStateSync.ts | 8 ++-- .../userDataSync/common/keybindingsSync.ts | 8 ++-- .../userDataSync/common/settingsSync.ts | 8 ++-- .../userDataSync/common/userDataAutoSync.ts | 17 ++++++-- .../userDataSync/common/userDataSync.ts | 16 +++++--- .../userDataSync/common/userDataSyncIpc.ts | 3 ++ .../common/userDataSyncStoreService.ts | 40 +++++++++++------- .../userDataSync/browser/userDataSync.ts | 41 ++++++++++++++++++- .../userDataAutoSyncService.ts | 4 +- 10 files changed, 110 insertions(+), 43 deletions(-) diff --git a/src/vs/platform/userDataSync/common/extensionsSync.ts b/src/vs/platform/userDataSync/common/extensionsSync.ts index d02dd14a69a..82eb46f185a 100644 --- a/src/vs/platform/userDataSync/common/extensionsSync.ts +++ b/src/vs/platform/userDataSync/common/extensionsSync.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IUserData, UserDataSyncStoreError, UserDataSyncStoreErrorCode, SyncStatus, IUserDataSyncStoreService, ISyncExtension, IUserDataSyncLogService, IUserDataSynchroniser, SyncSource } from 'vs/platform/userDataSync/common/userDataSync'; +import { IUserData, UserDataSyncError, UserDataSyncErrorCode, SyncStatus, IUserDataSyncStoreService, ISyncExtension, IUserDataSyncLogService, IUserDataSynchroniser, SyncSource } from 'vs/platform/userDataSync/common/userDataSync'; import { VSBuffer } from 'vs/base/common/buffer'; import { Emitter, Event } from 'vs/base/common/event'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; @@ -154,7 +154,7 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse await this.apply(previewResult); } catch (e) { this.setStatus(SyncStatus.Idle); - if (e instanceof UserDataSyncStoreError && e.code === UserDataSyncStoreErrorCode.Rejected) { + if (e instanceof UserDataSyncError && e.code === UserDataSyncErrorCode.Rejected) { // Rejected as there is a new remote version. Syncing again, this.logService.info('Extensions: Failed to synchronise extensions as there is a new remote version available. Synchronizing again...'); return this.sync(); @@ -345,12 +345,12 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse } private getRemoteUserData(lastSyncData?: IUserData | null): Promise { - return this.userDataSyncStoreService.read(ExtensionsSynchroniser.EXTERNAL_USER_DATA_EXTENSIONS_KEY, lastSyncData || null); + return this.userDataSyncStoreService.read(ExtensionsSynchroniser.EXTERNAL_USER_DATA_EXTENSIONS_KEY, lastSyncData || null, this.source); } private async writeToRemote(extensions: ISyncExtension[], ref: string | null): Promise { const content = JSON.stringify(extensions); - ref = await this.userDataSyncStoreService.write(ExtensionsSynchroniser.EXTERNAL_USER_DATA_EXTENSIONS_KEY, content, ref); + ref = await this.userDataSyncStoreService.write(ExtensionsSynchroniser.EXTERNAL_USER_DATA_EXTENSIONS_KEY, content, ref, this.source); return { content, ref }; } diff --git a/src/vs/platform/userDataSync/common/globalStateSync.ts b/src/vs/platform/userDataSync/common/globalStateSync.ts index f3275f87b1b..58b60ff6f6e 100644 --- a/src/vs/platform/userDataSync/common/globalStateSync.ts +++ b/src/vs/platform/userDataSync/common/globalStateSync.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IUserData, UserDataSyncStoreError, UserDataSyncStoreErrorCode, SyncStatus, IUserDataSyncStoreService, IUserDataSyncLogService, IGlobalState, SyncSource, IUserDataSynchroniser } from 'vs/platform/userDataSync/common/userDataSync'; +import { IUserData, UserDataSyncError, UserDataSyncErrorCode, SyncStatus, IUserDataSyncStoreService, IUserDataSyncLogService, IGlobalState, SyncSource, IUserDataSynchroniser } from 'vs/platform/userDataSync/common/userDataSync'; import { VSBuffer } from 'vs/base/common/buffer'; import { Emitter, Event } from 'vs/base/common/event'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; @@ -132,7 +132,7 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs this.logService.trace('UI State: Finished synchronizing ui state.'); } catch (e) { this.setStatus(SyncStatus.Idle); - if (e instanceof UserDataSyncStoreError && e.code === UserDataSyncStoreErrorCode.Rejected) { + if (e instanceof UserDataSyncError && e.code === UserDataSyncErrorCode.Rejected) { // Rejected as there is a new remote version. Syncing again, this.logService.info('UI State: Failed to synchronise ui state as there is a new remote version available. Synchronizing again...'); return this.sync(); @@ -259,12 +259,12 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs } private getRemoteUserData(lastSyncData?: IUserData | null): Promise { - return this.userDataSyncStoreService.read(GlobalStateSynchroniser.EXTERNAL_USER_DATA_GLOBAL_STATE_KEY, lastSyncData || null); + return this.userDataSyncStoreService.read(GlobalStateSynchroniser.EXTERNAL_USER_DATA_GLOBAL_STATE_KEY, lastSyncData || null, this.source); } private async writeToRemote(globalState: IGlobalState, ref: string | null): Promise { const content = JSON.stringify(globalState); - ref = await this.userDataSyncStoreService.write(GlobalStateSynchroniser.EXTERNAL_USER_DATA_GLOBAL_STATE_KEY, content, ref); + ref = await this.userDataSyncStoreService.write(GlobalStateSynchroniser.EXTERNAL_USER_DATA_GLOBAL_STATE_KEY, content, ref, this.source); return { content, ref }; } diff --git a/src/vs/platform/userDataSync/common/keybindingsSync.ts b/src/vs/platform/userDataSync/common/keybindingsSync.ts index d4841150c24..4d20c895d03 100644 --- a/src/vs/platform/userDataSync/common/keybindingsSync.ts +++ b/src/vs/platform/userDataSync/common/keybindingsSync.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { IFileService, FileSystemProviderErrorCode, FileSystemProviderError, IFileContent, FileOperationError, FileOperationResult } from 'vs/platform/files/common/files'; -import { IUserData, UserDataSyncStoreError, UserDataSyncStoreErrorCode, SyncStatus, IUserDataSyncStoreService, IUserDataSyncLogService, IUserDataSyncUtilService, SyncSource, IUserDataSynchroniser } from 'vs/platform/userDataSync/common/userDataSync'; +import { IUserData, UserDataSyncError, UserDataSyncErrorCode, SyncStatus, IUserDataSyncStoreService, IUserDataSyncLogService, IUserDataSyncUtilService, SyncSource, IUserDataSynchroniser } from 'vs/platform/userDataSync/common/userDataSync'; import { merge } from 'vs/platform/userDataSync/common/keybindingsMerge'; import { VSBuffer } from 'vs/base/common/buffer'; import { parse, ParseError } from 'vs/base/common/json'; @@ -266,7 +266,7 @@ export class KeybindingsSynchroniser extends AbstractSynchroniser implements IUs } catch (e) { this.syncPreviewResultPromise = null; this.setStatus(SyncStatus.Idle); - if (e instanceof UserDataSyncStoreError && e.code === UserDataSyncStoreErrorCode.Rejected) { + if (e instanceof UserDataSyncError && e.code === UserDataSyncErrorCode.Rejected) { // Rejected as there is a new remote version. Syncing again, this.logService.info('Keybindings: Failed to synchronise keybindings as there is a new remote version available. Synchronizing again...'); return this.sync(); @@ -433,11 +433,11 @@ export class KeybindingsSynchroniser extends AbstractSynchroniser implements IUs } private async getRemoteUserData(lastSyncData?: IUserData | null): Promise { - return this.userDataSyncStoreService.read(KeybindingsSynchroniser.EXTERNAL_USER_DATA_KEYBINDINGS_KEY, lastSyncData || null); + return this.userDataSyncStoreService.read(KeybindingsSynchroniser.EXTERNAL_USER_DATA_KEYBINDINGS_KEY, lastSyncData || null, this.source); } private async updateRemoteUserData(content: string, ref: string | null): Promise { - return this.userDataSyncStoreService.write(KeybindingsSynchroniser.EXTERNAL_USER_DATA_KEYBINDINGS_KEY, content, ref); + return this.userDataSyncStoreService.write(KeybindingsSynchroniser.EXTERNAL_USER_DATA_KEYBINDINGS_KEY, content, ref, this.source); } private getKeybindingsContentFromSyncContent(syncContent: string): string | null { diff --git a/src/vs/platform/userDataSync/common/settingsSync.ts b/src/vs/platform/userDataSync/common/settingsSync.ts index fc707cbc9b5..f8dd7b0ba63 100644 --- a/src/vs/platform/userDataSync/common/settingsSync.ts +++ b/src/vs/platform/userDataSync/common/settingsSync.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { IFileService, FileSystemProviderErrorCode, FileSystemProviderError, IFileContent, FileOperationError, FileOperationResult } from 'vs/platform/files/common/files'; -import { IUserData, UserDataSyncStoreError, UserDataSyncStoreErrorCode, SyncStatus, IUserDataSyncStoreService, DEFAULT_IGNORED_SETTINGS, IUserDataSyncLogService, IUserDataSyncUtilService, IConflictSetting, ISettingsSyncService, CONFIGURATION_SYNC_STORE_KEY, SyncSource } from 'vs/platform/userDataSync/common/userDataSync'; +import { IUserData, UserDataSyncError, UserDataSyncErrorCode, SyncStatus, IUserDataSyncStoreService, DEFAULT_IGNORED_SETTINGS, IUserDataSyncLogService, IUserDataSyncUtilService, IConflictSetting, ISettingsSyncService, CONFIGURATION_SYNC_STORE_KEY, SyncSource } from 'vs/platform/userDataSync/common/userDataSync'; import { VSBuffer } from 'vs/base/common/buffer'; import { parse, ParseError } from 'vs/base/common/json'; import { localize } from 'vs/nls'; @@ -302,7 +302,7 @@ export class SettingsSynchroniser extends AbstractSynchroniser implements ISetti } catch (e) { this.syncPreviewResultPromise = null; this.setStatus(SyncStatus.Idle); - if (e instanceof UserDataSyncStoreError && e.code === UserDataSyncStoreErrorCode.Rejected) { + if (e instanceof UserDataSyncError && e.code === UserDataSyncErrorCode.Rejected) { // Rejected as there is a new remote version. Syncing again, this.logService.info('Settings: Failed to synchronise settings as there is a new remote version available. Synchronizing again...'); return this.sync(); @@ -454,11 +454,11 @@ export class SettingsSynchroniser extends AbstractSynchroniser implements ISetti } private getRemoteUserData(lastSyncData?: IUserData | null): Promise { - return this.userDataSyncStoreService.read(SettingsSynchroniser.EXTERNAL_USER_DATA_SETTINGS_KEY, lastSyncData || null); + return this.userDataSyncStoreService.read(SettingsSynchroniser.EXTERNAL_USER_DATA_SETTINGS_KEY, lastSyncData || null, this.source); } private async writeToRemote(content: string, ref: string | null): Promise { - return this.userDataSyncStoreService.write(SettingsSynchroniser.EXTERNAL_USER_DATA_SETTINGS_KEY, content, ref); + return this.userDataSyncStoreService.write(SettingsSynchroniser.EXTERNAL_USER_DATA_SETTINGS_KEY, content, ref, this.source); } private async writeToLocal(newContent: string, oldContent: IFileContent | null): Promise { diff --git a/src/vs/platform/userDataSync/common/userDataAutoSync.ts b/src/vs/platform/userDataSync/common/userDataAutoSync.ts index 6f53447afb3..d3a5a7d52ec 100644 --- a/src/vs/platform/userDataSync/common/userDataAutoSync.ts +++ b/src/vs/platform/userDataSync/common/userDataAutoSync.ts @@ -4,16 +4,20 @@ *--------------------------------------------------------------------------------------------*/ import { timeout } from 'vs/base/common/async'; -import { Event } from 'vs/base/common/event'; +import { Event, Emitter } from 'vs/base/common/event'; import { Disposable } from 'vs/base/common/lifecycle'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { IUserDataSyncLogService, IUserDataSyncService, SyncStatus, IUserDataAuthTokenService, IUserDataAutoSyncService, IUserDataSyncUtilService } from 'vs/platform/userDataSync/common/userDataSync'; +import { IUserDataSyncLogService, IUserDataSyncService, SyncStatus, IUserDataAuthTokenService, IUserDataAutoSyncService, IUserDataSyncUtilService, UserDataSyncError, UserDataSyncErrorCode, SyncSource } from 'vs/platform/userDataSync/common/userDataSync'; export class UserDataAutoSync extends Disposable implements IUserDataAutoSyncService { _serviceBrand: any; private enabled: boolean = false; + private successiveFailures: number = 0; + + private readonly _onError: Emitter<{ code: UserDataSyncErrorCode, source?: SyncSource }> = this._register(new Emitter<{ code: UserDataSyncErrorCode, source?: SyncSource }>()); + readonly onError: Event<{ code: UserDataSyncErrorCode, source?: SyncSource }> = this._onError.event; constructor( @IConfigurationService private readonly configurationService: IConfigurationService, @@ -41,6 +45,7 @@ export class UserDataAutoSync extends Disposable implements IUserDataAutoSyncSer this.sync(true, auto); return; } else { + this.successiveFailures = 0; if (stopIfDisabled) { this.userDataSyncService.stop(); this.logService.info('Auto sync stopped.'); @@ -65,11 +70,17 @@ export class UserDataAutoSync extends Disposable implements IUserDataAutoSyncSer } } await this.userDataSyncService.sync(); + this.successiveFailures = 0; } catch (e) { + this.successiveFailures++; this.logService.error(e); + this._onError.fire(e instanceof UserDataSyncError ? { code: e.code, source: e.source } : { code: UserDataSyncErrorCode.Unknown }); + } + if (this.successiveFailures > 5) { + this._onError.fire({ code: UserDataSyncErrorCode.TooManyFailures }); } if (loop) { - await timeout(1000 * 60 * 5); // Loop sync for every 5 min. + await timeout(1000 * 60 * 5 * (this.successiveFailures + 1)); // Loop sync for every (successive failures count + 1) times 5 mins interval. this.sync(loop, true); } } diff --git a/src/vs/platform/userDataSync/common/userDataSync.ts b/src/vs/platform/userDataSync/common/userDataSync.ts index aeb1bd8425b..6580f517dfb 100644 --- a/src/vs/platform/userDataSync/common/userDataSync.ts +++ b/src/vs/platform/userDataSync/common/userDataSync.ts @@ -123,15 +123,18 @@ export interface IUserData { content: string | null; } -export enum UserDataSyncStoreErrorCode { +export enum UserDataSyncErrorCode { + TooLarge = 'TooLarge', Unauthroized = 'Unauthroized', Rejected = 'Rejected', - Unknown = 'Unknown' + Unknown = 'Unknown', + TooManyFailures = 'TooManyFailures', + ConnectionRefused = 'ConnectionRefused' } -export class UserDataSyncStoreError extends Error { +export class UserDataSyncError extends Error { - constructor(message: string, public readonly code: UserDataSyncStoreErrorCode) { + constructor(message: string, public readonly code: UserDataSyncErrorCode, public readonly source?: SyncSource) { super(message); } @@ -151,8 +154,8 @@ export const IUserDataSyncStoreService = createDecorator; - write(key: string, content: string, ref: string | null): Promise; + read(key: string, oldValue: IUserData | null, source?: SyncSource): Promise; + write(key: string, content: string, ref: string | null, source?: SyncSource): Promise; clear(): Promise; } @@ -217,6 +220,7 @@ export interface IUserDataSyncService extends ISynchroniser { export const IUserDataAutoSyncService = createDecorator('IUserDataAutoSyncService'); export interface IUserDataAutoSyncService { _serviceBrand: any; + onError: Event<{ code: UserDataSyncErrorCode, source?: SyncSource }>; triggerAutoSync(): Promise; } diff --git a/src/vs/platform/userDataSync/common/userDataSyncIpc.ts b/src/vs/platform/userDataSync/common/userDataSyncIpc.ts index c6340f89049..caf9856c728 100644 --- a/src/vs/platform/userDataSync/common/userDataSyncIpc.ts +++ b/src/vs/platform/userDataSync/common/userDataSyncIpc.ts @@ -84,6 +84,9 @@ export class UserDataAutoSyncChannel implements IServerChannel { constructor(private readonly service: IUserDataAutoSyncService) { } listen(_: unknown, event: string): Event { + switch (event) { + case 'onError': return this.service.onError; + } throw new Error(`Event not found: ${event}`); } diff --git a/src/vs/platform/userDataSync/common/userDataSyncStoreService.ts b/src/vs/platform/userDataSync/common/userDataSyncStoreService.ts index f33c1880600..a5e77ab721e 100644 --- a/src/vs/platform/userDataSync/common/userDataSyncStoreService.ts +++ b/src/vs/platform/userDataSync/common/userDataSyncStoreService.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { Disposable, } from 'vs/base/common/lifecycle'; -import { IUserData, IUserDataSyncStoreService, UserDataSyncStoreErrorCode, UserDataSyncStoreError, IUserDataSyncStore, getUserDataSyncStore, IUserDataAuthTokenService } from 'vs/platform/userDataSync/common/userDataSync'; +import { IUserData, IUserDataSyncStoreService, UserDataSyncErrorCode, UserDataSyncError, IUserDataSyncStore, getUserDataSyncStore, IUserDataAuthTokenService, SyncSource } from 'vs/platform/userDataSync/common/userDataSync'; import { IRequestService, asText, isSuccess } from 'vs/platform/request/common/request'; import { URI } from 'vs/base/common/uri'; import { joinPath } from 'vs/base/common/resources'; @@ -27,7 +27,7 @@ export class UserDataSyncStoreService extends Disposable implements IUserDataSyn this.userDataSyncStore = getUserDataSyncStore(configurationService); } - async read(key: string, oldValue: IUserData | null): Promise { + async read(key: string, oldValue: IUserData | null, source?: SyncSource): Promise { if (!this.userDataSyncStore) { throw new Error('No settings sync store url configured.'); } @@ -40,7 +40,7 @@ export class UserDataSyncStoreService extends Disposable implements IUserDataSyn headers['If-None-Match'] = oldValue.ref; } - const context = await this.request({ type: 'GET', url, headers }, CancellationToken.None); + const context = await this.request({ type: 'GET', url, headers }, source, CancellationToken.None); if (context.res.statusCode === 304) { // There is no new value. Hence return the old value. @@ -59,7 +59,7 @@ export class UserDataSyncStoreService extends Disposable implements IUserDataSyn return { ref, content }; } - async write(key: string, data: string, ref: string | null): Promise { + async write(key: string, data: string, ref: string | null, source?: SyncSource): Promise { if (!this.userDataSyncStore) { throw new Error('No settings sync store url configured.'); } @@ -70,12 +70,7 @@ export class UserDataSyncStoreService extends Disposable implements IUserDataSyn headers['If-Match'] = ref; } - const context = await this.request({ type: 'POST', url, data, headers }, CancellationToken.None); - - if (context.res.statusCode === 412) { - // There is a new value. Throw Rejected Error - throw new UserDataSyncStoreError('New data exists', UserDataSyncStoreErrorCode.Rejected); - } + const context = await this.request({ type: 'POST', url, data, headers }, source, CancellationToken.None); if (!isSuccess(context)) { throw new Error('Server returned ' + context.res.statusCode); @@ -96,14 +91,14 @@ export class UserDataSyncStoreService extends Disposable implements IUserDataSyn const url = joinPath(URI.parse(this.userDataSyncStore.url), 'resource').toString(); const headers: IHeaders = { 'Content-Type': 'text/plain' }; - const context = await this.request({ type: 'DELETE', url, headers }, CancellationToken.None); + const context = await this.request({ type: 'DELETE', url, headers }, undefined, CancellationToken.None); if (!isSuccess(context)) { throw new Error('Server returned ' + context.res.statusCode); } } - private async request(options: IRequestOptions, token: CancellationToken): Promise { + private async request(options: IRequestOptions, source: SyncSource | undefined, token: CancellationToken): Promise { const authToken = await this.authTokenService.getToken(); if (!authToken) { throw new Error('No Auth Token Available.'); @@ -111,15 +106,30 @@ export class UserDataSyncStoreService extends Disposable implements IUserDataSyn options.headers = options.headers || {}; options.headers['authorization'] = `Bearer ${authToken}`; - const context = await this.requestService.request(options, token); + let context; + + try { + context = await this.requestService.request(options, token); + } catch (e) { + throw new UserDataSyncError(`Connection refused for the request '${options.url?.toString()}'.`, UserDataSyncErrorCode.ConnectionRefused, source); + } if (context.res.statusCode === 401) { // Throw Unauthorized Error - throw new UserDataSyncStoreError('Unauthorized', UserDataSyncStoreErrorCode.Unauthroized); + throw new UserDataSyncError(`Request '${options.url?.toString()}' is not authorized.`, UserDataSyncErrorCode.Unauthroized, source); + } + + if (context.res.statusCode === 412) { + // There is a new value. Throw Rejected Error + throw new UserDataSyncError(`${options.type} request '${options.url?.toString()}' failed with precondition. There is new data exists for this resource. Make the request again with latest data.`, UserDataSyncErrorCode.Rejected, source); + } + + if (context.res.statusCode === 413) { + // Throw Too Large Payload Error + throw new UserDataSyncError(`${options.type} request '${options.url?.toString()}' failed because data is too large.`, UserDataSyncErrorCode.TooLarge, source); } return context; - } } diff --git a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts index 0612f07a58b..c6f4d0e9352 100644 --- a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts +++ b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; -import { IUserDataSyncService, SyncStatus, SyncSource, CONTEXT_SYNC_STATE, IUserDataSyncStore, registerConfiguration, getUserDataSyncStore, ISyncConfiguration, IUserDataAuthTokenService, IUserDataAutoSyncService, USER_DATA_SYNC_SCHEME, toRemoteContentResource, getSyncSourceFromRemoteContentResource } from 'vs/platform/userDataSync/common/userDataSync'; +import { IUserDataSyncService, SyncStatus, SyncSource, CONTEXT_SYNC_STATE, IUserDataSyncStore, registerConfiguration, getUserDataSyncStore, ISyncConfiguration, IUserDataAuthTokenService, IUserDataAutoSyncService, USER_DATA_SYNC_SCHEME, toRemoteContentResource, getSyncSourceFromRemoteContentResource, UserDataSyncErrorCode } from 'vs/platform/userDataSync/common/userDataSync'; import { localize } from 'vs/nls'; import { Disposable, MutableDisposable, toDisposable, DisposableStore, dispose } from 'vs/base/common/lifecycle'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; @@ -44,6 +44,8 @@ import { registerEditorContribution } from 'vs/editor/browser/editorExtensions'; import { areSame } from 'vs/platform/userDataSync/common/settingsMerge'; import { getIgnoredSettings } from 'vs/platform/userDataSync/common/settingsSync'; import type { IEditorInput } from 'vs/workbench/common/editor'; +import { Action } from 'vs/base/common/actions'; +import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences'; const enum AuthStatus { Initializing = 'Initializing', @@ -91,6 +93,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo @IUserDataAuthTokenService private readonly userDataAuthTokenService: IUserDataAuthTokenService, @IUserDataAutoSyncService userDataAutoSyncService: IUserDataAutoSyncService, @ITextModelService private readonly textModelResolverService: ITextModelService, + @IPreferencesService private readonly preferencesService: IPreferencesService, ) { super(); this.userDataSyncStore = getUserDataSyncStore(configurationService); @@ -104,6 +107,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo this._register(this.authenticationService.onDidRegisterAuthenticationProvider(e => this.onDidRegisterAuthenticationProvider(e))); this._register(this.authenticationService.onDidUnregisterAuthenticationProvider(e => this.onDidUnregisterAuthenticationProvider(e))); this._register(this.authenticationService.onDidChangeSessions(e => this.onDidChangeSessions(e))); + this._register(userDataAutoSyncService.onError(({ code, source }) => this.onAutoSyncError(code, source))); this.registerActions(); this.initializeActiveAccount().then(_ => { if (isWeb) { @@ -246,6 +250,35 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo } } + private onAutoSyncError(code: UserDataSyncErrorCode, source?: SyncSource): void { + switch (code) { + case UserDataSyncErrorCode.TooManyFailures: + this.disableSync(); + this.notificationService.notify({ + severity: Severity.Error, + message: localize('too many errors', "Turned off sync because of too many failure attempts. Please open Sync log to check the failures and turn on sync."), + actions: { + primary: [new Action('open sync log', localize('open log', "Show logs"), undefined, true, () => this.showSyncLog())] + } + }); + return; + case UserDataSyncErrorCode.TooLarge: + if (source === SyncSource.Keybindings || source === SyncSource.Settings) { + const sourceArea = getSyncAreaLabel(source); + this.disableSync(); + this.notificationService.notify({ + severity: Severity.Error, + message: localize('too large', "Turned off sync because size of the {0} file to sync is larger than {1}. Please open the file and reduce the size and turn on sync", sourceArea, '1MB'), + actions: { + primary: [new Action('open sync log', localize('open file', "Show {0} file", sourceArea), undefined, true, + () => source === SyncSource.Settings ? this.preferencesService.openGlobalSettings(true) : this.preferencesService.openGlobalKeybindingSettings(true))] + } + }); + } + return; + } + } + private async updateBadge(): Promise { this.badgeDisposable.clear(); @@ -406,13 +439,17 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo } }); if (result.confirmed) { - await this.configurationService.updateValue(UserDataSyncWorkbenchContribution.ENABLEMENT_SETTING, undefined, ConfigurationTarget.USER); + await this.disableSync(); if (result.checkboxChecked) { await this.userDataSyncService.reset(); } } } + private disableSync(): Promise { + return this.configurationService.updateValue(UserDataSyncWorkbenchContribution.ENABLEMENT_SETTING, undefined, ConfigurationTarget.USER); + } + private async signIn(): Promise { try { this.activeAccount = await this.authenticationService.login(this.userDataSyncStore!.authenticationProviderId, ['https://management.core.windows.net/.default', 'offline_access']); diff --git a/src/vs/workbench/services/userDataSync/electron-browser/userDataAutoSyncService.ts b/src/vs/workbench/services/userDataSync/electron-browser/userDataAutoSyncService.ts index f773ebf8be1..7976bb0fbdb 100644 --- a/src/vs/workbench/services/userDataSync/electron-browser/userDataAutoSyncService.ts +++ b/src/vs/workbench/services/userDataSync/electron-browser/userDataAutoSyncService.ts @@ -3,17 +3,19 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IUserDataAutoSyncService } from 'vs/platform/userDataSync/common/userDataSync'; +import { IUserDataAutoSyncService, UserDataSyncErrorCode, SyncSource } from 'vs/platform/userDataSync/common/userDataSync'; import { ISharedProcessService } from 'vs/platform/ipc/electron-browser/sharedProcessService'; import { Disposable } from 'vs/base/common/lifecycle'; import { IChannel } from 'vs/base/parts/ipc/common/ipc'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { Event } from 'vs/base/common/event'; export class UserDataAutoSyncService extends Disposable implements IUserDataAutoSyncService { _serviceBrand: undefined; private readonly channel: IChannel; + get onError(): Event<{ code: UserDataSyncErrorCode, source?: SyncSource }> { return this.channel.listen('onError'); } constructor( @ISharedProcessService sharedProcessService: ISharedProcessService From 2c35d9c048acb06ccccadbdc8a12c98e77a04c96 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Fri, 24 Jan 2020 21:57:03 +0100 Subject: [PATCH 104/801] :lipstick: --- .../api/browser/viewsExtensionPoint.ts | 32 +++---------------- 1 file changed, 5 insertions(+), 27 deletions(-) diff --git a/src/vs/workbench/api/browser/viewsExtensionPoint.ts b/src/vs/workbench/api/browser/viewsExtensionPoint.ts index e198a29483d..da0c5668f4e 100644 --- a/src/vs/workbench/api/browser/viewsExtensionPoint.ts +++ b/src/vs/workbench/api/browser/viewsExtensionPoint.ts @@ -8,7 +8,7 @@ import { forEach } from 'vs/base/common/collections'; import { IJSONSchema } from 'vs/base/common/jsonSchema'; import * as resources from 'vs/base/common/resources'; import { ExtensionMessageCollector, ExtensionsRegistry, IExtensionPoint, IExtensionPointUser } from 'vs/workbench/services/extensions/common/extensionsRegistry'; -import { ViewContainer, IViewsRegistry, ITreeViewDescriptor, IViewContainersRegistry, Extensions as ViewContainerExtensions, TEST_VIEW_CONTAINER_ID, IViewDescriptor, ViewContainerLocation, IViewDescriptorService } from 'vs/workbench/common/views'; +import { ViewContainer, IViewsRegistry, ITreeViewDescriptor, IViewContainersRegistry, Extensions as ViewContainerExtensions, TEST_VIEW_CONTAINER_ID, IViewDescriptor, ViewContainerLocation } from 'vs/workbench/common/views'; import { CustomTreeViewPane, CustomTreeView } from 'vs/workbench/browser/parts/views/customView'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { coalesce, } from 'vs/base/common/arrays'; @@ -23,14 +23,7 @@ import { VIEWLET_ID as REMOTE } from 'vs/workbench/contrib/remote/common/remote. import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { URI } from 'vs/base/common/uri'; import { ViewletRegistry, Extensions as ViewletExtensions, ShowViewletAction } from 'vs/workbench/browser/viewlet'; -import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; -import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; -import { IStorageService } from 'vs/platform/storage/common/storage'; -import { IThemeService } from 'vs/platform/theme/common/themeService'; -import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IWorkbenchActionRegistry, Extensions as ActionExtensions } from 'vs/workbench/common/actions'; @@ -313,28 +306,13 @@ class ViewsExtensionHandler implements IWorkbenchContribution { if (!viewContainer) { - - class CustomViewPaneContainer extends ViewPaneContainer { - constructor( - @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService, - @ITelemetryService telemetryService: ITelemetryService, - @IWorkspaceContextService protected contextService: IWorkspaceContextService, - @IStorageService protected storageService: IStorageService, - @IConfigurationService configurationService: IConfigurationService, - @IInstantiationService protected instantiationService: IInstantiationService, - @IThemeService themeService: IThemeService, - @IContextMenuService contextMenuService: IContextMenuService, - @IExtensionService extensionService: IExtensionService, - @IViewDescriptorService viewDescriptorService: IViewDescriptorService - ) { - super(id, `${id}.state`, { mergeViewWithContainerWhenSingleView: true }, instantiationService, configurationService, layoutService, contextMenuService, telemetryService, extensionService, themeService, storageService, contextService, viewDescriptorService); - } - } - viewContainer = this.viewContainersRegistry.registerViewContainer({ id, name: title, extensionId, - ctorDescriptor: new SyncDescriptor(CustomViewPaneContainer), + ctorDescriptor: new SyncDescriptor( + ViewPaneContainer, + [id, `${id}.state`, { mergeViewWithContainerWhenSingleView: true }] + ), hideIfEmpty: true, icon, }, ViewContainerLocation.Sidebar); From 8a2cae92c40dba648106ac7e6fef15b49adfac3c Mon Sep 17 00:00:00 2001 From: Maher Jendoubi Date: Fri, 24 Jan 2020 22:15:50 +0100 Subject: [PATCH 105/801] Contributing: fix typos --- src/vs/platform/notification/common/notification.ts | 4 ++-- .../platform/userDataSync/test/common/settingsMerge.test.ts | 2 +- src/vs/workbench/browser/dnd.ts | 4 ++-- src/vs/workbench/common/editor.ts | 2 +- src/vs/workbench/contrib/search/browser/openSymbolHandler.ts | 2 +- src/vs/workbench/contrib/webview/browser/pre/main.js | 2 +- src/vs/workbench/services/editor/common/editorService.ts | 2 +- src/vs/workbench/services/host/browser/browserHostService.ts | 2 +- src/vs/workbench/services/statusbar/common/statusbar.ts | 2 +- src/vs/workbench/services/textfile/common/textfiles.ts | 4 ++-- 10 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/vs/platform/notification/common/notification.ts b/src/vs/platform/notification/common/notification.ts index 6f10b476dc8..93429296cfe 100644 --- a/src/vs/platform/notification/common/notification.ts +++ b/src/vs/platform/notification/common/notification.ts @@ -64,7 +64,7 @@ export interface INeverShowAgainOptions { isSecondary?: boolean; /** - * Wether to persist the choice in the current workspace or for all workspaces. By + * Whether to persist the choice in the current workspace or for all workspaces. By * default it will be persisted for all workspaces. */ scope?: NeverShowAgainScope; @@ -192,7 +192,7 @@ export interface IPromptChoice { isSecondary?: boolean; /** - * Wether to keep the notification open after the choice was selected + * Whether to keep the notification open after the choice was selected * by the user. By default, will close the notification upon click. */ keepOpen?: boolean; diff --git a/src/vs/platform/userDataSync/test/common/settingsMerge.test.ts b/src/vs/platform/userDataSync/test/common/settingsMerge.test.ts index 2e8d2a42372..bbe7afd722e 100644 --- a/src/vs/platform/userDataSync/test/common/settingsMerge.test.ts +++ b/src/vs/platform/userDataSync/test/common/settingsMerge.test.ts @@ -1201,7 +1201,7 @@ suite('SettingsMerge - Add Setting', () => { assert.equal(actual, expected); }); - test('Insert before a setting and before a comment at the begining', () => { + test('Insert before a setting and before a comment at the beginning', () => { const sourceContent = ` { diff --git a/src/vs/workbench/browser/dnd.ts b/src/vs/workbench/browser/dnd.ts index 5fab49f84df..325714a925e 100644 --- a/src/vs/workbench/browser/dnd.ts +++ b/src/vs/workbench/browser/dnd.ts @@ -151,8 +151,8 @@ export function extractResources(e: DragEvent, externalOnly?: boolean): Array { this.bearing = result || this.bearing; diff --git a/src/vs/workbench/contrib/webview/browser/pre/main.js b/src/vs/workbench/contrib/webview/browser/pre/main.js index 138707c9a9b..63c9af47e20 100644 --- a/src/vs/workbench/contrib/webview/browser/pre/main.js +++ b/src/vs/workbench/contrib/webview/browser/pre/main.js @@ -452,7 +452,7 @@ const onLoad = (contentDocument, contentWindow) => { if (contentDocument && contentDocument.body) { // Workaround for https://github.com/Microsoft/vscode/issues/12865 - // check new scrollY and reset if neccessary + // check new scrollY and reset if necessary setInitialScrollPosition(contentDocument.body, contentWindow); } diff --git a/src/vs/workbench/services/editor/common/editorService.ts b/src/vs/workbench/services/editor/common/editorService.ts index 8c507a32df9..5f6bb85eff3 100644 --- a/src/vs/workbench/services/editor/common/editorService.ts +++ b/src/vs/workbench/services/editor/common/editorService.ts @@ -55,7 +55,7 @@ export interface ISaveEditorsOptions extends ISaveOptions { export interface IBaseSaveRevertAllEditorOptions { /** - * Wether to include untitled editors as well. + * Whether to include untitled editors as well. */ includeUntitled?: boolean; } diff --git a/src/vs/workbench/services/host/browser/browserHostService.ts b/src/vs/workbench/services/host/browser/browserHostService.ts index dfb857d8350..2d5d095b490 100644 --- a/src/vs/workbench/services/host/browser/browserHostService.ts +++ b/src/vs/workbench/services/host/browser/browserHostService.ts @@ -43,7 +43,7 @@ export interface IWorkspaceProvider { * * @param workspace the workspace to open. * @param options optional options for the workspace to open. - * - `reuse`: wether to open inside the current window or a new window + * - `reuse`: whether to open inside the current window or a new window * - `payload`: arbitrary payload that should be made available * to the opening window via the `IWorkspaceProvider.payload` property. */ diff --git a/src/vs/workbench/services/statusbar/common/statusbar.ts b/src/vs/workbench/services/statusbar/common/statusbar.ts index cd91a975bda..91519f20cbd 100644 --- a/src/vs/workbench/services/statusbar/common/statusbar.ts +++ b/src/vs/workbench/services/statusbar/common/statusbar.ts @@ -53,7 +53,7 @@ export interface IStatusbarEntry { readonly arguments?: any[]; /** - * Wether to show a beak above the status bar entry. + * Whether to show a beak above the status bar entry. */ readonly showBeak?: boolean; } diff --git a/src/vs/workbench/services/textfile/common/textfiles.ts b/src/vs/workbench/services/textfile/common/textfiles.ts index 95394f9471c..90c21e1127a 100644 --- a/src/vs/workbench/services/textfile/common/textfiles.ts +++ b/src/vs/workbench/services/textfile/common/textfiles.ts @@ -174,7 +174,7 @@ export interface IWriteTextFileOptions extends IWriteFileOptions { overwriteReadonly?: boolean; /** - * Wether to write to the file as elevated (admin) user. When setting this option a prompt will + * Whether to write to the file as elevated (admin) user. When setting this option a prompt will * ask the user to authenticate as super user. */ writeElevated?: boolean; @@ -255,7 +255,7 @@ export const enum ModelState { /** * Any error that happens during a save that is not causing the CONFLICT state. - * Models in error mode are always diry. + * Models in error mode are always dirty. */ ERROR } From 5d498736f42841b46b53cda82f23178a3f05070c Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Fri, 24 Jan 2020 23:39:40 +0100 Subject: [PATCH 106/801] revert back to electron 6 (#89245) --- .yarnrc | 2 +- cgmanifest.json | 12 +- .../src/singlefolder-tests/commands.test.ts | 2 +- .../src/singlefolder-tests/webview.test.ts | 3 +- package.json | 2 +- src/vs/code/electron-main/app.ts | 35 +- src/vs/code/electron-main/window.ts | 18 +- .../platform/dialogs/electron-main/dialogs.ts | 2 +- .../platform/driver/electron-main/driver.ts | 3 +- .../electron-main/electronMainService.ts | 16 +- .../theme/electron-main/themeMainService.ts | 6 +- .../electron-main/windowsMainService.ts | 11 +- .../browser/workbench.contribution.ts | 3 +- .../electron-browser/webviewElement.ts | 6 +- src/vs/workbench/electron-browser/window.ts | 6 +- yarn.lock | 540 ++++++++---------- 16 files changed, 289 insertions(+), 378 deletions(-) diff --git a/.yarnrc b/.yarnrc index 2c769cfba18..85baaa63a78 100644 --- a/.yarnrc +++ b/.yarnrc @@ -1,3 +1,3 @@ disturl "https://atom.io/download/electron" -target "7.1.7" +target "6.1.6" runtime "electron" diff --git a/cgmanifest.json b/cgmanifest.json index e9dfd3d6ef3..c102a04f705 100644 --- a/cgmanifest.json +++ b/cgmanifest.json @@ -6,7 +6,7 @@ "git": { "name": "chromium", "repositoryUrl": "https://chromium.googlesource.com/chromium/src", - "commitHash": "e4745133a1d3745f066e068b8033c6a269b59caf" + "commitHash": "91f08db83c2ce8c722ddf0911ead8f7c473bedfa" } }, "licenseDetail": [ @@ -40,7 +40,7 @@ "SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." ], "isOnlyProductionDependency": true, - "version": "78.0.3904.130" + "version": "76.0.3809.146" }, { "component": { @@ -48,11 +48,11 @@ "git": { "name": "nodejs", "repositoryUrl": "https://github.com/nodejs/node", - "commitHash": "787378879acfb212ed4ff824bf9f767a24a5cb43a" + "commitHash": "64219741218aa87e259cf8257596073b8e747f0a" } }, "isOnlyProductionDependency": true, - "version": "12.8.1" + "version": "12.4.0" }, { "component": { @@ -60,12 +60,12 @@ "git": { "name": "electron", "repositoryUrl": "https://github.com/electron/electron", - "commitHash": "bef0dd868b7d6d32716c319664ed480f2ae17396" + "commitHash": "19c705ab80cd6fdccca3d65803ec2c4addb9540a" } }, "isOnlyProductionDependency": true, "license": "MIT", - "version": "7.1.7" + "version": "6.1.6" }, { "component": { diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/commands.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/commands.test.ts index 739ce386371..fba9348435c 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/commands.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/commands.test.ts @@ -105,7 +105,7 @@ suite('commands namespace tests', () => { }); test('api-command: vscode.open', function () { - let uri = Uri.parse(workspace.workspaceFolders![0].uri.toString() + '/far.js'); + let uri = Uri.parse(workspace.workspaceFolders![0].uri.toString() + '/image.png'); let a = commands.executeCommand('vscode.open', uri).then(() => assert.ok(true), () => assert.ok(false)); let b = commands.executeCommand('vscode.open', uri, ViewColumn.Two).then(() => assert.ok(true), () => assert.ok(false)); let c = commands.executeCommand('vscode.open').then(() => assert.ok(false), () => assert.ok(true)); diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/webview.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/webview.test.ts index b59d91ff380..e785f1d4afb 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/webview.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/webview.test.ts @@ -13,8 +13,7 @@ const webviewId = 'myWebview'; const testDocument = join(vscode.workspace.rootPath || '', './bower.json'); -// TODO: Re-enable after https://github.com/microsoft/vscode/issues/88415 -suite.skip('Webview tests', () => { +suite('Webview tests', () => { const disposables: vscode.Disposable[] = []; function _register(disposable: T) { diff --git a/package.json b/package.json index f13416fb9a2..22ae11f83fe 100644 --- a/package.json +++ b/package.json @@ -91,7 +91,7 @@ "coveralls": "^2.11.11", "cson-parser": "^1.3.3", "debounce": "^1.0.0", - "electron": "7.1.7", + "electron": "6.1.6", "eslint": "6.8.0", "eslint-plugin-jsdoc": "^19.1.0", "event-stream": "3.3.4", diff --git a/src/vs/code/electron-main/app.ts b/src/vs/code/electron-main/app.ts index 712d6f05c5b..4d544450cc8 100644 --- a/src/vs/code/electron-main/app.ts +++ b/src/vs/code/electron-main/app.ts @@ -171,7 +171,7 @@ export class CodeApplication extends Disposable { app.on('web-contents-created', (_event: Event, contents) => { contents.on('will-attach-webview', (event: Event, webPreferences, params) => { - const isValidWebviewSource = (source: string | undefined): boolean => { + const isValidWebviewSource = (source: string): boolean => { if (!source) { return false; } @@ -191,12 +191,11 @@ export class CodeApplication extends Disposable { webPreferences.nodeIntegration = false; // Verify URLs being loaded - // https://github.com/electron/electron/issues/21553 - if (isValidWebviewSource(params.src) && isValidWebviewSource((webPreferences as { preloadURL: string }).preloadURL)) { + if (isValidWebviewSource(params.src) && isValidWebviewSource(webPreferences.preloadURL)) { return; } - delete (webPreferences as { preloadURL: string }).preloadURL; // https://github.com/electron/electron/issues/21553 + delete webPreferences.preloadUrl; // Otherwise prevent loading this.logService.error('webContents#web-contents-created: Prevented webview attach'); @@ -498,27 +497,27 @@ export class CodeApplication extends Disposable { this.logService.info(`Tracing: waiting for windows to get ready...`); let recordingStopped = false; - const stopRecording = async (timeout: boolean) => { + const stopRecording = (timeout: boolean) => { if (recordingStopped) { return; } recordingStopped = true; // only once - const path = await contentTracing.stopRecording(join(homedir(), `${product.applicationName}-${Math.random().toString(16).slice(-4)}.trace.txt`)); - - if (!timeout) { - if (this.dialogMainService) { - this.dialogMainService.showMessageBox({ - type: 'info', - message: localize('trace.message', "Successfully created trace."), - detail: localize('trace.detail', "Please create an issue and manually attach the following file:\n{0}", path), - buttons: [localize('trace.ok', "Ok")] - }, withNullAsUndefined(BrowserWindow.getFocusedWindow())); + contentTracing.stopRecording(join(homedir(), `${product.applicationName}-${Math.random().toString(16).slice(-4)}.trace.txt`), path => { + if (!timeout) { + if (this.dialogMainService) { + this.dialogMainService.showMessageBox({ + type: 'info', + message: localize('trace.message', "Successfully created trace."), + detail: localize('trace.detail', "Please create an issue and manually attach the following file:\n{0}", path), + buttons: [localize('trace.ok', "Ok")] + }, withNullAsUndefined(BrowserWindow.getFocusedWindow())); + } + } else { + this.logService.info(`Tracing: data recorded (after 30s timeout) to ${path}`); } - } else { - this.logService.info(`Tracing: data recorded (after 30s timeout) to ${path}`); - } + }); }; // Wait up to 30s before creating the trace anyways diff --git a/src/vs/code/electron-main/window.ts b/src/vs/code/electron-main/window.ts index 1f1c08a58f1..d0ea8752aae 100644 --- a/src/vs/code/electron-main/window.ts +++ b/src/vs/code/electron-main/window.ts @@ -8,7 +8,7 @@ import * as objects from 'vs/base/common/objects'; import * as nls from 'vs/nls'; import { Event as CommonEvent, Emitter } from 'vs/base/common/event'; import { URI } from 'vs/base/common/uri'; -import { screen, BrowserWindow, systemPreferences, app, TouchBar, nativeImage, Rectangle, Display, TouchBarSegmentedControl, NativeImage, BrowserWindowConstructorOptions, SegmentedControlSegment, nativeTheme } from 'electron'; +import { screen, BrowserWindow, systemPreferences, app, TouchBar, nativeImage, Rectangle, Display, TouchBarSegmentedControl, NativeImage, BrowserWindowConstructorOptions, SegmentedControlSegment } from 'electron'; import { IEnvironmentService, ParsedArgs } from 'vs/platform/environment/common/environment'; import { ILogService } from 'vs/platform/log/common/log'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; @@ -347,9 +347,9 @@ export class CodeWindow extends Disposable implements ICodeWindow { }); this._win.webContents.session.webRequest.onHeadersReceived(null!, (details, callback) => { - const responseHeaders = details.responseHeaders as Record; + const responseHeaders = details.responseHeaders as { [key: string]: string[] }; - const contentType = (responseHeaders['content-type'] || responseHeaders['Content-Type']); + const contentType: string[] = (responseHeaders['content-type'] || responseHeaders['Content-Type']); if (contentType && Array.isArray(contentType) && contentType.some(x => x.toLowerCase().indexOf('image/svg') >= 0)) { return callback({ cancel: true }); } @@ -441,7 +441,7 @@ export class CodeWindow extends Disposable implements ICodeWindow { // Inject headers when requests are incoming const urls = ['https://marketplace.visualstudio.com/*', 'https://*.vsassets.io/*']; this._win.webContents.session.webRequest.onBeforeSendHeaders({ urls }, (details, cb) => - this.marketplaceHeadersPromise.then(headers => cb({ cancel: false, requestHeaders: objects.assign(details.requestHeaders, headers) as Record }))); + this.marketplaceHeadersPromise.then(headers => cb({ cancel: false, requestHeaders: objects.assign(details.requestHeaders, headers) as { [key: string]: string | undefined } }))); } private onWindowError(error: WindowError): void { @@ -648,7 +648,7 @@ export class CodeWindow extends Disposable implements ICodeWindow { if (windowConfig?.autoDetectHighContrast === false) { autoDetectHighContrast = false; } - windowConfiguration.highContrast = isWindows && autoDetectHighContrast && nativeTheme.shouldUseInvertedColorScheme; + windowConfiguration.highContrast = isWindows && autoDetectHighContrast && systemPreferences.isInvertedColorScheme(); windowConfiguration.accessibilitySupport = app.accessibilitySupportEnabled; // Title style related @@ -1007,22 +1007,22 @@ export class CodeWindow extends Disposable implements ICodeWindow { switch (visibility) { case ('default'): this._win.setMenuBarVisibility(!isFullscreen); - this._win.autoHideMenuBar = isFullscreen; + this._win.setAutoHideMenuBar(isFullscreen); break; case ('visible'): this._win.setMenuBarVisibility(true); - this._win.autoHideMenuBar = false; + this._win.setAutoHideMenuBar(false); break; case ('toggle'): this._win.setMenuBarVisibility(false); - this._win.autoHideMenuBar = true; + this._win.setAutoHideMenuBar(true); break; case ('hidden'): this._win.setMenuBarVisibility(false); - this._win.autoHideMenuBar = false; + this._win.setAutoHideMenuBar(false); break; } } diff --git a/src/vs/platform/dialogs/electron-main/dialogs.ts b/src/vs/platform/dialogs/electron-main/dialogs.ts index c694c003e9b..7b49ca50c2e 100644 --- a/src/vs/platform/dialogs/electron-main/dialogs.ts +++ b/src/vs/platform/dialogs/electron-main/dialogs.ts @@ -173,7 +173,7 @@ export class DialogMainService implements IDialogMainService { showOpenDialog(options: OpenDialogOptions, window?: BrowserWindow): Promise { - function normalizePaths(paths: string[]): string[] { + function normalizePaths(paths: string[] | undefined): string[] | undefined { if (paths && paths.length > 0 && isMacintosh) { paths = paths.map(path => normalizeNFC(path)); // normalize paths returned from the OS } diff --git a/src/vs/platform/driver/electron-main/driver.ts b/src/vs/platform/driver/electron-main/driver.ts index bae55607623..e0beffc55ed 100644 --- a/src/vs/platform/driver/electron-main/driver.ts +++ b/src/vs/platform/driver/electron-main/driver.ts @@ -18,6 +18,7 @@ import { ScanCodeBinding } from 'vs/base/common/scanCode'; import { KeybindingParser } from 'vs/base/common/keybindingParser'; import { timeout } from 'vs/base/common/async'; import { IDriver, IElement, IWindowDriver } from 'vs/platform/driver/common/driver'; +import { NativeImage } from 'electron'; import { ILifecycleMainService } from 'vs/platform/lifecycle/electron-main/lifecycleMainService'; import { IElectronMainService } from 'vs/platform/electron/electron-main/electronMainService'; @@ -66,7 +67,7 @@ export class Driver implements IDriver, IWindowDriverRegistry { throw new Error('Invalid window'); } const webContents = window.win.webContents; - const image = await webContents.capturePage(); + const image = await new Promise(c => webContents.capturePage(c)); return image.toPNG().toString('base64'); } diff --git a/src/vs/platform/electron/electron-main/electronMainService.ts b/src/vs/platform/electron/electron-main/electronMainService.ts index 98b0e2a1f66..24ef0a029ab 100644 --- a/src/vs/platform/electron/electron-main/electronMainService.ts +++ b/src/vs/platform/electron/electron-main/electronMainService.ts @@ -367,13 +367,15 @@ export class ElectronMainService implements IElectronMainService { //#region Connectivity async resolveProxy(windowId: number | undefined, url: string): Promise { - const window = this.windowById(windowId); - const session = window?.win?.webContents?.session; - if (session) { - return session.resolveProxy(url); - } else { - return undefined; - } + return new Promise(resolve => { + const window = this.windowById(windowId); + const session = window?.win?.webContents?.session; + if (session) { + session.resolveProxy(url, proxy => resolve(proxy)); + } else { + resolve(); + } + }); } //#endregion diff --git a/src/vs/platform/theme/electron-main/themeMainService.ts b/src/vs/platform/theme/electron-main/themeMainService.ts index 6787d57bc27..8cd6ecdcaa7 100644 --- a/src/vs/platform/theme/electron-main/themeMainService.ts +++ b/src/vs/platform/theme/electron-main/themeMainService.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { isWindows, isMacintosh } from 'vs/base/common/platform'; -import { ipcMain as ipc, nativeTheme } from 'electron'; +import { systemPreferences, ipcMain as ipc } from 'electron'; import { IStateService } from 'vs/platform/state/node/state'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; @@ -42,14 +42,14 @@ export class ThemeMainService implements IThemeMainService { } getBackgroundColor(): string { - if (isWindows && nativeTheme.shouldUseInvertedColorScheme) { + if (isWindows && systemPreferences.isInvertedColorScheme()) { return DEFAULT_BG_HC_BLACK; } let background = this.stateService.getItem(THEME_BG_STORAGE_KEY, null); if (!background) { let baseTheme: string; - if (isWindows && nativeTheme.shouldUseInvertedColorScheme) { + if (isWindows && systemPreferences.isInvertedColorScheme()) { baseTheme = 'hc-black'; } else { baseTheme = this.stateService.getItem(THEME_STORAGE_KEY, 'vs-dark').split(' ')[0]; diff --git a/src/vs/platform/windows/electron-main/windowsMainService.ts b/src/vs/platform/windows/electron-main/windowsMainService.ts index 4dbaf176ef2..ca87b9be80c 100644 --- a/src/vs/platform/windows/electron-main/windowsMainService.ts +++ b/src/vs/platform/windows/electron-main/windowsMainService.ts @@ -13,7 +13,7 @@ import { IEmptyWindowBackupInfo } from 'vs/platform/backup/node/backup'; import { IEnvironmentService, ParsedArgs } from 'vs/platform/environment/common/environment'; import { IStateService } from 'vs/platform/state/node/state'; import { CodeWindow, defaultWindowState } from 'vs/code/electron-main/window'; -import { ipcMain as ipc, screen, BrowserWindow, MessageBoxOptions, Display, app, nativeTheme } from 'electron'; +import { ipcMain as ipc, screen, BrowserWindow, systemPreferences, MessageBoxOptions, Display, app } from 'electron'; import { parseLineAndColumnAware } from 'vs/code/node/paths'; import { ILifecycleMainService, UnloadReason, LifecycleMainService, LifecycleMainPhase } from 'vs/platform/lifecycle/electron-main/lifecycleMainService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; @@ -226,13 +226,16 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic // React to HC color scheme changes (Windows) if (isWindows) { - nativeTheme.on('updated', () => { - if (nativeTheme.shouldUseInvertedColorScheme || nativeTheme.shouldUseHighContrastColors) { + const onHighContrastChange = () => { + if (systemPreferences.isInvertedColorScheme() || systemPreferences.isHighContrastColorScheme()) { this.sendToAll('vscode:enterHighContrast'); } else { this.sendToAll('vscode:leaveHighContrast'); } - }); + }; + + systemPreferences.on('inverted-color-scheme-changed', () => onHighContrastChange()); + systemPreferences.on('high-contrast-color-scheme-changed', () => onHighContrastChange()); } // When a window looses focus, save all windows state. This allows to diff --git a/src/vs/workbench/browser/workbench.contribution.ts b/src/vs/workbench/browser/workbench.contribution.ts index 60b31ed492a..f2a68f351d3 100644 --- a/src/vs/workbench/browser/workbench.contribution.ts +++ b/src/vs/workbench/browser/workbench.contribution.ts @@ -133,7 +133,8 @@ import { URI } from 'vs/base/common/uri'; 'workbench.editor.mouseBackForwardToNavigate': { 'type': 'boolean', 'description': nls.localize('mouseBackForwardToNavigate', "Navigate between open files using mouse buttons four and five if provided."), - 'default': true + 'default': true, + 'included': !isMacintosh }, 'workbench.editor.restoreViewState': { 'type': 'boolean', diff --git a/src/vs/workbench/contrib/webview/electron-browser/webviewElement.ts b/src/vs/workbench/contrib/webview/electron-browser/webviewElement.ts index 00aa8576189..929b367097b 100644 --- a/src/vs/workbench/contrib/webview/electron-browser/webviewElement.ts +++ b/src/vs/workbench/contrib/webview/electron-browser/webviewElement.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { FindInPageOptions, OnBeforeRequestListenerDetails, OnHeadersReceivedListenerDetails, Response, WebContents, WebviewTag } from 'electron'; +import { FindInPageOptions, OnBeforeRequestDetails, OnHeadersReceivedDetails, Response, WebContents, WebviewTag } from 'electron'; import { addDisposableListener } from 'vs/base/browser/dom'; import { Emitter, Event } from 'vs/base/common/event'; import { once } from 'vs/base/common/functional'; @@ -65,8 +65,8 @@ class WebviewTagHandle extends Disposable { } } -type OnBeforeRequestDelegate = (details: OnBeforeRequestListenerDetails) => Promise; -type OnHeadersReceivedDelegate = (details: OnHeadersReceivedListenerDetails) => { cancel: boolean; } | undefined; +type OnBeforeRequestDelegate = (details: OnBeforeRequestDetails) => Promise; +type OnHeadersReceivedDelegate = (details: OnHeadersReceivedDetails) => { cancel: boolean; } | undefined; class WebviewSession extends Disposable { diff --git a/src/vs/workbench/electron-browser/window.ts b/src/vs/workbench/electron-browser/window.ts index 65d95f95ecd..07f56351ccc 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, CrashReporterStartOptions, Event as IpcEvent } 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'; @@ -537,13 +537,13 @@ export class ElectronWindow extends Disposable { } // base options with product info - const options: CrashReporterStartOptions = { + const options = { companyName, productName, submitURL: isWindows ? hockeyAppConfig[process.arch === 'ia32' ? 'win32-ia32' : 'win32-x64'] : isLinux ? hockeyAppConfig[`linux-x64`] : hockeyAppConfig.darwin, extra: { vscode_version: product.version, - vscode_commit: product.commit || '' + vscode_commit: product.commit } }; diff --git a/yarn.lock b/yarn.lock index 334e3f30543..54b64c3195c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -140,38 +140,11 @@ lodash "^4.17.13" to-fast-properties "^2.0.0" -"@electron/get@^1.0.1": - version "1.7.2" - resolved "https://registry.yarnpkg.com/@electron/get/-/get-1.7.2.tgz#286436a9fb56ff1a1fcdf0e80131fd65f4d1e0fd" - integrity sha512-LSE4LZGMjGS9TloDx0yO44D2UTbaeKRk+QjlhWLiQlikV6J4spgDCjb6z4YIcqmPAwNzlNCnWF4dubytwI+ATA== - dependencies: - debug "^4.1.1" - env-paths "^2.2.0" - fs-extra "^8.1.0" - got "^9.6.0" - sanitize-filename "^1.6.2" - sumchecker "^3.0.1" - optionalDependencies: - global-agent "^2.0.2" - global-tunnel-ng "^2.7.1" - "@istanbuljs/schema@^0.1.2": version "0.1.2" resolved "https://registry.yarnpkg.com/@istanbuljs/schema/-/schema-0.1.2.tgz#26520bf09abe4a5644cd5414e37125a8954241dd" integrity sha512-tsAQNx32a8CoFhjhijUIhI4kccIAgmGhy8LZMZgGfmXcpMbPRUqn5LWmgRttILi6yeGmBJd2xsPkFMs0PzgPCw== -"@sindresorhus/is@^0.14.0": - version "0.14.0" - resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-0.14.0.tgz#9fb3a3cf3132328151f353de4632e01e52102bea" - integrity sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ== - -"@szmarczak/http-timer@^1.1.2": - version "1.1.2" - resolved "https://registry.yarnpkg.com/@szmarczak/http-timer/-/http-timer-1.1.2.tgz#b1665e2c461a2cd92f4c1bbf50d5454de0d4b421" - integrity sha512-XIB2XbzHTN6ieIjfIMV9hlVcfPU26s2vafYWQcZHWXHOxiaRZYEDKEwdl129Zyg50+foYV2jCgtrqSA6qNuNSA== - dependencies: - defer-to-connect "^1.0.1" - "@types/applicationinsights@0.20.0": version "0.20.0" resolved "https://registry.yarnpkg.com/@types/applicationinsights/-/applicationinsights-0.20.0.tgz#fa7b36dc954f635fa9037cad27c378446b1048fb" @@ -249,10 +222,10 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-10.12.21.tgz#7e8a0c34cf29f4e17a36e9bd0ea72d45ba03908e" integrity sha512-CBgLNk4o3XMnqMc0rhb6lc77IwShMEglz05deDcn2lQxyXEZivfwgYJu7SMha9V5XcrP6qZuevTHV/QrN2vjKQ== -"@types/node@^12.0.12": - version "12.12.24" - resolved "https://registry.yarnpkg.com/@types/node/-/node-12.12.24.tgz#d4606afd8cf6c609036b854360367d1b2c78931f" - integrity sha512-1Ciqv9pqwVtW6FsIUKSZNB82E5Cu1I2bBTj1xuIHXLe/1zYLl3956Nbhg2MzSYHVfl9/rmanjbQIb7LibfCnug== +"@types/node@^10.12.18": + version "10.17.13" + resolved "https://registry.yarnpkg.com/@types/node/-/node-10.17.13.tgz#ccebcdb990bd6139cd16e84c39dc2fb1023ca90c" + integrity sha512-pMCcqU2zT4TjqYFrWtYHKal7Sl30Ims6ulZ4UFXxI4xbtQqK/qqKwkDoBFCfooRqqmRu9vY3xaJRwxSh673aYg== "@types/node@^12.11.7": version "12.12.14" @@ -902,6 +875,11 @@ array-each@^1.0.0, array-each@^1.0.1: resolved "https://registry.yarnpkg.com/array-each/-/array-each-1.0.1.tgz#a794af0c05ab1752846ee753a1f211a05ba0c44f" integrity sha1-p5SvDAWrF1KEbudTofIRoFugxE8= +array-find-index@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/array-find-index/-/array-find-index-1.0.2.tgz#df010aa1287e164bbda6f9723b0a96a1ec4187a1" + integrity sha1-3wEKoSh+Fku9pvlyOwqWoexBh6E= + array-flatten@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" @@ -1272,11 +1250,6 @@ boolbase@~1.0.0: resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e" integrity sha1-aN/1++YMUes3cl6p4+0xDcwed24= -boolean@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/boolean/-/boolean-3.0.0.tgz#fab78d5907dbae6216ab46d32733bb7b76b99e76" - integrity sha512-OElxJ1lUSinuoUnkpOgLmxp0DC4ytEhODEL6QJU0NpxE/mI4rUSh8h1P1Wkvfi3xQEBcxXR2gBIPNYNuaFcAbQ== - boom@2.x.x: version "2.10.1" resolved "https://registry.yarnpkg.com/boom/-/boom-2.10.1.tgz#39c8918ceff5799f83f9492a848f625add0c766f" @@ -1521,24 +1494,24 @@ cache-base@^1.0.1: union-value "^1.0.0" unset-value "^1.0.0" -cacheable-request@^6.0.0: - version "6.1.0" - resolved "https://registry.yarnpkg.com/cacheable-request/-/cacheable-request-6.1.0.tgz#20ffb8bd162ba4be11e9567d823db651052ca912" - integrity sha512-Oj3cAGPCqOZX7Rz64Uny2GYAZNliQSqfbePrgAQ1wKAihYmCUnraBtJtKcGR4xz7wF+LoJC+ssFZvv5BgF9Igg== - dependencies: - clone-response "^1.0.2" - get-stream "^5.1.0" - http-cache-semantics "^4.0.0" - keyv "^3.0.0" - lowercase-keys "^2.0.0" - normalize-url "^4.1.0" - responselike "^1.0.2" - callsites@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.0.0.tgz#fb7eb569b72ad7a45812f93fd9430a3e410b3dd3" integrity sha512-tWnkwu9YEq2uzlBDI4RcLn8jrFvF9AOi8PxDNU3hZZjJcjkcRAq3vCI+vZcg1SuxISDYe86k9VZFwAxDiJGoAw== +camelcase-keys@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/camelcase-keys/-/camelcase-keys-2.1.0.tgz#308beeaffdf28119051efa1d932213c91b8f92e7" + integrity sha1-MIvur/3ygRkFHvodkyITyRuPkuc= + dependencies: + camelcase "^2.0.0" + map-obj "^1.0.0" + +camelcase@^2.0.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-2.1.1.tgz#7c1d16d679a1bbe59ca02cacecfb011e201f5a1f" + integrity sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8= + camelcase@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-3.0.0.tgz#32fc4b9fcdaf845fcdf7e73bb97cac2261f0ab0a" @@ -1797,13 +1770,6 @@ clone-buffer@^1.0.0: resolved "https://registry.yarnpkg.com/clone-buffer/-/clone-buffer-1.0.0.tgz#e3e25b207ac4e701af721e2cb5a16792cac3dc58" integrity sha1-4+JbIHrE5wGvch4staFnksrD3Fg= -clone-response@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/clone-response/-/clone-response-1.0.2.tgz#d1dc973920314df67fbeb94223b4ee350239e96b" - integrity sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws= - dependencies: - mimic-response "^1.0.0" - clone-stats@^0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/clone-stats/-/clone-stats-0.0.1.tgz#b88f94a82cf38b8791d58046ea4029ad88ca99d1" @@ -2026,7 +1992,7 @@ concat-with-sourcemaps@^1.0.0: dependencies: source-map "^0.5.1" -config-chain@^1.1.11, config-chain@^1.1.12: +config-chain@^1.1.12: version "1.1.12" resolved "https://registry.yarnpkg.com/config-chain/-/config-chain-1.1.12.tgz#0fde8d091200eb5e808caf25fe618c02f48e4efa" integrity sha512-a1eOIcu8+7lUInge4Rpf/n4Krkf3Dd9lqhljRzII1/Zno/kRtUWnznPO3jOKBmTEktkt3fkxisUcivoj0ebzoA== @@ -2137,11 +2103,6 @@ copy-webpack-plugin@^4.5.2: p-limit "^1.0.0" serialize-javascript "^1.4.0" -core-js@^3.4.1: - version "3.6.1" - resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.6.1.tgz#39d5e2e346258cc01eb7d44345b1c3c014ca3f05" - integrity sha512-186WjSik2iTGfDjfdCZAxv2ormxtKgemjC3SI6PL31qOA0j5LhTDVjHChccoc7brwLvpvLPiMyRlcO88C4l1QQ== - core-util-is@1.0.2, core-util-is@~1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" @@ -2326,6 +2287,13 @@ cuint@^0.2.1: resolved "https://registry.yarnpkg.com/cuint/-/cuint-0.2.2.tgz#408086d409550c2631155619e9fa7bcadc3b991b" integrity sha1-QICG1AlVDCYxFVYZ6fp7ytw7mRs= +currently-unhandled@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/currently-unhandled/-/currently-unhandled-0.4.1.tgz#988df33feab191ef799a61369dd76c17adf957ea" + integrity sha1-mI3zP+qxke95mmE2nddsF635V+o= + dependencies: + array-find-index "^1.0.1" + cyclist@~0.2.2: version "0.2.2" resolved "https://registry.yarnpkg.com/cyclist/-/cyclist-0.2.2.tgz#1b33792e11e914a2fd6d6ed6447464444e5fa640" @@ -2362,7 +2330,7 @@ debug@2.2.0: dependencies: ms "0.7.1" -debug@2.6.9, debug@^2.1.2, debug@^2.2.0, debug@^2.3.3: +debug@2.6.9, debug@^2.1.2, debug@^2.1.3, debug@^2.2.0, debug@^2.3.3: version "2.6.9" resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== @@ -2376,7 +2344,7 @@ debug@3.1.0: dependencies: ms "2.0.0" -debug@^3.1.0: +debug@^3.0.0, debug@^3.1.0: version "3.2.6" resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b" integrity sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ== @@ -2447,12 +2415,7 @@ default-resolution@^2.0.0: resolved "https://registry.yarnpkg.com/default-resolution/-/default-resolution-2.0.0.tgz#bcb82baa72ad79b426a76732f1a81ad6df26d684" integrity sha1-vLgrqnKtebQmp2cy8aga1t8m1oQ= -defer-to-connect@^1.0.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/defer-to-connect/-/defer-to-connect-1.1.1.tgz#88ae694b93f67b81815a2c8c769aef6574ac8f2f" - integrity sha512-J7thop4u3mRTkYRQ+Vpfwy2G5Ehoy82I14+14W4YMDLKdWloI9gSzRbV30s/NckQGVJtPkWNcW4oMAUigTdqiQ== - -define-properties@^1.1.2, define-properties@^1.1.3: +define-properties@^1.1.2: version "1.1.3" resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1" integrity sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ== @@ -2552,11 +2515,6 @@ detect-libc@^1.0.2, detect-libc@^1.0.3: resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-1.0.3.tgz#fa137c4bd698edf55cd5cd02ac559f91a4c4ba9b" integrity sha1-+hN8S9aY7fVc1c0CrFWfkaTEups= -detect-node@^2.0.4: - version "2.0.4" - resolved "https://registry.yarnpkg.com/detect-node/-/detect-node-2.0.4.tgz#014ee8f8f669c5c58023da64b8179c083a28c46c" - integrity sha512-ZIzRpLJrOj7jjP2miAtgqIfmzbxa4ZOr5jJc601zklsfEx9oTzmmj2nVpIPRpNlRTIh8lc1kyViIY7BWSGNmKw== - diagnostic-channel-publishers@0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/diagnostic-channel-publishers/-/diagnostic-channel-publishers-0.2.1.tgz#8e2d607a8b6d79fe880b548bc58cc6beb288c4f3" @@ -2656,11 +2614,6 @@ domutils@^1.5.1: dom-serializer "0" domelementtype "1" -duplexer3@^0.1.4: - version "0.1.4" - resolved "https://registry.yarnpkg.com/duplexer3/-/duplexer3-0.1.4.tgz#ee01dd1cac0ed3cbc7fdbea37dc0a8f1ce002ce2" - integrity sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI= - duplexer@^0.1.1, duplexer@~0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/duplexer/-/duplexer-0.1.1.tgz#ace6ff808c1ce66b57d1ebf97977acb02334cfc1" @@ -2730,18 +2683,33 @@ ee-first@1.1.1: resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0= +electron-download@^4.1.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/electron-download/-/electron-download-4.1.1.tgz#02e69556705cc456e520f9e035556ed5a015ebe8" + integrity sha512-FjEWG9Jb/ppK/2zToP+U5dds114fM1ZOJqMAR4aXXL5CvyPE9fiqBK/9YcwC9poIFQTEJk/EM/zyRwziziRZrg== + dependencies: + debug "^3.0.0" + env-paths "^1.0.0" + fs-extra "^4.0.1" + minimist "^1.2.0" + nugget "^2.0.1" + path-exists "^3.0.0" + rc "^1.2.1" + semver "^5.4.1" + sumchecker "^2.0.2" + electron-to-chromium@^1.2.7: version "1.3.27" resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.27.tgz#78ecb8a399066187bb374eede35d9c70565a803d" integrity sha1-eOy4o5kGYYe7N07t412ccFZagD0= -electron@7.1.7: - version "7.1.7" - resolved "https://registry.yarnpkg.com/electron/-/electron-7.1.7.tgz#520e2bc422e3dfd4bae166dd3be62101f2cbdc52" - integrity sha512-aCLJ4BJwnvOckJgovNul22AYlMFDzm4S4KqKCG2iBlFJyMHBxXAKFKMsgYd40LBZWS3hcY6RHpaYjHSAPLS1pw== +electron@6.1.6: + version "6.1.6" + resolved "https://registry.yarnpkg.com/electron/-/electron-6.1.6.tgz#d63ea9c89b85b981a29eb3088bf391bf52bd8d73" + integrity sha512-4c4GiFTbWY2Mgv20HB4Bfhf1hDKb0MWgC35wkwNepNom1ioWfumocPHZrSs1xNAEe+tOmezY6lq74n3LbwTnVQ== dependencies: - "@electron/get" "^1.0.1" - "@types/node" "^12.0.12" + "@types/node" "^10.12.18" + electron-download "^4.1.0" extract-zip "^1.0.3" elliptic@^6.0.0: @@ -2779,11 +2747,6 @@ emojis-list@^2.0.0: resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-2.1.0.tgz#4daa4d9db00f9819880c79fa457ae5b09a1fd389" integrity sha1-TapNnbAPmBmIDHn6RXrlsJof04k= -encodeurl@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" - integrity sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k= - encodeurl@~1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.1.tgz#79e3d58655346909fe6f0f45a5de68103b294d20" @@ -2810,10 +2773,10 @@ entities@^1.1.1, entities@~1.1.1: resolved "https://registry.yarnpkg.com/entities/-/entities-1.1.1.tgz#6e5c2d0a5621b5dadaecef80b90edfb5cd7772f0" integrity sha1-blwtClYhtdra7O+AuQ7ftc13cvA= -env-paths@^2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/env-paths/-/env-paths-2.2.0.tgz#cdca557dc009152917d6166e2febe1f039685e43" - integrity sha512-6u0VYSCo/OW6IoD5WCLLy9JUGARbamfSavcNXry/eu8aHVFei6CD3Sw+VGX5alea1i9pgPHW0mbu6Xj0uBh7gA== +env-paths@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/env-paths/-/env-paths-1.0.0.tgz#4168133b42bb05c38a35b1ae4397c8298ab369e0" + integrity sha1-QWgTO0K7BcOKNbGuQ5fIKYqzaeA= errno@^0.1.3, errno@~0.1.7: version "0.1.7" @@ -2837,11 +2800,6 @@ es5-ext@^0.10.14, es5-ext@^0.10.35, es5-ext@^0.10.9, es5-ext@~0.10.14: es6-iterator "~2.0.1" es6-symbol "~3.1.1" -es6-error@^4.1.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/es6-error/-/es6-error-4.1.1.tgz#9e3af407459deed47e9a91f9b885a84eb05c561d" - integrity sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg== - es6-iterator@^2.0.1, es6-iterator@~2.0.1: version "2.0.3" resolved "https://registry.yarnpkg.com/es6-iterator/-/es6-iterator-2.0.3.tgz#a7de889141a05a94b0854403b2d0a0fbfa98f3b7" @@ -2896,11 +2854,6 @@ escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.3, escape-string-regexp@^ resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= -escape-string-regexp@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz#a30304e99daa32e23b2fd20f51babd07cffca344" - integrity sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w== - eslint-plugin-jsdoc@^19.1.0: version "19.1.0" resolved "https://registry.yarnpkg.com/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-19.1.0.tgz#fcc17f0378fdd6ee1c847a79b7211745cb05d014" @@ -3661,6 +3614,15 @@ fs-extra@0.26.7: path-is-absolute "^1.0.0" rimraf "^2.2.8" +fs-extra@^4.0.1: + version "4.0.3" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-4.0.3.tgz#0d852122e5bc5beb453fb028e9c0c9bf36340c94" + integrity sha512-q6rbdDd1o2mAnQreO7YADIxf/Whx4AHBiRf6d+/cVT8h44ss+lHgxf1FemcqDnQt9X3ct4McHr+JMGlYSsK7Cg== + dependencies: + graceful-fs "^4.1.2" + jsonfile "^4.0.0" + universalify "^0.1.0" + fs-extra@^7.0.0: version "7.0.1" resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-7.0.1.tgz#4f189c44aa123b895f722804f55ea23eadc348e9" @@ -3670,15 +3632,6 @@ fs-extra@^7.0.0: jsonfile "^4.0.0" universalify "^0.1.0" -fs-extra@^8.1.0: - version "8.1.0" - resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-8.1.0.tgz#49d43c45a88cd9677668cb7be1b46efdb8d2e1c0" - integrity sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g== - dependencies: - graceful-fs "^4.2.0" - jsonfile "^4.0.0" - universalify "^0.1.0" - fs-minipass@^1.2.5: version "1.2.5" resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-1.2.5.tgz#06c277218454ec288df77ada54a03b8702aacb9d" @@ -3791,20 +3744,18 @@ get-caller-file@^2.0.1: resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== -get-stream@^4.0.0, get-stream@^4.1.0: +get-stdin@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-4.0.1.tgz#b968c6b0a04384324902e8bf1a5df32579a450fe" + integrity sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4= + +get-stream@^4.0.0: version "4.1.0" resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-4.1.0.tgz#c1b255575f3dc21d59bfc79cd3d2b46b1c3a54b5" integrity sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w== dependencies: pump "^3.0.0" -get-stream@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-5.1.0.tgz#01203cdc92597f9b909067c3e656cc1f4d3c4dc9" - integrity sha512-EXr1FOzrzTfGeL0gQdeFEvOMm2mzMOglyiOXSTpPC+iAjAKftbr3jpCMWynogwYnM+eSj9sHGc6wjIcDvYiygw== - dependencies: - pump "^3.0.0" - get-value@^2.0.3, get-value@^2.0.6: version "2.0.6" resolved "https://registry.yarnpkg.com/get-value/-/get-value-2.0.6.tgz#dc15ca1c672387ca76bd37ac0a395ba2042a2c28" @@ -3970,19 +3921,6 @@ glob@^7.1.4, glob@^7.1.6: once "^1.3.0" path-is-absolute "^1.0.0" -global-agent@^2.0.2: - version "2.1.7" - resolved "https://registry.yarnpkg.com/global-agent/-/global-agent-2.1.7.tgz#12d7bc2b07cd862d0fa76b0f1b2c48cd5ffcf150" - integrity sha512-ooK7eqGYZku+LgnbfH/Iv0RJ74XfhrBZDlke1QSzcBt0bw1PmJcnRADPAQuFE+R45pKKDTynAr25SBasY2kvow== - dependencies: - boolean "^3.0.0" - core-js "^3.4.1" - es6-error "^4.1.1" - matcher "^2.0.0" - roarr "^2.14.5" - semver "^6.3.0" - serialize-error "^5.0.0" - global-modules@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/global-modules/-/global-modules-2.0.0.tgz#997605ad2345f27f51539bea26574421215c7780" @@ -4019,16 +3957,6 @@ global-prefix@^3.0.0: kind-of "^6.0.2" which "^1.3.1" -global-tunnel-ng@^2.7.1: - version "2.7.1" - resolved "https://registry.yarnpkg.com/global-tunnel-ng/-/global-tunnel-ng-2.7.1.tgz#d03b5102dfde3a69914f5ee7d86761ca35d57d8f" - integrity sha512-4s+DyciWBV0eK148wqXxcmVAbFVPqtc3sEtUE/GTQfuU80rySLcMhUmHKSHI7/LDj8q0gDYI1lIhRRB7ieRAqg== - dependencies: - encodeurl "^1.0.2" - lodash "^4.17.10" - npm-conf "^1.1.3" - tunnel "^0.0.6" - globals@^11.1.0: version "11.12.0" resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" @@ -4046,13 +3974,6 @@ globals@^12.1.0: dependencies: type-fest "^0.8.1" -globalthis@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/globalthis/-/globalthis-1.0.1.tgz#40116f5d9c071f9e8fb0037654df1ab3a83b7ef9" - integrity sha512-mJPRTc/P39NH/iNG4mXa9aIhNymaQikTrnspeCa2ZuJ+mH2QN/rXwtX3XwKrHqWgUQFbNZKtHM105aHzJalElw== - dependencies: - define-properties "^1.1.3" - globby@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/globby/-/globby-5.0.0.tgz#ebd84667ca0dbb330b99bcfc68eac2bc54370e0d" @@ -4084,33 +4005,11 @@ glogg@^1.0.0: dependencies: sparkles "^1.0.0" -got@^9.6.0: - version "9.6.0" - resolved "https://registry.yarnpkg.com/got/-/got-9.6.0.tgz#edf45e7d67f99545705de1f7bbeeeb121765ed85" - integrity sha512-R7eWptXuGYxwijs0eV+v3o6+XH1IqVK8dJOEecQfTmkncw9AV4dcw/Dhxi8MdlqPthxxpZyizMzyg8RTmEsG+Q== - dependencies: - "@sindresorhus/is" "^0.14.0" - "@szmarczak/http-timer" "^1.1.2" - cacheable-request "^6.0.0" - decompress-response "^3.3.0" - duplexer3 "^0.1.4" - get-stream "^4.1.0" - lowercase-keys "^1.0.1" - mimic-response "^1.0.1" - p-cancelable "^1.0.0" - to-readable-stream "^1.0.0" - url-parse-lax "^3.0.0" - graceful-fs@4.1.11, graceful-fs@^4.0.0, graceful-fs@^4.1.11, graceful-fs@^4.1.2, graceful-fs@^4.1.3, graceful-fs@^4.1.6, graceful-fs@^4.1.9: version "4.1.11" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.11.tgz#0e8bdfe4d1ddb8854d64e04ea7c00e2a026e5658" integrity sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg= -graceful-fs@^4.2.0: - version "4.2.3" - resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.3.tgz#4a12ff1b60376ef09862c2093edd908328be8423" - integrity sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ== - growl@1.9.2: version "1.9.2" resolved "https://registry.yarnpkg.com/growl/-/growl-1.9.2.tgz#0ea7743715db8d8de2c5ede1775e1b45ac85c02f" @@ -4573,11 +4472,6 @@ html-escaper@^2.0.0: inherits "^2.0.1" readable-stream "^2.0.2" -http-cache-semantics@^4.0.0: - version "4.0.3" - resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.0.3.tgz#495704773277eeef6e43f9ab2c2c7d259dda25c5" - integrity sha512-TcIMG3qeVLgDr1TEd2XvHaTnMPwYQUQMIBLy+5pLSDKYFc7UIqj39w8EGzZkaxoLv/l2K8HaI0t5AVA+YYgUew== - http-errors@1.6.2, http-errors@~1.6.2: version "1.6.2" resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.6.2.tgz#0a002cc85707192a7e7946ceedc11155f60ec736" @@ -4711,6 +4605,13 @@ imurmurhash@^0.1.4: resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" integrity sha1-khi5srkoojixPcT7a21XbyMUU+o= +indent-string@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-2.1.0.tgz#8e2d48348742121b4a8218b7a137e9a52049dc80" + integrity sha1-ji1INIdCEhtKghi3oTfppSBJ3IA= + dependencies: + repeating "^2.0.0" + indexes-of@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/indexes-of/-/indexes-of-1.0.1.tgz#f30f716c8e2bd346c7b67d3df3915566a7c05607" @@ -4966,6 +4867,13 @@ is-extglob@^2.1.0, is-extglob@^2.1.1: resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI= +is-finite@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-finite/-/is-finite-1.0.2.tgz#cc6677695602be550ef11e8b4aa6305342b6d0aa" + integrity sha1-zGZ3aVYCvlUO8R6LSqYwU0K20Ko= + dependencies: + number-is-nan "^1.0.0" + is-fullwidth-code-point@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz#ef9e31386f031a7f0d643af82fde50c457ef00cb" @@ -5361,11 +5269,6 @@ jsesc@^2.5.1: resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4" integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA== -json-buffer@3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.0.tgz#5b1f397afc75d677bde8bcfc0e47e1f9a3d9a898" - integrity sha1-Wx85evx11ne96Lz8Dkfh+aPZqJg= - json-edm-parser@0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/json-edm-parser/-/json-edm-parser-0.1.2.tgz#1e60b0fef1bc0af67bc0d146dfdde5486cd615b4" @@ -5405,7 +5308,7 @@ json-stable-stringify@^1.0.0: dependencies: jsonify "~0.0.0" -json-stringify-safe@^5.0.1, json-stringify-safe@~5.0.1: +json-stringify-safe@~5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" integrity sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus= @@ -5481,13 +5384,6 @@ keytar@^4.11.0: nan "2.14.0" prebuild-install "5.3.0" -keyv@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/keyv/-/keyv-3.1.0.tgz#ecc228486f69991e49e9476485a5be1e8fc5c4d9" - integrity sha512-9ykJ/46SN/9KPM/sichzQ7OvXyGDYKGTaDlKMGCAlg2UK8KRy4jb0d8sFc+0Tt0YYnThq8X2RZgCg74RPxgcVA== - dependencies: - json-buffer "3.0.0" - kind-of@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-1.1.0.tgz#140a3d2d41a36d2efcfa9377b62c24f8495a5c44" @@ -5761,15 +5657,13 @@ long@^3.2.0: resolved "https://registry.yarnpkg.com/long/-/long-3.2.0.tgz#d821b7138ca1cb581c172990ef14db200b5c474b" integrity sha1-2CG3E4yhy1gcFymQ7xTbIAtcR0s= -lowercase-keys@^1.0.0, lowercase-keys@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-1.0.1.tgz#6f9e30b47084d971a7c820ff15a6c5167b74c26f" - integrity sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA== - -lowercase-keys@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-2.0.0.tgz#2603e78b7b4b0006cbca2fbcc8a3202558ac9479" - integrity sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA== +loud-rejection@^1.0.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/loud-rejection/-/loud-rejection-1.6.0.tgz#5b46f80147edee578870f086d04821cf998e551f" + integrity sha1-W0b4AUft7leIcPCG0Eghz5mOVR8= + dependencies: + currently-unhandled "^0.4.1" + signal-exit "^3.0.0" lru-cache@2: version "2.7.3" @@ -5835,6 +5729,11 @@ map-cache@^0.2.0, map-cache@^0.2.2: resolved "https://registry.yarnpkg.com/map-cache/-/map-cache-0.2.2.tgz#c32abd0bd6525d9b051645bb4f26ac5dc98a0dbf" integrity sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8= +map-obj@^1.0.0, map-obj@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/map-obj/-/map-obj-1.0.1.tgz#d933ceb9205d82bdcf4886f6742bdc2b4dea146d" + integrity sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0= + map-stream@0.0.7: version "0.0.7" resolved "https://registry.yarnpkg.com/map-stream/-/map-stream-0.0.7.tgz#8a1f07896d82b10926bd3744a2420009f88974a8" @@ -5873,13 +5772,6 @@ matchdep@^2.0.0: resolve "^1.4.0" stack-trace "0.0.10" -matcher@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/matcher/-/matcher-2.1.0.tgz#64e1041c15b993e23b786f93320a7474bf833c28" - integrity sha512-o+nZr+vtJtgPNklyeUKkkH42OsK8WAfdgaJE2FNxcjLPg+5QbeEoT6vRj8Xq/iv18JlQ9cmKsEu0b94ixWf1YQ== - dependencies: - escape-string-regexp "^2.0.0" - math-expression-evaluator@^1.2.14: version "1.2.17" resolved "https://registry.yarnpkg.com/math-expression-evaluator/-/math-expression-evaluator-1.2.17.tgz#de819fdbcd84dccd8fae59c6aeb79615b9d266ac" @@ -5929,6 +5821,22 @@ memory-fs@^0.4.0, memory-fs@^0.4.1, memory-fs@~0.4.1: errno "^0.1.3" readable-stream "^2.0.1" +meow@^3.1.0: + version "3.7.0" + resolved "https://registry.yarnpkg.com/meow/-/meow-3.7.0.tgz#72cb668b425228290abbfa856892587308a801fb" + integrity sha1-cstmi0JSKCkKu/qFaJJYcwioAfs= + dependencies: + camelcase-keys "^2.0.0" + decamelize "^1.1.2" + loud-rejection "^1.0.0" + map-obj "^1.0.1" + minimist "^1.1.3" + normalize-package-data "^2.3.4" + object-assign "^4.0.1" + read-pkg-up "^1.0.1" + redent "^1.0.0" + trim-newlines "^1.0.0" + merge-descriptors@1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" @@ -6048,11 +5956,6 @@ mimic-response@^1.0.0: resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-1.0.0.tgz#df3d3652a73fded6b9b0b24146e6fd052353458e" integrity sha1-3z02Uqc/3ta5sLJBRub9BSNTRY4= -mimic-response@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-1.0.1.tgz#4923538878eef42063cb8a3e3b0798781487ab1b" - integrity sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ== - minimalistic-assert@^1.0.0, minimalistic-assert@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7" @@ -6083,7 +5986,7 @@ minimist@0.0.8: resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d" integrity sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0= -minimist@1.2.0, minimist@^1.2.0: +minimist@1.2.0, minimist@^1.1.0, minimist@^1.1.3, minimist@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284" integrity sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ= @@ -6422,6 +6325,16 @@ normalize-package-data@^2.3.2: semver "2 || 3 || 4 || 5" validate-npm-package-license "^3.0.1" +normalize-package-data@^2.3.4: + version "2.5.0" + resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8" + integrity sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA== + dependencies: + hosted-git-info "^2.1.4" + resolve "^1.10.0" + semver "2 || 3 || 4 || 5" + validate-npm-package-license "^3.0.1" + normalize-path@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-1.0.0.tgz#32d0e472f91ff345701c15a8311018d3b0a90379" @@ -6459,11 +6372,6 @@ normalize-url@^1.4.0: query-string "^4.1.0" sort-keys "^1.0.0" -normalize-url@^4.1.0: - version "4.5.0" - resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-4.5.0.tgz#453354087e6ca96957bd8f5baf753f5982142129" - integrity sha512-2s47yzUxdexf1OhyRi4Em83iQk0aPvwTddtFz4hnSSw9dCEsLEGf6SwIO8ss/19S9iBb5sJaOuTvTGDeZI00BQ== - now-and-later@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/now-and-later/-/now-and-later-2.0.0.tgz#bc61cbb456d79cb32207ce47ca05136ff2e7d6ee" @@ -6476,14 +6384,6 @@ npm-bundled@^1.0.1: resolved "https://registry.yarnpkg.com/npm-bundled/-/npm-bundled-1.0.3.tgz#7e71703d973af3370a9591bafe3a63aca0be2308" integrity sha512-ByQ3oJ/5ETLyglU2+8dBObvhfWXX8dtPZDMePCahptliFX2iIuhyEszyFk401PZUNQH20vvdW5MLjJxkwU80Ow== -npm-conf@^1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/npm-conf/-/npm-conf-1.1.3.tgz#256cc47bd0e218c259c4e9550bf413bc2192aff9" - integrity sha512-Yic4bZHJOt9RCFbRP3GgpqhScOY4HH3V2P8yBj6CeYq118Qr+BLXqT2JvpJ00mryLESpgOxf5XlFv4ZjXxLScw== - dependencies: - config-chain "^1.1.11" - pify "^3.0.0" - npm-packlist@^1.1.6: version "1.1.11" resolved "https://registry.yarnpkg.com/npm-packlist/-/npm-packlist-1.1.11.tgz#84e8c683cbe7867d34b1d357d893ce29e28a02de" @@ -6516,6 +6416,19 @@ nth-check@~1.0.1: dependencies: boolbase "~1.0.0" +nugget@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/nugget/-/nugget-2.0.1.tgz#201095a487e1ad36081b3432fa3cada4f8d071b0" + integrity sha1-IBCVpIfhrTYIGzQy+jytpPjQcbA= + dependencies: + debug "^2.1.3" + minimist "^1.1.0" + pretty-bytes "^1.0.2" + progress-stream "^1.1.0" + request "^2.45.0" + single-line-log "^1.1.2" + throttleit "0.0.2" + num2fraction@^1.2.2: version "1.2.2" resolved "https://registry.yarnpkg.com/num2fraction/-/num2fraction-1.2.2.tgz#6f682b6a027a4e9ddfa4564cd2589d1d4e669ede" @@ -6783,11 +6696,6 @@ p-all@^1.0.0: dependencies: p-map "^1.0.0" -p-cancelable@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-1.1.0.tgz#d078d15a3af409220c886f1d9a0ca2e441ab26cc" - integrity sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw== - p-defer@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/p-defer/-/p-defer-1.0.0.tgz#9f6eb182f6c9aa8cd743004a7d4f96b196b0fb0c" @@ -7406,16 +7314,19 @@ prepend-http@^1.0.0: resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-1.0.4.tgz#d4f4562b0ce3696e41ac52d0e002e57a635dc6dc" integrity sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw= -prepend-http@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-2.0.0.tgz#e92434bfa5ea8c19f41cdfd401d741a3c819d897" - integrity sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc= - preserve@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/preserve/-/preserve-0.2.0.tgz#815ed1f6ebc65926f865b310c0713bcb3315ce4b" integrity sha1-gV7R9uvGWSb4ZbMQwHE7yzMVzks= +pretty-bytes@^1.0.2: + version "1.0.4" + resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-1.0.4.tgz#0a22e8210609ad35542f8c8d5d2159aff0751c84" + integrity sha1-CiLoIQYJrTVUL4yNXSFZr/B1HIQ= + dependencies: + get-stdin "^4.0.1" + meow "^3.1.0" + pretty-hrtime@^1.0.0: version "1.0.3" resolved "https://registry.yarnpkg.com/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz#b7e3ea42435a4c9b2759d99e0f201eb195802ee1" @@ -7444,6 +7355,14 @@ process@^0.11.10: resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182" integrity sha1-czIwDoQBYb2j5podHZGn1LwW8YI= +progress-stream@^1.1.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/progress-stream/-/progress-stream-1.2.0.tgz#2cd3cfea33ba3a89c9c121ec3347abe9ab125f77" + integrity sha1-LNPP6jO6OonJwSHsM0er6asSX3c= + dependencies: + speedometer "~0.1.2" + through2 "~0.2.3" + progress@^1.1.8: version "1.1.8" resolved "https://registry.yarnpkg.com/progress/-/progress-1.1.8.tgz#e260c78f6161cdd9b0e56cc3e0a85de17c7a57be" @@ -7651,7 +7570,7 @@ raw-body@2.3.2: iconv-lite "0.4.19" unpipe "1.0.0" -rc@^1.2.7: +rc@^1.2.1, rc@^1.2.7: version "1.2.8" resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed" integrity sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw== @@ -7727,7 +7646,7 @@ read@^1.0.7: isarray "0.0.1" string_decoder "~0.10.x" -readable-stream@^1.1.8: +readable-stream@^1.1.8, readable-stream@~1.1.9: version "1.1.14" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.1.14.tgz#7cf4c54ef648e3813084c636dd2079e166c081d9" integrity sha1-fPTFTvZI44EwhMY23SB54WbAgdk= @@ -7795,6 +7714,14 @@ rechoir@^0.6.2: dependencies: resolve "^1.1.6" +redent@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/redent/-/redent-1.0.0.tgz#cf916ab1fd5f1f16dfb20822dd6ec7f730c2afde" + integrity sha1-z5Fqsf1fHxbfsggi3W7H9zDCr94= + dependencies: + indent-string "^2.1.0" + strip-indent "^1.0.1" + reduce-css-calc@^1.2.6: version "1.3.0" resolved "https://registry.yarnpkg.com/reduce-css-calc/-/reduce-css-calc-1.3.0.tgz#747c914e049614a4c9cfbba629871ad1d2927716" @@ -7874,6 +7801,13 @@ repeat-string@^1.6.1: resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637" integrity sha1-jcrkcOHIirwtYA//Sndihtp15jc= +repeating@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/repeating/-/repeating-2.0.1.tgz#5214c53a926d3552707527fbab415dbc08d06dda" + integrity sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo= + dependencies: + is-finite "^1.0.0" + replace-ext@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/replace-ext/-/replace-ext-0.0.1.tgz#29bbd92078a739f0bcce2b4ee41e837953522924" @@ -7956,7 +7890,7 @@ request@2.79.0: tunnel-agent "^0.6.0" uuid "^3.1.0" -request@^2.86.0, request@^2.88.0: +request@^2.45.0, request@^2.86.0, request@^2.88.0: version "2.88.0" resolved "https://registry.yarnpkg.com/request/-/request-2.88.0.tgz#9c2fca4f7d35b592efe57c7f0a55e81052124fef" integrity sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg== @@ -8050,6 +7984,13 @@ resolve@^1.1.6, resolve@^1.1.7: dependencies: path-parse "^1.0.5" +resolve@^1.10.0: + version "1.15.0" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.15.0.tgz#1b7ca96073ebb52e741ffd799f6b39ea462c67f5" + integrity sha512-+hTmAldEGE80U2wJJDC1lebb5jWqvTYAfm3YZ1ckk1gBr0MnCqUKlwK1e+anaFljIl+F5tR5IoZcm4ZDA1zMQw== + dependencies: + path-parse "^1.0.6" + resolve@^1.3.2: version "1.14.2" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.14.2.tgz#dbf31d0fa98b1f29aa5169783b9c290cb865fea2" @@ -8064,13 +8005,6 @@ resolve@^1.4.0: dependencies: path-parse "^1.0.6" -responselike@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/responselike/-/responselike-1.0.2.tgz#918720ef3b631c5642be068f15ade5a46f4ba1e7" - integrity sha1-kYcg7ztjHFZCvgaPFa3lpG9Loec= - dependencies: - lowercase-keys "^1.0.0" - restore-cursor@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-2.0.0.tgz#9f7ee287f82fd326d4fd162923d62129eee0dfaf" @@ -8126,18 +8060,6 @@ ripemd160@^2.0.0, ripemd160@^2.0.1: hash-base "^3.0.0" inherits "^2.0.1" -roarr@^2.14.5: - version "2.14.6" - resolved "https://registry.yarnpkg.com/roarr/-/roarr-2.14.6.tgz#cebe8ad7ecbfd15bfa37b02dacf00809dd633912" - integrity sha512-qjbw0BEesKA+3XFBPt+KVe1PC/Z6ShfJ4wPlx2XifqH5h2Lj8/KQT5XJTsy3n1Es5kai+BwKALaECW3F70B1cg== - dependencies: - boolean "^3.0.0" - detect-node "^2.0.4" - globalthis "^1.0.0" - json-stringify-safe "^5.0.1" - semver-compare "^1.0.0" - sprintf-js "^1.1.2" - run-async@^2.2.0: version "2.3.0" resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.3.0.tgz#0371ab4ae0bdd720d4166d7dfda64ff7a445a6c0" @@ -8198,13 +8120,6 @@ samsam@~1.1: resolved "https://registry.yarnpkg.com/samsam/-/samsam-1.1.3.tgz#9f5087419b4d091f232571e7fa52e90b0f552621" integrity sha1-n1CHQZtNCR8jJXHn+lLpCw9VJiE= -sanitize-filename@^1.6.2: - version "1.6.3" - resolved "https://registry.yarnpkg.com/sanitize-filename/-/sanitize-filename-1.6.3.tgz#755ebd752045931977e30b2025d340d7c9090378" - integrity sha512-y/52Mcy7aw3gRm7IrcGDFx/bCk4AhRh2eI9luHOQM86nZsqwiRkkq2GekHXBBD+SmPidc8i2PqtYZl+pWJ8Oeg== - dependencies: - truncate-utf8-bytes "^1.0.0" - sax@0.5.x: version "0.5.8" resolved "https://registry.yarnpkg.com/sax/-/sax-0.5.8.tgz#d472db228eb331c2506b0e8c15524adb939d12c1" @@ -8223,11 +8138,6 @@ schema-utils@^0.4.4, schema-utils@^0.4.5: ajv "^6.1.0" ajv-keywords "^3.1.0" -semver-compare@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/semver-compare/-/semver-compare-1.0.0.tgz#0dee216a1c941ab37e9efb1788f6afc5ff5537fc" - integrity sha1-De4hahyUGrN+nvsXiPavxf9VN/w= - semver-greatest-satisfied-range@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/semver-greatest-satisfied-range/-/semver-greatest-satisfied-range-1.1.0.tgz#13e8c2658ab9691cb0cd71093240280d36f77a5b" @@ -8289,13 +8199,6 @@ send@0.16.1: range-parser "~1.2.0" statuses "~1.3.1" -serialize-error@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/serialize-error/-/serialize-error-5.0.0.tgz#a7ebbcdb03a5d71a6ed8461ffe0fc1a1afed62ac" - integrity sha512-/VtpuyzYf82mHYTtI4QKtwHa79vAdU5OQpNPAmE/0UDdlGT0ZxHwC+J6gXkw29wwoVI8fMPsfcVHOwXtUQYYQA== - dependencies: - type-fest "^0.8.0" - serialize-javascript@^1.4.0: version "1.5.0" resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-1.5.0.tgz#1aa336162c88a890ddad5384baebc93a655161fe" @@ -8405,6 +8308,13 @@ simple-get@^2.7.0: once "^1.3.1" simple-concat "^1.0.0" +single-line-log@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/single-line-log/-/single-line-log-1.1.2.tgz#c2f83f273a3e1a16edb0995661da0ed5ef033364" + integrity sha1-wvg/Jzo+GhbtsJlWYdoO1e8DM2Q= + dependencies: + string-width "^1.0.1" + sinon@^1.17.2: version "1.17.7" resolved "https://registry.yarnpkg.com/sinon/-/sinon-1.17.7.tgz#4542a4f49ba0c45c05eb2e9dd9d203e2b8efe0bf" @@ -8588,6 +8498,11 @@ spdx-license-ids@^3.0.0: resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.5.tgz#3694b5804567a458d3c8045842a6358632f62654" integrity sha512-J+FWzZoynJEXGphVIS+XEh3kFSjZX/1i9gFBaWQcB+/tmpe2qUsSBABpcxqxnAxFdiUFEgAX1bjYGQvIZmoz9Q== +speedometer@~0.1.2: + version "0.1.4" + resolved "https://registry.yarnpkg.com/speedometer/-/speedometer-0.1.4.tgz#9876dbd2a169d3115402d48e6ea6329c8816a50d" + integrity sha1-mHbb0qFp0xFUAtSObqYynIgWpQ0= + split-string@^3.0.1, split-string@^3.0.2: version "3.1.0" resolved "https://registry.yarnpkg.com/split-string/-/split-string-3.1.0.tgz#7cb09dda3a86585705c64b39a6466038682e8fe2" @@ -8609,11 +8524,6 @@ split@^1.0.1: dependencies: through "2" -sprintf-js@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.1.2.tgz#da1765262bf8c0f571749f2ad6c26300207ae673" - integrity sha512-VE0SOVEHCk7Qc8ulkWw3ntAzXuqf7S2lvwQaDLRnUeIEaKNQJzV6BwmLKhOqT61aGhfUMrXeaBk+oDGCzvhcug== - sprintf-js@~1.0.2: version "1.0.3" resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" @@ -8859,6 +8769,13 @@ strip-eof@^1.0.0: resolved "https://registry.yarnpkg.com/strip-eof/-/strip-eof-1.0.0.tgz#bb43ff5598a6eb05d89b59fcd129c983313606bf" integrity sha1-u0P/VZim6wXYm1n80SnJgzE2Br8= +strip-indent@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-1.0.1.tgz#0c7962a6adefa7bbd4ac366460a638552ae1a0a2" + integrity sha1-DHlipq3vp7vUrDZkYKY4VSrhoKI= + dependencies: + get-stdin "^4.0.1" + strip-json-comments@^2.0.1, strip-json-comments@~2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" @@ -8874,12 +8791,12 @@ sudo-prompt@9.1.1: resolved "https://registry.yarnpkg.com/sudo-prompt/-/sudo-prompt-9.1.1.tgz#73853d729770392caec029e2470db9c221754db0" integrity sha512-es33J1g2HjMpyAhz8lOR+ICmXXAqTuKbuXuUWLhOLew20oN9oUCgCJx615U/v7aioZg7IX5lIh9x34vwneu4pA== -sumchecker@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/sumchecker/-/sumchecker-3.0.1.tgz#6377e996795abb0b6d348e9b3e1dfb24345a8e42" - integrity sha512-MvjXzkz/BOfyVDkG0oFOtBxHX2u3gKbMHIF/dXblZsgD3BWOFLmHovIpZY7BykJdAjcqRCBi1WYBNdEC9yI7vg== +sumchecker@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/sumchecker/-/sumchecker-2.0.2.tgz#0f42c10e5d05da5d42eea3e56c3399a37d6c5b3e" + integrity sha1-D0LBDl0F2l1C7qPlbDOZo31sWz4= dependencies: - debug "^4.1.0" + debug "^2.2.0" supports-color@1.2.0: version "1.2.0" @@ -9035,6 +8952,11 @@ textextensions@~1.0.0: resolved "https://registry.yarnpkg.com/textextensions/-/textextensions-1.0.2.tgz#65486393ee1f2bb039a60cbba05b0b68bd9501d2" integrity sha1-ZUhjk+4fK7A5pgy7oFsLaL2VAdI= +throttleit@0.0.2: + version "0.0.2" + resolved "https://registry.yarnpkg.com/throttleit/-/throttleit-0.0.2.tgz#cfedf88e60c00dd9697b61fdd2a8343a9b680eaf" + integrity sha1-z+34jmDADdlpe2H90qg0OptoDq8= + through2-filter@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/through2-filter/-/through2-filter-2.0.0.tgz#60bc55a0dacb76085db1f9dae99ab43f83d622ec" @@ -9067,6 +8989,14 @@ through2@^3.0.0: readable-stream "2 || 3" xtend "~4.0.1" +through2@~0.2.3: + version "0.2.3" + resolved "https://registry.yarnpkg.com/through2/-/through2-0.2.3.tgz#eb3284da4ea311b6cc8ace3653748a52abf25a3f" + integrity sha1-6zKE2k6jEbbMis42U3SKUqvyWj8= + dependencies: + readable-stream "~1.1.9" + xtend "~2.1.1" + through2@~0.4.0: version "0.4.2" resolved "https://registry.yarnpkg.com/through2/-/through2-0.4.2.tgz#dbf5866031151ec8352bb6c4db64a2292a840b9b" @@ -9155,11 +9085,6 @@ to-object-path@^0.3.0: dependencies: kind-of "^3.0.2" -to-readable-stream@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/to-readable-stream/-/to-readable-stream-1.0.0.tgz#ce0aa0c2f3df6adf852efb404a783e77c0475771" - integrity sha512-Iq25XBt6zD5npPhlLVXGFN3/gyR2/qODcKNNyTMd4vbm39HUaOiAM4PMq0eMVC/Tkxz+Zjdsc55g9yyz+Yq00Q== - to-regex-range@^2.1.0: version "2.1.1" resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-2.1.1.tgz#7c80c17b9dfebe599e27367e0d4dd5590141db38" @@ -9219,12 +9144,10 @@ tough-cookie@~2.4.3: resolved "https://registry.yarnpkg.com/traverse/-/traverse-0.3.9.tgz#717b8f220cc0bb7b44e40514c22b2e8bbc70d8b9" integrity sha1-cXuPIgzAu3tE5AUUwisui7xw2Lk= -truncate-utf8-bytes@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/truncate-utf8-bytes/-/truncate-utf8-bytes-1.0.2.tgz#405923909592d56f78a5818434b0b78489ca5f2b" - integrity sha1-QFkjkJWS1W94pYGENLC3hInKXys= - dependencies: - utf8-byte-length "^1.0.1" +trim-newlines@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-1.0.0.tgz#5887966bb582a4503a41eb524f7d35011815a613" + integrity sha1-WIeWa7WCpFA6QetST301ARgVphM= ts-loader@^4.4.2: version "4.4.2" @@ -9271,11 +9194,6 @@ tunnel@0.0.4: resolved "https://registry.yarnpkg.com/tunnel/-/tunnel-0.0.4.tgz#2d3785a158c174c9a16dc2c046ec5fc5f1742213" integrity sha1-LTeFoVjBdMmhbcLARuxfxfF0IhM= -tunnel@^0.0.6: - version "0.0.6" - resolved "https://registry.yarnpkg.com/tunnel/-/tunnel-0.0.6.tgz#72f1314b34a5b192db012324df2cc587ca47f92c" - integrity sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg== - tweetnacl@^0.14.3, tweetnacl@~0.14.0: version "0.14.5" resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64" @@ -9288,7 +9206,7 @@ type-check@~0.3.2: dependencies: prelude-ls "~1.1.2" -type-fest@^0.8.0, type-fest@^0.8.1: +type-fest@^0.8.1: version "0.8.1" resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d" integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA== @@ -9483,13 +9401,6 @@ url-join@^1.1.0: resolved "https://registry.yarnpkg.com/url-join/-/url-join-1.1.0.tgz#741c6c2f4596c4830d6718460920d0c92202dc78" integrity sha1-dBxsL0WWxIMNZxhGCSDQySIC3Hg= -url-parse-lax@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/url-parse-lax/-/url-parse-lax-3.0.0.tgz#16b5cafc07dbe3676c1b1999177823d6503acb0c" - integrity sha1-FrXK/Afb42dsGxmZF3gj1lA6yww= - dependencies: - prepend-http "^2.0.0" - url@^0.11.0: version "0.11.0" resolved "https://registry.yarnpkg.com/url/-/url-0.11.0.tgz#3838e97cfc60521eb73c525a8e55bfdd9e2e28f1" @@ -9503,11 +9414,6 @@ use@^3.1.0: resolved "https://registry.yarnpkg.com/use/-/use-3.1.1.tgz#d50c8cac79a19fbc20f2911f56eb973f4e10070f" integrity sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ== -utf8-byte-length@^1.0.1: - version "1.0.4" - resolved "https://registry.yarnpkg.com/utf8-byte-length/-/utf8-byte-length-1.0.4.tgz#f45f150c4c66eee968186505ab93fcbb8ad6bf61" - integrity sha1-9F8VDExm7uloGGUFq5P8u4rWv2E= - util-deprecate@^1.0.1, util-deprecate@~1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" From 0621245e32359b97b87f782ab98bd101a66cda2e Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Fri, 24 Jan 2020 15:43:42 -0800 Subject: [PATCH 107/801] Make sure we dispose of every custom editors input individually Fixes #88229 There should a 1:1 mapping between VS Code's custom editors and custom editors (specifically the webviews) that extensions know about. This was previously not true as a single `CustomEditorInput` could end up being shared between multiple editor groups. With this change, we always create a copy of the custom editor input during split, and then add a listener for `onDidCloseEditor` so that we can make sure we get rid of the unique inputs (and notify extensions of this) --- src/vs/workbench/common/editor.ts | 6 ++-- .../customEditor/browser/customEditors.ts | 33 ++++++++++++++++--- .../webview/browser/webviewEditorInput.ts | 8 +++-- 3 files changed, 37 insertions(+), 10 deletions(-) diff --git a/src/vs/workbench/common/editor.ts b/src/vs/workbench/common/editor.ts index b7eabf42b67..055db825de2 100644 --- a/src/vs/workbench/common/editor.ts +++ b/src/vs/workbench/common/editor.ts @@ -552,8 +552,10 @@ export abstract class EditorInput extends Disposable implements IEditorInput { } dispose(): void { - this.disposed = true; - this._onDispose.fire(); + if (!this.disposed) { + this.disposed = true; + this._onDispose.fire(); + } super.dispose(); } diff --git a/src/vs/workbench/contrib/customEditor/browser/customEditors.ts b/src/vs/workbench/contrib/customEditor/browser/customEditors.ts index 2049d25b6ba..dacdefa02ea 100644 --- a/src/vs/workbench/contrib/customEditor/browser/customEditors.ts +++ b/src/vs/workbench/contrib/customEditor/browser/customEditors.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { coalesce } from 'vs/base/common/arrays'; +import { Emitter } from 'vs/base/common/event'; import { Lazy } from 'vs/base/common/lazy'; import { Disposable } from 'vs/base/common/lifecycle'; import { basename, isEqual } from 'vs/base/common/resources'; @@ -15,9 +16,11 @@ import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/c import { IEditorOptions, ITextEditorOptions } from 'vs/platform/editor/common/editor'; import { FileOperation, IFileService } from 'vs/platform/files/common/files'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { ILabelService } from 'vs/platform/label/common/label'; import { IQuickInputService, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput'; import * as colorRegistry from 'vs/platform/theme/common/colorRegistry'; import { registerThemingParticipant } from 'vs/platform/theme/common/themeService'; +import { EditorServiceImpl } from 'vs/workbench/browser/parts/editor/editor'; import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { EditorInput, EditorOptions, IEditor, IEditorInput } from 'vs/workbench/common/editor'; import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput'; @@ -30,8 +33,6 @@ import { IEditorGroup, IEditorGroupsService } from 'vs/workbench/services/editor import { IEditorService, IOpenEditorOverride } from 'vs/workbench/services/editor/common/editorService'; import { IWorkingCopyService } from 'vs/workbench/services/workingCopy/common/workingCopyService'; import { CustomFileEditorInput } from './customEditorInput'; -import { Emitter } from 'vs/base/common/event'; -import { ILabelService } from 'vs/platform/label/common/label'; export const defaultEditorId = 'default'; @@ -314,12 +315,26 @@ export const customEditorsAssociationsKey = 'workbench.experimental.editorAssoci export type CustomEditorsAssociations = readonly (CustomEditorSelector & { readonly viewType: string; })[]; -export class CustomEditorContribution implements IWorkbenchContribution { +export class CustomEditorContribution extends Disposable implements IWorkbenchContribution { constructor( - @IEditorService private readonly editorService: IEditorService, + @IEditorService private readonly editorService: EditorServiceImpl, @ICustomEditorService private readonly customEditorService: ICustomEditorService, ) { - this.editorService.overrideOpenEditor((editor, options, group) => this.onEditorOpening(editor, options, group)); + super(); + + this._register(this.editorService.overrideOpenEditor((editor, options, group) => { + return this.onEditorOpening(editor, options, group); + })); + + this._register(this.editorService.onDidCloseEditor(({ editor }) => { + if (!(editor instanceof CustomFileEditorInput)) { + return; + } + + if (!this.editorService.editors.some(other => other === editor)) { + editor.dispose(); + } + })); } private onEditorOpening( @@ -329,7 +344,15 @@ export class CustomEditorContribution implements IWorkbenchContribution { ): IOpenEditorOverride | undefined { if (editor instanceof CustomFileEditorInput) { if (editor.group === group.id) { + // No need to do anything return undefined; + } else { + // Create a copy of the input. + // Unlike normal editor inputs, we do not want to share custom editor inputs + // between multiple editors / groups. + return { + override: this.customEditorService.openWith(editor.getResource(), editor.viewType, options, group) + }; } } diff --git a/src/vs/workbench/contrib/webview/browser/webviewEditorInput.ts b/src/vs/workbench/contrib/webview/browser/webviewEditorInput.ts index ec0ee6fa63b..770b2038393 100644 --- a/src/vs/workbench/contrib/webview/browser/webviewEditorInput.ts +++ b/src/vs/workbench/contrib/webview/browser/webviewEditorInput.ts @@ -39,9 +39,11 @@ export class WebviewInput extends EditorInput { } dispose() { - if (!this._didSomeoneTakeMyWebview) { - this._webview?.rawValue?.dispose(); - this._onDisposeWebview.fire(); + if (!this.isDisposed()) { + if (!this._didSomeoneTakeMyWebview) { + this._webview?.rawValue?.dispose(); + this._onDisposeWebview.fire(); + } } super.dispose(); } From 89c8087c776ff4690dcfce6454ed621160abfac8 Mon Sep 17 00:00:00 2001 From: Jackson Kearl Date: Fri, 24 Jan 2020 16:12:20 -0800 Subject: [PATCH 108/801] Persist search editor viewstate --- .../contrib/search/browser/searchEditor.ts | 39 +++++++++++++++---- .../search/browser/searchEditorInput.ts | 11 +++++- 2 files changed, 41 insertions(+), 9 deletions(-) diff --git a/src/vs/workbench/contrib/search/browser/searchEditor.ts b/src/vs/workbench/contrib/search/browser/searchEditor.ts index f32a5af1a4e..6dbe05adc3e 100644 --- a/src/vs/workbench/contrib/search/browser/searchEditor.ts +++ b/src/vs/workbench/contrib/search/browser/searchEditor.ts @@ -7,7 +7,7 @@ import * as DOM from 'vs/base/browser/dom'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { CancellationToken } from 'vs/base/common/cancellation'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; -import { assertIsDefined } from 'vs/base/common/types'; +import { assertIsDefined, withNullAsUndefined } from 'vs/base/common/types'; import { URI } from 'vs/base/common/uri'; import 'vs/css!./media/searchEditor'; import { CodeEditorWidget, ICodeEditorWidgetOptions } from 'vs/editor/browser/widget/codeEditorWidget'; @@ -169,7 +169,7 @@ export class SearchEditor extends BaseEditor { } }); - + this._register(this.onDidBlur(() => this.saveViewState())); this._register(this.searchResultEditor.onKeyDown(e => e.keyCode === KeyCode.Escape && this.queryEditorWidget.searchInput.focus())); @@ -183,6 +183,10 @@ export class SearchEditor extends BaseEditor { }); } + focus() { + this.restoreViewState(); + } + focusNextInput() { if (this.queryEditorWidget.searchInputHasFocus()) { if (this.showingIncludesExcludes) { @@ -334,10 +338,6 @@ export class SearchEditor extends BaseEditor { this.reLayout(); } - focusInput() { - this.queryEditorWidget.focus(); - } - getSelected() { const selection = this.searchResultEditor.getSelection(); if (selection) { @@ -360,6 +360,8 @@ export class SearchEditor extends BaseEditor { } async setInput(newInput: SearchEditorInput, options: EditorOptions | undefined, token: CancellationToken): Promise { + this.saveViewState(); + await super.setInput(newInput, options, token); this.inSearchEditorContextKey.set(true); @@ -379,7 +381,7 @@ export class SearchEditor extends BaseEditor { this.inputPatternExcludes.setUseExcludesAndIgnoreFiles(query.useIgnores); this.toggleIncludesExcludes(query.showIncludesExcludes); - this.focusInput(); + this.restoreViewState(); this.pauseSearching = false; } @@ -404,7 +406,30 @@ export class SearchEditor extends BaseEditor { return this.searchResultEditor.getModel(); } + private saveViewState() { + const input = this.getInput(); + if (!input) { return; } + + if (this.searchResultEditor.hasWidgetFocus()) { + const viewState = this.searchResultEditor.saveViewState(); + input.viewState = { focused: 'editor', state: assertIsDefined(withNullAsUndefined(viewState)) }; + } else { + input.viewState = { focused: 'input' }; + } + } + + private restoreViewState() { + const input = this.getInput(); + if (input && input.viewState && input.viewState.focused === 'editor') { + this.searchResultEditor.restoreViewState(input.viewState.state); + this.searchResultEditor.focus(); + } else { + this.queryEditorWidget.focus(); + } + } + clearInput() { + this.saveViewState(); super.clearInput(); this.inSearchEditorContextKey.set(false); } diff --git a/src/vs/workbench/contrib/search/browser/searchEditorInput.ts b/src/vs/workbench/contrib/search/browser/searchEditorInput.ts index 34faa8107c5..bc446b0d025 100644 --- a/src/vs/workbench/contrib/search/browser/searchEditorInput.ts +++ b/src/vs/workbench/contrib/search/browser/searchEditorInput.ts @@ -27,6 +27,7 @@ import { IWorkingCopyService, WorkingCopyCapabilities, IWorkingCopy, IWorkingCop import { IBackupFileService } from 'vs/workbench/services/backup/common/backup'; import { assertIsDefined } from 'vs/base/common/types'; import { extractSearchQuery, serializeSearchConfiguration } from 'vs/workbench/contrib/search/browser/searchEditorSerialization'; +import type { ICodeEditorViewState } from 'vs/editor/common/editorCommon'; export type SearchConfiguration = { query: string, @@ -40,12 +41,17 @@ export type SearchConfiguration = { showIncludesExcludes: boolean, }; +type SearchEditorViewState = + | { focused: 'input' } + | { focused: 'editor', state: ICodeEditorViewState }; + export class SearchEditorInput extends EditorInput { static readonly ID: string = 'workbench.editorinputs.searchEditorInput'; private dirty: boolean = false; private readonly model: Promise; private resolvedModel?: { model: ITextModel, query: SearchConfiguration }; + viewState: SearchEditorViewState = { focused: 'input' }; constructor( public readonly resource: URI, @@ -249,13 +255,14 @@ export class SearchEditorInputFactory implements IEditorInputFactory { const config = input.getConfigSync(); - return JSON.stringify({ resource, dirty: input.isDirty(), config }); + return JSON.stringify({ resource, dirty: input.isDirty(), config, viewState: input.viewState }); } deserialize(instantiationService: IInstantiationService, serializedEditorInput: string): SearchEditorInput | undefined { - const { resource, dirty, config } = JSON.parse(serializedEditorInput); + const { resource, dirty, config, viewState } = JSON.parse(serializedEditorInput); if (config && (config.query !== undefined)) { const input = instantiationService.invokeFunction(getOrMakeSearchEditorInput, { text: serializeSearchConfiguration(config), uri: URI.parse(resource) }); + input.viewState = viewState; input.setDirty(dirty); return input; } From 689b2fa16c388d6c7470c2161356cae25c5b24f6 Mon Sep 17 00:00:00 2001 From: Jackson Kearl Date: Fri, 24 Jan 2020 16:42:26 -0800 Subject: [PATCH 109/801] Fix bug causing saved code searches to not be selected in explorer --- src/vs/workbench/contrib/search/browser/searchEditorInput.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/vs/workbench/contrib/search/browser/searchEditorInput.ts b/src/vs/workbench/contrib/search/browser/searchEditorInput.ts index bc446b0d025..018f83bc0a9 100644 --- a/src/vs/workbench/contrib/search/browser/searchEditorInput.ts +++ b/src/vs/workbench/contrib/search/browser/searchEditorInput.ts @@ -85,6 +85,10 @@ export class SearchEditorInput extends EditorInput { this.workingCopyService.registerWorkingCopy(workingCopyAdapter); } + getResource() { + return this.resource; + } + async save(group: GroupIdentifier, options?: ITextFileSaveOptions): Promise { if (this.resource.scheme === 'search-editor') { const path = await this.promptForPath(this.resource, await this.suggestFileName(), options?.availableFileSystems); From 46b1f610a84a25261c39285f4ff4f5378400577c Mon Sep 17 00:00:00 2001 From: Jackson Kearl Date: Fri, 24 Jan 2020 16:42:48 -0800 Subject: [PATCH 110/801] Fix null assertion error with saved searches --- src/vs/workbench/contrib/search/browser/searchEditor.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/search/browser/searchEditor.ts b/src/vs/workbench/contrib/search/browser/searchEditor.ts index 6dbe05adc3e..643922bfc3b 100644 --- a/src/vs/workbench/contrib/search/browser/searchEditor.ts +++ b/src/vs/workbench/contrib/search/browser/searchEditor.ts @@ -412,7 +412,9 @@ export class SearchEditor extends BaseEditor { if (this.searchResultEditor.hasWidgetFocus()) { const viewState = this.searchResultEditor.saveViewState(); - input.viewState = { focused: 'editor', state: assertIsDefined(withNullAsUndefined(viewState)) }; + if (viewState) { + input.viewState = { focused: 'editor', state: viewState }; + } } else { input.viewState = { focused: 'input' }; } From a3c5a15b03aef5447974890e60811b5015d3798a Mon Sep 17 00:00:00 2001 From: Jackson Kearl Date: Fri, 24 Jan 2020 16:55:43 -0800 Subject: [PATCH 111/801] remove unused --- src/vs/workbench/contrib/search/browser/searchEditor.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/search/browser/searchEditor.ts b/src/vs/workbench/contrib/search/browser/searchEditor.ts index 643922bfc3b..c30466e50f6 100644 --- a/src/vs/workbench/contrib/search/browser/searchEditor.ts +++ b/src/vs/workbench/contrib/search/browser/searchEditor.ts @@ -7,7 +7,7 @@ import * as DOM from 'vs/base/browser/dom'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { CancellationToken } from 'vs/base/common/cancellation'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; -import { assertIsDefined, withNullAsUndefined } from 'vs/base/common/types'; +import { assertIsDefined } from 'vs/base/common/types'; import { URI } from 'vs/base/common/uri'; import 'vs/css!./media/searchEditor'; import { CodeEditorWidget, ICodeEditorWidgetOptions } from 'vs/editor/browser/widget/codeEditorWidget'; From ad29830c84c35e3b774ae802716d58c30f52fb91 Mon Sep 17 00:00:00 2001 From: Jackson Kearl Date: Fri, 24 Jan 2020 17:23:48 -0800 Subject: [PATCH 112/801] Add command to rerun search editor --- .../search/browser/search.contribution.ts | 8 +++++++- .../contrib/search/browser/searchActions.ts | 20 +++++++++++++++++++ .../contrib/search/browser/searchEditor.ts | 2 +- .../contrib/search/common/constants.ts | 1 + 4 files changed, 29 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/search/browser/search.contribution.ts b/src/vs/workbench/contrib/search/browser/search.contribution.ts index bbea9b6271b..515a6dc0fe6 100644 --- a/src/vs/workbench/contrib/search/browser/search.contribution.ts +++ b/src/vs/workbench/contrib/search/browser/search.contribution.ts @@ -38,7 +38,7 @@ import { ExplorerFolderContext, ExplorerRootContext, FilesExplorerFocusCondition import { OpenAnythingHandler } from 'vs/workbench/contrib/search/browser/openAnythingHandler'; import { OpenSymbolHandler } from 'vs/workbench/contrib/search/browser/openSymbolHandler'; import { registerContributions as replaceContributions } from 'vs/workbench/contrib/search/browser/replaceContributions'; -import { clearHistoryCommand, ClearSearchResultsAction, CloseReplaceAction, CollapseDeepestExpandedLevelAction, copyAllCommand, copyMatchCommand, copyPathCommand, FocusNextInputAction, FocusNextSearchResultAction, FocusPreviousInputAction, FocusPreviousSearchResultAction, focusSearchListCommand, getSearchView, openSearchView, OpenSearchViewletAction, RefreshAction, RemoveAction, ReplaceAction, ReplaceAllAction, ReplaceAllInFolderAction, ReplaceInFilesAction, toggleCaseSensitiveCommand, toggleRegexCommand, toggleWholeWordCommand, FindInFilesCommand, ToggleSearchOnTypeAction, OpenResultsInEditorAction, ExpandAllAction, OpenSearchEditorAction, toggleSearchEditorCaseSensitiveCommand, toggleSearchEditorWholeWordCommand, toggleSearchEditorRegexCommand, toggleSearchEditorContextLinesCommand } from 'vs/workbench/contrib/search/browser/searchActions'; +import { clearHistoryCommand, ClearSearchResultsAction, CloseReplaceAction, CollapseDeepestExpandedLevelAction, copyAllCommand, copyMatchCommand, copyPathCommand, FocusNextInputAction, FocusNextSearchResultAction, FocusPreviousInputAction, FocusPreviousSearchResultAction, focusSearchListCommand, getSearchView, openSearchView, OpenSearchViewletAction, RefreshAction, RemoveAction, ReplaceAction, ReplaceAllAction, ReplaceAllInFolderAction, ReplaceInFilesAction, toggleCaseSensitiveCommand, toggleRegexCommand, toggleWholeWordCommand, FindInFilesCommand, ToggleSearchOnTypeAction, OpenResultsInEditorAction, ExpandAllAction, OpenSearchEditorAction, toggleSearchEditorCaseSensitiveCommand, toggleSearchEditorWholeWordCommand, toggleSearchEditorRegexCommand, toggleSearchEditorContextLinesCommand, ReRunSearchEditorSearchAction } from 'vs/workbench/contrib/search/browser/searchActions'; import { SearchView } from 'vs/workbench/contrib/search/browser/searchView'; import { registerContributions as searchWidgetContributions } from 'vs/workbench/contrib/search/browser/searchWidget'; import * as Constants from 'vs/workbench/contrib/search/common/constants'; @@ -641,6 +641,12 @@ registry.registerWorkbenchAction(SyncActionDescriptor.create(ToggleSearchOnTypeA registry.registerWorkbenchAction(SyncActionDescriptor.create(RefreshAction, RefreshAction.ID, RefreshAction.LABEL), 'Search: Refresh', category); registry.registerWorkbenchAction(SyncActionDescriptor.create(ClearSearchResultsAction, ClearSearchResultsAction.ID, ClearSearchResultsAction.LABEL), 'Search: Clear Search Results', category); + +registry.registerWorkbenchAction( + SyncActionDescriptor.create(ReRunSearchEditorSearchAction, ReRunSearchEditorSearchAction.ID, ReRunSearchEditorSearchAction.LABEL), + 'Search Editor: Rerun search', category, ContextKeyExpr.and(Constants.InSearchEditor) +); + registry.registerWorkbenchAction( SyncActionDescriptor.create(OpenResultsInEditorAction, OpenResultsInEditorAction.ID, OpenResultsInEditorAction.LABEL, { mac: { primary: KeyMod.CtrlCmd | KeyCode.Enter } }, diff --git a/src/vs/workbench/contrib/search/browser/searchActions.ts b/src/vs/workbench/contrib/search/browser/searchActions.ts index 06abc43baf2..85cde05c712 100644 --- a/src/vs/workbench/contrib/search/browser/searchActions.ts +++ b/src/vs/workbench/contrib/search/browser/searchActions.ts @@ -574,6 +574,26 @@ export class OpenResultsInEditorAction extends Action { } +export class ReRunSearchEditorSearchAction extends Action { + + static readonly ID = 'searchEditor.rerunSerach'; + static readonly LABEL = nls.localize('search.rerunSearch', "Rerun Search in Editor"); + + constructor(id: string, label: string, + @IEditorService private readonly editorService: IEditorService) { + super(id, label); + } + + async run() { + const input = this.editorService.activeEditor; + if (input instanceof SearchEditorInput) { + await (this.editorService.activeControl as SearchEditor).runSearch(); + } + } +} + + + export class FocusNextSearchResultAction extends Action { static readonly ID = 'search.action.focusNextSearchResult'; static readonly LABEL = nls.localize('FocusNextSearchResult.label', "Focus Next Search Result"); diff --git a/src/vs/workbench/contrib/search/browser/searchEditor.ts b/src/vs/workbench/contrib/search/browser/searchEditor.ts index c30466e50f6..360bda0be25 100644 --- a/src/vs/workbench/contrib/search/browser/searchEditor.ts +++ b/src/vs/workbench/contrib/search/browser/searchEditor.ts @@ -234,7 +234,7 @@ export class SearchEditor extends BaseEditor { this.queryEditorWidget.toggleContextLines(); } - private async runSearch(instant = false) { + async runSearch(instant = false) { if (!this.pauseSearching) { this.runSearchDelayer.trigger(() => this.doRunSearch(), instant ? 0 : undefined); } diff --git a/src/vs/workbench/contrib/search/common/constants.ts b/src/vs/workbench/contrib/search/common/constants.ts index 64ac782a467..0e1cceb6b97 100644 --- a/src/vs/workbench/contrib/search/common/constants.ts +++ b/src/vs/workbench/contrib/search/common/constants.ts @@ -30,6 +30,7 @@ export const ToggleSearchEditorCaseSensitiveCommandId = 'toggleSearchEditorCaseS export const ToggleSearchEditorWholeWordCommandId = 'toggleSearchEditorWholeWord'; export const ToggleSearchEditorRegexCommandId = 'toggleSearchEditorRegex'; export const ToggleSearchEditorContextLinesCommandId = 'toggleSearchEditorContextLines'; +export const RerunSearchEditorSearchCommandId = 'rerunSearchEditorSearch'; export const AddCursorsAtSearchResults = 'addCursorsAtSearchResults'; export const RevealInSideBarForSearchResults = 'search.action.revealInSideBar'; From 3a1e245648c08dc4ec59b95954d2659a36f4bca6 Mon Sep 17 00:00:00 2001 From: Jackson Kearl Date: Fri, 24 Jan 2020 17:24:03 -0800 Subject: [PATCH 113/801] Remove outdated extension contributed commands --- extensions/search-result/package.json | 40 ----------------------- extensions/search-result/package.nls.json | 4 +-- extensions/search-result/src/extension.ts | 2 -- 3 files changed, 1 insertion(+), 45 deletions(-) diff --git a/extensions/search-result/package.json b/extensions/search-result/package.json index 40d59e2bcd7..6a55968bda1 100644 --- a/extensions/search-result/package.json +++ b/extensions/search-result/package.json @@ -25,46 +25,6 @@ "editor.lineNumbers": "off" } }, - "commands": [ - { - "command": "searchResult.rerunSearch", - "title": "%searchResult.rerunSearch.title%", - "category": "Search Result", - "icon": { - "light": "./src/media/refresh-light.svg", - "dark": "./src/media/refresh-dark.svg" - } - }, - { - "command": "searchResult.rerunSearchWithContext", - "title": "%searchResult.rerunSearchWithContext.title%", - "category": "Search Result", - "icon": { - "light": "./src/media/refresh-light.svg", - "dark": "./src/media/refresh-dark.svg" - } - } - ], - "menus": { - "commandPalette": [ - { - "command": "searchResult.rerunSearch", - "when": "false" - }, - { - "command": "searchResult.rerunSearchWithContext", - "when": "false" - } - ], - "editor/title": [ - { - "command": "searchResult.rerunSearch", - "when": "editorLangId == search-result", - "alt": "searchResult.rerunSearchWithContext", - "group": "navigation" - } - ] - }, "languages": [ { "id": "search-result", diff --git a/extensions/search-result/package.nls.json b/extensions/search-result/package.nls.json index ce90d23c09c..324fd97bcd2 100644 --- a/extensions/search-result/package.nls.json +++ b/extensions/search-result/package.nls.json @@ -1,6 +1,4 @@ { "displayName": "Search Result", - "description": "Provides syntax highlighting and language features for tabbed search results.", - "searchResult.rerunSearch.title": "Search Again", - "searchResult.rerunSearchWithContext.title": "Search Again (With Context)" + "description": "Provides syntax highlighting and language features for tabbed search results." } diff --git a/extensions/search-result/src/extension.ts b/extensions/search-result/src/extension.ts index 0bd5e12188e..f7453a45d19 100644 --- a/extensions/search-result/src/extension.ts +++ b/extensions/search-result/src/extension.ts @@ -34,8 +34,6 @@ export function activate(context: vscode.ExtensionContext) { } context.subscriptions.push( - vscode.commands.registerCommand('searchResult.rerunSearch', () => vscode.commands.executeCommand('search.action.rerunEditorSearch')), - vscode.commands.registerCommand('searchResult.rerunSearchWithContext', () => vscode.commands.executeCommand('search.action.rerunEditorSearchWithContext')), vscode.languages.registerDocumentSymbolProvider(SEARCH_RESULT_SELECTOR, { provideDocumentSymbols(document: vscode.TextDocument, token: vscode.CancellationToken): vscode.DocumentSymbol[] { From df8cdd94427efd0d1d9478077c6a537950445cb6 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Sat, 25 Jan 2020 17:57:05 -0700 Subject: [PATCH 114/801] Fix #89106 --- src/vs/workbench/contrib/search/browser/searchView.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/search/browser/searchView.ts b/src/vs/workbench/contrib/search/browser/searchView.ts index a0ba9324449..baa12121c5d 100644 --- a/src/vs/workbench/contrib/search/browser/searchView.ts +++ b/src/vs/workbench/contrib/search/browser/searchView.ts @@ -802,7 +802,7 @@ export class SearchView extends ViewPane { } // Expand until first child is a Match - while (!(next instanceof Match)) { + while (next && !(next instanceof Match)) { if (this.tree.isCollapsed(next)) { this.tree.expand(next); } From 5fd42b7617545ad7cb22a7b06127755684b90741 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Sun, 26 Jan 2020 09:23:27 +0100 Subject: [PATCH 115/801] editor - add group to revert() --- .../browser/parts/editor/editorActions.ts | 4 +-- .../browser/parts/editor/editorGroupView.ts | 6 ++-- .../browser/parts/titlebar/titlebarPart.ts | 4 +-- src/vs/workbench/common/editor.ts | 30 ++++++++++++++----- .../common/editor/untitledTextEditorInput.ts | 2 +- .../customEditor/browser/customEditorInput.ts | 2 +- .../files/common/editors/fileEditorInput.ts | 6 +--- .../test/browser/fileEditorInput.test.ts | 2 +- .../search/browser/searchEditorInput.ts | 6 ++-- .../services/editor/browser/editorService.ts | 2 +- .../editor/test/browser/editorService.test.ts | 2 +- .../textfile/browser/textFileService.ts | 2 +- .../common/editor/untitledTextEditor.test.ts | 8 ++--- 13 files changed, 43 insertions(+), 33 deletions(-) diff --git a/src/vs/workbench/browser/parts/editor/editorActions.ts b/src/vs/workbench/browser/parts/editor/editorActions.ts index 621adcab2cd..89bf027eb3a 100644 --- a/src/vs/workbench/browser/parts/editor/editorActions.ts +++ b/src/vs/workbench/browser/parts/editor/editorActions.ts @@ -543,13 +543,13 @@ export class RevertAndCloseEditorAction extends Action { // first try a normal revert where the contents of the editor are restored try { - await editor.revert(); + await editor.revert(group.id); } catch (error) { // if that fails, since we are about to close the editor, we accept that // the editor cannot be reverted and instead do a soft revert that just // enables us to close the editor. With this, a user can always close a // dirty editor even when reverting fails. - await editor.revert({ soft: true }); + await editor.revert(group.id, { soft: true }); } group.closeEditor(editor); diff --git a/src/vs/workbench/browser/parts/editor/editorGroupView.ts b/src/vs/workbench/browser/parts/editor/editorGroupView.ts index 6de26a8919b..4e1ef44c838 100644 --- a/src/vs/workbench/browser/parts/editor/editorGroupView.ts +++ b/src/vs/workbench/browser/parts/editor/editorGroupView.ts @@ -1330,7 +1330,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { // Otherwise, handle accordingly switch (res) { case ConfirmResult.SAVE: - const result = await editor.save(this._group.id, { reason: SaveReason.EXPLICIT, context: SaveContext.EDITOR_CLOSE }); + const result = await editor.save(this.id, { reason: SaveReason.EXPLICIT, context: SaveContext.EDITOR_CLOSE }); return !result; case ConfirmResult.DONT_SAVE: @@ -1338,7 +1338,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { try { // first try a normal revert where the contents of the editor are restored - const result = await editor.revert(); + const result = await editor.revert(this.id); return !result; } catch (error) { @@ -1346,7 +1346,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { // the editor cannot be reverted and instead do a soft revert that just // enables us to close the editor. With this, a user can always close a // dirty editor even when reverting fails. - const result = await editor.revert({ soft: true }); + const result = await editor.revert(this.id, { soft: true }); return !result; } diff --git a/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts b/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts index ab700375161..f23aacde162 100644 --- a/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts +++ b/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts @@ -16,7 +16,7 @@ import { IConfigurationService, IConfigurationChangeEvent } from 'vs/platform/co import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { DisposableStore, dispose } from 'vs/base/common/lifecycle'; import * as nls from 'vs/nls'; -import { EditorInput, toResource, Verbosity, SideBySideEditor } from 'vs/workbench/common/editor'; +import { toResource, Verbosity, SideBySideEditor } from 'vs/workbench/common/editor'; 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'; @@ -195,7 +195,7 @@ export class TitlebarPart extends Part implements ITitleService { // Apply listener for dirty and label changes const activeEditor = this.editorService.activeEditor; - if (activeEditor instanceof EditorInput) { + if (activeEditor) { this.activeEditorListeners.add(activeEditor.onDidChangeDirty(() => this.titleUpdater.schedule())); this.activeEditorListeners.add(activeEditor.onDidChangeLabel(() => this.titleUpdater.schedule())); } diff --git a/src/vs/workbench/common/editor.ts b/src/vs/workbench/common/editor.ts index 055db825de2..a14a5ea456c 100644 --- a/src/vs/workbench/common/editor.ts +++ b/src/vs/workbench/common/editor.ts @@ -362,6 +362,16 @@ export interface IEditorInput extends IDisposable { */ readonly onDispose: Event; + /** + * Triggered when this input changes its dirty state. + */ + readonly onDidChangeDirty: Event; + + /** + * Triggered when this input changes its label + */ + readonly onDidChangeLabel: Event; + /** * Returns the associated resource of this input. */ @@ -416,9 +426,9 @@ export interface IEditorInput extends IDisposable { isSaving(): boolean; /** - * Saves the editor. The provided groupId helps - * implementors to e.g. preserve view state of the editor - * and re-open it in the correct group after saving. + * Saves the editor. The provided groupId helps implementors + * to e.g. preserve view state of the editor and re-open it + * in the correct group after saving. */ save(groupId: GroupIdentifier, options?: ISaveOptions): Promise; @@ -430,9 +440,9 @@ export interface IEditorInput extends IDisposable { saveAs(groupId: GroupIdentifier, options?: ISaveOptions): Promise; /** - * Reverts this input. + * Reverts this input from the provided group. */ - revert(options?: IRevertOptions): Promise; + revert(group: GroupIdentifier, options?: IRevertOptions): Promise; /** * Returns if the other object matches this input. @@ -532,7 +542,7 @@ export abstract class EditorInput extends Disposable implements IEditorInput { return true; } - async revert(options?: IRevertOptions): Promise { + async revert(group: GroupIdentifier, options?: IRevertOptions): Promise { return true; } @@ -614,6 +624,10 @@ export abstract class TextEditorInput extends EditorInput { return true; } + + revert(group: GroupIdentifier, options?: IRevertOptions): Promise { + return this.textFileService.revert(this.resource, options); + } } export const enum EncodingMode { @@ -743,8 +757,8 @@ export class SideBySideEditorInput extends EditorInput { return this.master.saveAs(groupId, options); } - revert(options?: IRevertOptions): Promise { - return this.master.revert(options); + revert(group: GroupIdentifier, options?: IRevertOptions): Promise { + return this.master.revert(group, options); } getTelemetryDescriptor(): { [key: string]: unknown } { diff --git a/src/vs/workbench/common/editor/untitledTextEditorInput.ts b/src/vs/workbench/common/editor/untitledTextEditorInput.ts index 1ef371be66f..29761f55cec 100644 --- a/src/vs/workbench/common/editor/untitledTextEditorInput.ts +++ b/src/vs/workbench/common/editor/untitledTextEditorInput.ts @@ -193,7 +193,7 @@ export class UntitledTextEditorInput extends TextEditorInput implements IEncodin return this.doSaveAs(group, options, () => this.textFileService.saveAs(this.resource, undefined, options), true /* replace editor across all groups */); } - async revert(options?: IRevertOptions): Promise { + async revert(group: GroupIdentifier, options?: IRevertOptions): Promise { if (this.cachedModel) { this.cachedModel.revert(); } diff --git a/src/vs/workbench/contrib/customEditor/browser/customEditorInput.ts b/src/vs/workbench/contrib/customEditor/browser/customEditorInput.ts index b8cb60b0b62..552ed8f2507 100644 --- a/src/vs/workbench/contrib/customEditor/browser/customEditorInput.ts +++ b/src/vs/workbench/contrib/customEditor/browser/customEditorInput.ts @@ -149,7 +149,7 @@ export class CustomFileEditorInput extends LazilyResolvedWebviewEditorInput { return true; } - public revert(options?: IRevertOptions): Promise { + public revert(group: GroupIdentifier, options?: IRevertOptions): Promise { return this._model ? this._model.revert(options) : Promise.resolve(false); } diff --git a/src/vs/workbench/contrib/files/common/editors/fileEditorInput.ts b/src/vs/workbench/contrib/files/common/editors/fileEditorInput.ts index e4656bacabc..372492b034c 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, IFileEditorInput, ITextEditorModel, Verbosity, TextEditorInput, IRevertOptions } from 'vs/workbench/common/editor'; +import { EncodingMode, IFileEditorInput, ITextEditorModel, Verbosity, TextEditorInput } 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, FileSystemProviderCapabilities } from 'vs/platform/files/common/files'; @@ -262,10 +262,6 @@ export class FileEditorInput extends TextEditorInput implements IFileEditorInput return false; } - revert(options?: IRevertOptions): Promise { - return this.textFileService.revert(this.resource, options); - } - getPreferredEditorId(candidates: string[]): string { return this.forceOpenAs === ForceOpenAs.Binary ? BINARY_FILE_EDITOR_ID : TEXT_FILE_EDITOR_ID; } diff --git a/src/vs/workbench/contrib/files/test/browser/fileEditorInput.test.ts b/src/vs/workbench/contrib/files/test/browser/fileEditorInput.test.ts index d1a86fceb9a..4a059d873ec 100644 --- a/src/vs/workbench/contrib/files/test/browser/fileEditorInput.test.ts +++ b/src/vs/workbench/contrib/files/test/browser/fileEditorInput.test.ts @@ -152,7 +152,7 @@ suite('Files - FileEditorInput', () => { resolved.textEditorModel!.setValue('changed'); assert.ok(input.isDirty()); - assert.ok(await input.revert()); + assert.ok(await input.revert(0)); assert.ok(!input.isDirty()); input.dispose(); diff --git a/src/vs/workbench/contrib/search/browser/searchEditorInput.ts b/src/vs/workbench/contrib/search/browser/searchEditorInput.ts index 018f83bc0a9..93e8033c27b 100644 --- a/src/vs/workbench/contrib/search/browser/searchEditorInput.ts +++ b/src/vs/workbench/contrib/search/browser/searchEditorInput.ts @@ -79,7 +79,7 @@ export class SearchEditorInput extends EditorInput { isDirty: () => this.isDirty(), backup: () => this.backup(), save: (options) => this.save(0, options), - revert: () => this.revert(), + revert: () => this.revert(0), }; this.workingCopyService.registerWorkingCopy(workingCopyAdapter); @@ -181,9 +181,9 @@ export class SearchEditorInput extends EditorInput { return false; } - async revert(options?: IRevertOptions) { + async revert(group: GroupIdentifier, options?: IRevertOptions) { // TODO: this should actually revert the contents. But it needs to set dirty false. - super.revert(options); + super.revert(group, options); this.setDirty(false); return true; } diff --git a/src/vs/workbench/services/editor/browser/editorService.ts b/src/vs/workbench/services/editor/browser/editorService.ts index e946a5dfc90..8793d7cbf19 100644 --- a/src/vs/workbench/services/editor/browser/editorService.ts +++ b/src/vs/workbench/services/editor/browser/editorService.ts @@ -737,7 +737,7 @@ export class EditorService extends Disposable implements EditorServiceImpl { // Use revert as a hint to pin the editor this.editorGroupService.getGroup(groupId)?.pinEditor(editor); - return editor.revert(options); + return editor.revert(groupId, options); })); return result.every(success => !!success); diff --git a/src/vs/workbench/services/editor/test/browser/editorService.test.ts b/src/vs/workbench/services/editor/test/browser/editorService.test.ts index 041ae8729b8..3e2fe4b515a 100644 --- a/src/vs/workbench/services/editor/test/browser/editorService.test.ts +++ b/src/vs/workbench/services/editor/test/browser/editorService.test.ts @@ -80,7 +80,7 @@ class TestEditorInput extends EditorInput implements IFileEditorInput { this.gotSavedAs = true; return true; } - async revert(options?: IRevertOptions): Promise { + async revert(group: GroupIdentifier, options?: IRevertOptions): Promise { this.gotReverted = true; this.gotSaved = false; this.gotSavedAs = false; diff --git a/src/vs/workbench/services/textfile/browser/textFileService.ts b/src/vs/workbench/services/textfile/browser/textFileService.ts index 3090dcfe774..5b931d99340 100644 --- a/src/vs/workbench/services/textfile/browser/textFileService.ts +++ b/src/vs/workbench/services/textfile/browser/textFileService.ts @@ -579,7 +579,7 @@ export abstract class AbstractTextFileService extends Disposable implements ITex if (resource.scheme === Schemas.untitled) { const model = this.untitled.get(resource); if (model) { - return model.revert(options); + return model.revert(0, options); } return false; diff --git a/src/vs/workbench/test/common/editor/untitledTextEditor.test.ts b/src/vs/workbench/test/common/editor/untitledTextEditor.test.ts index c5b76cba3b2..3d0d97cfd48 100644 --- a/src/vs/workbench/test/common/editor/untitledTextEditor.test.ts +++ b/src/vs/workbench/test/common/editor/untitledTextEditor.test.ts @@ -62,7 +62,7 @@ suite('Workbench untitled text editors', () => { assert.equal(service.get(input2.getResource()), input2); // revert() - input1.revert(); + input1.revert(0); assert.ok(input1.isDisposed()); assert.ok(!service.get(input1.getResource())); @@ -82,8 +82,8 @@ suite('Workbench untitled text editors', () => { assert.ok(workingCopyService.isDirty(input2.getResource())); assert.equal(workingCopyService.dirtyCount, 1); - input1.revert(); - input2.revert(); + input1.revert(0); + input2.revert(0); assert.ok(!service.get(input1.getResource())); assert.ok(!service.get(input2.getResource())); assert.ok(!input2.isDirty()); @@ -92,7 +92,7 @@ suite('Workbench untitled text editors', () => { assert.ok(!workingCopyService.isDirty(input2.getResource())); assert.equal(workingCopyService.dirtyCount, 0); - assert.ok(input1.revert()); + assert.ok(input1.revert(0)); assert.ok(input1.isDisposed()); assert.ok(!service.exists(input1.getResource())); From d8251943cc3787092c49fd9d20b1c00209f0afec Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Sun, 26 Jan 2020 09:26:13 +0100 Subject: [PATCH 116/801] untitled - share save/saveas logic in one place --- .../api/browser/mainThreadDocuments.ts | 2 +- src/vs/workbench/common/editor.ts | 51 +++++++++++-------- .../common/editor/untitledTextEditorInput.ts | 31 ++--------- .../common/editor/untitledTextEditorModel.ts | 6 ++- .../textfile/browser/textFileService.ts | 14 ++--- .../services/textfile/common/textfiles.ts | 6 +-- .../textfile/test/textFileService.test.ts | 4 +- 7 files changed, 49 insertions(+), 65 deletions(-) diff --git a/src/vs/workbench/api/browser/mainThreadDocuments.ts b/src/vs/workbench/api/browser/mainThreadDocuments.ts index 96eb4dee60e..68454e67aca 100644 --- a/src/vs/workbench/api/browser/mainThreadDocuments.ts +++ b/src/vs/workbench/api/browser/mainThreadDocuments.ts @@ -160,7 +160,7 @@ export class MainThreadDocuments implements MainThreadDocumentsShape { // --- from extension host process $trySaveDocument(uri: UriComponents): Promise { - return this._textFileService.save(URI.revive(uri)); + return this._textFileService.save(URI.revive(uri)).then(target => !!target); } $tryOpenDocument(_uri: UriComponents): Promise { diff --git a/src/vs/workbench/common/editor.ts b/src/vs/workbench/common/editor.ts index a14a5ea456c..17810628940 100644 --- a/src/vs/workbench/common/editor.ts +++ b/src/vs/workbench/common/editor.ts @@ -26,6 +26,7 @@ import { IEditorService } from 'vs/workbench/services/editor/common/editorServic import { isEqual } from 'vs/base/common/resources'; import { IPanel } from 'vs/workbench/common/panel'; import { IRange } from 'vs/editor/common/core/range'; +import { Schemas } from 'vs/base/common/network'; export const DirtyWorkingCopiesContext = new RawContextKey('dirtyWorkingCopies', false); export const ActiveEditorContext = new RawContextKey('activeEditor', null); @@ -430,14 +431,14 @@ export interface IEditorInput extends IDisposable { * to e.g. preserve view state of the editor and re-open it * in the correct group after saving. */ - save(groupId: GroupIdentifier, options?: ISaveOptions): Promise; + save(group: GroupIdentifier, options?: ISaveOptions): Promise; /** * Saves the editor to a different location. The provided groupId * helps implementors to e.g. preserve view state of the editor * and re-open it in the correct group after saving. */ - saveAs(groupId: GroupIdentifier, options?: ISaveOptions): Promise; + saveAs(group: GroupIdentifier, options?: ISaveOptions): Promise; /** * Reverts this input from the provided group. @@ -534,11 +535,11 @@ export abstract class EditorInput extends Disposable implements IEditorInput { return false; } - async save(groupId: GroupIdentifier, options?: ISaveOptions): Promise { + async save(group: GroupIdentifier, options?: ISaveOptions): Promise { return true; } - async saveAs(groupId: GroupIdentifier, options?: ISaveOptions): Promise { + async saveAs(group: GroupIdentifier, options?: ISaveOptions): Promise { return true; } @@ -586,26 +587,36 @@ export abstract class TextEditorInput extends EditorInput { return this.resource; } - async save(groupId: GroupIdentifier, options?: ITextFileSaveOptions): Promise { - return this.textFileService.save(this.resource, options); + async save(group: GroupIdentifier, options?: ITextFileSaveOptions): Promise { + return this.doSave(group, options, false); } saveAs(group: GroupIdentifier, options?: ITextFileSaveOptions): Promise { - return this.doSaveAs(group, options, () => this.textFileService.saveAs(this.resource, undefined, options)); + return this.doSave(group, options, true); } - protected async doSaveAs(group: GroupIdentifier, options: ISaveOptions | undefined, saveRunnable: () => Promise, replaceAllEditors?: boolean): Promise { + private async doSave(group: GroupIdentifier, options: ISaveOptions | undefined, saveAs: boolean): Promise { + const isUntitled = this.resource.scheme === Schemas.untitled; - // Preserve view state by opening the editor first. In addition - // this allows the user to review the contents of the editor. + // Preserve view state by opening the editor first if the editor + // is untitled or we "Save As" and we are not closing editors. + // In addition this allows the user to review the contents of the editor. let viewState: IEditorViewState | undefined = undefined; - const editor = await this.editorService.openEditor(this, undefined, group); - if (isTextEditor(editor)) { - viewState = editor.getViewState(); + if (options?.context !== SaveContext.EDITOR_CLOSE && (saveAs || isUntitled)) { + const editor = await this.editorService.openEditor(this, undefined, group); + if (isTextEditor(editor)) { + viewState = editor.getViewState(); + } + } + + // Save / Save As + let target: URI | undefined; + if (saveAs) { + target = await this.textFileService.saveAs(this.resource, undefined, options); + } else { + target = await this.textFileService.save(this.resource, options); } - // Save as - const target = await saveRunnable(); if (!target) { return false; // save cancelled } @@ -616,7 +627,7 @@ export abstract class TextEditorInput extends EditorInput { // (because in that case we do not want to open a different editor anyway) if (options?.context !== SaveContext.EDITOR_CLOSE && !isEqual(target, this.resource)) { const replacement = this.editorService.createInput({ resource: target }); - const targetGroups = replaceAllEditors ? this.editorGroupService.groups.map(group => group.id) : [group]; + const targetGroups = isUntitled ? this.editorGroupService.groups.map(group => group.id) /* untitled replaces across all groups */ : [group]; for (const group of targetGroups) { await this.editorService.replaceEditors([{ editor: this, replacement, options: { pinned: true, viewState } }], group); } @@ -749,12 +760,12 @@ export class SideBySideEditorInput extends EditorInput { return this.master.isSaving(); } - save(groupId: GroupIdentifier, options?: ISaveOptions): Promise { - return this.master.save(groupId, options); + save(group: GroupIdentifier, options?: ISaveOptions): Promise { + return this.master.save(group, options); } - saveAs(groupId: GroupIdentifier, options?: ISaveOptions): Promise { - return this.master.saveAs(groupId, options); + saveAs(group: GroupIdentifier, options?: ISaveOptions): Promise { + return this.master.saveAs(group, options); } revert(group: GroupIdentifier, options?: IRevertOptions): Promise { diff --git a/src/vs/workbench/common/editor/untitledTextEditorInput.ts b/src/vs/workbench/common/editor/untitledTextEditorInput.ts index 29761f55cec..838204454e5 100644 --- a/src/vs/workbench/common/editor/untitledTextEditorInput.ts +++ b/src/vs/workbench/common/editor/untitledTextEditorInput.ts @@ -7,17 +7,16 @@ import { URI } from 'vs/base/common/uri'; 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, toLocalResource } from 'vs/base/common/resources'; +import { basenameOrAuthority, dirname } from 'vs/base/common/resources'; import { IEncodingSupport, EncodingMode, Verbosity, IModeSupport, TextEditorInput, GroupIdentifier, IRevertOptions } from 'vs/workbench/common/editor'; import { UntitledTextEditorModel } from 'vs/workbench/common/editor/untitledTextEditorModel'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { Emitter } from 'vs/base/common/event'; -import { ITextFileService, ITextFileSaveOptions } from 'vs/workbench/services/textfile/common/textfiles'; +import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import { ILabelService } from 'vs/platform/label/common/label'; import { IResolvedTextEditorModel } from 'vs/editor/common/services/resolverService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; -import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; /** * An editor input to be used for untitled text buffers. @@ -47,8 +46,7 @@ export class UntitledTextEditorInput extends TextEditorInput implements IEncodin @ITextFileService textFileService: ITextFileService, @ILabelService private readonly labelService: ILabelService, @IEditorService editorService: IEditorService, - @IEditorGroupsService editorGroupService: IEditorGroupsService, - @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService + @IEditorGroupsService editorGroupService: IEditorGroupsService ) { super(resource, editorService, editorGroupService, textFileService); @@ -170,29 +168,6 @@ export class UntitledTextEditorInput extends TextEditorInput implements IEncodin return this.hasAssociatedFilePath; } - save(group: GroupIdentifier, options?: ITextFileSaveOptions): Promise { - return this.doSaveAs(group, options, async () => { - - // With associated file path, save to the path that is - // associated. Make sure to convert the result using - // remote authority properly. - if (this.hasAssociatedFilePath) { - if (await this.textFileService.save(this.resource, options)) { - return toLocalResource(this.resource, this.environmentService.configuration.remoteAuthority); - } - - return; - } - - // Without associated file path, do a normal "Save As" - return this.textFileService.saveAs(this.resource, undefined, options); - }, true /* replace editor across all groups */); - } - - saveAs(group: GroupIdentifier, options?: ITextFileSaveOptions): Promise { - return this.doSaveAs(group, options, () => this.textFileService.saveAs(this.resource, undefined, options), true /* replace editor across all groups */); - } - async revert(group: GroupIdentifier, options?: IRevertOptions): Promise { if (this.cachedModel) { this.cachedModel.revert(); diff --git a/src/vs/workbench/common/editor/untitledTextEditorModel.ts b/src/vs/workbench/common/editor/untitledTextEditorModel.ts index b7773dd818c..e4f905cdaff 100644 --- a/src/vs/workbench/common/editor/untitledTextEditorModel.ts +++ b/src/vs/workbench/common/editor/untitledTextEditorModel.ts @@ -136,8 +136,10 @@ export class UntitledTextEditorModel extends BaseTextEditorModel implements IUnt this._onDidChangeDirty.fire(); } - save(options?: ISaveOptions): Promise { - return this.textFileService.save(this.resource, options); + async save(options?: ISaveOptions): Promise { + const target = await this.textFileService.save(this.resource, options); + + return !!target; } async revert(): Promise { diff --git a/src/vs/workbench/services/textfile/browser/textFileService.ts b/src/vs/workbench/services/textfile/browser/textFileService.ts index 5b931d99340..0bd126ec16e 100644 --- a/src/vs/workbench/services/textfile/browser/textFileService.ts +++ b/src/vs/workbench/services/textfile/browser/textFileService.ts @@ -298,7 +298,7 @@ export abstract class AbstractTextFileService extends Disposable implements ITex //#region save - async save(resource: URI, options?: ITextFileSaveOptions): Promise { + async save(resource: URI, options?: ITextFileSaveOptions): Promise { // Untitled if (resource.scheme === Schemas.untitled) { @@ -318,9 +318,7 @@ export abstract class AbstractTextFileService extends Disposable implements ITex // Save as if target provided if (targetUri) { - await this.saveAs(resource, targetUri, options); - - return true; + return this.saveAs(resource, targetUri, options); } } } @@ -333,11 +331,11 @@ export abstract class AbstractTextFileService extends Disposable implements ITex // Save with options await model.save(options); - return !model.isDirty(); + return !model.isDirty() ? resource : undefined; } } - return false; + return undefined; } protected async promptForPath(resource: URI, defaultUri: URI, availableFileSystems?: string[]): Promise { @@ -378,9 +376,7 @@ export abstract class AbstractTextFileService extends Disposable implements ITex // Just save if target is same as models own resource if (source.toString() === target.toString()) { - await this.save(source, options); - - return source; + return this.save(source, options); } // Do it diff --git a/src/vs/workbench/services/textfile/common/textfiles.ts b/src/vs/workbench/services/textfile/common/textfiles.ts index 95394f9471c..08844c7e8ab 100644 --- a/src/vs/workbench/services/textfile/common/textfiles.ts +++ b/src/vs/workbench/services/textfile/common/textfiles.ts @@ -62,9 +62,9 @@ export interface ITextFileService extends IDisposable { * * @param resource the resource to save * @param options optional save options - * @return true if the resource was saved. + * @return Path of the saved resource or undefined if canceled. */ - save(resource: URI, options?: ISaveOptions): Promise; + save(resource: URI, options?: ISaveOptions): Promise; /** * Saves the provided resource asking the user for a file name or using the provided one. @@ -72,7 +72,7 @@ export interface ITextFileService extends IDisposable { * @param resource the resource to save as. * @param targetResource the optional target to save to. * @param options optional save options - * @return Path of the saved resource. + * @return Path of the saved resource or undefined if canceled. */ saveAs(resource: URI, targetResource?: URI, options?: ISaveOptions): Promise; diff --git a/src/vs/workbench/services/textfile/test/textFileService.test.ts b/src/vs/workbench/services/textfile/test/textFileService.test.ts index 16f4b6e7f4f..63f2405cada 100644 --- a/src/vs/workbench/services/textfile/test/textFileService.test.ts +++ b/src/vs/workbench/services/textfile/test/textFileService.test.ts @@ -81,7 +81,7 @@ suite('Files - TextFileService', () => { assert.ok(accessor.textFileService.isDirty(model.resource)); const res = await accessor.textFileService.save(model.resource); - assert.ok(res); + assert.equal(res?.toString(), model.resource.toString()); assert.ok(!accessor.textFileService.isDirty(model.resource)); }); @@ -94,7 +94,7 @@ suite('Files - TextFileService', () => { assert.ok(accessor.textFileService.isDirty(model.resource)); const res = await accessor.textFileService.save(model.resource); - assert.ok(res); + assert.equal(res?.toString(), model.resource.toString()); assert.ok(!accessor.textFileService.isDirty(model.resource)); }); From 7130288fbb17e895de981a8260fe5bea21cd6b29 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Sun, 26 Jan 2020 09:28:28 +0100 Subject: [PATCH 117/801] textfiles - properly pick default file path --- .../search/browser/searchEditorInput.ts | 16 +++------- .../textfile/browser/textFileService.ts | 32 ++++++++++--------- .../electron-browser/nativeTextFileService.ts | 4 +-- .../workbench/test/workbenchTestServices.ts | 2 -- 4 files changed, 22 insertions(+), 32 deletions(-) diff --git a/src/vs/workbench/contrib/search/browser/searchEditorInput.ts b/src/vs/workbench/contrib/search/browser/searchEditorInput.ts index 93e8033c27b..3143a092eaf 100644 --- a/src/vs/workbench/contrib/search/browser/searchEditorInput.ts +++ b/src/vs/workbench/contrib/search/browser/searchEditorInput.ts @@ -18,8 +18,7 @@ import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editor import { ITextFileSaveOptions, ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import type { IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { FileEditorInput } from 'vs/workbench/contrib/files/common/editors/fileEditorInput'; -import { dirname, joinPath, isEqual } from 'vs/base/common/resources'; -import { IHistoryService } from 'vs/workbench/services/history/common/history'; +import { joinPath, isEqual } from 'vs/base/common/resources'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { IFileDialogService } from 'vs/platform/dialogs/common/dialogs'; import { basename } from 'vs/base/common/path'; @@ -60,7 +59,6 @@ export class SearchEditorInput extends EditorInput { @IEditorService protected readonly editorService: IEditorService, @IEditorGroupsService protected readonly editorGroupService: IEditorGroupsService, @ITextFileService protected readonly textFileService: ITextFileService, - @IHistoryService private readonly historyService: IHistoryService, @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService, @IFileDialogService private readonly fileDialogService: IFileDialogService, @IInstantiationService private readonly instantiationService: IInstantiationService, @@ -203,15 +201,9 @@ export class SearchEditorInput extends EditorInput { const remoteAuthority = this.environmentService.configuration.remoteAuthority; const schemeFilter = remoteAuthority ? network.Schemas.vscodeRemote : network.Schemas.file; - const lastActiveFile = this.historyService.getLastActiveFile(schemeFilter); - if (lastActiveFile) { - const lastDir = dirname(lastActiveFile); - return joinPath(lastDir, searchFileName); - } - - const lastActiveFolder = this.historyService.getLastActiveWorkspaceRoot(schemeFilter); - if (lastActiveFolder) { - return joinPath(lastActiveFolder, searchFileName); + const defaultFilePath = this.fileDialogService.defaultFilePath(schemeFilter); + if (defaultFilePath) { + return joinPath(defaultFilePath, searchFileName); } return URI.from({ scheme: schemeFilter, path: searchFileName }); diff --git a/src/vs/workbench/services/textfile/browser/textFileService.ts b/src/vs/workbench/services/textfile/browser/textFileService.ts index 0bd126ec16e..f02d1dc4379 100644 --- a/src/vs/workbench/services/textfile/browser/textFileService.ts +++ b/src/vs/workbench/services/textfile/browser/textFileService.ts @@ -18,7 +18,6 @@ import { TextFileEditorModelManager } from 'vs/workbench/services/textfile/commo import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ResourceMap } from 'vs/base/common/map'; import { Schemas } from 'vs/base/common/network'; -import { IHistoryService } from 'vs/workbench/services/history/common/history'; import { createTextBufferFactoryFromSnapshot, createTextBufferFactoryFromStream } from 'vs/editor/common/model/textModel'; import { IModelService } from 'vs/editor/common/services/modelService'; import { isEqualOrParent, isEqual, joinPath, dirname, basename, toLocalResource } from 'vs/base/common/resources'; @@ -65,7 +64,6 @@ export abstract class AbstractTextFileService extends Disposable implements ITex @IInstantiationService protected readonly instantiationService: IInstantiationService, @IModelService private readonly modelService: IModelService, @IWorkbenchEnvironmentService protected readonly environmentService: IWorkbenchEnvironmentService, - @IHistoryService private readonly historyService: IHistoryService, @IDialogService private readonly dialogService: IDialogService, @IFileDialogService private readonly fileDialogService: IFileDialogService, @IEditorService private readonly editorService: IEditorService, @@ -313,7 +311,7 @@ export abstract class AbstractTextFileService extends Disposable implements ITex // Otherwise ask user else { - targetUri = await this.promptForPath(resource, this.suggestFilePath(resource)); + targetUri = await this.promptForPath(resource, this.suggestUntitledFilePath(resource)); } // Save as if target provided @@ -364,7 +362,7 @@ export abstract class AbstractTextFileService extends Disposable implements ITex if (!target) { let dialogPath = source; if (source.scheme === Schemas.untitled) { - dialogPath = this.suggestFilePath(source); + dialogPath = this.suggestUntitledFilePath(source); } target = await this.promptForPath(source, dialogPath, options?.availableFileSystems); @@ -546,23 +544,27 @@ export abstract class AbstractTextFileService extends Disposable implements ITex return (await this.dialogService.confirm(confirm)).confirmed; } - private suggestFilePath(untitledResource: URI): URI { - const untitledFileName = this.untitled.get(untitledResource)?.suggestFileName() ?? basename(untitledResource); + private suggestUntitledFilePath(untitledResource: URI): URI { const remoteAuthority = this.environmentService.configuration.remoteAuthority; - const schemeFilter = remoteAuthority ? Schemas.vscodeRemote : Schemas.file; + const targetScheme = remoteAuthority ? Schemas.vscodeRemote : Schemas.file; - const lastActiveFile = this.historyService.getLastActiveFile(schemeFilter); - if (lastActiveFile) { - const lastDir = dirname(lastActiveFile); - return joinPath(lastDir, untitledFileName); + // Untitled with associated file path + const model = this.untitledTextEditorService.get(untitledResource); + if (model?.hasAssociatedFilePath) { + return untitledResource.with({ scheme: targetScheme }); } - const lastActiveFolder = this.historyService.getLastActiveWorkspaceRoot(schemeFilter); - if (lastActiveFolder) { - return joinPath(lastActiveFolder, untitledFileName); + // Untitled without associated file path + const untitledFileName = model?.suggestFileName() ?? basename(untitledResource); + + // Try to place where last active file was if any + const defaultFilePath = this.fileDialogService.defaultFilePath(targetScheme); + if (defaultFilePath) { + return joinPath(defaultFilePath, untitledFileName); } - return untitledResource.with({ path: untitledFileName }); + // Finally fallback to suggest just the file name + return untitledResource.with({ scheme: targetScheme, path: untitledFileName }); } //#endregion diff --git a/src/vs/workbench/services/textfile/electron-browser/nativeTextFileService.ts b/src/vs/workbench/services/textfile/electron-browser/nativeTextFileService.ts index aa380e043fe..522e997de5e 100644 --- a/src/vs/workbench/services/textfile/electron-browser/nativeTextFileService.ts +++ b/src/vs/workbench/services/textfile/electron-browser/nativeTextFileService.ts @@ -32,7 +32,6 @@ import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IModelService } from 'vs/editor/common/services/modelService'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; -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 { assign } from 'vs/base/common/objects'; @@ -49,7 +48,6 @@ export class NativeTextFileService extends AbstractTextFileService { @IInstantiationService instantiationService: IInstantiationService, @IModelService modelService: IModelService, @IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService, - @IHistoryService historyService: IHistoryService, @IDialogService dialogService: IDialogService, @IFileDialogService fileDialogService: IFileDialogService, @IEditorService editorService: IEditorService, @@ -59,7 +57,7 @@ export class NativeTextFileService extends AbstractTextFileService { @ITextModelService textModelService: ITextModelService, @ICodeEditorService codeEditorService: ICodeEditorService ) { - super(fileService, untitledTextEditorService, lifecycleService, instantiationService, modelService, environmentService, historyService, dialogService, fileDialogService, editorService, textResourceConfigurationService, filesConfigurationService, textModelService, codeEditorService); + super(fileService, untitledTextEditorService, lifecycleService, instantiationService, modelService, environmentService, dialogService, fileDialogService, editorService, textResourceConfigurationService, filesConfigurationService, textModelService, codeEditorService); } private _encoding: EncodingOracle | undefined; diff --git a/src/vs/workbench/test/workbenchTestServices.ts b/src/vs/workbench/test/workbenchTestServices.ts index bca2b4ab1ca..60eb4847bbc 100644 --- a/src/vs/workbench/test/workbenchTestServices.ts +++ b/src/vs/workbench/test/workbenchTestServices.ts @@ -212,7 +212,6 @@ export class TestTextFileService extends NativeTextFileService { @IInstantiationService instantiationService: IInstantiationService, @IModelService modelService: IModelService, @IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService, - @IHistoryService historyService: IHistoryService, @IDialogService dialogService: IDialogService, @IFileDialogService fileDialogService: IFileDialogService, @IEditorService editorService: IEditorService, @@ -229,7 +228,6 @@ export class TestTextFileService extends NativeTextFileService { instantiationService, modelService, environmentService, - historyService, dialogService, fileDialogService, editorService, From 432961cb0dc9b032e09bb3cf3ea990202704e54d Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Sun, 26 Jan 2020 09:29:34 +0100 Subject: [PATCH 118/801] editors - let resource editor extend text editor --- .../common/editor/resourceEditorInput.ts | 43 ++++--------------- .../contrib/output/browser/logViewer.ts | 5 ++- .../electron-browser/perfviewEditor.ts | 7 ++- .../common/preferencesEditorInput.ts | 6 ++- 4 files changed, 21 insertions(+), 40 deletions(-) diff --git a/src/vs/workbench/common/editor/resourceEditorInput.ts b/src/vs/workbench/common/editor/resourceEditorInput.ts index c97918ed39a..25ce5ca8fb4 100644 --- a/src/vs/workbench/common/editor/resourceEditorInput.ts +++ b/src/vs/workbench/common/editor/resourceEditorInput.ts @@ -3,21 +3,21 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { EditorInput, ITextEditorModel, IModeSupport, GroupIdentifier, isTextEditor } from 'vs/workbench/common/editor'; +import { ITextEditorModel, IModeSupport, TextEditorInput } from 'vs/workbench/common/editor'; import { URI } from 'vs/base/common/uri'; import { IReference } from 'vs/base/common/lifecycle'; import { ITextModelService } from 'vs/editor/common/services/resolverService'; import { ResourceEditorModel } from 'vs/workbench/common/editor/resourceEditorModel'; import { basename } from 'vs/base/common/resources'; -import { ITextFileSaveOptions, ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; -import { IEditorViewState } from 'vs/editor/common/editorCommon'; +import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; /** * A read-only text editor input whos contents are made of the provided resource that points to an existing * code editor model. */ -export class ResourceEditorInput extends EditorInput implements IModeSupport { +export class ResourceEditorInput extends TextEditorInput implements IModeSupport { static readonly ID: string = 'workbench.editors.resourceEditorInput'; @@ -27,17 +27,14 @@ export class ResourceEditorInput extends EditorInput implements IModeSupport { constructor( private name: string | undefined, private description: string | undefined, - private readonly resource: URI, + resource: URI, private preferredMode: string | undefined, @ITextModelService private readonly textModelResolverService: ITextModelService, - @ITextFileService private readonly textFileService: ITextFileService, - @IEditorService private readonly editorService: IEditorService + @ITextFileService textFileService: ITextFileService, + @IEditorService editorService: IEditorService, + @IEditorGroupsService editorGroupService: IEditorGroupsService ) { - super(); - - this.name = name; - this.description = description; - this.resource = resource; + super(resource, editorService, editorGroupService, textFileService); } getResource(): URI { @@ -109,28 +106,6 @@ export class ResourceEditorInput extends EditorInput implements IModeSupport { return model; } - async saveAs(group: GroupIdentifier, options?: ITextFileSaveOptions): Promise { - - // Preserve view state by opening the editor first. In addition - // this allows the user to review the contents of the editor. - let viewState: IEditorViewState | undefined = undefined; - const editor = await this.editorService.openEditor(this, undefined, group); - if (isTextEditor(editor)) { - viewState = editor.getViewState(); - } - - // Save as - const target = await this.textFileService.saveAs(this.resource, undefined, options); - if (!target) { - return false; // save cancelled - } - - // Open the target - await this.editorService.openEditor({ resource: target, options: { viewState, pinned: true } }, group); - - return true; - } - matches(otherInput: unknown): boolean { if (super.matches(otherInput) === true) { return true; diff --git a/src/vs/workbench/contrib/output/browser/logViewer.ts b/src/vs/workbench/contrib/output/browser/logViewer.ts index 33c74fdae1f..fab74175772 100644 --- a/src/vs/workbench/contrib/output/browser/logViewer.ts +++ b/src/vs/workbench/contrib/output/browser/logViewer.ts @@ -28,9 +28,10 @@ export class LogViewerInput extends ResourceEditorInput { private readonly outputChannelDescriptor: IFileOutputChannelDescriptor, @ITextModelService textModelResolverService: ITextModelService, @ITextFileService textFileService: ITextFileService, - @IEditorService editorService: IEditorService + @IEditorService editorService: IEditorService, + @IEditorGroupsService editorGroupService: IEditorGroupsService ) { - super(basename(outputChannelDescriptor.file.path), dirname(outputChannelDescriptor.file.path), URI.from({ scheme: LOG_SCHEME, path: outputChannelDescriptor.id }), undefined, textModelResolverService, textFileService, editorService); + super(basename(outputChannelDescriptor.file.path), dirname(outputChannelDescriptor.file.path), URI.from({ scheme: LOG_SCHEME, path: outputChannelDescriptor.id }), undefined, textModelResolverService, textFileService, editorService, editorGroupService); } getTypeId(): string { diff --git a/src/vs/workbench/contrib/performance/electron-browser/perfviewEditor.ts b/src/vs/workbench/contrib/performance/electron-browser/perfviewEditor.ts index 31ab953bff3..bb5033348d3 100644 --- a/src/vs/workbench/contrib/performance/electron-browser/perfviewEditor.ts +++ b/src/vs/workbench/contrib/performance/electron-browser/perfviewEditor.ts @@ -23,6 +23,7 @@ import { mergeSort } from 'vs/base/common/arrays'; import product from 'vs/platform/product/common/product'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; export class PerfviewContrib { @@ -48,7 +49,8 @@ export class PerfviewInput extends ResourceEditorInput { constructor( @ITextModelService textModelResolverService: ITextModelService, @ITextFileService textFileService: ITextFileService, - @IEditorService editorService: IEditorService + @IEditorService editorService: IEditorService, + @IEditorGroupsService editorGroupService: IEditorGroupsService ) { super( localize('name', "Startup Performance"), @@ -57,7 +59,8 @@ export class PerfviewInput extends ResourceEditorInput { undefined, textModelResolverService, textFileService, - editorService + editorService, + editorGroupService ); } diff --git a/src/vs/workbench/services/preferences/common/preferencesEditorInput.ts b/src/vs/workbench/services/preferences/common/preferencesEditorInput.ts index 501a673894f..4201d713077 100644 --- a/src/vs/workbench/services/preferences/common/preferencesEditorInput.ts +++ b/src/vs/workbench/services/preferences/common/preferencesEditorInput.ts @@ -15,6 +15,7 @@ import { IPreferencesService } from 'vs/workbench/services/preferences/common/pr import { Settings2EditorModel } from 'vs/workbench/services/preferences/common/preferencesModels'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; export class PreferencesEditorInput extends SideBySideEditorInput { static readonly ID: string = 'workbench.editorinputs.preferencesEditorInput'; @@ -34,9 +35,10 @@ export class DefaultPreferencesEditorInput extends ResourceEditorInput { defaultSettingsResource: URI, @ITextModelService textModelResolverService: ITextModelService, @ITextFileService textFileService: ITextFileService, - @IEditorService editorService: IEditorService + @IEditorService editorService: IEditorService, + @IEditorGroupsService editorGroupService: IEditorGroupsService ) { - super(nls.localize('settingsEditorName', "Default Settings"), '', defaultSettingsResource, undefined, textModelResolverService, textFileService, editorService); + super(nls.localize('settingsEditorName', "Default Settings"), '', defaultSettingsResource, undefined, textModelResolverService, textFileService, editorService, editorGroupService); } getTypeId(): string { From f5a52f87cfac596733fc417e2ff3069ddfd939d4 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Sun, 26 Jan 2020 09:30:33 +0100 Subject: [PATCH 119/801] text files - move revert() error handler into model --- .../textfile/browser/textFileService.ts | 25 +++++-------------- .../textfile/common/textFileEditorModel.ts | 10 +++++--- 2 files changed, 13 insertions(+), 22 deletions(-) diff --git a/src/vs/workbench/services/textfile/browser/textFileService.ts b/src/vs/workbench/services/textfile/browser/textFileService.ts index f02d1dc4379..930a74e0fe6 100644 --- a/src/vs/workbench/services/textfile/browser/textFileService.ts +++ b/src/vs/workbench/services/textfile/browser/textFileService.ts @@ -598,26 +598,13 @@ export abstract class AbstractTextFileService extends Disposable implements ITex }); await Promise.all(fileModels.map(async model => { - try { - await model.revert(options); + await model.revert(options); - // If model is still dirty, mark the resulting operation as error - if (model.isDirty()) { - const result = mapResourceToResult.get(model.resource); - if (result) { - result.error = true; - } - } - } catch (error) { - - // FileNotFound means the file got deleted meanwhile, so ignore it - if ((error).fileOperationResult === FileOperationResult.FILE_NOT_FOUND) { - return; - } - - // Otherwise bubble up the error - else { - throw error; + // If model is still dirty, mark the resulting operation as error + if (model.isDirty()) { + const result = mapResourceToResult.get(model.resource); + if (result) { + result.error = true; } } })); diff --git a/src/vs/workbench/services/textfile/common/textFileEditorModel.ts b/src/vs/workbench/services/textfile/common/textFileEditorModel.ts index d0f22c9fb60..fb23bde3b4f 100644 --- a/src/vs/workbench/services/textfile/common/textFileEditorModel.ts +++ b/src/vs/workbench/services/textfile/common/textFileEditorModel.ts @@ -230,10 +230,14 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil await this.load({ forceReadFromDisk: true }); } catch (error) { - // Set flags back to previous values, we are still dirty if revert failed - undo(); + // FileNotFound means the file got deleted meanwhile, so ignore it + if ((error).fileOperationResult !== FileOperationResult.FILE_NOT_FOUND) { - throw error; + // Set flags back to previous values, we are still dirty if revert failed + undo(); + + throw error; + } } } From dc25752860919a275d86aab8074706caa5e71143 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Sun, 26 Jan 2020 09:32:11 +0100 Subject: [PATCH 120/801] text files - centralize file path suggestion for picker --- .../common/editor/untitledTextEditorInput.ts | 15 ----- .../textfile/browser/textFileService.ts | 62 ++++++++++++------- .../common/editor/untitledTextEditor.test.ts | 8 --- 3 files changed, 40 insertions(+), 45 deletions(-) diff --git a/src/vs/workbench/common/editor/untitledTextEditorInput.ts b/src/vs/workbench/common/editor/untitledTextEditorInput.ts index 838204454e5..85e20c82d6e 100644 --- a/src/vs/workbench/common/editor/untitledTextEditorInput.ts +++ b/src/vs/workbench/common/editor/untitledTextEditorInput.ts @@ -4,9 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { URI } from 'vs/base/common/uri'; -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 { IEncodingSupport, EncodingMode, Verbosity, IModeSupport, TextEditorInput, GroupIdentifier, IRevertOptions } from 'vs/workbench/common/editor'; import { UntitledTextEditorModel } from 'vs/workbench/common/editor/untitledTextEditorModel'; @@ -178,19 +176,6 @@ export class UntitledTextEditorInput extends TextEditorInput implements IEncodin return true; } - suggestFileName(): string { - if (!this.hasAssociatedFilePath) { - if (this.cachedModel) { - const mode = this.cachedModel.getMode(); - if (mode !== PLAINTEXT_MODE_ID) { // do not suggest when the mode ID is simple plain text - return suggestFilename(mode, this.getName()); - } - } - } - - return this.getName(); - } - getEncoding(): string | undefined { if (this.cachedModel) { return this.cachedModel.getEncoding(); diff --git a/src/vs/workbench/services/textfile/browser/textFileService.ts b/src/vs/workbench/services/textfile/browser/textFileService.ts index 930a74e0fe6..18d6e9f3946 100644 --- a/src/vs/workbench/services/textfile/browser/textFileService.ts +++ b/src/vs/workbench/services/textfile/browser/textFileService.ts @@ -33,6 +33,7 @@ import { ITextModelService, IResolvedTextEditorModel } from 'vs/editor/common/se import { BaseTextEditorModel } from 'vs/workbench/common/editor/textEditorModel'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; import { coalesce } from 'vs/base/common/arrays'; +import { suggestFilename } from 'vs/base/common/mime'; /** * The workbench file service implementation implements the raw file service spec and adds additional methods on top. @@ -306,12 +307,12 @@ export abstract class AbstractTextFileService extends Disposable implements ITex // Untitled with associated file path don't need to prompt if (model.hasAssociatedFilePath) { - targetUri = toLocalResource(resource, this.environmentService.configuration.remoteAuthority); + targetUri = this.suggestSavePath(resource); } // Otherwise ask user else { - targetUri = await this.promptForPath(resource, this.suggestUntitledFilePath(resource)); + targetUri = await this.promptForPath(resource, options?.availableFileSystems); } // Save as if target provided @@ -336,12 +337,12 @@ export abstract class AbstractTextFileService extends Disposable implements ITex return undefined; } - protected async promptForPath(resource: URI, defaultUri: URI, availableFileSystems?: string[]): Promise { + protected async promptForPath(resource: URI, availableFileSystems?: string[]): Promise { // Help user to find a name for the file by opening it first await this.editorService.openEditor({ resource, options: { revealIfOpened: true, preserveFocus: true } }); - return this.fileDialogService.pickFileToSave(defaultUri, availableFileSystems); + return this.fileDialogService.pickFileToSave(this.suggestSavePath(resource), availableFileSystems); } private getFileModels(resources?: URI[]): ITextFileEditorModel[] { @@ -360,12 +361,7 @@ export abstract class AbstractTextFileService extends Disposable implements ITex // Get to target resource if (!target) { - let dialogPath = source; - if (source.scheme === Schemas.untitled) { - dialogPath = this.suggestUntitledFilePath(source); - } - - target = await this.promptForPath(source, dialogPath, options?.availableFileSystems); + target = await this.promptForPath(source, options?.availableFileSystems); } if (!target) { @@ -544,27 +540,49 @@ export abstract class AbstractTextFileService extends Disposable implements ITex return (await this.dialogService.confirm(confirm)).confirmed; } - private suggestUntitledFilePath(untitledResource: URI): URI { - const remoteAuthority = this.environmentService.configuration.remoteAuthority; - const targetScheme = remoteAuthority ? Schemas.vscodeRemote : Schemas.file; + private suggestSavePath(resource: URI): URI { - // Untitled with associated file path - const model = this.untitledTextEditorService.get(untitledResource); - if (model?.hasAssociatedFilePath) { - return untitledResource.with({ scheme: targetScheme }); + // Just take the resource as is if the file service can handle it + if (this.fileService.canHandleResource(resource)) { + return resource; } - // Untitled without associated file path - const untitledFileName = model?.suggestFileName() ?? basename(untitledResource); + const remoteAuthority = this.environmentService.configuration.remoteAuthority; + + // Otherwise try to suggest a path that can be saved + let suggestedFilename: string | undefined = undefined; + if (resource.scheme === Schemas.untitled) { + const model = this.untitledTextEditorService.get(resource); + if (model) { + + // Untitled with associated file path + if (model.hasAssociatedFilePath) { + return toLocalResource(resource, remoteAuthority); + } + + // Untitled without associated file path + const mode = model.getMode(); + if (mode !== PLAINTEXT_MODE_ID) { // do not suggest when the mode ID is simple plain text + suggestedFilename = suggestFilename(mode, model.getName()); + } else { + suggestedFilename = model.getName(); + } + } + } + + // Fallback to basename of resource + if (!suggestedFilename) { + suggestedFilename = basename(resource); + } // Try to place where last active file was if any - const defaultFilePath = this.fileDialogService.defaultFilePath(targetScheme); + const defaultFilePath = this.fileDialogService.defaultFilePath(); if (defaultFilePath) { - return joinPath(defaultFilePath, untitledFileName); + return joinPath(defaultFilePath, suggestedFilename); } // Finally fallback to suggest just the file name - return untitledResource.with({ scheme: targetScheme, path: untitledFileName }); + return toLocalResource(resource.with({ path: suggestedFilename }), remoteAuthority); } //#endregion diff --git a/src/vs/workbench/test/common/editor/untitledTextEditor.test.ts b/src/vs/workbench/test/common/editor/untitledTextEditor.test.ts index 3d0d97cfd48..88b5a018a1b 100644 --- a/src/vs/workbench/test/common/editor/untitledTextEditor.test.ts +++ b/src/vs/workbench/test/common/editor/untitledTextEditor.test.ts @@ -168,14 +168,6 @@ suite('Workbench untitled text editors', () => { input.dispose(); }); - test('suggest name', function () { - const service = accessor.untitledTextEditorService; - const input = service.create(); - - assert.ok(input.suggestFileName().length > 0); - input.dispose(); - }); - test('associated path remains dirty when content gets empty', async () => { const service = accessor.untitledTextEditorService; const file = URI.file(join('C:\\', '/foo/file.txt')); From 0ad5da56a2c791bee40e21d0ecb112b535c16abe Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Sun, 26 Jan 2020 09:33:17 +0100 Subject: [PATCH 121/801] working copies - have an action to log working copies --- .../browser/actions/developerActions.ts | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/src/vs/workbench/browser/actions/developerActions.ts b/src/vs/workbench/browser/actions/developerActions.ts index cf9666dc65d..d4784a72e85 100644 --- a/src/vs/workbench/browser/actions/developerActions.ts +++ b/src/vs/workbench/browser/actions/developerActions.ts @@ -25,6 +25,8 @@ import { IStorageService } from 'vs/platform/storage/common/storage'; import { clamp } from 'vs/base/common/numbers'; import { KeyCode } from 'vs/base/common/keyCodes'; import { IConfigurationRegistry, Extensions as ConfigurationExtensions } from 'vs/platform/configuration/common/configurationRegistry'; +import { ILogService } from 'vs/platform/log/common/log'; +import { IWorkingCopyService } from 'vs/workbench/services/workingCopy/common/workingCopyService'; class InspectContextKeysAction extends Action { @@ -210,6 +212,33 @@ class LogStorageAction extends Action { } } +class LogWorkingCopiesAction extends Action { + + static readonly ID = 'workbench.action.logWorkingCopies'; + static readonly LABEL = nls.localize({ key: 'logWorkingCopies', comment: ['A developer only action to log the working copies that exist.'] }, "Log Working Copies"); + + constructor( + id: string, + label: string, + @ILogService private logService: ILogService, + @IWorkingCopyService private workingCopyService: IWorkingCopyService + ) { + super(id, label); + } + + async run(): Promise { + const msg = [ + `Dirty Working Copies:`, + ...this.workingCopyService.dirtyWorkingCopies.map(workingCopy => workingCopy.resource.toString(true)), + ``, + `All Working Copies:`, + ...this.workingCopyService.workingCopies.map(workingCopy => workingCopy.resource.toString(true)), + ]; + + this.logService.info(msg.join('\n')); + } +} + // --- Actions Registration const developerCategory = nls.localize('developer', "Developer"); @@ -217,6 +246,7 @@ const registry = Registry.as(Extensions.WorkbenchActio registry.registerWorkbenchAction(SyncActionDescriptor.create(InspectContextKeysAction, InspectContextKeysAction.ID, InspectContextKeysAction.LABEL), 'Developer: Inspect Context Keys', developerCategory); registry.registerWorkbenchAction(SyncActionDescriptor.create(ToggleScreencastModeAction, ToggleScreencastModeAction.ID, ToggleScreencastModeAction.LABEL), 'Developer: Toggle Screencast Mode', developerCategory); registry.registerWorkbenchAction(SyncActionDescriptor.create(LogStorageAction, LogStorageAction.ID, LogStorageAction.LABEL), 'Developer: Log Storage Database Contents', developerCategory); +registry.registerWorkbenchAction(SyncActionDescriptor.create(LogWorkingCopiesAction, LogWorkingCopiesAction.ID, LogWorkingCopiesAction.LABEL), 'Developer: Log Working Copies', developerCategory); // Screencast Mode const configurationRegistry = Registry.as(ConfigurationExtensions.Configuration); From acdf6a7d05d3efdbc2ce62c0b41d33da6d9fc9d7 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Sun, 26 Jan 2020 09:34:37 +0100 Subject: [PATCH 122/801] untitled - implement revert() on model --- .../common/editor/untitledTextEditorInput.ts | 15 +--- .../common/editor/untitledTextEditorModel.ts | 13 ++- .../textfile/browser/textFileService.ts | 6 +- .../common/editor/untitledTextEditor.test.ts | 90 ++++++++++++------- .../workbench/test/workbenchTestServices.ts | 2 +- 5 files changed, 75 insertions(+), 51 deletions(-) diff --git a/src/vs/workbench/common/editor/untitledTextEditorInput.ts b/src/vs/workbench/common/editor/untitledTextEditorInput.ts index 85e20c82d6e..633d95016c8 100644 --- a/src/vs/workbench/common/editor/untitledTextEditorInput.ts +++ b/src/vs/workbench/common/editor/untitledTextEditorInput.ts @@ -6,7 +6,7 @@ import { URI } from 'vs/base/common/uri'; import { createMemoizer } from 'vs/base/common/decorators'; import { basenameOrAuthority, dirname } from 'vs/base/common/resources'; -import { IEncodingSupport, EncodingMode, Verbosity, IModeSupport, TextEditorInput, GroupIdentifier, IRevertOptions } from 'vs/workbench/common/editor'; +import { IEncodingSupport, EncodingMode, Verbosity, IModeSupport, TextEditorInput } from 'vs/workbench/common/editor'; import { UntitledTextEditorModel } from 'vs/workbench/common/editor/untitledTextEditorModel'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { Emitter } from 'vs/base/common/event'; @@ -166,16 +166,6 @@ export class UntitledTextEditorInput extends TextEditorInput implements IEncodin return this.hasAssociatedFilePath; } - async revert(group: GroupIdentifier, options?: IRevertOptions): Promise { - if (this.cachedModel) { - this.cachedModel.revert(); - } - - this.dispose(); // a reverted untitled text editor is no longer valid, so we dispose it - - return true; - } - getEncoding(): string | undefined { if (this.cachedModel) { return this.cachedModel.getEncoding(); @@ -246,6 +236,9 @@ export class UntitledTextEditorInput extends TextEditorInput implements IEncodin this._register(model.onDidChangeDirty(() => this._onDidChangeDirty.fire())); this._register(model.onDidChangeEncoding(() => this._onDidModelChangeEncoding.fire())); this._register(model.onDidChangeName(() => this._onDidChangeLabel.fire())); + + // a disposed untitled text editor model renders this input disposed + this._register(model.onDispose(() => this.dispose())); } matches(otherInput: unknown): boolean { diff --git a/src/vs/workbench/common/editor/untitledTextEditorModel.ts b/src/vs/workbench/common/editor/untitledTextEditorModel.ts index e4f905cdaff..b27ebf1b85a 100644 --- a/src/vs/workbench/common/editor/untitledTextEditorModel.ts +++ b/src/vs/workbench/common/editor/untitledTextEditorModel.ts @@ -123,6 +123,10 @@ export class UntitledTextEditorModel extends BaseTextEditorModel implements IUnt } } + isReadonly(): boolean { + return false; + } + isDirty(): boolean { return this.dirty; } @@ -145,6 +149,11 @@ export class UntitledTextEditorModel extends BaseTextEditorModel implements IUnt async revert(): Promise { this.setDirty(false); + // A reverted untitled model is invalid because it has + // no actual source on disk to revert to. As such we + // dispose the model. + this.dispose(); + return true; } @@ -252,8 +261,4 @@ export class UntitledTextEditorModel extends BaseTextEditorModel implements IUnt this._onDidChangeName.fire(); } } - - isReadonly(): boolean { - return false; - } } diff --git a/src/vs/workbench/services/textfile/browser/textFileService.ts b/src/vs/workbench/services/textfile/browser/textFileService.ts index 18d6e9f3946..430c21bc99f 100644 --- a/src/vs/workbench/services/textfile/browser/textFileService.ts +++ b/src/vs/workbench/services/textfile/browser/textFileService.ts @@ -593,9 +593,9 @@ export abstract class AbstractTextFileService extends Disposable implements ITex // Untitled if (resource.scheme === Schemas.untitled) { - const model = this.untitled.get(resource); + const model = this.untitled.exists(resource) ? await this.untitled.resolve({ untitledResource: resource }) : undefined; if (model) { - return model.revert(0, options); + return model.revert(options); } return false; @@ -616,6 +616,8 @@ export abstract class AbstractTextFileService extends Disposable implements ITex }); await Promise.all(fileModels.map(async model => { + + // Revert through model await model.revert(options); // If model is still dirty, mark the resulting operation as error diff --git a/src/vs/workbench/test/common/editor/untitledTextEditor.test.ts b/src/vs/workbench/test/common/editor/untitledTextEditor.test.ts index 88b5a018a1b..d452310ba74 100644 --- a/src/vs/workbench/test/common/editor/untitledTextEditor.test.ts +++ b/src/vs/workbench/test/common/editor/untitledTextEditor.test.ts @@ -43,11 +43,12 @@ suite('Workbench untitled text editors', () => { (accessor.untitledTextEditorService as UntitledTextEditorService).dispose(); }); - test('basics', async (done) => { + test('basics', async () => { const service = accessor.untitledTextEditorService; const workingCopyService = accessor.workingCopyService; const input1 = service.create(); + await input1.resolve(); assert.equal(input1, service.create({ untitledResource: input1.getResource() })); assert.equal(service.get(input1.getResource()), input1); @@ -62,52 +63,58 @@ suite('Workbench untitled text editors', () => { assert.equal(service.get(input2.getResource()), input2); // revert() - input1.revert(0); + await input1.revert(0); assert.ok(input1.isDisposed()); assert.ok(!service.get(input1.getResource())); // dirty const model = await input2.resolve(); assert.equal(await service.resolve({ untitledResource: input2.getResource() }), model); + assert.ok(service.exists(model.resource)); assert.ok(!input2.isDirty()); - const listener = service.onDidChangeDirty(resource => { - listener.dispose(); - - assert.equal(resource.toString(), input2.getResource().toString()); - - assert.ok(input2.isDirty()); - - assert.ok(workingCopyService.isDirty(input2.getResource())); - assert.equal(workingCopyService.dirtyCount, 1); - - input1.revert(0); - input2.revert(0); - assert.ok(!service.get(input1.getResource())); - assert.ok(!service.get(input2.getResource())); - assert.ok(!input2.isDirty()); - assert.ok(!model.isDirty()); - - assert.ok(!workingCopyService.isDirty(input2.getResource())); - assert.equal(workingCopyService.dirtyCount, 0); - - assert.ok(input1.revert(0)); - assert.ok(input1.isDisposed()); - assert.ok(!service.exists(input1.getResource())); - - input2.dispose(); - assert.ok(!service.exists(input2.getResource())); - - done(); - }); + const resourcePromise = awaitDidChangeDirty(accessor.untitledTextEditorService); model.textEditorModel.setValue('foo bar'); - model.dispose(); - input1.dispose(); + + const resource = await resourcePromise; + + assert.equal(resource.toString(), input2.getResource().toString()); + + assert.ok(input2.isDirty()); + + assert.ok(workingCopyService.isDirty(input2.getResource())); + assert.equal(workingCopyService.dirtyCount, 1); + + await input1.revert(0); + await input2.revert(0); + assert.ok(!service.get(input1.getResource())); + assert.ok(!service.get(input2.getResource())); + assert.ok(!input2.isDirty()); + assert.ok(!model.isDirty()); + + assert.ok(!workingCopyService.isDirty(input2.getResource())); + assert.equal(workingCopyService.dirtyCount, 0); + + assert.equal(await input1.revert(0), false); + assert.ok(input1.isDisposed()); + assert.ok(!service.exists(input1.getResource())); + input2.dispose(); + assert.ok(!service.exists(input2.getResource())); }); + function awaitDidChangeDirty(service: IUntitledTextEditorService): Promise { + return new Promise(c => { + const listener = service.onDidChangeDirty(async resource => { + listener.dispose(); + + c(resource); + }); + }); + } + test('associated resource is dirty', () => { const service = accessor.untitledTextEditorService; const file = URI.file(join('C:\\', '/foo/file.txt')); @@ -359,6 +366,23 @@ suite('Workbench untitled text editors', () => { model.dispose(); }); + test('model#onDispose when reverted', async function () { + const service = accessor.untitledTextEditorService; + const input = service.create(); + + let counter = 0; + + const model = await input.resolve(); + model.onDispose(() => counter++); + + model.textEditorModel.setValue('foo'); + + await model.revert(); + + assert.ok(input.isDisposed()); + assert.ok(counter > 1); + }); + test('model#onDidChangeName and input name', async function () { const service = accessor.untitledTextEditorService; const input = service.create(); diff --git a/src/vs/workbench/test/workbenchTestServices.ts b/src/vs/workbench/test/workbenchTestServices.ts index 60eb4847bbc..3b94817b811 100644 --- a/src/vs/workbench/test/workbenchTestServices.ts +++ b/src/vs/workbench/test/workbenchTestServices.ts @@ -268,7 +268,7 @@ export class TestTextFileService extends NativeTextFileService { }; } - promptForPath(_resource: URI, _defaultPath: URI): Promise { + promptForPath(_resource: URI, availableFileSystems?: string[]): Promise { return Promise.resolve(this.promptPath); } } From 3b854d96032563b5fca0e8c17a0ea2e7602168b6 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Sun, 26 Jan 2020 09:35:27 +0100 Subject: [PATCH 123/801] editors - remove some obsolete editor commands --- .../parts/editor/editor.contribution.ts | 6 ++-- .../browser/parts/editor/editorActions.ts | 30 ------------------- .../terminal/browser/terminalInstance.ts | 2 -- 3 files changed, 2 insertions(+), 36 deletions(-) diff --git a/src/vs/workbench/browser/parts/editor/editor.contribution.ts b/src/vs/workbench/browser/parts/editor/editor.contribution.ts index 310c9d2e9b3..2ae742d6559 100644 --- a/src/vs/workbench/browser/parts/editor/editor.contribution.ts +++ b/src/vs/workbench/browser/parts/editor/editor.contribution.ts @@ -30,13 +30,13 @@ import { NavigateBetweenGroupsAction, FocusActiveGroupAction, FocusFirstGroupAction, ResetGroupSizesAction, MaximizeGroupAction, MinimizeOtherGroupsAction, FocusPreviousGroup, FocusNextGroup, toEditorQuickOpenEntry, CloseLeftEditorsInGroupAction, OpenNextEditor, OpenPreviousEditor, NavigateBackwardsAction, NavigateForwardAction, NavigateLastAction, ReopenClosedEditorAction, QuickOpenPreviousRecentlyUsedEditorInGroupAction, QuickOpenPreviousEditorFromHistoryAction, ShowAllEditorsByAppearanceAction, ClearEditorHistoryAction, MoveEditorRightInGroupAction, OpenNextEditorInGroup, - OpenPreviousEditorInGroup, OpenNextRecentlyUsedEditorAction, OpenPreviousRecentlyUsedEditorAction, QuickOpenNextRecentlyUsedEditorInGroupAction, MoveEditorToPreviousGroupAction, + OpenPreviousEditorInGroup, OpenNextRecentlyUsedEditorAction, OpenPreviousRecentlyUsedEditorAction, MoveEditorToPreviousGroupAction, MoveEditorToNextGroupAction, MoveEditorToFirstGroupAction, MoveEditorLeftInGroupAction, ClearRecentFilesAction, OpenLastEditorInGroup, ShowEditorsInActiveGroupByMostRecentlyUsedAction, MoveEditorToLastGroupAction, OpenFirstEditorInGroup, MoveGroupUpAction, MoveGroupDownAction, FocusLastGroupAction, SplitEditorLeftAction, SplitEditorRightAction, SplitEditorUpAction, SplitEditorDownAction, MoveEditorToLeftGroupAction, MoveEditorToRightGroupAction, MoveEditorToAboveGroupAction, MoveEditorToBelowGroupAction, CloseAllEditorGroupsAction, JoinAllGroupsAction, FocusLeftGroup, FocusAboveGroup, FocusRightGroup, FocusBelowGroup, EditorLayoutSingleAction, EditorLayoutTwoColumnsAction, EditorLayoutThreeColumnsAction, EditorLayoutTwoByTwoGridAction, EditorLayoutTwoRowsAction, EditorLayoutThreeRowsAction, EditorLayoutTwoColumnsBottomAction, EditorLayoutTwoRowsRightAction, NewEditorGroupLeftAction, NewEditorGroupRightAction, - NewEditorGroupAboveAction, NewEditorGroupBelowAction, SplitEditorOrthogonalAction, CloseEditorInAllGroupsAction, NavigateToLastEditLocationAction, ToggleGroupSizesAction, ShowAllEditorsByMostRecentlyUsedAction, QuickOpenNextRecentlyUsedEditorAction, QuickOpenPreviousRecentlyUsedEditorAction, OpenPreviousRecentlyUsedEditorInGroupAction, OpenNextRecentlyUsedEditorInGroupAction + NewEditorGroupAboveAction, NewEditorGroupBelowAction, SplitEditorOrthogonalAction, CloseEditorInAllGroupsAction, NavigateToLastEditLocationAction, ToggleGroupSizesAction, ShowAllEditorsByMostRecentlyUsedAction, QuickOpenPreviousRecentlyUsedEditorAction, OpenPreviousRecentlyUsedEditorInGroupAction, OpenNextRecentlyUsedEditorInGroupAction } from 'vs/workbench/browser/parts/editor/editorActions'; import * as editorCommands from 'vs/workbench/browser/parts/editor/editorCommands'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; @@ -428,9 +428,7 @@ registry.registerWorkbenchAction(SyncActionDescriptor.create(EditorLayoutTwoColu // Register Quick Editor Actions including built in quick navigate support for some const quickOpenNextRecentlyUsedEditorInGroupKeybinding = { primary: KeyMod.CtrlCmd | KeyCode.Tab, mac: { primary: KeyMod.WinCtrl | KeyCode.Tab } }; const quickOpenPreviousRecentlyUsedEditorInGroupKeybinding = { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.Tab, mac: { primary: KeyMod.WinCtrl | KeyMod.Shift | KeyCode.Tab } }; -registry.registerWorkbenchAction(SyncActionDescriptor.create(QuickOpenNextRecentlyUsedEditorAction, QuickOpenNextRecentlyUsedEditorAction.ID, QuickOpenNextRecentlyUsedEditorAction.LABEL), 'View: Quick Open Next Recently Used Editor', category); registry.registerWorkbenchAction(SyncActionDescriptor.create(QuickOpenPreviousRecentlyUsedEditorAction, QuickOpenPreviousRecentlyUsedEditorAction.ID, QuickOpenPreviousRecentlyUsedEditorAction.LABEL), 'View: Quick Open Previous Recently Used Editor', category); -registry.registerWorkbenchAction(SyncActionDescriptor.create(QuickOpenNextRecentlyUsedEditorInGroupAction, QuickOpenNextRecentlyUsedEditorInGroupAction.ID, QuickOpenNextRecentlyUsedEditorInGroupAction.LABEL, quickOpenNextRecentlyUsedEditorInGroupKeybinding), 'View: Quick Open Next Recently Used Editor in Group', category); registry.registerWorkbenchAction(SyncActionDescriptor.create(QuickOpenPreviousRecentlyUsedEditorInGroupAction, QuickOpenPreviousRecentlyUsedEditorInGroupAction.ID, QuickOpenPreviousRecentlyUsedEditorInGroupAction.LABEL, quickOpenPreviousRecentlyUsedEditorInGroupKeybinding), 'View: Quick Open Previous Recently Used Editor in Group', category); registry.registerWorkbenchAction(SyncActionDescriptor.create(QuickOpenPreviousEditorFromHistoryAction, QuickOpenPreviousEditorFromHistoryAction.ID, QuickOpenPreviousEditorFromHistoryAction.LABEL), 'Quick Open Previous Editor from History'); diff --git a/src/vs/workbench/browser/parts/editor/editorActions.ts b/src/vs/workbench/browser/parts/editor/editorActions.ts index 89bf027eb3a..1cae01b8b04 100644 --- a/src/vs/workbench/browser/parts/editor/editorActions.ts +++ b/src/vs/workbench/browser/parts/editor/editorActions.ts @@ -1337,21 +1337,6 @@ export class QuickOpenPreviousRecentlyUsedEditorAction extends BaseQuickOpenEdit } } -export class QuickOpenNextRecentlyUsedEditorAction extends BaseQuickOpenEditorAction { - - static readonly ID = 'workbench.action.quickOpenNextRecentlyUsedEditor'; - static readonly LABEL = nls.localize('quickOpenNextRecentlyUsedEditor', "Quick Open Next Recently Used Editor"); - - constructor( - id: string, - label: string, - @IQuickOpenService quickOpenService: IQuickOpenService, - @IKeybindingService keybindingService: IKeybindingService - ) { - super(id, label, NAVIGATE_ALL_EDITORS_BY_MOST_RECENTLY_USED_PREFIX, quickOpenService, keybindingService); - } -} - export class QuickOpenPreviousRecentlyUsedEditorInGroupAction extends BaseQuickOpenEditorAction { static readonly ID = 'workbench.action.quickOpenPreviousRecentlyUsedEditorInGroup'; @@ -1367,21 +1352,6 @@ export class QuickOpenPreviousRecentlyUsedEditorInGroupAction extends BaseQuickO } } -export class QuickOpenNextRecentlyUsedEditorInGroupAction extends BaseQuickOpenEditorAction { - - static readonly ID = 'workbench.action.quickOpenNextRecentlyUsedEditorInGroup'; - static readonly LABEL = nls.localize('quickOpenNextRecentlyUsedEditorInGroup', "Quick Open Next Recently Used Editor in Group"); - - constructor( - id: string, - label: string, - @IQuickOpenService quickOpenService: IQuickOpenService, - @IKeybindingService keybindingService: IKeybindingService - ) { - super(id, label, NAVIGATE_IN_ACTIVE_GROUP_BY_MOST_RECENTLY_USED_PREFIX, quickOpenService, keybindingService); - } -} - export class QuickOpenPreviousEditorFromHistoryAction extends Action { static readonly ID = 'workbench.action.openPreviousEditorFromHistory'; diff --git a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts index aea2f505d7c..4f7afb964a4 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts @@ -147,9 +147,7 @@ export const DEFAULT_COMMANDS_TO_SKIP_SHELL: string[] = [ 'workbench.action.openPreviousRecentlyUsedEditor', 'workbench.action.openNextRecentlyUsedEditorInGroup', 'workbench.action.openPreviousRecentlyUsedEditorInGroup', - 'workbench.action.quickOpenNextRecentlyUsedEditor', 'workbench.action.quickOpenPreviousRecentlyUsedEditor', - 'workbench.action.quickOpenNextRecentlyUsedEditorInGroup', 'workbench.action.quickOpenPreviousRecentlyUsedEditorInGroup', 'workbench.action.focusActiveEditorGroup', 'workbench.action.focusFirstEditorGroup', From 92980e12bf4609e6446e0a3f1880729f665c4279 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Sun, 26 Jan 2020 10:21:31 +0100 Subject: [PATCH 124/801] editors - extract editor opening before saving to reusable place --- .../customEditor/browser/customEditorInput.ts | 10 +--------- .../contrib/search/browser/searchEditorInput.ts | 10 +--------- .../services/editor/browser/editorService.ts | 11 +++++++---- .../textfile/browser/textFileService.ts | 14 ++------------ .../electron-browser/nativeTextFileService.ts | 4 +--- .../textfile/test/textFileService.test.ts | 4 ++-- src/vs/workbench/test/workbenchTestServices.ts | 17 +++++------------ 7 files changed, 19 insertions(+), 51 deletions(-) diff --git a/src/vs/workbench/contrib/customEditor/browser/customEditorInput.ts b/src/vs/workbench/contrib/customEditor/browser/customEditorInput.ts index 552ed8f2507..e4a15129bcf 100644 --- a/src/vs/workbench/contrib/customEditor/browser/customEditorInput.ts +++ b/src/vs/workbench/contrib/customEditor/browser/customEditorInput.ts @@ -132,7 +132,7 @@ export class CustomFileEditorInput extends LazilyResolvedWebviewEditorInput { } let dialogPath = this._editorResource; - const target = await this.promptForPath(this._editorResource, dialogPath, options?.availableFileSystems); + const target = await this.fileDialogService.pickFileToSave(dialogPath, options?.availableFileSystems); if (!target) { return false; // save cancelled } @@ -162,14 +162,6 @@ export class CustomFileEditorInput extends LazilyResolvedWebviewEditorInput { return await super.resolve(); } - private async promptForPath(resource: URI, defaultUri: URI, availableFileSystems?: string[]): Promise { - - // Help user to find a name for the file by opening it first - await this.editorService.openEditor({ resource, options: { revealIfOpened: true, preserveFocus: true } }); - - return this.fileDialogService.pickFileToSave(defaultUri, availableFileSystems); - } - public handleMove(groupId: GroupIdentifier, uri: URI, options?: ITextEditorOptions): IEditorInput | undefined { const editorInfo = this.customEditorService.getCustomEditor(this.viewType); if (editorInfo?.matches(uri)) { diff --git a/src/vs/workbench/contrib/search/browser/searchEditorInput.ts b/src/vs/workbench/contrib/search/browser/searchEditorInput.ts index 3143a092eaf..9d79d36999e 100644 --- a/src/vs/workbench/contrib/search/browser/searchEditorInput.ts +++ b/src/vs/workbench/contrib/search/browser/searchEditorInput.ts @@ -89,7 +89,7 @@ export class SearchEditorInput extends EditorInput { async save(group: GroupIdentifier, options?: ITextFileSaveOptions): Promise { if (this.resource.scheme === 'search-editor') { - const path = await this.promptForPath(this.resource, await this.suggestFileName(), options?.availableFileSystems); + const path = await this.fileDialogService.pickFileToSave(await this.suggestFileName(), options?.availableFileSystems); if (path) { if (await this.textFileService.saveAs(this.resource, path, options)) { this.setDirty(false); @@ -109,14 +109,6 @@ export class SearchEditorInput extends EditorInput { } } - // Brining this over from textFileService because it only suggests for untitled scheme. - // In the future I may just use the untitled scheme. I dont get particular benefit from using search-editor... - private async promptForPath(resource: URI, defaultUri: URI, availableFileSystems?: string[]): Promise { - // Help user to find a name for the file by opening it first - await this.editorService.openEditor({ resource, options: { revealIfOpened: true, preserveFocus: true } }); - return this.fileDialogService.pickFileToSave(defaultUri, availableFileSystems); - } - getTypeId(): string { return SearchEditorInput.ID; } diff --git a/src/vs/workbench/services/editor/browser/editorService.ts b/src/vs/workbench/services/editor/browser/editorService.ts index 8793d7cbf19..252650f4386 100644 --- a/src/vs/workbench/services/editor/browser/editorService.ts +++ b/src/vs/workbench/services/editor/browser/editorService.ts @@ -681,13 +681,13 @@ export class EditorService extends Disposable implements EditorServiceImpl { // editors are potentially bringing up some UI and thus run // sequentially. const editorsToSaveParallel: IEditorIdentifier[] = []; - const editorsToSaveAsSequentially: IEditorIdentifier[] = []; + const editorsToSaveSequentially: IEditorIdentifier[] = []; if (options?.saveAs) { - editorsToSaveAsSequentially.push(...editors); + editorsToSaveSequentially.push(...editors); } else { for (const { groupId, editor } of editors) { if (editor.isUntitled()) { - editorsToSaveAsSequentially.push({ groupId, editor }); + editorsToSaveSequentially.push({ groupId, editor }); } else { editorsToSaveParallel.push({ groupId, editor }); } @@ -707,11 +707,14 @@ export class EditorService extends Disposable implements EditorServiceImpl { })); // Editors to save sequentially - for (const { groupId, editor } of editorsToSaveAsSequentially) { + for (const { groupId, editor } of editorsToSaveSequentially) { if (editor.isDisposed()) { continue; // might have been disposed from from the save already } + // bring editor to front to help user make a decision about file names + await this.openEditor(editor, undefined, groupId); + const result = options?.saveAs ? await editor.saveAs(groupId, options) : await editor.save(groupId, options); if (!result) { return false; // failed or cancelled, abort diff --git a/src/vs/workbench/services/textfile/browser/textFileService.ts b/src/vs/workbench/services/textfile/browser/textFileService.ts index 430c21bc99f..57c9f401b6e 100644 --- a/src/vs/workbench/services/textfile/browser/textFileService.ts +++ b/src/vs/workbench/services/textfile/browser/textFileService.ts @@ -22,7 +22,6 @@ import { createTextBufferFactoryFromSnapshot, createTextBufferFactoryFromStream import { IModelService } from 'vs/editor/common/services/modelService'; import { isEqualOrParent, isEqual, joinPath, dirname, basename, toLocalResource } from 'vs/base/common/resources'; import { IDialogService, IFileDialogService, IConfirmation } from 'vs/platform/dialogs/common/dialogs'; -import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { VSBuffer } from 'vs/base/common/buffer'; import { ITextSnapshot, ITextModel } from 'vs/editor/common/model'; import { ITextResourceConfigurationService } from 'vs/editor/common/services/textResourceConfigurationService'; @@ -67,7 +66,6 @@ export abstract class AbstractTextFileService extends Disposable implements ITex @IWorkbenchEnvironmentService protected readonly environmentService: IWorkbenchEnvironmentService, @IDialogService private readonly dialogService: IDialogService, @IFileDialogService private readonly fileDialogService: IFileDialogService, - @IEditorService private readonly editorService: IEditorService, @ITextResourceConfigurationService protected readonly textResourceConfigurationService: ITextResourceConfigurationService, @IFilesConfigurationService protected readonly filesConfigurationService: IFilesConfigurationService, @ITextModelService private readonly textModelService: ITextModelService, @@ -312,7 +310,7 @@ export abstract class AbstractTextFileService extends Disposable implements ITex // Otherwise ask user else { - targetUri = await this.promptForPath(resource, options?.availableFileSystems); + targetUri = await this.fileDialogService.pickFileToSave(this.suggestSavePath(resource), options?.availableFileSystems); } // Save as if target provided @@ -337,14 +335,6 @@ export abstract class AbstractTextFileService extends Disposable implements ITex return undefined; } - protected async promptForPath(resource: URI, availableFileSystems?: string[]): Promise { - - // Help user to find a name for the file by opening it first - await this.editorService.openEditor({ resource, options: { revealIfOpened: true, preserveFocus: true } }); - - return this.fileDialogService.pickFileToSave(this.suggestSavePath(resource), availableFileSystems); - } - private getFileModels(resources?: URI[]): ITextFileEditorModel[] { if (Array.isArray(resources)) { return coalesce(resources.map(resource => this.files.get(resource))); @@ -361,7 +351,7 @@ export abstract class AbstractTextFileService extends Disposable implements ITex // Get to target resource if (!target) { - target = await this.promptForPath(source, options?.availableFileSystems); + target = await this.fileDialogService.pickFileToSave(this.suggestSavePath(source), options?.availableFileSystems); } if (!target) { diff --git a/src/vs/workbench/services/textfile/electron-browser/nativeTextFileService.ts b/src/vs/workbench/services/textfile/electron-browser/nativeTextFileService.ts index 522e997de5e..ec01c5e5fe2 100644 --- a/src/vs/workbench/services/textfile/electron-browser/nativeTextFileService.ts +++ b/src/vs/workbench/services/textfile/electron-browser/nativeTextFileService.ts @@ -33,7 +33,6 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { IModelService } from 'vs/editor/common/services/modelService'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { IDialogService, IFileDialogService } from 'vs/platform/dialogs/common/dialogs'; -import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { assign } from 'vs/base/common/objects'; import { IFilesConfigurationService } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; import { ITextModelService } from 'vs/editor/common/services/resolverService'; @@ -50,14 +49,13 @@ export class NativeTextFileService extends AbstractTextFileService { @IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService, @IDialogService dialogService: IDialogService, @IFileDialogService fileDialogService: IFileDialogService, - @IEditorService editorService: IEditorService, @ITextResourceConfigurationService textResourceConfigurationService: ITextResourceConfigurationService, @IProductService private readonly productService: IProductService, @IFilesConfigurationService filesConfigurationService: IFilesConfigurationService, @ITextModelService textModelService: ITextModelService, @ICodeEditorService codeEditorService: ICodeEditorService ) { - super(fileService, untitledTextEditorService, lifecycleService, instantiationService, modelService, environmentService, dialogService, fileDialogService, editorService, textResourceConfigurationService, filesConfigurationService, textModelService, codeEditorService); + super(fileService, untitledTextEditorService, lifecycleService, instantiationService, modelService, environmentService, dialogService, fileDialogService, textResourceConfigurationService, filesConfigurationService, textModelService, codeEditorService); } private _encoding: EncodingOracle | undefined; diff --git a/src/vs/workbench/services/textfile/test/textFileService.test.ts b/src/vs/workbench/services/textfile/test/textFileService.test.ts index 63f2405cada..ff656aaf791 100644 --- a/src/vs/workbench/services/textfile/test/textFileService.test.ts +++ b/src/vs/workbench/services/textfile/test/textFileService.test.ts @@ -101,7 +101,7 @@ suite('Files - TextFileService', () => { test('saveAs - file', async function () { model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file.txt'), 'utf8', undefined); (accessor.textFileService.files).add(model.resource, model); - accessor.textFileService.setPromptPath(model.resource); + accessor.fileDialogService.setPickFileToSave(model.resource); await model.load(); model.textEditorModel!.setValue('foo'); @@ -115,7 +115,7 @@ suite('Files - TextFileService', () => { test('revert - file', async function () { model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file.txt'), 'utf8', undefined); (accessor.textFileService.files).add(model.resource, model); - accessor.textFileService.setPromptPath(model.resource); + accessor.fileDialogService.setPickFileToSave(model.resource); await model.load(); model!.textEditorModel!.setValue('foo'); diff --git a/src/vs/workbench/test/workbenchTestServices.ts b/src/vs/workbench/test/workbenchTestServices.ts index 3b94817b811..4075d675593 100644 --- a/src/vs/workbench/test/workbenchTestServices.ts +++ b/src/vs/workbench/test/workbenchTestServices.ts @@ -202,7 +202,6 @@ export class TestContextService implements IWorkspaceContextService { } export class TestTextFileService extends NativeTextFileService { - private promptPath!: URI; private resolveTextContentError!: FileOperationError | null; constructor( @@ -214,7 +213,6 @@ export class TestTextFileService extends NativeTextFileService { @IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService, @IDialogService dialogService: IDialogService, @IFileDialogService fileDialogService: IFileDialogService, - @IEditorService editorService: IEditorService, @ITextResourceConfigurationService textResourceConfigurationService: ITextResourceConfigurationService, @IProductService productService: IProductService, @IFilesConfigurationService filesConfigurationService: IFilesConfigurationService, @@ -230,7 +228,6 @@ export class TestTextFileService extends NativeTextFileService { environmentService, dialogService, fileDialogService, - editorService, textResourceConfigurationService, productService, filesConfigurationService, @@ -239,10 +236,6 @@ export class TestTextFileService extends NativeTextFileService { ); } - setPromptPath(path: URI): void { - this.promptPath = path; - } - setResolveTextContentErrorOnce(error: FileOperationError): void { this.resolveTextContentError = error; } @@ -267,10 +260,6 @@ export class TestTextFileService extends NativeTextFileService { size: 10 }; } - - promptForPath(_resource: URI, availableFileSystems?: string[]): Promise { - return Promise.resolve(this.promptPath); - } } export interface ITestInstantiationService extends IInstantiationService { @@ -423,8 +412,12 @@ export class TestFileDialogService implements IFileDialogService { pickWorkspaceAndOpen(_options: IPickAndOpenOptions): Promise { return Promise.resolve(0); } + private fileToSave!: URI; + setPickFileToSave(path: URI): void { + this.fileToSave = path; + } pickFileToSave(defaultUri: URI, availableFileSystems?: string[]): Promise { - return Promise.resolve(undefined); + return Promise.resolve(this.fileToSave); } showSaveDialog(_options: ISaveDialogOptions): Promise { return Promise.resolve(undefined); From f4b952c9043ae5c6dda41f53cb455ddb9eb5d7f2 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Sun, 26 Jan 2020 14:03:49 +0100 Subject: [PATCH 125/801] editors - move replaceEditors call into editor service --- .../browser/parts/editor/editorGroupView.ts | 4 +- src/vs/workbench/common/editor.ts | 71 ++++++------------- .../customEditor/browser/customEditorInput.ts | 37 +++++----- .../preferences/browser/preferencesEditor.ts | 2 +- .../search/browser/searchEditorInput.ts | 20 +++--- .../services/editor/browser/editorService.ts | 23 +++++- .../editor/test/browser/editorService.test.ts | 10 +-- 7 files changed, 78 insertions(+), 89 deletions(-) diff --git a/src/vs/workbench/browser/parts/editor/editorGroupView.ts b/src/vs/workbench/browser/parts/editor/editorGroupView.ts index 4e1ef44c838..b83e75ca610 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, SideBySideEditorInput, CloseDirection, IEditorCloseEvent, EditorGroupActiveEditorDirtyContext, IEditor, EditorGroupEditorsCountContext, SaveReason, SaveContext, IEditorPartOptionsChangeEvent, EditorsOrder } from 'vs/workbench/common/editor'; +import { EditorInput, EditorOptions, GroupIdentifier, SideBySideEditorInput, CloseDirection, IEditorCloseEvent, EditorGroupActiveEditorDirtyContext, IEditor, EditorGroupEditorsCountContext, SaveReason, IEditorPartOptionsChangeEvent, EditorsOrder } 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'; @@ -1330,7 +1330,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { // Otherwise, handle accordingly switch (res) { case ConfirmResult.SAVE: - const result = await editor.save(this.id, { reason: SaveReason.EXPLICIT, context: SaveContext.EDITOR_CLOSE }); + const result = await editor.save(this.id, { reason: SaveReason.EXPLICIT }); return !result; case ConfirmResult.DONT_SAVE: diff --git a/src/vs/workbench/common/editor.ts b/src/vs/workbench/common/editor.ts index 17810628940..c42abbee5c9 100644 --- a/src/vs/workbench/common/editor.ts +++ b/src/vs/workbench/common/editor.ts @@ -26,7 +26,6 @@ import { IEditorService } from 'vs/workbench/services/editor/common/editorServic import { isEqual } from 'vs/base/common/resources'; import { IPanel } from 'vs/workbench/common/panel'; import { IRange } from 'vs/editor/common/core/range'; -import { Schemas } from 'vs/base/common/network'; export const DirtyWorkingCopiesContext = new RawContextKey('dirtyWorkingCopies', false); export const ActiveEditorContext = new RawContextKey('activeEditor', null); @@ -300,15 +299,6 @@ export const enum SaveReason { WINDOW_CHANGE = 4 } -export const enum SaveContext { - - /** - * Indicates that the editor is saved because it - * is being closed by the user. - */ - EDITOR_CLOSE = 1, -} - export interface ISaveOptions { /** @@ -316,11 +306,6 @@ export interface ISaveOptions { */ reason?: SaveReason; - /** - * Additional information about the context of the save. - */ - context?: SaveContext; - /** * Forces to save the contents of the working copy * again even if the working copy is not dirty. @@ -430,15 +415,21 @@ export interface IEditorInput extends IDisposable { * Saves the editor. The provided groupId helps implementors * to e.g. preserve view state of the editor and re-open it * in the correct group after saving. + * + * @returns the resulting editor input of this operation. Can + * be the same editor input. */ - save(group: GroupIdentifier, options?: ISaveOptions): Promise; + save(group: GroupIdentifier, options?: ISaveOptions): Promise; /** * Saves the editor to a different location. The provided groupId * helps implementors to e.g. preserve view state of the editor * and re-open it in the correct group after saving. + * + * @returns the resulting editor input of this operation. Typically + * a different editor input. */ - saveAs(group: GroupIdentifier, options?: ISaveOptions): Promise; + saveAs(group: GroupIdentifier, options?: ISaveOptions): Promise; /** * Reverts this input from the provided group. @@ -535,12 +526,12 @@ export abstract class EditorInput extends Disposable implements IEditorInput { return false; } - async save(group: GroupIdentifier, options?: ISaveOptions): Promise { - return true; + async save(group: GroupIdentifier, options?: ISaveOptions): Promise { + return this; } - async saveAs(group: GroupIdentifier, options?: ISaveOptions): Promise { - return true; + async saveAs(group: GroupIdentifier, options?: ISaveOptions): Promise { + return this; } async revert(group: GroupIdentifier, options?: IRevertOptions): Promise { @@ -587,27 +578,15 @@ export abstract class TextEditorInput extends EditorInput { return this.resource; } - async save(group: GroupIdentifier, options?: ITextFileSaveOptions): Promise { + async save(group: GroupIdentifier, options?: ITextFileSaveOptions): Promise { return this.doSave(group, options, false); } - saveAs(group: GroupIdentifier, options?: ITextFileSaveOptions): Promise { + saveAs(group: GroupIdentifier, options?: ITextFileSaveOptions): Promise { return this.doSave(group, options, true); } - private async doSave(group: GroupIdentifier, options: ISaveOptions | undefined, saveAs: boolean): Promise { - const isUntitled = this.resource.scheme === Schemas.untitled; - - // Preserve view state by opening the editor first if the editor - // is untitled or we "Save As" and we are not closing editors. - // In addition this allows the user to review the contents of the editor. - let viewState: IEditorViewState | undefined = undefined; - if (options?.context !== SaveContext.EDITOR_CLOSE && (saveAs || isUntitled)) { - const editor = await this.editorService.openEditor(this, undefined, group); - if (isTextEditor(editor)) { - viewState = editor.getViewState(); - } - } + private async doSave(group: GroupIdentifier, options: ISaveOptions | undefined, saveAs: boolean): Promise { // Save / Save As let target: URI | undefined; @@ -618,22 +597,14 @@ export abstract class TextEditorInput extends EditorInput { } if (!target) { - return false; // save cancelled + return undefined; // save cancelled } - // Replace editor preserving viewstate (either across all groups or - // only selected group) if the target is different from the current resource - // and if the editor is not being saved because it is being closed - // (because in that case we do not want to open a different editor anyway) - if (options?.context !== SaveContext.EDITOR_CLOSE && !isEqual(target, this.resource)) { - const replacement = this.editorService.createInput({ resource: target }); - const targetGroups = isUntitled ? this.editorGroupService.groups.map(group => group.id) /* untitled replaces across all groups */ : [group]; - for (const group of targetGroups) { - await this.editorService.replaceEditors([{ editor: this, replacement, options: { pinned: true, viewState } }], group); - } + if (!isEqual(target, this.resource)) { + return this.editorService.createInput({ resource: target }); } - return true; + return this; } revert(group: GroupIdentifier, options?: IRevertOptions): Promise { @@ -760,11 +731,11 @@ export class SideBySideEditorInput extends EditorInput { return this.master.isSaving(); } - save(group: GroupIdentifier, options?: ISaveOptions): Promise { + save(group: GroupIdentifier, options?: ISaveOptions): Promise { return this.master.save(group, options); } - saveAs(group: GroupIdentifier, options?: ISaveOptions): Promise { + saveAs(group: GroupIdentifier, options?: ISaveOptions): Promise { return this.master.saveAs(group, options); } diff --git a/src/vs/workbench/contrib/customEditor/browser/customEditorInput.ts b/src/vs/workbench/contrib/customEditor/browser/customEditorInput.ts index e4a15129bcf..57b85f7a094 100644 --- a/src/vs/workbench/contrib/customEditor/browser/customEditorInput.ts +++ b/src/vs/workbench/contrib/customEditor/browser/customEditorInput.ts @@ -14,13 +14,12 @@ import { IFileDialogService } from 'vs/platform/dialogs/common/dialogs'; import { IEditorModel, ITextEditorOptions } from 'vs/platform/editor/common/editor'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ILabelService } from 'vs/platform/label/common/label'; -import { GroupIdentifier, IEditorInput, IRevertOptions, ISaveOptions, SaveContext, Verbosity } from 'vs/workbench/common/editor'; +import { GroupIdentifier, IEditorInput, IRevertOptions, ISaveOptions, Verbosity } from 'vs/workbench/common/editor'; import { ICustomEditorModel, ICustomEditorService } from 'vs/workbench/contrib/customEditor/common/customEditor'; -import { FileEditorInput } from 'vs/workbench/contrib/files/common/editors/fileEditorInput'; import { WebviewEditorOverlay, IWebviewService } from 'vs/workbench/contrib/webview/browser/webview'; import { IWebviewWorkbenchService, LazilyResolvedWebviewEditorInput } from 'vs/workbench/contrib/webview/browser/webviewWorkbenchService'; -import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { AutoSaveMode, IFilesConfigurationService } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; export class CustomFileEditorInput extends LazilyResolvedWebviewEditorInput { @@ -39,9 +38,9 @@ export class CustomFileEditorInput extends LazilyResolvedWebviewEditorInput { @IInstantiationService private readonly instantiationService: IInstantiationService, @ILabelService private readonly labelService: ILabelService, @ICustomEditorService private readonly customEditorService: ICustomEditorService, - @IEditorService private readonly editorService: IEditorService, @IFileDialogService private readonly fileDialogService: IFileDialogService, - @IFilesConfigurationService private readonly filesConfigurationService: IFilesConfigurationService + @IFilesConfigurationService private readonly filesConfigurationService: IFilesConfigurationService, + @IEditorService private readonly editorService: IEditorService ) { super(id, viewType, '', webview, webviewService, webviewWorkbenchService); this._editorResource = resource; @@ -122,31 +121,35 @@ export class CustomFileEditorInput extends LazilyResolvedWebviewEditorInput { return false; } - public save(groupId: GroupIdentifier, options?: ISaveOptions): Promise { - return this._model ? this._model.save(options) : Promise.resolve(false); + public async save(groupId: GroupIdentifier, options?: ISaveOptions): Promise { + if (!this._model) { + return undefined; + } + + const result = await this._model.save(options); + if (!result) { + return undefined; + } + + return this; } - public async saveAs(groupId: GroupIdentifier, options?: ISaveOptions): Promise { + public async saveAs(groupId: GroupIdentifier, options?: ISaveOptions): Promise { if (!this._model) { - return false; + return undefined; } let dialogPath = this._editorResource; const target = await this.fileDialogService.pickFileToSave(dialogPath, options?.availableFileSystems); if (!target) { - return false; // save cancelled + return undefined; // save cancelled } if (!await this._model.saveAs(this._editorResource, target, options)) { - return false; + return undefined; } - if (options?.context !== SaveContext.EDITOR_CLOSE) { - const replacement = this.handleMove(groupId, target) || this.instantiationService.createInstance(FileEditorInput, target, undefined, undefined); - await this.editorService.replaceEditors([{ editor: this, replacement, options: { pinned: true } }], groupId); - } - - return true; + return this.handleMove(groupId, target) || this.editorService.createInput({ resource: target, forceFile: true }); } public revert(group: GroupIdentifier, options?: IRevertOptions): Promise { diff --git a/src/vs/workbench/contrib/preferences/browser/preferencesEditor.ts b/src/vs/workbench/contrib/preferences/browser/preferencesEditor.ts index 87b7abe01b3..89b598ba0af 100644 --- a/src/vs/workbench/contrib/preferences/browser/preferencesEditor.ts +++ b/src/vs/workbench/contrib/preferences/browser/preferencesEditor.ts @@ -252,7 +252,7 @@ export class PreferencesEditor extends BaseEditor { if (this.editorService.activeControl !== this) { this.focus(); } - const promise: Promise = this.input && this.input.isDirty() ? this.input.save(this.group!.id) : Promise.resolve(true); + const promise: Promise = this.input && this.input.isDirty() ? this.input.save(this.group!.id).then(editor => !!editor) : Promise.resolve(true); promise.then(() => { if (target === ConfigurationTarget.USER_LOCAL) { this.preferencesService.switchSettings(ConfigurationTarget.USER_LOCAL, this.preferencesService.userSettingsResource, true); diff --git a/src/vs/workbench/contrib/search/browser/searchEditorInput.ts b/src/vs/workbench/contrib/search/browser/searchEditorInput.ts index 9d79d36999e..035ad1164e5 100644 --- a/src/vs/workbench/contrib/search/browser/searchEditorInput.ts +++ b/src/vs/workbench/contrib/search/browser/searchEditorInput.ts @@ -11,7 +11,7 @@ import { ITextModel, ITextBufferFactory } from 'vs/editor/common/model'; import { localize } from 'vs/nls'; import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { IEditorInputFactory, GroupIdentifier, EditorInput, SaveContext, IRevertOptions } from 'vs/workbench/common/editor'; +import { IEditorInputFactory, GroupIdentifier, EditorInput, IRevertOptions, IEditorInput } from 'vs/workbench/common/editor'; import { IModelService } from 'vs/editor/common/services/modelService'; import { IModeService } from 'vs/editor/common/services/modeService'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; @@ -76,7 +76,7 @@ export class SearchEditorInput extends EditorInput { onDidChangeContent: this.onDidChangeDirty, isDirty: () => this.isDirty(), backup: () => this.backup(), - save: (options) => this.save(0, options), + save: (options) => this.save(0, options).then(editor => !!editor), revert: () => this.revert(0), }; @@ -87,25 +87,23 @@ export class SearchEditorInput extends EditorInput { return this.resource; } - async save(group: GroupIdentifier, options?: ITextFileSaveOptions): Promise { + async save(group: GroupIdentifier, options?: ITextFileSaveOptions): Promise { if (this.resource.scheme === 'search-editor') { const path = await this.fileDialogService.pickFileToSave(await this.suggestFileName(), options?.availableFileSystems); if (path) { if (await this.textFileService.saveAs(this.resource, path, options)) { this.setDirty(false); - if (options?.context !== SaveContext.EDITOR_CLOSE && !isEqual(path, this.resource)) { - const replacement = this.instantiationService.invokeFunction(getOrMakeSearchEditorInput, { uri: path }); - await this.editorService.replaceEditors([{ editor: this, replacement, options: { pinned: true } }], group); - return true; - } else if (options?.context === SaveContext.EDITOR_CLOSE) { - return true; + if (!isEqual(path, this.resource)) { + return this.instantiationService.invokeFunction(getOrMakeSearchEditorInput, { uri: path }); } + return this; } } - return false; + return undefined; } else { this.setDirty(false); - return !!this.textFileService.write(this.resource, (await this.model).getValue(), options); + const res = await !!this.textFileService.write(this.resource, (await this.model).getValue(), options); + return res ? this : undefined; } } diff --git a/src/vs/workbench/services/editor/browser/editorService.ts b/src/vs/workbench/services/editor/browser/editorService.ts index 252650f4386..c3475bd870a 100644 --- a/src/vs/workbench/services/editor/browser/editorService.ts +++ b/src/vs/workbench/services/editor/browser/editorService.ts @@ -5,7 +5,7 @@ import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { IResourceInput, ITextEditorOptions, IEditorOptions, EditorActivation } from 'vs/platform/editor/common/editor'; -import { IEditorInput, IEditor, GroupIdentifier, IFileEditorInput, IUntitledTextResourceInput, IResourceDiffInput, IResourceSideBySideInput, IEditorInputFactoryRegistry, Extensions as EditorExtensions, IFileInputFactory, EditorInput, SideBySideEditorInput, IEditorInputWithOptions, isEditorInputWithOptions, EditorOptions, TextEditorOptions, IEditorIdentifier, IEditorCloseEvent, ITextEditor, ITextDiffEditor, ITextSideBySideEditor, IRevertOptions, SaveReason, EditorsOrder } from 'vs/workbench/common/editor'; +import { IEditorInput, IEditor, GroupIdentifier, IFileEditorInput, IUntitledTextResourceInput, IResourceDiffInput, IResourceSideBySideInput, IEditorInputFactoryRegistry, Extensions as EditorExtensions, IFileInputFactory, EditorInput, SideBySideEditorInput, IEditorInputWithOptions, isEditorInputWithOptions, EditorOptions, TextEditorOptions, IEditorIdentifier, IEditorCloseEvent, ITextEditor, ITextDiffEditor, ITextSideBySideEditor, IRevertOptions, SaveReason, EditorsOrder, isTextEditor } from 'vs/workbench/common/editor'; import { ResourceEditorInput } from 'vs/workbench/common/editor/resourceEditorInput'; import { Registry } from 'vs/platform/registry/common/platform'; import { ResourceMap } from 'vs/base/common/map'; @@ -27,6 +27,7 @@ import { ILabelService } from 'vs/platform/label/common/label'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { withNullAsUndefined } from 'vs/base/common/types'; import { EditorsObserver } from 'vs/workbench/browser/parts/editor/editorsObserver'; +import { IEditorViewState } from 'vs/editor/common/editorCommon'; type CachedEditorInput = ResourceEditorInput | IFileEditorInput; type OpenInEditorGroup = IEditorGroup | GroupIdentifier | SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE; @@ -712,13 +713,29 @@ export class EditorService extends Disposable implements EditorServiceImpl { continue; // might have been disposed from from the save already } - // bring editor to front to help user make a decision about file names - await this.openEditor(editor, undefined, groupId); + // Preserve view state by opening the editor first if the editor + // is untitled or we "Save As". This also allows the user to review + // the contents of the editor before making a decision. + let viewState: IEditorViewState | undefined = undefined; + const control = await this.openEditor(editor, undefined, groupId); + if (isTextEditor(control)) { + viewState = control.getViewState(); + } const result = options?.saveAs ? await editor.saveAs(groupId, options) : await editor.save(groupId, options); if (!result) { return false; // failed or cancelled, abort } + + // Replace editor preserving viewstate (either across all groups or + // only selected group) if the resulting editor is different from the + // current one. + if (!result.matches(editor)) { + const targetGroups = editor.isUntitled() ? this.editorGroupService.groups.map(group => group.id) /* untitled replaces across all groups */ : [groupId]; + for (const group of targetGroups) { + await this.replaceEditors([{ editor, replacement: result, options: { pinned: true, viewState } }], group); + } + } } return true; diff --git a/src/vs/workbench/services/editor/test/browser/editorService.test.ts b/src/vs/workbench/services/editor/test/browser/editorService.test.ts index 3e2fe4b515a..ddec69f8f72 100644 --- a/src/vs/workbench/services/editor/test/browser/editorService.test.ts +++ b/src/vs/workbench/services/editor/test/browser/editorService.test.ts @@ -7,7 +7,7 @@ import * as assert from 'assert'; import { EditorActivation, IEditorModel } from 'vs/platform/editor/common/editor'; import { URI } from 'vs/base/common/uri'; import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor'; -import { EditorInput, EditorOptions, IFileEditorInput, GroupIdentifier, ISaveOptions, IRevertOptions, EditorsOrder } from 'vs/workbench/common/editor'; +import { EditorInput, EditorOptions, IFileEditorInput, GroupIdentifier, ISaveOptions, IRevertOptions, EditorsOrder, IEditorInput } from 'vs/workbench/common/editor'; import { workbenchInstantiationService, TestStorageService } from 'vs/workbench/test/workbenchTestServices'; import { ResourceEditorInput } from 'vs/workbench/common/editor/resourceEditorInput'; import { TestThemeService } from 'vs/platform/theme/test/common/testThemeService'; @@ -72,13 +72,13 @@ class TestEditorInput extends EditorInput implements IFileEditorInput { setFailToOpen(): void { this.fails = true; } - async save(groupId: GroupIdentifier, options?: ISaveOptions): Promise { + async save(groupId: GroupIdentifier, options?: ISaveOptions): Promise { this.gotSaved = true; - return true; + return this; } - async saveAs(groupId: GroupIdentifier, options?: ISaveOptions): Promise { + async saveAs(groupId: GroupIdentifier, options?: ISaveOptions): Promise { this.gotSavedAs = true; - return true; + return this; } async revert(group: GroupIdentifier, options?: IRevertOptions): Promise { this.gotReverted = true; From 467c9990926539155a6c444187bf3a0f0485699d Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Sun, 26 Jan 2020 17:47:38 +0100 Subject: [PATCH 126/801] Fix #86297 --- .../common/userDataSyncService.ts | 23 +++++-- .../userDataSync/browser/userDataSync.ts | 61 ++++++++++--------- 2 files changed, 51 insertions(+), 33 deletions(-) diff --git a/src/vs/platform/userDataSync/common/userDataSyncService.ts b/src/vs/platform/userDataSync/common/userDataSyncService.ts index 270bab9d5a2..770f57e941f 100644 --- a/src/vs/platform/userDataSync/common/userDataSyncService.ts +++ b/src/vs/platform/userDataSync/common/userDataSyncService.ts @@ -14,6 +14,11 @@ import { KeybindingsSynchroniser } from 'vs/platform/userDataSync/common/keybind import { GlobalStateSynchroniser } from 'vs/platform/userDataSync/common/globalStateSync'; import { toErrorMessage } from 'vs/base/common/errorMessage'; import { localize } from 'vs/nls'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; + +type SyncConflictsClassification = { + source?: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true }; +}; export class UserDataSyncService extends Disposable implements IUserDataSyncService { @@ -41,6 +46,7 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ @ISettingsSyncService private readonly settingsSynchroniser: ISettingsSyncService, @IUserDataSyncLogService private readonly logService: IUserDataSyncLogService, @IUserDataAuthTokenService private readonly userDataAuthTokenService: IUserDataAuthTokenService, + @ITelemetryService private readonly telemetryService: ITelemetryService, ) { super(); this.keybindingsSynchroniser = this._register(this.instantiationService.createInstance(KeybindingsSynchroniser)); @@ -252,13 +258,20 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ } private updateStatus(): void { - this._conflictsSource = this.computeConflictsSource(); - this.setStatus(this.computeStatus()); - } - - private setStatus(status: SyncStatus): void { + const status = this.computeStatus(); if (this._status !== status) { + const oldStatus = this._status; + const oldConflictsSource = this._conflictsSource; + this._conflictsSource = this.computeConflictsSource(); this._status = status; + if (status === SyncStatus.HasConflicts) { + // Log to telemetry when there is a sync conflict + this.telemetryService.publicLog2<{ source: string }, SyncConflictsClassification>('sync/conflictsDetected', { source: this._conflictsSource! }); + } + if (oldStatus === SyncStatus.HasConflicts && status === SyncStatus.Idle) { + // Log to telemetry when conflicts are resolved + this.telemetryService.publicLog2<{ source: string }, SyncConflictsClassification>('sync/conflictsResolved', { source: oldConflictsSource! }); + } this._onDidChangeStatus.fire(status); } } diff --git a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts index c6f4d0e9352..34eaab54565 100644 --- a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts +++ b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts @@ -41,11 +41,10 @@ import type { IEditorContribution } from 'vs/editor/common/editorCommon'; import type { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { FloatingClickWidget } from 'vs/workbench/browser/parts/editor/editorWidgets'; import { registerEditorContribution } from 'vs/editor/browser/editorExtensions'; -import { areSame } from 'vs/platform/userDataSync/common/settingsMerge'; -import { getIgnoredSettings } from 'vs/platform/userDataSync/common/settingsSync'; import type { IEditorInput } from 'vs/workbench/common/editor'; import { Action } from 'vs/base/common/actions'; import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; const enum AuthStatus { Initializing = 'Initializing', @@ -65,6 +64,14 @@ function getSyncAreaLabel(source: SyncSource): string { } } +type FirstTimeSyncClassification = { + action: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true }; +}; + +type SyncErrorClassification = { + source: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true }; +}; + export class UserDataSyncWorkbenchContribution extends Disposable implements IWorkbenchContribution { private static readonly ENABLEMENT_SETTING = 'sync.enable'; @@ -92,8 +99,9 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo @IOutputService private readonly outputService: IOutputService, @IUserDataAuthTokenService private readonly userDataAuthTokenService: IUserDataAuthTokenService, @IUserDataAutoSyncService userDataAutoSyncService: IUserDataAutoSyncService, - @ITextModelService private readonly textModelResolverService: ITextModelService, + @ITextModelService textModelResolverService: ITextModelService, @IPreferencesService private readonly preferencesService: IPreferencesService, + @ITelemetryService private readonly telemetryService: ITelemetryService, ) { super(); this.userDataSyncStore = getUserDataSyncStore(configurationService); @@ -253,6 +261,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo private onAutoSyncError(code: UserDataSyncErrorCode, source?: SyncSource): void { switch (code) { case UserDataSyncErrorCode.TooManyFailures: + this.telemetryService.publicLog2('sync/errorTooMany'); this.disableSync(); this.notificationService.notify({ severity: Severity.Error, @@ -263,6 +272,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo }); return; case UserDataSyncErrorCode.TooLarge: + this.telemetryService.publicLog2<{ source: string }, SyncErrorClassification>('sync/errorTooLarge', { source: source! }); if (source === SyncSource.Keybindings || source === SyncSource.Settings) { const sourceArea = getSyncAreaLabel(source); this.disableSync(); @@ -418,9 +428,17 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo } ); switch (result.choice) { - case 0: await this.userDataSyncService.sync(); break; - case 1: throw canceled(); - case 2: await this.userDataSyncService.pull(); break; + case 0: + this.telemetryService.publicLog2<{ action: string }, FirstTimeSyncClassification>('sync/firstTimeSync', { action: 'merge' }); + await this.userDataSyncService.sync(); + break; + case 1: + this.telemetryService.publicLog2<{ action: string }, FirstTimeSyncClassification>('sync/firstTimeSync', { action: 'cancelled' }); + throw canceled(); + case 2: + this.telemetryService.publicLog2<{ action: string }, FirstTimeSyncClassification>('sync/firstTimeSync', { action: 'replace-local' }); + await this.userDataSyncService.pull(); + break; } } @@ -441,6 +459,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo if (result.confirmed) { await this.disableSync(); if (result.checkboxChecked) { + this.telemetryService.publicLog2('sync/turnOffEveryWhere'); await this.userDataSyncService.reset(); } } @@ -492,7 +511,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo } if (previewResource) { const remoteContentResource = toRemoteContentResource(this.userDataSyncService.conflictsSource!); - const editor = await this.editorService.openEditor({ + await this.editorService.openEditor({ leftResource: remoteContentResource, rightResource: previewResource, label, @@ -502,27 +521,6 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo revealIfVisible: true, }, }); - if (editor?.input) { - const disposable = editor.input.onDispose(async () => { - disposable.dispose(); - const source = getSyncSourceFromRemoteContentResource(remoteContentResource); - if (source === undefined || this.userDataSyncService.conflictsSource !== source) { - return; - } - - const remoteModelRef = await this.textModelResolverService.createModelReference(remoteContentResource); - const previewModelRef = await this.textModelResolverService.createModelReference(previewResource!); - const remoteModelContent = remoteModelRef.object.textEditorModel.getValue(); - const preivewContent = previewModelRef.object.textEditorModel.getValue(); - remoteModelRef.dispose(); - previewModelRef.dispose(); - if (remoteModelContent !== preivewContent - || (source === SyncSource.Settings && !areSame(remoteModelContent, preivewContent, getIgnoredSettings(this.configurationService)))) { - return; - } - await this.userDataSyncService.resolveConflictsAndContinueSync(preivewContent); - }); - } } } @@ -682,6 +680,11 @@ class UserDataRemoteContentProvider implements ITextModelContentProvider { } } +type SyncConflictsClassification = { + source: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true }; + action: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true }; +}; + class AcceptChangesContribution extends Disposable implements IEditorContribution { static get(editor: ICodeEditor): AcceptChangesContribution { @@ -700,6 +703,7 @@ class AcceptChangesContribution extends Disposable implements IEditorContributio @INotificationService private readonly notificationService: INotificationService, @IDialogService private readonly dialogService: IDialogService, @IConfigurationService private readonly configurationService: IConfigurationService, + @ITelemetryService private readonly telemetryService: ITelemetryService ) { super(); @@ -760,6 +764,7 @@ class AcceptChangesContribution extends Disposable implements IEditorContributio if (model) { const conflictsSource = this.userDataSyncService.conflictsSource; const syncSource = getSyncSourceFromRemoteContentResource(model.uri); + this.telemetryService.publicLog2<{ source: string, action: string }, SyncConflictsClassification>('sync/handleConflicts', { source: conflictsSource!, action: syncSource !== undefined ? 'replaceLocal' : 'apply' }); if (syncSource !== undefined) { const syncAreaLabel = getSyncAreaLabel(syncSource); const result = await this.dialogService.confirm({ From c13bf7adf997c19c6b9fb663ef8a145df7043dfb Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Sun, 26 Jan 2020 18:08:00 +0100 Subject: [PATCH 127/801] add telelemtry --- .../workbench/contrib/userDataSync/browser/userDataSync.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts index 34eaab54565..b42fb3f2583 100644 --- a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts +++ b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts @@ -221,7 +221,10 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo [ { label: localize('show conflicts', "Show Conflicts"), - run: () => this.handleConflicts() + run: () => { + this.telemetryService.publicLog2('sync/showConflicts'); + this.handleConflicts(); + } } ], { From e715f396b356233547eb392a7c72df01e8a92a88 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Sun, 26 Jan 2020 19:10:13 +0100 Subject: [PATCH 128/801] :lipstick: --- .../common/abstractSynchronizer.ts | 101 +++++++++++++++++- .../userDataSync/common/extensionsSync.ts | 95 ++-------------- .../userDataSync/common/globalStateSync.ts | 74 ++----------- .../userDataSync/common/keybindingsSync.ts | 91 ++-------------- .../userDataSync/common/settingsSync.ts | 98 +++-------------- .../userDataSync/common/userDataSync.ts | 5 +- .../userDataSync/common/userDataSyncIpc.ts | 1 - .../common/userDataSyncService.ts | 7 +- .../userDataSync/browser/userDataSync.ts | 4 +- .../electron-browser/userDataSyncService.ts | 5 - 10 files changed, 138 insertions(+), 343 deletions(-) diff --git a/src/vs/platform/userDataSync/common/abstractSynchronizer.ts b/src/vs/platform/userDataSync/common/abstractSynchronizer.ts index ded00afd7ce..53f53612572 100644 --- a/src/vs/platform/userDataSync/common/abstractSynchronizer.ts +++ b/src/vs/platform/userDataSync/common/abstractSynchronizer.ts @@ -4,30 +4,87 @@ *--------------------------------------------------------------------------------------------*/ import { Disposable } from 'vs/base/common/lifecycle'; -import { IFileService } from 'vs/platform/files/common/files'; +import { IFileService, IFileContent } from 'vs/platform/files/common/files'; import { VSBuffer } from 'vs/base/common/buffer'; import { URI } from 'vs/base/common/uri'; -import { SyncSource } from 'vs/platform/userDataSync/common/userDataSync'; +import { SyncSource, SyncStatus, IUserData, IUserDataSyncStoreService } from 'vs/platform/userDataSync/common/userDataSync'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -import { joinPath } from 'vs/base/common/resources'; +import { joinPath, dirname } from 'vs/base/common/resources'; import { toLocalISOString } from 'vs/base/common/date'; import { ThrottledDelayer } from 'vs/base/common/async'; +import { Emitter, Event } from 'vs/base/common/event'; export abstract class AbstractSynchroniser extends Disposable { protected readonly syncFolder: URI; private cleanUpDelayer: ThrottledDelayer; + private _status: SyncStatus = SyncStatus.Idle; + get status(): SyncStatus { return this._status; } + private _onDidChangStatus: Emitter = this._register(new Emitter()); + readonly onDidChangeStatus: Event = this._onDidChangStatus.event; + + protected readonly _onDidChangeLocal: Emitter = this._register(new Emitter()); + readonly onDidChangeLocal: Event = this._onDidChangeLocal.event; + + protected readonly lastSyncResource: URI; + constructor( readonly source: SyncSource, @IFileService protected readonly fileService: IFileService, - @IEnvironmentService environmentService: IEnvironmentService + @IEnvironmentService environmentService: IEnvironmentService, + @IUserDataSyncStoreService protected readonly userDataSyncStoreService: IUserDataSyncStoreService, ) { super(); this.syncFolder = joinPath(environmentService.userDataSyncHome, source); + this.lastSyncResource = joinPath(this.syncFolder, `.lastSync${source}.json`); this.cleanUpDelayer = new ThrottledDelayer(50); } + protected setStatus(status: SyncStatus): void { + if (this._status !== status) { + this._status = status; + this._onDidChangStatus.fire(status); + } + } + + async hasPreviouslySynced(): Promise { + const lastSyncData = await this.getLastSyncUserData(); + return !!lastSyncData; + } + + async hasRemoteData(): Promise { + const remoteUserData = await this.getRemoteUserData(); + return remoteUserData.content !== null; + } + + async resetLocal(): Promise { + try { + await this.fileService.del(this.lastSyncResource); + } catch (e) { /* ignore */ } + } + + protected async getLastSyncUserData(): Promise { + try { + const content = await this.fileService.readFile(this.lastSyncResource); + return JSON.parse(content.value.toString()); + } catch (error) { + return null; + } + } + + protected async updateLastSyncUserData(lastSyncUserData: T): Promise { + await this.fileService.writeFile(this.lastSyncResource, VSBuffer.fromString(JSON.stringify(lastSyncUserData))); + } + + protected getRemoteUserData(lastSyncData?: IUserData | null): Promise { + return this.userDataSyncStoreService.read(this.getRemoteDataResourceKey(), lastSyncData || null, this.source); + } + + protected async updateRemoteUserData(content: string, ref: string | null): Promise { + return this.userDataSyncStoreService.write(this.getRemoteDataResourceKey(), content, ref, this.source); + } + protected async backupLocal(content: VSBuffer): Promise { const resource = joinPath(this.syncFolder, toLocalISOString(new Date()).replace(/-|:|\.\d+Z$/g, '')); await this.fileService.writeFile(resource, content); @@ -43,4 +100,40 @@ export abstract class AbstractSynchroniser extends Disposable { } } + protected abstract getRemoteDataResourceKey(): string; +} + +export abstract class AbstractFileSynchroniser extends AbstractSynchroniser { + + constructor( + protected readonly file: URI, + readonly source: SyncSource, + @IFileService fileService: IFileService, + @IEnvironmentService environmentService: IEnvironmentService, + @IUserDataSyncStoreService userDataSyncStoreService: IUserDataSyncStoreService, + ) { + super(source, fileService, environmentService, userDataSyncStoreService); + this._register(this.fileService.watch(dirname(file))); + this._register(Event.filter(this.fileService.onFileChanges, e => e.contains(file))(() => this._onDidChangeLocal.fire())); + } + + protected async getLocalFileContent(): Promise { + try { + return await this.fileService.readFile(this.file); + } catch (error) { + return null; + } + } + + protected async updateLocalFileContent(newContent: string, oldContent: IFileContent | null): Promise { + if (oldContent) { + // file exists already + await this.backupLocal(oldContent.value); + await this.fileService.writeFile(this.file, VSBuffer.fromString(newContent), oldContent); + } else { + // file does not exist + await this.fileService.createFile(this.file, VSBuffer.fromString(newContent), { overwrite: false }); + } + } + } diff --git a/src/vs/platform/userDataSync/common/extensionsSync.ts b/src/vs/platform/userDataSync/common/extensionsSync.ts index 82eb46f185a..95d03f6bc0b 100644 --- a/src/vs/platform/userDataSync/common/extensionsSync.ts +++ b/src/vs/platform/userDataSync/common/extensionsSync.ts @@ -4,16 +4,12 @@ *--------------------------------------------------------------------------------------------*/ import { IUserData, UserDataSyncError, UserDataSyncErrorCode, SyncStatus, IUserDataSyncStoreService, ISyncExtension, IUserDataSyncLogService, IUserDataSynchroniser, SyncSource } from 'vs/platform/userDataSync/common/userDataSync'; -import { VSBuffer } from 'vs/base/common/buffer'; -import { Emitter, Event } from 'vs/base/common/event'; +import { Event } from 'vs/base/common/event'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -import { URI } from 'vs/base/common/uri'; -import { joinPath } from 'vs/base/common/resources'; import { IExtensionManagementService, IExtensionGalleryService, IGlobalExtensionEnablementService } from 'vs/platform/extensionManagement/common/extensionManagement'; import { ExtensionType, IExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { IFileService } from 'vs/platform/files/common/files'; -import { Queue } from 'vs/base/common/async'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { localize } from 'vs/nls'; import { merge } from 'vs/platform/userDataSync/common/extensionsMerge'; @@ -35,32 +31,17 @@ interface ILastSyncUserData extends IUserData { export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUserDataSynchroniser { - private static EXTERNAL_USER_DATA_EXTENSIONS_KEY: string = 'extensions'; - - private _status: SyncStatus = SyncStatus.Idle; - get status(): SyncStatus { return this._status; } - private _onDidChangStatus: Emitter = this._register(new Emitter()); - readonly onDidChangeStatus: Event = this._onDidChangStatus.event; - - private _onDidChangeLocal: Emitter = this._register(new Emitter()); - readonly onDidChangeLocal: Event = this._onDidChangeLocal.event; - - private readonly lastSyncExtensionsResource: URI; - private readonly replaceQueue: Queue; - constructor( @IEnvironmentService environmentService: IEnvironmentService, @IFileService fileService: IFileService, - @IUserDataSyncStoreService private readonly userDataSyncStoreService: IUserDataSyncStoreService, + @IUserDataSyncStoreService userDataSyncStoreService: IUserDataSyncStoreService, @IExtensionManagementService private readonly extensionManagementService: IExtensionManagementService, @IGlobalExtensionEnablementService private readonly extensionEnablementService: IGlobalExtensionEnablementService, @IUserDataSyncLogService private readonly logService: IUserDataSyncLogService, @IExtensionGalleryService private readonly extensionGalleryService: IExtensionGalleryService, @IConfigurationService private readonly configurationService: IConfigurationService, ) { - super(SyncSource.Extensions, fileService, environmentService); - this.replaceQueue = this._register(new Queue()); - this.lastSyncExtensionsResource = joinPath(this.syncFolder, '.lastSyncExtensions'); + super(SyncSource.Extensions, fileService, environmentService, userDataSyncStoreService); this._register( Event.debounce( Event.any( @@ -69,12 +50,7 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse () => undefined, 500)(() => this._onDidChangeLocal.fire())); } - private setStatus(status: SyncStatus): void { - if (this._status !== status) { - this._status = status; - this._onDidChangStatus.fire(status); - } - } + protected getRemoteDataResourceKey(): string { return 'extensions'; } async pull(): Promise { if (!this.configurationService.getValue('sync.enableExtensions')) { @@ -176,16 +152,6 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse throw new Error('Extensions: Conflicts should not occur'); } - async hasPreviouslySynced(): Promise { - const lastSyncData = await this.getLastSyncUserData(); - return !!lastSyncData; - } - - async hasRemoteData(): Promise { - const remoteUserData = await this.getRemoteUserData(); - return remoteUserData.content !== null; - } - async hasLocalData(): Promise { try { const localExtensions = await this.getLocalExtensions(); @@ -202,30 +168,8 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse return null; } - removeExtension(identifier: IExtensionIdentifier): Promise { - return this.replaceQueue.queue(async () => { - const remoteData = await this.userDataSyncStoreService.read(ExtensionsSynchroniser.EXTERNAL_USER_DATA_EXTENSIONS_KEY, null); - const remoteExtensions: ISyncExtension[] = remoteData.content ? JSON.parse(remoteData.content) : []; - const ignoredExtensions = this.configurationService.getValue('sync.ignoredExtensions') || []; - const removedExtensions = remoteExtensions.filter(e => !ignoredExtensions.some(id => areSameExtensions({ id }, e.identifier)) && areSameExtensions(e.identifier, identifier)); - if (removedExtensions.length) { - for (const removedExtension of removedExtensions) { - remoteExtensions.splice(remoteExtensions.indexOf(removedExtension), 1); - } - this.logService.info(`Extensions: Removing extension '${identifier.id}' from remote.`); - await this.writeToRemote(remoteExtensions, remoteData.ref); - } - }); - } - - async resetLocal(): Promise { - try { - await this.fileService.del(this.lastSyncExtensionsResource); - } catch (e) { /* ignore */ } - } - private async getPreview(): Promise { - const lastSyncData = await this.getLastSyncUserData(); + const lastSyncData = await this.getLastSyncUserData(); const lastSyncExtensions: ISyncExtension[] | null = lastSyncData ? JSON.parse(lastSyncData.content!) : null; const skippedExtensions: ISyncExtension[] = lastSyncData ? lastSyncData.skippedExtensions || [] : []; @@ -262,13 +206,15 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse if (remote) { // update remote this.logService.info('Extensions: Updating remote extensions...'); - remoteUserData = await this.writeToRemote(remote, forcePush ? null : remoteUserData.ref); + const content = JSON.stringify(remote); + const ref = await this.updateRemoteUserData(content, forcePush ? null : remoteUserData.ref); + remoteUserData = { ref, content }; } if (remoteUserData.content) { // update last sync this.logService.info('Extensions: Updating last synchronised extensions...'); - await this.updateLastSyncValue({ ...remoteUserData, skippedExtensions }); + await this.updateLastSyncUserData({ ...remoteUserData, skippedExtensions }); } } @@ -331,27 +277,4 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse .map(({ identifier }) => ({ identifier, enabled: !disabledExtensions.some(disabledExtension => areSameExtensions(disabledExtension, identifier)) })); } - private async getLastSyncUserData(): Promise { - try { - const content = await this.fileService.readFile(this.lastSyncExtensionsResource); - return JSON.parse(content.value.toString()); - } catch (error) { - return null; - } - } - - private async updateLastSyncValue(lastSyncUserData: ILastSyncUserData): Promise { - await this.fileService.writeFile(this.lastSyncExtensionsResource, VSBuffer.fromString(JSON.stringify(lastSyncUserData))); - } - - private getRemoteUserData(lastSyncData?: IUserData | null): Promise { - return this.userDataSyncStoreService.read(ExtensionsSynchroniser.EXTERNAL_USER_DATA_EXTENSIONS_KEY, lastSyncData || null, this.source); - } - - private async writeToRemote(extensions: ISyncExtension[], ref: string | null): Promise { - const content = JSON.stringify(extensions); - ref = await this.userDataSyncStoreService.write(ExtensionsSynchroniser.EXTERNAL_USER_DATA_EXTENSIONS_KEY, content, ref, this.source); - return { content, ref }; - } - } diff --git a/src/vs/platform/userDataSync/common/globalStateSync.ts b/src/vs/platform/userDataSync/common/globalStateSync.ts index 58b60ff6f6e..dd865d65b8a 100644 --- a/src/vs/platform/userDataSync/common/globalStateSync.ts +++ b/src/vs/platform/userDataSync/common/globalStateSync.ts @@ -5,10 +5,9 @@ import { IUserData, UserDataSyncError, UserDataSyncErrorCode, SyncStatus, IUserDataSyncStoreService, IUserDataSyncLogService, IGlobalState, SyncSource, IUserDataSynchroniser } from 'vs/platform/userDataSync/common/userDataSync'; import { VSBuffer } from 'vs/base/common/buffer'; -import { Emitter, Event } from 'vs/base/common/event'; +import { Event } from 'vs/base/common/event'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -import { URI } from 'vs/base/common/uri'; -import { joinPath, dirname } from 'vs/base/common/resources'; +import { dirname } from 'vs/base/common/resources'; import { IFileService } from 'vs/platform/files/common/files'; import { IStringDictionary } from 'vs/base/common/collections'; import { edit } from 'vs/platform/userDataSync/common/content'; @@ -27,37 +26,19 @@ interface ISyncPreviewResult { export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUserDataSynchroniser { - private static EXTERNAL_USER_DATA_GLOBAL_STATE_KEY: string = 'globalState'; - - private _status: SyncStatus = SyncStatus.Idle; - get status(): SyncStatus { return this._status; } - private _onDidChangStatus: Emitter = this._register(new Emitter()); - readonly onDidChangeStatus: Event = this._onDidChangStatus.event; - - private _onDidChangeLocal: Emitter = this._register(new Emitter()); - readonly onDidChangeLocal: Event = this._onDidChangeLocal.event; - - private readonly lastSyncGlobalStateResource: URI; - constructor( @IFileService fileService: IFileService, - @IUserDataSyncStoreService private readonly userDataSyncStoreService: IUserDataSyncStoreService, + @IUserDataSyncStoreService userDataSyncStoreService: IUserDataSyncStoreService, @IUserDataSyncLogService private readonly logService: IUserDataSyncLogService, @IEnvironmentService private readonly environmentService: IEnvironmentService, @IConfigurationService private readonly configurationService: IConfigurationService, ) { - super(SyncSource.UIState, fileService, environmentService); - this.lastSyncGlobalStateResource = joinPath(this.syncFolder, '.lastSyncGlobalState'); + super(SyncSource.GlobalState, fileService, environmentService, userDataSyncStoreService); this._register(this.fileService.watch(dirname(this.environmentService.argvResource))); this._register(Event.filter(this.fileService.onFileChanges, e => e.contains(this.environmentService.argvResource))(() => this._onDidChangeLocal.fire())); } - private setStatus(status: SyncStatus): void { - if (this._status !== status) { - this._status = status; - this._onDidChangStatus.fire(status); - } - } + protected getRemoteDataResourceKey(): string { return 'globalState'; } async pull(): Promise { if (!this.configurationService.getValue('sync.enableUIState')) { @@ -153,16 +134,6 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs throw new Error('UI State: Conflicts should not occur'); } - async hasPreviouslySynced(): Promise { - const lastSyncData = await this.getLastSyncUserData(); - return !!lastSyncData; - } - - async hasRemoteData(): Promise { - const remoteUserData = await this.getRemoteUserData(); - return remoteUserData.content !== null; - } - async hasLocalData(): Promise { try { const localGloablState = await this.getLocalGlobalState(); @@ -179,12 +150,6 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs return null; } - async resetLocal(): Promise { - try { - await this.fileService.del(this.lastSyncGlobalStateResource); - } catch (e) { /* ignore */ } - } - private async getPreview(): Promise { const lastSyncData = await this.getLastSyncUserData(); const lastSyncGlobalState = lastSyncData && lastSyncData.content ? JSON.parse(lastSyncData.content) : null; @@ -209,13 +174,15 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs if (remote) { // update remote this.logService.info('UI State: Updating remote ui state...'); - remoteUserData = await this.writeToRemote(remote, forcePush ? null : remoteUserData.ref); + const content = JSON.stringify(remote); + const ref = await this.updateRemoteUserData(content, forcePush ? null : remoteUserData.ref); + remoteUserData = { ref, content }; } if (remoteUserData.content) { // update last sync this.logService.info('UI State: Updating last synchronised ui state...'); - await this.updateLastSyncValue(remoteUserData); + await this.updateLastSyncUserData(remoteUserData); } } @@ -245,27 +212,4 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs } } - private async getLastSyncUserData(): Promise { - try { - const content = await this.fileService.readFile(this.lastSyncGlobalStateResource); - return JSON.parse(content.value.toString()); - } catch (error) { - return null; - } - } - - private async updateLastSyncValue(remoteUserData: IUserData): Promise { - await this.fileService.writeFile(this.lastSyncGlobalStateResource, VSBuffer.fromString(JSON.stringify(remoteUserData))); - } - - private getRemoteUserData(lastSyncData?: IUserData | null): Promise { - return this.userDataSyncStoreService.read(GlobalStateSynchroniser.EXTERNAL_USER_DATA_GLOBAL_STATE_KEY, lastSyncData || null, this.source); - } - - private async writeToRemote(globalState: IGlobalState, ref: string | null): Promise { - const content = JSON.stringify(globalState); - ref = await this.userDataSyncStoreService.write(GlobalStateSynchroniser.EXTERNAL_USER_DATA_GLOBAL_STATE_KEY, content, ref, this.source); - return { content, ref }; - } - } diff --git a/src/vs/platform/userDataSync/common/keybindingsSync.ts b/src/vs/platform/userDataSync/common/keybindingsSync.ts index 4d20c895d03..dc75dba9008 100644 --- a/src/vs/platform/userDataSync/common/keybindingsSync.ts +++ b/src/vs/platform/userDataSync/common/keybindingsSync.ts @@ -9,18 +9,15 @@ import { merge } from 'vs/platform/userDataSync/common/keybindingsMerge'; import { VSBuffer } from 'vs/base/common/buffer'; import { parse, ParseError } from 'vs/base/common/json'; import { localize } from 'vs/nls'; -import { Emitter, Event } from 'vs/base/common/event'; import { CancelablePromise, createCancelablePromise } from 'vs/base/common/async'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -import { URI } from 'vs/base/common/uri'; -import { joinPath, dirname } from 'vs/base/common/resources'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { CancellationToken } from 'vs/base/common/cancellation'; import { OS, OperatingSystem } from 'vs/base/common/platform'; import { isUndefined } from 'vs/base/common/types'; import { FormattingOptions } from 'vs/base/common/jsonFormatter'; import { isNonEmptyArray } from 'vs/base/common/arrays'; -import { AbstractSynchroniser } from 'vs/platform/userDataSync/common/abstractSynchronizer'; +import { AbstractFileSynchroniser } from 'vs/platform/userDataSync/common/abstractSynchronizer'; interface ISyncContent { mac?: string; @@ -37,42 +34,22 @@ interface ISyncPreviewResult { readonly hasConflicts: boolean; } -export class KeybindingsSynchroniser extends AbstractSynchroniser implements IUserDataSynchroniser { - - private static EXTERNAL_USER_DATA_KEYBINDINGS_KEY: string = 'keybindings'; +export class KeybindingsSynchroniser extends AbstractFileSynchroniser implements IUserDataSynchroniser { private syncPreviewResultPromise: CancelablePromise | null = null; - private _status: SyncStatus = SyncStatus.Idle; - get status(): SyncStatus { return this._status; } - private _onDidChangStatus: Emitter = this._register(new Emitter()); - readonly onDidChangeStatus: Event = this._onDidChangStatus.event; - - private _onDidChangeLocal: Emitter = this._register(new Emitter()); - readonly onDidChangeLocal: Event = this._onDidChangeLocal.event; - - private readonly lastSyncKeybindingsResource: URI; - constructor( - @IUserDataSyncStoreService private readonly userDataSyncStoreService: IUserDataSyncStoreService, + @IUserDataSyncStoreService userDataSyncStoreService: IUserDataSyncStoreService, @IUserDataSyncLogService private readonly logService: IUserDataSyncLogService, @IConfigurationService private readonly configurationService: IConfigurationService, @IFileService fileService: IFileService, @IEnvironmentService private readonly environmentService: IEnvironmentService, @IUserDataSyncUtilService private readonly userDataSyncUtilService: IUserDataSyncUtilService, ) { - super(SyncSource.Keybindings, fileService, environmentService); - this.lastSyncKeybindingsResource = joinPath(this.syncFolder, '.lastSyncKeybindings.json'); - this._register(this.fileService.watch(dirname(this.environmentService.keybindingsResource))); - this._register(Event.filter(this.fileService.onFileChanges, e => e.contains(this.environmentService.keybindingsResource))(() => this._onDidChangeLocal.fire())); + super(environmentService.keybindingsResource, SyncSource.Keybindings, fileService, environmentService, userDataSyncStoreService); } - private setStatus(status: SyncStatus): void { - if (this._status !== status) { - this._status = status; - this._onDidChangStatus.fire(status); - } - } + protected getRemoteDataResourceKey(): string { return 'keybindings'; } async pull(): Promise { if (!this.configurationService.getValue('sync.enableKeybindings')) { @@ -204,16 +181,6 @@ export class KeybindingsSynchroniser extends AbstractSynchroniser implements IUs } } - async hasPreviouslySynced(): Promise { - const lastSyncData = await this.getLastSyncUserData(); - return !!lastSyncData; - } - - async hasRemoteData(): Promise { - const remoteUserData = await this.getRemoteUserData(); - return remoteUserData.content !== null; - } - async hasLocalData(): Promise { try { const localFileContent = await this.getLocalFileContent(); @@ -243,12 +210,6 @@ export class KeybindingsSynchroniser extends AbstractSynchroniser implements IUs return content ? this.getKeybindingsContentFromSyncContent(content) : null; } - async resetLocal(): Promise { - try { - await this.fileService.del(this.lastSyncKeybindingsResource); - } catch (e) { /* ignore */ } - } - private async doSync(): Promise { try { const result = await this.getPreview(); @@ -306,7 +267,7 @@ export class KeybindingsSynchroniser extends AbstractSynchroniser implements IUs } if (hasLocalChanged) { this.logService.info('Keybindings: Updating local keybindings'); - await this.updateLocalContent(content, fileContent); + await this.updateLocalFileContent(content, fileContent); } if (hasRemoteChanged) { this.logService.info('Keybindings: Updating remote keybindings'); @@ -400,46 +361,6 @@ export class KeybindingsSynchroniser extends AbstractSynchroniser implements IUs return this._formattingOptions; } - private async getLocalFileContent(): Promise { - try { - return await this.fileService.readFile(this.environmentService.keybindingsResource); - } catch (error) { - return null; - } - } - - private async updateLocalContent(newContent: string, oldContent: IFileContent | null): Promise { - if (oldContent) { - // file exists already - await this.backupLocal(oldContent.value); - await this.fileService.writeFile(this.environmentService.keybindingsResource, VSBuffer.fromString(newContent), oldContent); - } else { - // file does not exist - await this.fileService.createFile(this.environmentService.keybindingsResource, VSBuffer.fromString(newContent), { overwrite: false }); - } - } - - private async getLastSyncUserData(): Promise { - try { - const content = await this.fileService.readFile(this.lastSyncKeybindingsResource); - return JSON.parse(content.value.toString()); - } catch (error) { - return null; - } - } - - private async updateLastSyncUserData(remoteUserData: IUserData): Promise { - await this.fileService.writeFile(this.lastSyncKeybindingsResource, VSBuffer.fromString(JSON.stringify(remoteUserData))); - } - - private async getRemoteUserData(lastSyncData?: IUserData | null): Promise { - return this.userDataSyncStoreService.read(KeybindingsSynchroniser.EXTERNAL_USER_DATA_KEYBINDINGS_KEY, lastSyncData || null, this.source); - } - - private async updateRemoteUserData(content: string, ref: string | null): Promise { - return this.userDataSyncStoreService.write(KeybindingsSynchroniser.EXTERNAL_USER_DATA_KEYBINDINGS_KEY, content, ref, this.source); - } - private getKeybindingsContentFromSyncContent(syncContent: string): string | null { try { const parsed = JSON.parse(syncContent); diff --git a/src/vs/platform/userDataSync/common/settingsSync.ts b/src/vs/platform/userDataSync/common/settingsSync.ts index f8dd7b0ba63..74de5ba7397 100644 --- a/src/vs/platform/userDataSync/common/settingsSync.ts +++ b/src/vs/platform/userDataSync/common/settingsSync.ts @@ -11,8 +11,6 @@ import { localize } from 'vs/nls'; import { Emitter, Event } from 'vs/base/common/event'; import { CancelablePromise, createCancelablePromise } from 'vs/base/common/async'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -import { URI } from 'vs/base/common/uri'; -import { joinPath, dirname } from 'vs/base/common/resources'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { startsWith } from 'vs/base/common/strings'; import { CancellationToken } from 'vs/base/common/cancellation'; @@ -22,7 +20,7 @@ import * as arrays from 'vs/base/common/arrays'; import * as objects from 'vs/base/common/objects'; import { isEmptyObject } from 'vs/base/common/types'; import { edit } from 'vs/platform/userDataSync/common/content'; -import { AbstractSynchroniser } from 'vs/platform/userDataSync/common/abstractSynchronizer'; +import { AbstractFileSynchroniser } from 'vs/platform/userDataSync/common/abstractSynchronizer'; interface ISyncPreviewResult { readonly fileContent: IFileContent | null; @@ -34,49 +32,33 @@ interface ISyncPreviewResult { readonly conflictSettings: IConflictSetting[]; } -export class SettingsSynchroniser extends AbstractSynchroniser implements ISettingsSyncService { +export class SettingsSynchroniser extends AbstractFileSynchroniser implements ISettingsSyncService { _serviceBrand: any; - private static EXTERNAL_USER_DATA_SETTINGS_KEY: string = 'settings'; - private syncPreviewResultPromise: CancelablePromise | null = null; - private _status: SyncStatus = SyncStatus.Idle; - get status(): SyncStatus { return this._status; } - private _onDidChangStatus: Emitter = this._register(new Emitter()); - readonly onDidChangeStatus: Event = this._onDidChangStatus.event; - private _conflicts: IConflictSetting[] = []; get conflicts(): IConflictSetting[] { return this._conflicts; } private _onDidChangeConflicts: Emitter = this._register(new Emitter()); readonly onDidChangeConflicts: Event = this._onDidChangeConflicts.event; - private _onDidChangeLocal: Emitter = this._register(new Emitter()); - readonly onDidChangeLocal: Event = this._onDidChangeLocal.event; - - private readonly lastSyncSettingsResource: URI; - constructor( @IFileService fileService: IFileService, @IEnvironmentService private readonly environmentService: IEnvironmentService, - @IUserDataSyncStoreService private readonly userDataSyncStoreService: IUserDataSyncStoreService, + @IUserDataSyncStoreService userDataSyncStoreService: IUserDataSyncStoreService, @IUserDataSyncLogService private readonly logService: IUserDataSyncLogService, @IUserDataSyncUtilService private readonly userDataSyncUtilService: IUserDataSyncUtilService, @IConfigurationService private readonly configurationService: IConfigurationService, ) { - super(SyncSource.Settings, fileService, environmentService); - this.lastSyncSettingsResource = joinPath(this.syncFolder, '.lastSyncSettings.json'); - this._register(this.fileService.watch(dirname(this.environmentService.settingsResource))); - this._register(Event.filter(this.fileService.onFileChanges, e => e.contains(this.environmentService.settingsResource))(() => this._onDidChangeLocal.fire())); + super(environmentService.settingsResource, SyncSource.Settings, fileService, environmentService, userDataSyncStoreService); } - private setStatus(status: SyncStatus): void { - if (this._status !== status) { - this._status = status; - this._onDidChangStatus.fire(status); - } - if (this._status !== SyncStatus.HasConflicts) { + protected getRemoteDataResourceKey(): string { return 'settings'; } + + protected setStatus(status: SyncStatus): void { + super.setStatus(status); + if (this.status !== SyncStatus.HasConflicts) { this.setConflicts([]); } } @@ -206,16 +188,6 @@ export class SettingsSynchroniser extends AbstractSynchroniser implements ISetti this.setStatus(SyncStatus.Idle); } - async hasPreviouslySynced(): Promise { - const lastSyncData = await this.getLastSyncUserData(); - return !!lastSyncData; - } - - async hasRemoteData(): Promise { - const remoteUserData = await this.getRemoteUserData(); - return remoteUserData.content !== null; - } - async hasLocalData(): Promise { try { const localFileContent = await this.getLocalFileContent(); @@ -279,12 +251,6 @@ export class SettingsSynchroniser extends AbstractSynchroniser implements ISetti } } - async resetLocal(): Promise { - try { - await this.fileService.del(this.lastSyncSettingsResource); - } catch (e) { /* ignore */ } - } - private async doSync(resolvedConflicts: { key: string, value: any | undefined }[]): Promise { try { const result = await this.getPreview(resolvedConflicts); @@ -343,18 +309,18 @@ export class SettingsSynchroniser extends AbstractSynchroniser implements ISetti } if (hasLocalChanged) { this.logService.info('Settings: Updating local settings'); - await this.writeToLocal(content, fileContent); + await this.updateLocalFileContent(content, fileContent); } if (hasRemoteChanged) { const formatUtils = await this.getFormattingOptions(); const remoteContent = updateIgnoredSettings(content, remoteUserData.content || '{}', getIgnoredSettings(this.configurationService, content), formatUtils); this.logService.info('Settings: Updating remote settings'); - const ref = await this.writeToRemote(remoteContent, forcePush ? null : remoteUserData.ref); + const ref = await this.updateRemoteUserData(remoteContent, forcePush ? null : remoteUserData.ref); remoteUserData = { ref, content }; } if (remoteUserData.content) { this.logService.info('Settings: Updating last synchronised sttings'); - await this.updateLastSyncValue(remoteUserData); + await this.updateLastSyncUserData(remoteUserData); } // Delete the preview @@ -436,46 +402,6 @@ export class SettingsSynchroniser extends AbstractSynchroniser implements ISetti return this._formattingOptions; } - private async getLastSyncUserData(): Promise { - try { - const content = await this.fileService.readFile(this.lastSyncSettingsResource); - return JSON.parse(content.value.toString()); - } catch (error) { - return null; - } - } - - private async getLocalFileContent(): Promise { - try { - return await this.fileService.readFile(this.environmentService.settingsResource); - } catch (error) { - return null; - } - } - - private getRemoteUserData(lastSyncData?: IUserData | null): Promise { - return this.userDataSyncStoreService.read(SettingsSynchroniser.EXTERNAL_USER_DATA_SETTINGS_KEY, lastSyncData || null, this.source); - } - - private async writeToRemote(content: string, ref: string | null): Promise { - return this.userDataSyncStoreService.write(SettingsSynchroniser.EXTERNAL_USER_DATA_SETTINGS_KEY, content, ref, this.source); - } - - private async writeToLocal(newContent: string, oldContent: IFileContent | null): Promise { - if (oldContent) { - // file exists already - await this.backupLocal(oldContent.value); - await this.fileService.writeFile(this.environmentService.settingsResource, VSBuffer.fromString(newContent), oldContent); - } else { - // file does not exist - await this.fileService.createFile(this.environmentService.settingsResource, VSBuffer.fromString(newContent), { overwrite: false }); - } - } - - private async updateLastSyncValue(remoteUserData: IUserData): Promise { - await this.fileService.writeFile(this.lastSyncSettingsResource, VSBuffer.fromString(JSON.stringify(remoteUserData))); - } - } export function getIgnoredSettings(configurationService: IConfigurationService, settingsContent?: string): string[] { diff --git a/src/vs/platform/userDataSync/common/userDataSync.ts b/src/vs/platform/userDataSync/common/userDataSync.ts index 6580f517dfb..ccdc00d60f1 100644 --- a/src/vs/platform/userDataSync/common/userDataSync.ts +++ b/src/vs/platform/userDataSync/common/userDataSync.ts @@ -174,7 +174,7 @@ export const enum SyncSource { Settings = 'Settings', Keybindings = 'Keybindings', Extensions = 'Extensions', - UIState = 'UI State' + GlobalState = 'GlobalState' } export const enum SyncStatus { @@ -212,7 +212,6 @@ export interface IUserDataSyncService extends ISynchroniser { isFirstTimeSyncAndHasUserData(): Promise; reset(): Promise; resetLocal(): Promise; - removeExtension(identifier: IExtensionIdentifier): Promise; getRemoteContent(source: SyncSource): Promise; resolveConflictsAndContinueSync(content: string): Promise; } @@ -267,5 +266,5 @@ export function toRemoteContentResource(source: SyncSource): URI { return URI.from({ scheme: USER_DATA_SYNC_SCHEME, path: `${source}/remoteContent` }); } export function getSyncSourceFromRemoteContentResource(uri: URI): SyncSource | undefined { - return [SyncSource.Settings, SyncSource.Keybindings, SyncSource.Extensions, SyncSource.UIState].filter(source => isEqual(uri, toRemoteContentResource(source)))[0]; + return [SyncSource.Settings, SyncSource.Keybindings, SyncSource.Extensions, SyncSource.GlobalState].filter(source => isEqual(uri, toRemoteContentResource(source)))[0]; } diff --git a/src/vs/platform/userDataSync/common/userDataSyncIpc.ts b/src/vs/platform/userDataSync/common/userDataSyncIpc.ts index caf9856c728..4519914e70c 100644 --- a/src/vs/platform/userDataSync/common/userDataSyncIpc.ts +++ b/src/vs/platform/userDataSync/common/userDataSyncIpc.ts @@ -30,7 +30,6 @@ export class UserDataSyncChannel implements IServerChannel { case 'push': return this.service.push(); case '_getInitialStatus': return Promise.resolve(this.service.status); case 'getConflictsSource': return Promise.resolve(this.service.conflictsSource); - case 'removeExtension': return this.service.removeExtension(args[0]); case 'stop': this.service.stop(); return Promise.resolve(); case 'restart': return this.service.restart().then(() => this.service.status); case 'reset': return this.service.reset(); diff --git a/src/vs/platform/userDataSync/common/userDataSyncService.ts b/src/vs/platform/userDataSync/common/userDataSyncService.ts index 770f57e941f..141533aa02f 100644 --- a/src/vs/platform/userDataSync/common/userDataSyncService.ts +++ b/src/vs/platform/userDataSync/common/userDataSyncService.ts @@ -9,7 +9,6 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { SettingsSynchroniser } from 'vs/platform/userDataSync/common/settingsSync'; import { Emitter, Event } from 'vs/base/common/event'; import { ExtensionsSynchroniser } from 'vs/platform/userDataSync/common/extensionsSync'; -import { IExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { KeybindingsSynchroniser } from 'vs/platform/userDataSync/common/keybindingsSync'; import { GlobalStateSynchroniser } from 'vs/platform/userDataSync/common/globalStateSync'; import { toErrorMessage } from 'vs/base/common/errorMessage'; @@ -253,10 +252,6 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ this.logService.info('Completed resetting local cache'); } - removeExtension(identifier: IExtensionIdentifier): Promise { - return this.extensionsSynchroniser.removeExtension(identifier); - } - private updateStatus(): void { const status = this.computeStatus(); if (this._status !== status) { @@ -309,7 +304,7 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ if (synchroniser instanceof ExtensionsSynchroniser) { return SyncSource.Extensions; } - return SyncSource.UIState; + return SyncSource.GlobalState; } private onDidChangeAuthTokenStatus(token: string | undefined): void { diff --git a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts index b42fb3f2583..2901974d03c 100644 --- a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts +++ b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts @@ -60,7 +60,7 @@ function getSyncAreaLabel(source: SyncSource): string { case SyncSource.Settings: return localize('settings', "Settings"); case SyncSource.Keybindings: return localize('keybindings', "Keybindings"); case SyncSource.Extensions: return localize('extensions', "Extensions"); - case SyncSource.UIState: return localize('ui state label', "UI State"); + case SyncSource.GlobalState: return localize('ui state label', "UI State"); } } @@ -367,7 +367,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo label: getSyncAreaLabel(SyncSource.Extensions) }, { id: 'sync.enableUIState', - label: getSyncAreaLabel(SyncSource.UIState), + label: getSyncAreaLabel(SyncSource.GlobalState), description: localize('ui state description', "Display Language (Only)") }]; } diff --git a/src/vs/workbench/services/userDataSync/electron-browser/userDataSyncService.ts b/src/vs/workbench/services/userDataSync/electron-browser/userDataSyncService.ts index c0fda78d2f9..43a904996c6 100644 --- a/src/vs/workbench/services/userDataSync/electron-browser/userDataSyncService.ts +++ b/src/vs/workbench/services/userDataSync/electron-browser/userDataSyncService.ts @@ -9,7 +9,6 @@ import { Disposable } from 'vs/base/common/lifecycle'; import { Emitter, Event } from 'vs/base/common/event'; import { IChannel } from 'vs/base/parts/ipc/common/ipc'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; -import { IExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; export class UserDataSyncService extends Disposable implements IUserDataSyncService { @@ -91,10 +90,6 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ return this.channel.call('isFirstTimeSyncAndHasUserData'); } - removeExtension(identifier: IExtensionIdentifier): Promise { - return this.channel.call('removeExtension', [identifier]); - } - private async updateStatus(status: SyncStatus): Promise { this._conflictsSource = await this.channel.call('getConflictsSource'); this._status = status; From 3c6f6af7347d38e87bc6406024e8dcf9e9bce229 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Sun, 26 Jan 2020 23:11:52 +0100 Subject: [PATCH 129/801] improve inspect tokens hover --- .../common/tokenClassificationRegistry.ts | 4 +- .../inspectEditorTokens.ts | 121 +++++++++++++----- .../services/themes/common/colorThemeData.ts | 41 ++++-- 3 files changed, 125 insertions(+), 41 deletions(-) diff --git a/src/vs/platform/theme/common/tokenClassificationRegistry.ts b/src/vs/platform/theme/common/tokenClassificationRegistry.ts index 2c8860d6c69..d5afc64d3bf 100644 --- a/src/vs/platform/theme/common/tokenClassificationRegistry.ts +++ b/src/vs/platform/theme/common/tokenClassificationRegistry.ts @@ -104,6 +104,7 @@ export interface TokenStylingDefaultRule { export interface TokenStylingRule { match(classification: TokenClassification): number; value: TokenStyle; + selector: TokenClassification; } /** @@ -294,7 +295,8 @@ class TokenClassificationRegistry implements ITokenClassificationRegistry { public getTokenStylingRule(selector: TokenClassification, value: TokenStyle): TokenStylingRule { return { match: this.newMatcher(selector), - value + value, + selector }; } diff --git a/src/vs/workbench/contrib/codeEditor/browser/inspectEditorTokens/inspectEditorTokens.ts b/src/vs/workbench/contrib/codeEditor/browser/inspectEditorTokens/inspectEditorTokens.ts index 2c2f237b11b..f5317a3d938 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/inspectEditorTokens/inspectEditorTokens.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/inspectEditorTokens/inspectEditorTokens.ts @@ -26,6 +26,8 @@ import { findMatchingThemeRule } from 'vs/workbench/services/textMate/common/TMH import { ITextMateService, IGrammar, IToken, StackElement } from 'vs/workbench/services/textMate/common/textMateService'; import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService'; import { CancellationTokenSource } from 'vs/base/common/cancellation'; +import { ColorThemeData, TokenStyleDefinitions, TokenStyleDefinition } from 'vs/workbench/services/themes/common/colorThemeData'; +import { TokenStylingRule } from 'vs/platform/theme/common/tokenClassificationRegistry'; class InspectEditorTokensController extends Disposable implements IEditorContribution { @@ -121,7 +123,8 @@ interface ISemanticTokenInfo { type: string; modifiers: string[]; range: Range; - metadata: IDecodedMetadata + metadata: IDecodedMetadata, + definitions: TokenStyleDefinitions } interface IDecodedMetadata { @@ -248,17 +251,21 @@ class InspectEditorTokensWidget extends Disposable implements IContentWidget { const semanticTokenInfo = semanticTokens && this._getSemanticTokenAtPosition(semanticTokens, position); let tokenText; + let metadata: IDecodedMetadata | undefined; - let primary: IDecodedMetadata | undefined; - if (textMateTokenInfo) { + let tmFallback: IDecodedMetadata | undefined; + + if (semanticTokenInfo) { + tokenText = this._model.getValueInRange(semanticTokenInfo.range); + metadata = semanticTokenInfo.metadata; + if (textMateTokenInfo) { + tmFallback = textMateTokenInfo.metadata; + } + } else if (textMateTokenInfo) { let tokenStartIndex = textMateTokenInfo.token.startIndex; let tokenEndIndex = textMateTokenInfo.token.endIndex; tokenText = this._model.getLineContent(position.lineNumber).substring(tokenStartIndex, tokenEndIndex); metadata = textMateTokenInfo.metadata; - primary = semanticTokenInfo?.metadata; - } else if (semanticTokenInfo) { - tokenText = this._model.getValueInRange(semanticTokenInfo.range); - metadata = semanticTokenInfo.metadata; } else { return 'No grammar or semantic tokens available.'; } @@ -268,28 +275,36 @@ class InspectEditorTokensWidget extends Disposable implements IContentWidget { result += ``; result += ``; - result += ``; - result += ``; - if (semanticTokenInfo) { - result += ``; - const modifiers = semanticTokenInfo.modifiers.join(' ') || '-'; - result += ``; - } + result += ``; + result += ``; result += ``; result += ``; result += ``; - result += this._formatMetadata(metadata, primary); + result += this._formatMetadata(metadata, tmFallback); result += ``; + if (semanticTokenInfo) { + result += ``; + result += ``; + result += ``; + const modifiers = semanticTokenInfo.modifiers.join(' ') || '-'; + result += ``; + result += ``; + + result += `
${this._renderTokenStyleDefinition(semanticTokenInfo.definitions.foreground)}
`; + } + if (textMateTokenInfo) { let theme = this._themeService.getColorTheme(); result += ``; - let matchingRule = findMatchingThemeRule(theme, textMateTokenInfo.token.scopes, false); - if (matchingRule) { - result += `${matchingRule.rawSelector}\n${JSON.stringify(matchingRule.settings, null, '\t')}`; - } else { - result += `No theme selector.`; + if (!semanticTokenInfo) { + let matchingRule = findMatchingThemeRule(theme, textMateTokenInfo.token.scopes, false); + if (matchingRule) { + result += `${matchingRule.rawSelector}\n${JSON.stringify(matchingRule.settings, null, '\t')}`; + } else { + result += `No theme selector.`; + } } result += `