diff --git a/src/vs/base/browser/ui/list/list.ts b/src/vs/base/browser/ui/list/list.ts index 05ab5d56cd8..df2fbfde7d3 100644 --- a/src/vs/base/browser/ui/list/list.ts +++ b/src/vs/base/browser/ui/list/list.ts @@ -17,6 +17,12 @@ export interface IRenderer { disposeTemplate(templateData: TTemplateData): void; } +export interface IListOpenEvent { + elements: T[]; + indexes: number[]; + browserEvent?: UIEvent; +} + export interface IListEvent { elements: T[]; indexes: number[]; diff --git a/src/vs/base/browser/ui/list/listPaging.ts b/src/vs/base/browser/ui/list/listPaging.ts index 2de68902c7c..fb3984e4699 100644 --- a/src/vs/base/browser/ui/list/listPaging.ts +++ b/src/vs/base/browser/ui/list/listPaging.ts @@ -6,7 +6,7 @@ import 'vs/css!./list'; import { IDisposable } from 'vs/base/common/lifecycle'; import { range } from 'vs/base/common/arrays'; -import { IDelegate, IRenderer, IListEvent } from './list'; +import { IDelegate, IRenderer, IListEvent, IListOpenEvent } from './list'; import { List, IListStyles, IListOptions } from './listWidget'; import { IPagedModel } from 'vs/base/common/paging'; import Event, { mapEvent } from 'vs/base/common/event'; @@ -97,6 +97,10 @@ export class PagedList { return mapEvent(this.list.onFocusChange, ({ elements, indexes }) => ({ elements: elements.map(e => this._model.get(e)), indexes })); } + get onOpen(): Event> { + return mapEvent(this.list.onOpen, ({ elements, indexes, browserEvent }) => ({ elements: elements.map(e => this._model.get(e)), indexes, browserEvent })); + } + get onSelectionChange(): Event> { return mapEvent(this.list.onSelectionChange, ({ elements, indexes }) => ({ elements: elements.map(e => this._model.get(e)), indexes })); } @@ -126,8 +130,8 @@ export class PagedList { this.list.scrollTop = scrollTop; } - open(indexes: number[]): void { - this.list.open(indexes); + open(indexes: number[], browserEvent?: UIEvent): void { + this.list.open(indexes, browserEvent); } setFocus(indexes: number[]): void { diff --git a/src/vs/base/browser/ui/list/listWidget.ts b/src/vs/base/browser/ui/list/listWidget.ts index d1c7b582008..804081a26f0 100644 --- a/src/vs/base/browser/ui/list/listWidget.ts +++ b/src/vs/base/browser/ui/list/listWidget.ts @@ -15,7 +15,7 @@ import { KeyCode } from 'vs/base/common/keyCodes'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import Event, { Emitter, EventBufferer, chain, mapEvent, anyEvent } from 'vs/base/common/event'; import { domEvent } from 'vs/base/browser/event'; -import { IDelegate, IRenderer, IListEvent, IListContextMenuEvent, IListMouseEvent, IListTouchEvent, IListGestureEvent } from './list'; +import { IDelegate, IRenderer, IListEvent, IListContextMenuEvent, IListMouseEvent, IListTouchEvent, IListGestureEvent, IListOpenEvent } from './list'; import { ListView, IListViewOptions } from './listView'; import { Color } from 'vs/base/common/color'; import { mixin } from 'vs/base/common/objects'; @@ -261,6 +261,7 @@ function isInputElement(e: HTMLElement): boolean { class KeyboardController implements IDisposable { private disposables: IDisposable[]; + private openController: IOpenController; constructor( private list: List, @@ -270,6 +271,8 @@ class KeyboardController implements IDisposable { const multipleSelectionSupport = !(options.multipleSelectionSupport === false); this.disposables = []; + this.openController = options.openController || DefaultOpenController; + const onKeyDown = chain(domEvent(view.domNode, 'keydown')) .filter(e => !isInputElement(e.target as HTMLElement)) .map(e => new StandardKeyboardEvent(e)); @@ -290,7 +293,10 @@ class KeyboardController implements IDisposable { e.preventDefault(); e.stopPropagation(); this.list.setSelection(this.list.getFocus()); - this.list.open(this.list.getFocus()); + + if (this.openController.shouldOpen(e.browserEvent)) { + this.list.open(this.list.getFocus(), e.browserEvent); + } } private onUpArrow(e: StandardKeyboardEvent): void { @@ -357,10 +363,15 @@ const DefaultMultipleSelectionContoller = { isSelectionRangeChangeEvent }; +const DefaultOpenController = { + shouldOpen: (event: UIEvent) => true +}; + class MouseController implements IDisposable { private multipleSelectionSupport: boolean; - private multipleSelectionController: IMultipleSelectionController | undefined; + private multipleSelectionController: IMultipleSelectionController; + private openController: IOpenController; private didJustPressContextMenuKey: boolean = false; private disposables: IDisposable[] = []; @@ -406,6 +417,8 @@ class MouseController implements IDisposable { this.multipleSelectionController = options.multipleSelectionController || DefaultMultipleSelectionContoller; } + this.openController = options.openController || DefaultOpenController; + view.onMouseDown(this.onMouseDown, this, this.disposables); view.onMouseClick(this.onPointer, this, this.disposables); view.onMouseDblClick(this.onDoubleClick, this, this.disposables); @@ -458,7 +471,10 @@ class MouseController implements IDisposable { if (this.options.selectOnMouseDown) { this.list.setSelection([focus]); - this.list.open([focus]); + + if (this.openController.shouldOpen(e.browserEvent)) { + this.list.open([focus], e.browserEvent); + } } } @@ -470,7 +486,10 @@ class MouseController implements IDisposable { if (!this.options.selectOnMouseDown) { const focus = this.list.getFocus(); this.list.setSelection(focus); - this.list.open(focus); + + if (this.openController.shouldOpen(e.browserEvent)) { + this.list.open(focus, e.browserEvent); + } } } @@ -523,6 +542,10 @@ export interface IMultipleSelectionController { isSelectionRangeChangeEvent(event: IListMouseEvent | IListTouchEvent): boolean; } +export interface IOpenController { + shouldOpen(event: UIEvent): boolean; +} + export interface IListOptions extends IListViewOptions, IListStyles { identityProvider?: IIdentityProvider; ariaLabel?: string; @@ -533,6 +556,7 @@ export interface IListOptions extends IListViewOptions, IListStyles { verticalScrollMode?: ScrollbarVisibility; multipleSelectionSupport?: boolean; multipleSelectionController?: IMultipleSelectionController; + openController?: IOpenController; } export interface IListStyles { @@ -708,9 +732,9 @@ export class List implements ISpliceable, IDisposable { readonly onContextMenu: Event> = Event.None; - private _onOpen = new Emitter(); - @memoize get onOpen(): Event> { - return mapEvent(this._onOpen.event, indexes => this.toListEvent({ indexes })); + private _onOpen = new Emitter>(); + @memoize get onOpen(): Event> { + return this._onOpen.event; } private _onPin = new Emitter(); @@ -973,8 +997,8 @@ export class List implements ISpliceable, IDisposable { return this.view.domNode; } - open(indexes: number[]): void { - this._onOpen.fire(indexes); + open(indexes: number[], browserEvent?: UIEvent): void { + this._onOpen.fire({ indexes, elements: indexes.map(i => this.view.element(i)), browserEvent }); } pin(indexes: number[]): void { diff --git a/src/vs/base/parts/tree/browser/treeDefaults.ts b/src/vs/base/parts/tree/browser/treeDefaults.ts index 05e19027d4f..845ec66aa4c 100644 --- a/src/vs/base/parts/tree/browser/treeDefaults.ts +++ b/src/vs/base/parts/tree/browser/treeDefaults.ts @@ -38,8 +38,14 @@ export enum ClickBehavior { ON_MOUSE_UP } +export enum OpenMode { + SINGLE_CLICK, + DOUBLE_CLICK +} + export interface IControllerOptions { clickBehavior?: ClickBehavior; + openMode?: OpenMode; keyboardSupport?: boolean; } @@ -82,7 +88,7 @@ export class DefaultController implements _.IController { private options: IControllerOptions; - constructor(options: IControllerOptions = { clickBehavior: ClickBehavior.ON_MOUSE_UP, keyboardSupport: true }) { + constructor(options: IControllerOptions = { clickBehavior: ClickBehavior.ON_MOUSE_UP, keyboardSupport: true, openMode: OpenMode.SINGLE_CLICK }) { this.options = options; this.downKeyBindingDispatcher = new KeybindingDispatcher(); @@ -153,6 +159,7 @@ export class DefaultController implements _.IController { protected onLeftClick(tree: _.ITree, element: any, eventish: ICancelableEvent, origin: string = 'mouse'): boolean { const payload = { origin: origin, originalEvent: eventish }; + const isDoubleClick = (origin === 'mouse' && (eventish).detail === 2); if (tree.getInput() === element) { tree.clearFocus(payload); @@ -168,16 +175,26 @@ export class DefaultController implements _.IController { tree.setSelection([element], payload); tree.setFocus(element, payload); - if (tree.isExpanded(element)) { - tree.collapse(element).done(null, errors.onUnexpectedError); - } else { - tree.expand(element).done(null, errors.onUnexpectedError); + if (this.openOnSingleClick || isDoubleClick) { + if (tree.isExpanded(element)) { + tree.collapse(element).done(null, errors.onUnexpectedError); + } else { + tree.expand(element).done(null, errors.onUnexpectedError); + } } } return true; } + protected setOpenMode(openMode: OpenMode) { + this.options.openMode = openMode; + } + + protected get openOnSingleClick(): boolean { + return this.options.openMode === OpenMode.SINGLE_CLICK; + } + public onContextMenu(tree: _.ITree, element: any, event: _.ContextMenuEvent): boolean { if (event.target && event.target.tagName && event.target.tagName.toLowerCase() === 'input') { return false; // allow context menu on input fields diff --git a/src/vs/editor/contrib/referenceSearch/referencesWidget.ts b/src/vs/editor/contrib/referenceSearch/referencesWidget.ts index 136e54c22a8..e5fff92bdae 100644 --- a/src/vs/editor/contrib/referenceSearch/referencesWidget.ts +++ b/src/vs/editor/contrib/referenceSearch/referencesWidget.ts @@ -23,7 +23,6 @@ import { GestureEvent } from 'vs/base/browser/touch'; import { CountBadge } from 'vs/base/browser/ui/countBadge/countBadge'; import { FileLabel } from 'vs/base/browser/ui/iconLabel/iconLabel'; import * as tree from 'vs/base/parts/tree/browser/tree'; -import { DefaultController } from 'vs/base/parts/tree/browser/treeDefaults'; import { IInstantiationService, optional } from 'vs/platform/instantiation/common/instantiation'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { Range, IRange } from 'vs/editor/common/core/range'; @@ -41,7 +40,7 @@ import { IEditorOptions } from 'vs/editor/common/config/editorOptions'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import URI from 'vs/base/common/uri'; import { TrackedRangeStickiness, IModelDeltaDecoration } from 'vs/editor/common/model'; -import { WorkbenchTree } from 'vs/platform/list/browser/listService'; +import { WorkbenchTree, WorkbenchTreeController } from 'vs/platform/list/browser/listService'; import { RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { Location } from 'vs/editor/common/modes'; @@ -218,7 +217,7 @@ class DataSource implements tree.IDataSource { } } -class Controller extends DefaultController { +class Controller extends WorkbenchTreeController { private _onDidFocus = new Emitter(); readonly onDidFocus: Event = this._onDidFocus.event; @@ -243,19 +242,22 @@ class Controller extends DefaultController { } public onMouseDown(tree: tree.ITree, element: any, event: IMouseEvent): boolean { + var isDoubleClick = event.detail === 2; if (event.leftButton) { if (element instanceof FileReferences) { - event.preventDefault(); - event.stopPropagation(); - return this._expandCollapse(tree, element); + if (this.openOnSingleClick || isDoubleClick) { + event.preventDefault(); + event.stopPropagation(); + return this._expandCollapse(tree, element); + } } var result = super.onClick(tree, element, event); if (event.ctrlKey || event.metaKey || event.altKey) { this._onDidOpenToSide.fire(element); - } else if (event.detail === 2) { + } else if (isDoubleClick) { this._onDidSelect.fire(element); - } else { + } else if (this.openOnSingleClick) { this._onDidFocus.fire(element); } return result; @@ -631,7 +633,9 @@ export class ReferenceWidget extends PeekViewWidget { // tree container.div({ 'class': 'ref-tree inline' }, (div: Builder) => { - const controller = new Controller(); + var controller = this._instantiationService.createInstance(Controller, {}); + this._callOnDispose.push(controller); + var config = { dataSource: this._instantiationService.createInstance(DataSource), renderer: this._instantiationService.createInstance(Renderer), @@ -641,8 +645,7 @@ export class ReferenceWidget extends PeekViewWidget { var options: tree.ITreeOptions = { twistiePixels: 20, - ariaLabel: nls.localize('treeAriaLabel', "References"), - keyboardSupport: false + ariaLabel: nls.localize('treeAriaLabel', "References") }; this._tree = this._instantiationService.createInstance(WorkbenchTree, div.getHTMLElement(), config, options); diff --git a/src/vs/platform/list/browser/listService.ts b/src/vs/platform/list/browser/listService.ts index d283b200549..e654a5d56b1 100644 --- a/src/vs/platform/list/browser/listService.ts +++ b/src/vs/platform/list/browser/listService.ts @@ -5,9 +5,9 @@ 'use strict'; import { ITree, ITreeConfiguration, ITreeOptions } from 'vs/base/parts/tree/browser/tree'; -import { List, IListOptions, isSelectionRangeChangeEvent, isSelectionSingleChangeEvent, IMultipleSelectionController } from 'vs/base/browser/ui/list/listWidget'; +import { List, IListOptions, isSelectionRangeChangeEvent, isSelectionSingleChangeEvent, IMultipleSelectionController, IOpenController } from 'vs/base/browser/ui/list/listWidget'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; -import { IDisposable, toDisposable, combinedDisposable, dispose } from 'vs/base/common/lifecycle'; +import { IDisposable, toDisposable, combinedDisposable, dispose, Disposable } from 'vs/base/common/lifecycle'; import { IContextKeyService, IContextKey, RawContextKey, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { PagedList, IPagedRenderer } from 'vs/base/browser/ui/list/listPaging'; import { IDelegate, IRenderer, IListMouseEvent, IListTouchEvent } from 'vs/base/browser/ui/list/list'; @@ -20,7 +20,10 @@ import { mixin } from 'vs/base/common/objects'; import { localize } from 'vs/nls'; import { Registry } from 'vs/platform/registry/common/platform'; import { Extensions as ConfigurationExtensions, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry'; - +import { DefaultController, IControllerOptions, OpenMode } from 'vs/base/parts/tree/browser/treeDefaults'; +import { isUndefinedOrNull } from 'vs/base/common/types'; +import { IEditorOptions } from 'vs/platform/editor/common/editor'; +import Event, { Emitter } from 'vs/base/common/event'; export type ListWidget = List | PagedList | ITree; export const IListService = createDecorator('listService'); @@ -94,12 +97,17 @@ function createScopedContextKeyService(contextKeyService: IContextKeyService, wi return result; } -export const multiSelectModifierSettingKey = 'workbench.multiSelectModifier'; +export const multiSelectModifierSettingKey = 'workbench.list.multiSelectModifier'; +export const openModeSettingKey = 'workbench.list.openMode'; -export function useAltAsMultipleSelectionModifier(configurationService: IConfigurationService): boolean { +function useAltAsMultipleSelectionModifier(configurationService: IConfigurationService): boolean { return configurationService.getValue(multiSelectModifierSettingKey) === 'alt'; } +function useSingleClickToOpen(configurationService: IConfigurationService): boolean { + return configurationService.getValue(openModeSettingKey) !== 'doubleClick'; +} + class MultipleSelectionController implements IMultipleSelectionController { constructor(private configurationService: IConfigurationService) { } @@ -117,11 +125,39 @@ class MultipleSelectionController implements IMultipleSelectionController } } +class OpenController implements IOpenController { + + constructor(private configurationService: IConfigurationService) { } + + shouldOpen(event: UIEvent): boolean { + if (event instanceof MouseEvent) { + const isDoubleClick = event.detail === 2; + + return useSingleClickToOpen(this.configurationService) || isDoubleClick; + } + + return true; + } +} + +function handleListControllers(options: IListOptions, configurationService: IConfigurationService): IListOptions { + if (options.multipleSelectionSupport === true && !options.multipleSelectionController) { + options.multipleSelectionController = new MultipleSelectionController(configurationService); + } + + options.openController = new OpenController(configurationService); + + return options; +} + export class WorkbenchList extends List { readonly contextKeyService: IContextKeyService; + private listDoubleSelection: IContextKey; + private _useAltAsMultipleSelectionModifier: boolean; + constructor( container: HTMLElement, delegate: IDelegate, @@ -130,31 +166,45 @@ export class WorkbenchList extends List { @IContextKeyService contextKeyService: IContextKeyService, @IListService listService: IListService, @IThemeService themeService: IThemeService, - @IConfigurationService configurationService: IConfigurationService + @IConfigurationService private configurationService: IConfigurationService ) { - const multipleSelectionSupport = !(options.multipleSelectionSupport === false); + super(container, delegate, renderers, mixin(handleListControllers(options, configurationService), { keyboardSupport: false } as IListOptions, false)); - if (multipleSelectionSupport && !options.multipleSelectionController) { - options.multipleSelectionController = new MultipleSelectionController(configurationService); - } - - super(container, delegate, renderers, mixin(options, useAltAsMultipleSelectionModifier(configurationService))); this.contextKeyService = createScopedContextKeyService(contextKeyService, this); this.listDoubleSelection = WorkbenchListDoubleSelection.bindTo(this.contextKeyService); + this._useAltAsMultipleSelectionModifier = useAltAsMultipleSelectionModifier(configurationService); + this.disposables.push(combinedDisposable([ this.contextKeyService, (listService as ListService).register(this), attachListStyler(this, themeService), this.onSelectionChange(() => this.listDoubleSelection.set(this.getSelection().length === 2)) ])); + + this.registerListeners(); + } + + public get useAltAsMultipleSelectionModifier(): boolean { + return this._useAltAsMultipleSelectionModifier; + } + + private registerListeners(): void { + this.disposables.push(this.configurationService.onDidChangeConfiguration(e => { + if (e.affectsConfiguration(multiSelectModifierSettingKey)) { + this._useAltAsMultipleSelectionModifier = useAltAsMultipleSelectionModifier(this.configurationService); + } + })); } } export class WorkbenchPagedList extends PagedList { readonly contextKeyService: IContextKeyService; - private disposable: IDisposable; + + private disposables: IDisposable[] = []; + + private _useAltAsMultipleSelectionModifier: boolean; constructor( container: HTMLElement, @@ -164,57 +214,100 @@ export class WorkbenchPagedList extends PagedList { @IContextKeyService contextKeyService: IContextKeyService, @IListService listService: IListService, @IThemeService themeService: IThemeService, - @IConfigurationService configurationService: IConfigurationService + @IConfigurationService private configurationService: IConfigurationService ) { - const multipleSelectionSupport = !(options.multipleSelectionSupport === false); + super(container, delegate, renderers, mixin(handleListControllers(options, configurationService), { keyboardSupport: false } as IListOptions, false)); - if (multipleSelectionSupport && !options.multipleSelectionController) { - options.multipleSelectionController = new MultipleSelectionController(configurationService); - } - - super(container, delegate, renderers, mixin(options, useAltAsMultipleSelectionModifier(configurationService))); this.contextKeyService = createScopedContextKeyService(contextKeyService, this); - this.disposable = combinedDisposable([ + this._useAltAsMultipleSelectionModifier = useAltAsMultipleSelectionModifier(configurationService); + + this.disposables.push(combinedDisposable([ this.contextKeyService, (listService as ListService).register(this), attachListStyler(this, themeService) - ]); + ])); + + this.registerListeners(); + } + + public get useAltAsMultipleSelectionModifier(): boolean { + return this._useAltAsMultipleSelectionModifier; + } + + private registerListeners(): void { + this.disposables.push(this.configurationService.onDidChangeConfiguration(e => { + if (e.affectsConfiguration(multiSelectModifierSettingKey)) { + this._useAltAsMultipleSelectionModifier = useAltAsMultipleSelectionModifier(this.configurationService); + } + })); } dispose(): void { - this.disposable.dispose(); + this.disposables = dispose(this.disposables); } } export class WorkbenchTree extends Tree { readonly contextKeyService: IContextKeyService; + protected disposables: IDisposable[] = []; + private listDoubleSelection: IContextKey; + private _openOnSingleClick: boolean; + private _useAltAsMultipleSelectionModifier: boolean; + constructor( container: HTMLElement, configuration: ITreeConfiguration, options: ITreeOptions, @IContextKeyService contextKeyService: IContextKeyService, @IListService listService: IListService, - @IThemeService themeService: IThemeService + @IThemeService themeService: IThemeService, + @IConfigurationService private configurationService: IConfigurationService ) { - super(container, configuration, options); + super(container, configuration, mixin(options, { keyboardSupport: false } as ITreeOptions, false)); this.contextKeyService = createScopedContextKeyService(contextKeyService, this); this.listDoubleSelection = WorkbenchListDoubleSelection.bindTo(this.contextKeyService); + this._openOnSingleClick = useSingleClickToOpen(configurationService); + this._useAltAsMultipleSelectionModifier = useAltAsMultipleSelectionModifier(configurationService); + this.disposables.push( this.contextKeyService, (listService as ListService).register(this), attachListStyler(this, themeService) ); + + this.registerListeners(); + } + + public get openOnSingleClick(): boolean { + return this._openOnSingleClick; + } + + public get useAltAsMultipleSelectionModifier(): boolean { + return this._useAltAsMultipleSelectionModifier; + } + + private registerListeners(): void { this.disposables.push(this.onDidChangeSelection(() => { const selection = this.getSelection(); this.listDoubleSelection.set(selection && selection.length === 2); })); + + this.disposables.push(this.configurationService.onDidChangeConfiguration(e => { + if (e.affectsConfiguration(openModeSettingKey)) { + this._openOnSingleClick = useSingleClickToOpen(this.configurationService); + } + + if (e.affectsConfiguration(multiSelectModifierSettingKey)) { + this._useAltAsMultipleSelectionModifier = useAltAsMultipleSelectionModifier(this.configurationService); + } + })); } dispose(): void { @@ -222,6 +315,123 @@ export class WorkbenchTree extends Tree { } } +export class WorkbenchTreeController extends DefaultController { + + protected disposables: IDisposable[] = []; + + constructor( + options: IControllerOptions, + @IConfigurationService private configurationService: IConfigurationService + ) { + super(options); + + // if the open mode is not set, we configure it based on settings + if (isUndefinedOrNull(options.openMode)) { + this.setOpenMode(this.getOpenModeSetting()); + this.registerListeners(); + } + } + + private registerListeners(): void { + this.disposables.push(this.configurationService.onDidChangeConfiguration(e => { + if (e.affectsConfiguration(openModeSettingKey)) { + this.setOpenMode(this.getOpenModeSetting()); + } + })); + } + + private getOpenModeSetting(): OpenMode { + return useSingleClickToOpen(this.configurationService) ? OpenMode.SINGLE_CLICK : OpenMode.DOUBLE_CLICK; + } + + dispose(): void { + this.disposables = dispose(this.disposables); + } +} + +export interface IOpenResourceOptions { + editorOptions: IEditorOptions; + sideBySide: boolean; + element: any; + payload: any; +} + +export interface IResourceResultsNavigationOptions { + openOnFocus: boolean; +} + +export default class ResourceResultsNavigation extends Disposable { + + private _openResource: Emitter = new Emitter(); + public readonly openResource: Event = this._openResource.event; + + constructor(private tree: WorkbenchTree, private options?: IResourceResultsNavigationOptions) { + super(); + + this.registerListeners(); + } + + private registerListeners(): void { + if (this.options && this.options.openOnFocus) { + this._register(this.tree.onDidChangeFocus(e => this.onFocus(e))); + } + + this._register(this.tree.onDidChangeSelection(e => this.onSelection(e))); + } + + private onFocus({ payload }: any): void { + const element = this.tree.getFocus(); + this.tree.setSelection([element], { fromFocus: true }); + + const originalEvent: KeyboardEvent | MouseEvent = payload && payload.originalEvent; + const isMouseEvent = payload && payload.origin === 'mouse'; + const isDoubleClick = isMouseEvent && originalEvent && originalEvent.detail === 2; + + if (!isMouseEvent || this.tree.openOnSingleClick || isDoubleClick) { + this._openResource.fire({ + editorOptions: { + preserveFocus: true, + pinned: false, + revealIfVisible: true + }, + sideBySide: false, + element, + payload + }); + } + } + + private onSelection({ payload }: any): void { + if (payload && payload.fromFocus) { + return; + } + + const originalEvent: KeyboardEvent | MouseEvent = payload && payload.originalEvent; + const isMouseEvent = payload && payload.origin === 'mouse'; + const isDoubleClick = isMouseEvent && originalEvent && originalEvent.detail === 2; + + if (!isMouseEvent || this.tree.openOnSingleClick || isDoubleClick) { + if (isDoubleClick && originalEvent) { + originalEvent.preventDefault(); // focus moves to editor, we need to prevent default + } + + const isFromKeyboard = payload && payload.origin === 'keyboard'; + const sideBySide = (originalEvent && (originalEvent.ctrlKey || originalEvent.metaKey || originalEvent.altKey)); + const preserveFocus = !((isFromKeyboard && (!payload || !payload.preserveFocus)) || isDoubleClick || (payload && payload.focusEditor)); + this._openResource.fire({ + editorOptions: { + preserveFocus, + pinned: isDoubleClick, + revealIfVisible: true + }, + sideBySide, + element: this.tree.getSelection()[0], + payload + }); + } + } +} + const configurationRegistry = Registry.as(ConfigurationExtensions.Configuration); configurationRegistry.registerConfiguration({ @@ -230,7 +440,7 @@ configurationRegistry.registerConfiguration({ 'title': localize('workbenchConfigurationTitle', "Workbench"), 'type': 'object', 'properties': { - 'workbench.multiSelectModifier': { + 'workbench.list.multiSelectModifier': { 'type': 'string', 'enum': ['ctrlCmd', 'alt'], 'enumDescriptions': [ @@ -244,7 +454,20 @@ configurationRegistry.registerConfiguration({ '- `ctrlCmd` refers to a value the setting can take and should not be localized.', '- `Control` and `Command` refer to the modifier keys Ctrl or Cmd on the keyboard and can be localized.' ] - }, "The modifier to be used to add an item to a multi-selection with the mouse (for example in trees and lists, if supported). `ctrlCmd` maps to `Control` on Windows and Linux and to `Command` on macOS. The 'Open to Side' mouse gestures - if supported - will adapt such that they do not conflict with the multiselect modifier.") + }, "The modifier to be used to add an item in trees and lists to a multi-selection with the mouse (if supported). `ctrlCmd` maps to `Control` on Windows and Linux and to `Command` on macOS. The 'Open to Side' mouse gestures - if supported - will adapt such that they do not conflict with the multiselect modifier.") + }, + 'workbench.list.openMode': { + 'type': 'string', + 'enum': ['singleClick', 'doubleClick'], + 'enumDescriptions': [ + localize('openMode.singleClick', "Opens items on mouse single click."), + localize('openMode.doubleClick', "Open items on mouse double click.") + ], + 'default': 'singleClick', + 'description': localize({ + key: 'openModeModifier', + comment: ['`singleClick` and `doubleClick` refers to a value the setting can take and should not be localized.'] + }, "Controls how to open items in trees and lists using the mouse (if supported). Set to `singleClick` to open items with a single mouse click and `doubleClick` to only open via mouse double click.") } } }); \ No newline at end of file diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index 802c06809ff..92abf31ae24 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -159,7 +159,7 @@ declare module 'vscode' { export function registerFileSystemProvider(scheme: string, provider: FileSystemProvider): Disposable; /** - * Updates the workspace folders of the currently opened workspace. This method allows to add, remove + * Updates the [workspace folders](#workspace.workspaceFolders) of the currently opened workspace. This method allows to add, remove * and change workspace folders a the same time. Use the [onDidChangeWorkspaceFolders()](#onDidChangeWorkspaceFolders) * event to get notified when the workspace folders have been updated. * @@ -181,11 +181,11 @@ declare module 'vscode' { * It is valid to remove an existing workspace folder and add it again with a different name * to rename that folder. * - * Note: if the first workspace folder is added, removed or changed, all extensions will be restarted + * **Note:** if the first workspace folder is added, removed or changed, all extensions will be restarted * so that the (deprecated) `rootPath` property is updated to point to the first workspace * folder. * - * Note: it is not valid to call [updateWorkspaceFolders()](#updateWorkspaceFolders) multiple times + * **Note:** it is not valid to call [updateWorkspaceFolders()](#updateWorkspaceFolders) multiple times * without waiting for the [onDidChangeWorkspaceFolders()](#onDidChangeWorkspaceFolders) to fire. * * @param start the zero-based location in the list of currently opened [workspace folders](#WorkspaceFolder) diff --git a/src/vs/workbench/api/node/extHostWorkspace.ts b/src/vs/workbench/api/node/extHostWorkspace.ts index ec70503062e..fabbcd0ab66 100644 --- a/src/vs/workbench/api/node/extHostWorkspace.ts +++ b/src/vs/workbench/api/node/extHostWorkspace.ts @@ -222,7 +222,9 @@ export class ExtHostWorkspace implements ExtHostWorkspaceShape { } // Try to accept directly - return this.trySetWorkspaceFolders(newWorkspaceFolders); + this.trySetWorkspaceFolders(newWorkspaceFolders); + + return true; } getWorkspaceFolder(uri: vscode.Uri, resolveParent?: boolean): vscode.WorkspaceFolder { @@ -281,7 +283,7 @@ export class ExtHostWorkspace implements ExtHostWorkspaceShape { return normalize(result, true); } - private trySetWorkspaceFolders(folders: vscode.WorkspaceFolder[]): boolean { + private trySetWorkspaceFolders(folders: vscode.WorkspaceFolder[]): void { // Update directly here. The workspace is unconfirmed as long as we did not get an // acknowledgement from the main side (via $acceptWorkspaceData) @@ -292,11 +294,7 @@ export class ExtHostWorkspace implements ExtHostWorkspaceShape { configuration: this._actualWorkspace.configuration, folders } as IWorkspaceData, this._actualWorkspace).workspace; - - return true; } - - return false; } $acceptWorkspaceData(data: IWorkspaceData): void { diff --git a/src/vs/workbench/browser/parts/views/treeView.ts b/src/vs/workbench/browser/parts/views/treeView.ts index 98235ba28b6..9a2137363ef 100644 --- a/src/vs/workbench/browser/parts/views/treeView.ts +++ b/src/vs/workbench/browser/parts/views/treeView.ts @@ -14,7 +14,7 @@ import { IAction, IActionItem, ActionRunner } from 'vs/base/common/actions'; import { IMessageService } from 'vs/platform/message/common/message'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; -import { ClickBehavior, DefaultController } from 'vs/base/parts/tree/browser/treeDefaults'; +import { ClickBehavior } from 'vs/base/parts/tree/browser/treeDefaults'; import { IMenuService, MenuId, MenuItemAction } from 'vs/platform/actions/common/actions'; import { IThemeService, LIGHT } from 'vs/platform/theme/common/themeService'; import { createActionItem, fillInActions } from 'vs/platform/actions/browser/menuItemActionItem'; @@ -26,12 +26,13 @@ import { ViewsRegistry, TreeItemCollapsibleState, ITreeItem, ITreeViewDataProvid import { IExtensionService } from 'vs/platform/extensions/common/extensions'; import { IViewletViewOptions, IViewOptions, TreeViewsViewletPanel, FileIconThemableWorkbenchTree } from 'vs/workbench/browser/parts/views/viewsViewlet'; import { ICommandService } from 'vs/platform/commands/common/commands'; -import { WorkbenchTree } from 'vs/platform/list/browser/listService'; +import { WorkbenchTree, WorkbenchTreeController } from 'vs/platform/list/browser/listService'; import { ResourceLabel } from 'vs/workbench/browser/labels'; import URI from 'vs/base/common/uri'; import { basename } from 'vs/base/common/paths'; import { FileKind } from 'vs/platform/files/common/files'; import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; export class TreeView extends TreeViewsViewletPanel { @@ -92,11 +93,11 @@ export class TreeView extends TreeViewsViewletPanel { const tree = this.instantiationService.createInstance(FileIconThemableWorkbenchTree, container.getHTMLElement(), { dataSource, renderer, controller }, - { keyboardSupport: false } + {} ); tree.contextKeyService.createKey(this.id, true); - this.disposables.push(tree.onDidChangeSelection(() => this.onSelection())); + this.disposables.push(tree.onDidChangeSelection(e => this.onSelection(e))); return tree; } @@ -159,11 +160,17 @@ export class TreeView extends TreeViewsViewletPanel { return DOM.getLargestChildWidth(parentNode, childNodes); } - private onSelection(): void { + private onSelection({ payload }: any): void { const selection: ITreeItem = this.tree.getSelection()[0]; if (selection) { if (selection.command) { - this.commandService.executeCommand(selection.command.id, ...(selection.command.arguments || [])); + const originalEvent: KeyboardEvent | MouseEvent = payload && payload.originalEvent; + const isMouseEvent = payload && payload.origin === 'mouse'; + const isDoubleClick = isMouseEvent && originalEvent && originalEvent.detail === 2; + + if (!isMouseEvent || this.tree.openOnSingleClick || isDoubleClick) { + this.commandService.executeCommand(selection.command.id, ...(selection.command.arguments || [])); + } } } } @@ -347,15 +354,16 @@ class TreeRenderer implements IRenderer { } } -class TreeController extends DefaultController { +class TreeController extends WorkbenchTreeController { constructor( private treeViewId: string, private menus: Menus, @IContextMenuService private contextMenuService: IContextMenuService, - @IKeybindingService private _keybindingService: IKeybindingService + @IKeybindingService private _keybindingService: IKeybindingService, + @IConfigurationService configurationService: IConfigurationService ) { - super({ clickBehavior: ClickBehavior.ON_MOUSE_UP /* do not change to not break DND */, keyboardSupport: false }); + super({ clickBehavior: ClickBehavior.ON_MOUSE_UP /* do not change to not break DND */, keyboardSupport: false }, configurationService); } public onContextMenu(tree: ITree, node: ITreeItem, event: ContextMenuEvent): boolean { diff --git a/src/vs/workbench/browser/parts/views/viewsViewlet.ts b/src/vs/workbench/browser/parts/views/viewsViewlet.ts index 1ab63b6f749..0e90dbc8772 100644 --- a/src/vs/workbench/browser/parts/views/viewsViewlet.ts +++ b/src/vs/workbench/browser/parts/views/viewsViewlet.ts @@ -31,6 +31,7 @@ import { WorkbenchTree, IListService } from 'vs/platform/list/browser/listServic import { IWorkbenchThemeService, IFileIconTheme } from 'vs/workbench/services/themes/common/workbenchThemeService'; import { ITreeConfiguration, ITreeOptions } from 'vs/base/parts/tree/browser/tree'; import Event, { Emitter } from 'vs/base/common/event'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; export interface IViewOptions extends IPanelOptions { id: string; @@ -771,9 +772,10 @@ export class FileIconThemableWorkbenchTree extends WorkbenchTree { options: ITreeOptions, @IContextKeyService contextKeyService: IContextKeyService, @IListService listService: IListService, - @IThemeService themeService: IWorkbenchThemeService + @IThemeService themeService: IWorkbenchThemeService, + @IConfigurationService configurationService: IConfigurationService ) { - super(container, configuration, { ...options, ...{ showTwistie: false, twistiePixels: 12 } }, contextKeyService, listService, themeService); + super(container, configuration, { ...options, ...{ showTwistie: false, twistiePixels: 12 } }, contextKeyService, listService, themeService, configurationService); DOM.addClass(container, 'file-icon-themable-tree'); DOM.addClass(container, 'show-file-icons'); diff --git a/src/vs/workbench/parts/debug/common/debugModel.ts b/src/vs/workbench/parts/debug/common/debugModel.ts index 7b162f1caab..4b4295d0fa8 100644 --- a/src/vs/workbench/parts/debug/common/debugModel.ts +++ b/src/vs/workbench/parts/debug/common/debugModel.ts @@ -380,9 +380,9 @@ export class StackFrame implements IStackFrame { return `${this.name} (${this.source.inMemory ? this.source.name : this.source.uri.fsPath}:${this.range.startLineNumber})`; } - public openInEditor(editorService: IWorkbenchEditorService, preserveFocus?: boolean, sideBySide?: boolean): TPromise { + public openInEditor(editorService: IWorkbenchEditorService, preserveFocus?: boolean, sideBySide?: boolean, pinned?: boolean): TPromise { return !this.source.available ? TPromise.as(null) : - this.source.openInEditor(editorService, this.range, preserveFocus, sideBySide); + this.source.openInEditor(editorService, this.range, preserveFocus, sideBySide, pinned); } } diff --git a/src/vs/workbench/parts/debug/common/debugSource.ts b/src/vs/workbench/parts/debug/common/debugSource.ts index 22a60851431..3816e33cb70 100644 --- a/src/vs/workbench/parts/debug/common/debugSource.ts +++ b/src/vs/workbench/parts/debug/common/debugSource.ts @@ -56,7 +56,7 @@ export class Source { return this.uri.scheme === DEBUG_SCHEME; } - public openInEditor(editorService: IWorkbenchEditorService, selection: IRange, preserveFocus?: boolean, sideBySide?: boolean): TPromise { + public openInEditor(editorService: IWorkbenchEditorService, selection: IRange, preserveFocus?: boolean, sideBySide?: boolean, pinned?: boolean): TPromise { return !this.available ? TPromise.as(null) : editorService.openEditor({ resource: this.uri, description: this.origin, @@ -65,7 +65,7 @@ export class Source { selection, revealIfVisible: true, revealInCenterIfOutsideViewport: true, - pinned: !preserveFocus && !this.inMemory + pinned: pinned || (!preserveFocus && !this.inMemory) } }, sideBySide); } diff --git a/src/vs/workbench/parts/debug/electron-browser/baseDebugView.ts b/src/vs/workbench/parts/debug/electron-browser/baseDebugView.ts index e7e49585dfc..51060145213 100644 --- a/src/vs/workbench/parts/debug/electron-browser/baseDebugView.ts +++ b/src/vs/workbench/parts/debug/electron-browser/baseDebugView.ts @@ -15,11 +15,13 @@ import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { once } from 'vs/base/common/functional'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IMenuService, MenuId, IMenu } from 'vs/platform/actions/common/actions'; -import { ClickBehavior, DefaultController } from 'vs/base/parts/tree/browser/treeDefaults'; +import { ClickBehavior, IControllerOptions } from 'vs/base/parts/tree/browser/treeDefaults'; import { fillInActions } from 'vs/platform/actions/browser/menuItemActionItem'; import { KeyCode } from 'vs/base/common/keyCodes'; import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { onUnexpectedError } from 'vs/base/common/errors'; +import { WorkbenchTreeController } from 'vs/platform/list/browser/listService'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; export const MAX_VALUE_RENDER_LENGTH_IN_VIEWLET = 1024; export const twistiePixels = 20; @@ -190,19 +192,23 @@ export function renderRenameBox(debugService: IDebugService, contextViewService: })); } -export class BaseDebugController extends DefaultController { +export const DefaultDebugControllerOptions: IControllerOptions = { clickBehavior: ClickBehavior.ON_MOUSE_UP, keyboardSupport: false }; + +export class BaseDebugController extends WorkbenchTreeController { private contributedContextMenu: IMenu; constructor( private actionProvider: IActionProvider, menuId: MenuId, + options: IControllerOptions, @IDebugService protected debugService: IDebugService, @IContextMenuService private contextMenuService: IContextMenuService, @IContextKeyService contextKeyService: IContextKeyService, - @IMenuService menuService: IMenuService + @IMenuService menuService: IMenuService, + @IConfigurationService configurationService: IConfigurationService ) { - super({ clickBehavior: ClickBehavior.ON_MOUSE_UP, keyboardSupport: false }); + super(options, configurationService); this.contributedContextMenu = menuService.createMenu(menuId, contextKeyService); } diff --git a/src/vs/workbench/parts/debug/electron-browser/breakpointsView.ts b/src/vs/workbench/parts/debug/electron-browser/breakpointsView.ts index 6e4c6862c91..c6692a98a85 100644 --- a/src/vs/workbench/parts/debug/electron-browser/breakpointsView.ts +++ b/src/vs/workbench/parts/debug/electron-browser/breakpointsView.ts @@ -78,29 +78,39 @@ export class BreakpointsView extends ViewsViewletPanel { this.list.onContextMenu(this.onListContextMenu, this, this.disposables); - const handleBreakpointFocus = (preserveFocuse: boolean, sideBySide: boolean, selectFunctionBreakpoint: boolean) => { + const handleBreakpointFocus = (preserveFocus: boolean, sideBySide: boolean, selectFunctionBreakpoint: boolean) => { const focused = this.list.getFocusedElements(); const element = focused.length ? focused[0] : undefined; if (element instanceof Breakpoint) { - openBreakpointSource(element, sideBySide, preserveFocuse, this.debugService, this.editorService).done(undefined, onUnexpectedError); + openBreakpointSource(element, sideBySide, preserveFocus, this.debugService, this.editorService).done(undefined, onUnexpectedError); } if (selectFunctionBreakpoint && element instanceof FunctionBreakpoint && element !== this.debugService.getViewModel().getSelectedFunctionBreakpoint()) { this.debugService.getViewModel().setSelectedFunctionBreakpoint(element); this.onBreakpointsChange(); } }; + this.disposables.push(this.list.onOpen(e => { + let isSingleClick = false; + let isDoubleClick = false; + let openToSide = false; + + const browserEvent = e.browserEvent; + if (browserEvent instanceof MouseEvent) { + isSingleClick = browserEvent.detail === 1; + isDoubleClick = browserEvent.detail === 2; + openToSide = (browserEvent.ctrlKey || browserEvent.metaKey || browserEvent.altKey); + } + + handleBreakpointFocus(isSingleClick, openToSide, isDoubleClick); + })); + + // TODO@Isidor this should be a command (breakpoints.openToSide) this.disposables.push(this.list.onKeyUp(e => { const event = new StandardKeyboardEvent(e); - if (event.equals(KeyCode.Enter)) { - handleBreakpointFocus(false, event && (event.ctrlKey || event.metaKey || event.altKey), false); + if (event.equals(KeyCode.Enter) && (event.ctrlKey || event.metaKey || event.altKey)) { + handleBreakpointFocus(false, true, false); } })); - this.disposables.push(this.list.onMouseDblClick(e => { - handleBreakpointFocus(false, e.browserEvent.altKey, true); - })); - this.disposables.push(this.list.onMouseClick(e => { - handleBreakpointFocus(true, e.browserEvent.altKey, false); - })); this.list.splice(0, this.list.length, this.elements); } diff --git a/src/vs/workbench/parts/debug/electron-browser/callStackView.ts b/src/vs/workbench/parts/debug/electron-browser/callStackView.ts index 123bcb9be6c..696f4d33598 100644 --- a/src/vs/workbench/parts/debug/electron-browser/callStackView.ts +++ b/src/vs/workbench/parts/debug/electron-browser/callStackView.ts @@ -15,7 +15,7 @@ import { IContextMenuService } from 'vs/platform/contextview/browser/contextView import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { MenuId } from 'vs/platform/actions/common/actions'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; -import { BaseDebugController, twistiePixels, renderViewTree } from 'vs/workbench/parts/debug/electron-browser/baseDebugView'; +import { BaseDebugController, twistiePixels, renderViewTree, DefaultDebugControllerOptions } from 'vs/workbench/parts/debug/electron-browser/baseDebugView'; import { ITree, IActionProvider, IDataSource, IRenderer, IAccessibilityProvider } from 'vs/base/parts/tree/browser/tree'; import { IAction, IActionItem } from 'vs/base/common/actions'; import { RestartAction, StopAction, ContinueAction, StepOverAction, StepIntoAction, StepOutAction, PauseAction, RestartFrameAction } from 'vs/workbench/parts/debug/browser/debugActions'; @@ -24,9 +24,8 @@ import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/ import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { Source } from 'vs/workbench/parts/debug/common/debugSource'; import { basenameOrAuthority } from 'vs/base/common/resources'; -import { WorkbenchTree } from 'vs/platform/list/browser/listService'; +import ResourceResultsNavigation, { WorkbenchTree } from 'vs/platform/list/browser/listService'; import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService'; -import FileResultsNavigation from 'vs/workbench/parts/files/browser/fileResultsNavigation'; const $ = dom.$; @@ -93,7 +92,7 @@ export class CallStackView extends TreeViewsViewletPanel { dom.addClass(container, 'debug-call-stack'); this.treeContainer = renderViewTree(container); const actionProvider = new CallStackActionProvider(this.debugService, this.keybindingService); - const controller = this.instantiationService.createInstance(CallStackController, actionProvider, MenuId.DebugCallStackContext); + const controller = this.instantiationService.createInstance(CallStackController, actionProvider, MenuId.DebugCallStackContext, DefaultDebugControllerOptions); this.tree = this.instantiationService.createInstance(WorkbenchTree, this.treeContainer, { dataSource: new CallStackDataSource(), @@ -102,13 +101,12 @@ export class CallStackView extends TreeViewsViewletPanel { controller }, { ariaLabel: nls.localize({ comment: ['Debug is a noun in this context, not a verb.'], key: 'callStackAriaLabel' }, "Debug Call Stack"), - twistiePixels, - keyboardSupport: false + twistiePixels }); - const fileResultsNavigation = new FileResultsNavigation(this.tree); + const fileResultsNavigation = new ResourceResultsNavigation(this.tree); this.disposables.push(fileResultsNavigation); - this.disposables.push(fileResultsNavigation.openFile(e => { + this.disposables.push(fileResultsNavigation.openResource(e => { if (this.ignoreSelectionChangedEvent) { return; } @@ -116,7 +114,7 @@ export class CallStackView extends TreeViewsViewletPanel { const element = e.element; if (element instanceof StackFrame) { this.debugService.focusStackFrame(element, element.thread, element.thread.process, true); - element.openInEditor(this.editorService, e.editorOptions.preserveFocus, e.sideBySide).done(undefined, errors.onUnexpectedError); + element.openInEditor(this.editorService, e.editorOptions.preserveFocus, e.sideBySide, e.editorOptions.pinned).done(undefined, errors.onUnexpectedError); } if (element instanceof Thread) { this.debugService.focusStackFrame(undefined, element, element.process, true); diff --git a/src/vs/workbench/parts/debug/electron-browser/debugHover.ts b/src/vs/workbench/parts/debug/electron-browser/debugHover.ts index 30c4fca4496..a187adff67f 100644 --- a/src/vs/workbench/parts/debug/electron-browser/debugHover.ts +++ b/src/vs/workbench/parts/debug/electron-browser/debugHover.ts @@ -11,7 +11,7 @@ import { ScrollbarVisibility } from 'vs/base/common/scrollable'; import * as dom from 'vs/base/browser/dom'; import { ITree } from 'vs/base/parts/tree/browser/tree'; import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent'; -import { DefaultController, ICancelableEvent, ClickBehavior } from 'vs/base/parts/tree/browser/treeDefaults'; +import { DefaultController, ICancelableEvent, ClickBehavior, OpenMode } from 'vs/base/parts/tree/browser/treeDefaults'; import { IConfigurationChangedEvent } from 'vs/editor/common/config/editorOptions'; import { Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; @@ -75,8 +75,7 @@ export class DebugHoverWidget implements IContentWidget { }, { indentPixels: 6, twistiePixels: 15, - ariaLabel: nls.localize('treeAriaLabel', "Debug Hover"), - keyboardSupport: false + ariaLabel: nls.localize('treeAriaLabel', "Debug Hover") }); this.valueContainer = $('.value'); @@ -337,7 +336,7 @@ export class DebugHoverWidget implements IContentWidget { class DebugHoverController extends DefaultController { constructor(private editor: ICodeEditor) { - super({ clickBehavior: ClickBehavior.ON_MOUSE_UP, keyboardSupport: false }); + super({ clickBehavior: ClickBehavior.ON_MOUSE_UP, keyboardSupport: false, openMode: OpenMode.SINGLE_CLICK }); } protected onLeftClick(tree: ITree, element: any, eventish: ICancelableEvent, origin = 'mouse'): boolean { diff --git a/src/vs/workbench/parts/debug/electron-browser/repl.ts b/src/vs/workbench/parts/debug/electron-browser/repl.ts index 6ea2b74afef..87596c77d68 100644 --- a/src/vs/workbench/parts/debug/electron-browser/repl.ts +++ b/src/vs/workbench/parts/debug/electron-browser/repl.ts @@ -44,13 +44,13 @@ import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { WorkbenchTree } from 'vs/platform/list/browser/listService'; import { memoize } from 'vs/base/common/decorators'; import { dispose } from 'vs/base/common/lifecycle'; +import { ClickBehavior, OpenMode } from 'vs/base/parts/tree/browser/treeDefaults'; const $ = dom.$; const replTreeOptions: ITreeOptions = { twistiePixels: 20, - ariaLabel: nls.localize('replAriaLabel', "Read Eval Print Loop Panel"), - keyboardSupport: false + ariaLabel: nls.localize('replAriaLabel', "Read Eval Print Loop Panel") }; const HISTORY_STORAGE_KEY = 'debug.repl.history'; @@ -135,7 +135,7 @@ export class Repl extends Panel implements IPrivateReplService { this.createReplInput(this.container); this.renderer = this.instantiationService.createInstance(ReplExpressionsRenderer); - const controller = this.instantiationService.createInstance(ReplExpressionsController, new ReplExpressionsActionProvider(this.instantiationService), MenuId.DebugConsoleContext); + const controller = this.instantiationService.createInstance(ReplExpressionsController, new ReplExpressionsActionProvider(this.instantiationService), MenuId.DebugConsoleContext, { clickBehavior: ClickBehavior.ON_MOUSE_UP, keyboardSupport: false, openMode: OpenMode.SINGLE_CLICK }); controller.toFocusOnClick = this.replInput; this.tree = this.instantiationService.createInstance(WorkbenchTree, this.treeContainer, { diff --git a/src/vs/workbench/parts/debug/electron-browser/variablesView.ts b/src/vs/workbench/parts/debug/electron-browser/variablesView.ts index e157a61a862..8ef474462bd 100644 --- a/src/vs/workbench/parts/debug/electron-browser/variablesView.ts +++ b/src/vs/workbench/parts/debug/electron-browser/variablesView.ts @@ -28,6 +28,7 @@ import { ViewModel } from 'vs/workbench/parts/debug/common/debugViewModel'; import { equalsIgnoreCase } from 'vs/base/common/strings'; import { IMouseEvent } from 'vs/base/browser/mouseEvent'; import { WorkbenchTree } from 'vs/platform/list/browser/listService'; +import { ClickBehavior, OpenMode } from 'vs/base/parts/tree/browser/treeDefaults'; const $ = dom.$; @@ -87,11 +88,10 @@ export class VariablesView extends TreeViewsViewletPanel { dataSource: new VariablesDataSource(), renderer: this.instantiationService.createInstance(VariablesRenderer), accessibilityProvider: new VariablesAccessibilityProvider(), - controller: this.instantiationService.createInstance(VariablesController, new VariablesActionProvider(this.debugService, this.keybindingService), MenuId.DebugVariablesContext) + controller: this.instantiationService.createInstance(VariablesController, new VariablesActionProvider(this.debugService, this.keybindingService), MenuId.DebugVariablesContext, { clickBehavior: ClickBehavior.ON_MOUSE_UP, keyboardSupport: false, openMode: OpenMode.SINGLE_CLICK }) }, { ariaLabel: nls.localize('variablesAriaTreeLabel', "Debug Variables"), - twistiePixels, - keyboardSupport: false + twistiePixels }); CONTEXT_VARIABLES_FOCUSED.bindTo(this.tree.contextKeyService); diff --git a/src/vs/workbench/parts/debug/electron-browser/watchExpressionsView.ts b/src/vs/workbench/parts/debug/electron-browser/watchExpressionsView.ts index dbbd7bfeb29..23b519b6472 100644 --- a/src/vs/workbench/parts/debug/electron-browser/watchExpressionsView.ts +++ b/src/vs/workbench/parts/debug/electron-browser/watchExpressionsView.ts @@ -26,7 +26,7 @@ import { CopyValueAction } from 'vs/workbench/parts/debug/electron-browser/elect import { Separator } from 'vs/base/browser/ui/actionbar/actionbar'; import { equalsIgnoreCase } from 'vs/base/common/strings'; import { IMouseEvent, DragMouseEvent } from 'vs/base/browser/mouseEvent'; -import { DefaultDragAndDrop } from 'vs/base/parts/tree/browser/treeDefaults'; +import { DefaultDragAndDrop, ClickBehavior, OpenMode } from 'vs/base/parts/tree/browser/treeDefaults'; import { IVariableTemplateData, renderVariable, renderRenameBox, renderExpressionValue, BaseDebugController, twistiePixels, renderViewTree } from 'vs/workbench/parts/debug/electron-browser/baseDebugView'; import { WorkbenchTree } from 'vs/platform/list/browser/listService'; @@ -65,12 +65,11 @@ export class WatchExpressionsView extends TreeViewsViewletPanel { dataSource: new WatchExpressionsDataSource(this.debugService), renderer: this.instantiationService.createInstance(WatchExpressionsRenderer), accessibilityProvider: new WatchExpressionsAccessibilityProvider(), - controller: this.instantiationService.createInstance(WatchExpressionsController, actionProvider, MenuId.DebugWatchContext), + controller: this.instantiationService.createInstance(WatchExpressionsController, actionProvider, MenuId.DebugWatchContext, { clickBehavior: ClickBehavior.ON_MOUSE_UP, keyboardSupport: false, openMode: OpenMode.SINGLE_CLICK }), dnd: new WatchExpressionsDragAndDrop(this.debugService) }, { ariaLabel: nls.localize({ comment: ['Debug is a noun in this context, not a verb.'], key: 'watchAriaTreeLabel' }, "Debug Watch Expressions"), - twistiePixels, - keyboardSupport: false + twistiePixels }); CONTEXT_WATCH_EXPRESSIONS_FOCUSED.bindTo(this.tree.contextKeyService); diff --git a/src/vs/workbench/parts/extensions/browser/dependenciesViewer.ts b/src/vs/workbench/parts/extensions/browser/dependenciesViewer.ts index b4c3abe5334..ae9cd5f371c 100644 --- a/src/vs/workbench/parts/extensions/browser/dependenciesViewer.ts +++ b/src/vs/workbench/parts/extensions/browser/dependenciesViewer.ts @@ -9,13 +9,15 @@ import { IMouseEvent } from 'vs/base/browser/mouseEvent'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { TPromise, Promise } from 'vs/base/common/winjs.base'; import { IDataSource, ITree, IRenderer } from 'vs/base/parts/tree/browser/tree'; -import { DefaultController, ClickBehavior } from 'vs/base/parts/tree/browser/treeDefaults'; +import { ClickBehavior } from 'vs/base/parts/tree/browser/treeDefaults'; import { Action } from 'vs/base/common/actions'; import { IExtensionDependencies, IExtensionsWorkbenchService } from 'vs/workbench/parts/extensions/common/extensions'; import { once } from 'vs/base/common/event'; import { domEvent } from 'vs/base/browser/event'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { KeyMod, KeyCode } from 'vs/base/common/keyCodes'; +import { WorkbenchTreeController } from 'vs/platform/list/browser/listService'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; export interface IExtensionTemplateData { icon: HTMLImageElement; @@ -156,21 +158,24 @@ export class Renderer implements IRenderer { } } -export class Controller extends DefaultController { +export class Controller extends WorkbenchTreeController { - constructor( @IExtensionsWorkbenchService private extensionsWorkdbenchService: IExtensionsWorkbenchService) { - super({ clickBehavior: ClickBehavior.ON_MOUSE_UP, keyboardSupport: false }); + constructor( + @IExtensionsWorkbenchService private extensionsWorkdbenchService: IExtensionsWorkbenchService, + @IConfigurationService configurationService: IConfigurationService + ) { + super({ clickBehavior: ClickBehavior.ON_MOUSE_UP, keyboardSupport: false }, configurationService); // TODO@Sandeep this should be a command this.downKeyBindingDispatcher.set(KeyMod.CtrlCmd | KeyCode.Enter, (tree: ITree, event: any) => this.openExtension(tree, true)); } protected onLeftClick(tree: ITree, element: IExtensionDependencies, event: IMouseEvent): boolean { - let currentFoucssed = tree.getFocus(); + let currentFocused = tree.getFocus(); if (super.onLeftClick(tree, element, event)) { if (element.dependent === null) { - if (currentFoucssed) { - tree.setFocus(currentFoucssed); + if (currentFocused) { + tree.setFocus(currentFocused); } else { tree.focusFirst(); } diff --git a/src/vs/workbench/parts/extensions/browser/extensionEditor.ts b/src/vs/workbench/parts/extensions/browser/extensionEditor.ts index f101cbd65d6..8e6b31e1f55 100644 --- a/src/vs/workbench/parts/extensions/browser/extensionEditor.ts +++ b/src/vs/workbench/parts/extensions/browser/extensionEditor.ts @@ -526,8 +526,7 @@ export class ExtensionEditor extends BaseEditor { controller }, { indentPixels: 40, - twistiePixels: 20, - keyboardSupport: false + twistiePixels: 20 }); tree.setInput(extensionDependencies); diff --git a/src/vs/workbench/parts/extensions/electron-browser/extensionsViews.ts b/src/vs/workbench/parts/extensions/electron-browser/extensionsViews.ts index 69e6766f25c..c3806ceef8d 100644 --- a/src/vs/workbench/parts/extensions/electron-browser/extensionsViews.ts +++ b/src/vs/workbench/parts/extensions/electron-browser/extensionsViews.ts @@ -78,11 +78,10 @@ export class ExtensionsListView extends ViewsViewletPanel { const delegate = new Delegate(); const renderer = this.instantiationService.createInstance(Renderer); this.list = this.instantiationService.createInstance(WorkbenchPagedList, this.extensionsList, delegate, [renderer], { - ariaLabel: localize('extensions', "Extensions"), - keyboardSupport: false + ariaLabel: localize('extensions', "Extensions") }); - chain(this.list.onSelectionChange) + chain(this.list.onOpen) .map(e => e.elements[0]) .filter(e => !!e) .on(this.openExtension, this, this.disposables); @@ -443,6 +442,7 @@ export class ExtensionsListView extends ViewsViewletPanel { const activeEditorInput = this.editorService.getActiveEditorInput(); this.editorInputService.pinEditor(activeEditor.position, activeEditorInput); + activeEditor.focus(); } diff --git a/src/vs/workbench/parts/files/browser/fileResultsNavigation.ts b/src/vs/workbench/parts/files/browser/fileResultsNavigation.ts deleted file mode 100644 index 46558d347a0..00000000000 --- a/src/vs/workbench/parts/files/browser/fileResultsNavigation.ts +++ /dev/null @@ -1,76 +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 Event, { Emitter } from 'vs/base/common/event'; -import { IEditorOptions } from 'vs/platform/editor/common/editor'; -import { ITree } from 'vs/base/parts/tree/browser/tree'; - -export interface IOpenFileOptions { - editorOptions: IEditorOptions; - sideBySide: boolean; - element: any; - payload: any; -} - -export interface IFileResultsNavigationOptions { - openOnFocus: boolean; -} - -export default class FileResultsNavigation extends Disposable { - - private _openFile: Emitter = new Emitter(); - public readonly openFile: Event = this._openFile.event; - - constructor(private tree: ITree, options?: IFileResultsNavigationOptions) { - super(); - if (options && options.openOnFocus) { - this._register(this.tree.onDidChangeFocus(e => this.onFocus(e))); - } - this._register(this.tree.onDidChangeSelection(e => this.onSelection(e))); - } - - private onFocus(event: any): void { - const element = this.tree.getFocus(); - this.tree.setSelection([element], { fromFocus: true }); - this._openFile.fire({ - editorOptions: { - preserveFocus: true, - pinned: false, - revealIfVisible: true - }, - sideBySide: false, - element, - payload: event.payload - }); - } - - private onSelection({ payload }: any): void { - if (payload && payload.fromFocus) { - return; - } - - const keyboard = payload && payload.origin === 'keyboard'; - const originalEvent: KeyboardEvent | MouseEvent = payload && payload.originalEvent; - - const pinned = (payload && payload.origin === 'mouse' && originalEvent && originalEvent.detail === 2); - if (pinned && originalEvent) { - originalEvent.preventDefault(); // focus moves to editor, we need to prevent default - } - - const sideBySide = (originalEvent && (originalEvent.ctrlKey || originalEvent.metaKey || originalEvent.altKey)); - const preserveFocus = !((keyboard && (!payload || !payload.preserveFocus)) || pinned || (payload && payload.focusEditor)); - this._openFile.fire({ - editorOptions: { - preserveFocus, - pinned, - revealIfVisible: true - }, - sideBySide, - element: this.tree.getSelection()[0], - payload - }); - } -} diff --git a/src/vs/workbench/parts/files/electron-browser/views/explorerView.ts b/src/vs/workbench/parts/files/electron-browser/views/explorerView.ts index 9d28ff82fa4..d60b9e72bcb 100644 --- a/src/vs/workbench/parts/files/electron-browser/views/explorerView.ts +++ b/src/vs/workbench/parts/files/electron-browser/views/explorerView.ts @@ -403,8 +403,7 @@ export class ExplorerView extends TreeViewsViewletPanel implements IExplorerView accessibilityProvider }, { autoExpandSingleChildren: true, - ariaLabel: nls.localize('treeAriaLabel', "Files Explorer"), - keyboardSupport: false + ariaLabel: nls.localize('treeAriaLabel', "Files Explorer") }); // Bind context keys diff --git a/src/vs/workbench/parts/files/electron-browser/views/explorerViewer.ts b/src/vs/workbench/parts/files/electron-browser/views/explorerViewer.ts index b5f34511a47..eaf035bc91e 100644 --- a/src/vs/workbench/parts/files/electron-browser/views/explorerViewer.ts +++ b/src/vs/workbench/parts/files/electron-browser/views/explorerViewer.ts @@ -29,7 +29,7 @@ import { ResourceMap } from 'vs/base/common/map'; import { DuplicateFileAction, ImportFileAction, IEditableData, IFileViewletState } from 'vs/workbench/parts/files/electron-browser/fileActions'; import { IDataSource, ITree, IAccessibilityProvider, IRenderer, ContextMenuEvent, ISorter, IFilter, IDragAndDropData, IDragOverReaction, DRAG_OVER_ACCEPT_BUBBLE_DOWN, DRAG_OVER_ACCEPT_BUBBLE_DOWN_COPY, DRAG_OVER_ACCEPT_BUBBLE_UP, DRAG_OVER_ACCEPT_BUBBLE_UP_COPY, DRAG_OVER_REJECT } from 'vs/base/parts/tree/browser/tree'; import { DesktopDragAndDropData, ExternalElementsDragAndDropData, SimpleFileResourceDragAndDrop } from 'vs/base/parts/tree/browser/treeDnd'; -import { ClickBehavior, DefaultController } from 'vs/base/parts/tree/browser/treeDefaults'; +import { ClickBehavior } from 'vs/base/parts/tree/browser/treeDefaults'; import { FileStat, NewStatPlaceholder, Model } from 'vs/workbench/parts/files/common/explorerModel'; import { DragMouseEvent, IMouseEvent } from 'vs/base/browser/mouseEvent'; import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService'; @@ -56,7 +56,7 @@ import { extractResources } from 'vs/workbench/browser/editor'; import { relative } from 'path'; import { DataTransfers } from 'vs/base/browser/dnd'; import { distinctParents } from 'vs/base/common/resources'; -import { WorkbenchTree, multiSelectModifierSettingKey } from 'vs/platform/list/browser/listService'; +import { WorkbenchTree, WorkbenchTreeController } from 'vs/platform/list/browser/listService'; export class FileDataSource implements IDataSource { constructor( @@ -325,12 +325,11 @@ export class FileAccessibilityProvider implements IAccessibilityProvider { } // Explorer Controller -export class FileController extends DefaultController implements IDisposable { +export class FileController extends WorkbenchTreeController implements IDisposable { private contributedContextMenu: IMenu; private toDispose: IDisposable[]; private previousSelectionRangeStop: FileStat; - private useAltAsMultiSelectModifier: boolean; constructor( @IWorkbenchEditorService private editorService: IWorkbenchEditorService, @@ -338,25 +337,14 @@ export class FileController extends DefaultController implements IDisposable { @ITelemetryService private telemetryService: ITelemetryService, @IMenuService private menuService: IMenuService, @IContextKeyService contextKeyService: IContextKeyService, - @IConfigurationService private configurationService: IConfigurationService + @IConfigurationService configurationService: IConfigurationService ) { - super({ clickBehavior: ClickBehavior.ON_MOUSE_UP /* do not change to not break DND */, keyboardSupport: false /* handled via IListService */ }); + super({ clickBehavior: ClickBehavior.ON_MOUSE_UP /* do not change to not break DND */, keyboardSupport: false /* handled via IListService */ }, configurationService); - this.useAltAsMultiSelectModifier = configurationService.getValue(multiSelectModifierSettingKey) === 'alt'; this.toDispose = []; - - this.registerListeners(); } - private registerListeners(): void { - this.toDispose.push(this.configurationService.onDidChangeConfiguration(e => { - if (e.affectsConfiguration(multiSelectModifierSettingKey)) { - this.useAltAsMultiSelectModifier = this.configurationService.getValue(multiSelectModifierSettingKey) === 'alt'; - } - })); - } - - public onLeftClick(tree: ITree, stat: FileStat | Model, event: IMouseEvent, origin: string = 'mouse'): boolean { + public onLeftClick(tree: WorkbenchTree, stat: FileStat | Model, event: IMouseEvent, origin: string = 'mouse'): boolean { const payload = { origin: origin }; const isDoubleClick = (origin === 'mouse' && event.detail === 2); @@ -394,7 +382,7 @@ export class FileController extends DefaultController implements IDisposable { } // Allow to multiselect - if ((this.useAltAsMultiSelectModifier && event.altKey) || !this.useAltAsMultiSelectModifier && (event.ctrlKey || event.metaKey)) { + if ((tree.useAltAsMultipleSelectionModifier && event.altKey) || !tree.useAltAsMultipleSelectionModifier && (event.ctrlKey || event.metaKey)) { const selection = tree.getSelection(); this.previousSelectionRangeStop = undefined; if (selection.indexOf(stat) >= 0) { @@ -419,9 +407,12 @@ export class FileController extends DefaultController implements IDisposable { // Select, Focus and open files else { + // Expand / Collapse - tree.toggleExpansion(stat, event.altKey); - this.previousSelectionRangeStop = undefined; + if (isDoubleClick || this.openOnSingleClick) { + tree.toggleExpansion(stat, event.altKey); + this.previousSelectionRangeStop = undefined; + } const preserveFocus = !isDoubleClick; tree.setFocus(stat, payload); @@ -432,10 +423,10 @@ export class FileController extends DefaultController implements IDisposable { tree.setSelection([stat], payload); - if (!stat.isDirectory) { + if (!stat.isDirectory && (isDoubleClick || this.openOnSingleClick)) { let sideBySide = false; if (event) { - sideBySide = this.useAltAsMultiSelectModifier ? (event.ctrlKey || event.metaKey) : event.altKey; + sideBySide = tree.useAltAsMultipleSelectionModifier ? (event.ctrlKey || event.metaKey) : event.altKey; } this.openEditor(stat, { preserveFocus, sideBySide, pinned: isDoubleClick }); diff --git a/src/vs/workbench/parts/files/electron-browser/views/openEditorsView.ts b/src/vs/workbench/parts/files/electron-browser/views/openEditorsView.ts index e5a86bf07b3..92fea9e8412 100644 --- a/src/vs/workbench/parts/files/electron-browser/views/openEditorsView.ts +++ b/src/vs/workbench/parts/files/electron-browser/views/openEditorsView.ts @@ -28,15 +28,13 @@ import { EditorGroup } from 'vs/workbench/common/editor/editorStacksModel'; import { attachStylerCallback } from 'vs/platform/theme/common/styler'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { badgeBackground, badgeForeground, contrastBorder } from 'vs/platform/theme/common/colorRegistry'; -import { WorkbenchList, useAltAsMultipleSelectionModifier } from 'vs/platform/list/browser/listService'; -import { IDelegate, IRenderer, IListContextMenuEvent, IListMouseEvent } from 'vs/base/browser/ui/list/list'; +import { WorkbenchList } from 'vs/platform/list/browser/listService'; +import { IDelegate, IRenderer, IListContextMenuEvent } from 'vs/base/browser/ui/list/list'; import { EditorLabel } from 'vs/workbench/browser/labels'; import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; import { TPromise } from 'vs/base/common/winjs.base'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; -import { KeyCode } from 'vs/base/common/keyCodes'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { fillInActions } from 'vs/platform/actions/browser/menuItemActionItem'; import { IMenuService, MenuId, IMenu } from 'vs/platform/actions/common/actions'; @@ -153,7 +151,6 @@ export class OpenEditorsView extends ViewsViewletPanel { new EditorGroupRenderer(this.keybindingService, this.instantiationService, this.editorGroupService), new OpenEditorRenderer(getSelectedElements, this.instantiationService, this.keybindingService, this.configurationService, this.editorGroupService) ], { - keyboardSupport: false, identityProvider: element => element instanceof OpenEditor ? element.getId() : element.id.toString() }); @@ -185,15 +182,28 @@ export class OpenEditorsView extends ViewsViewletPanel { }); // Open when selecting via keyboard - this.disposables.push(this.list.onMouseClick(e => this.onMouseClick(e, false))); - this.disposables.push(this.list.onMouseDblClick(e => this.onMouseClick(e, true))); - this.disposables.push(this.list.onKeyDown(e => { - const event = new StandardKeyboardEvent(e); - if (event.keyCode === KeyCode.Enter) { - const focused = this.list.getFocusedElements(); - const element = focused.length ? focused[0] : undefined; - if (element instanceof OpenEditor) { - this.openEditor(element, { pinned: false, sideBySide: !!(event.altKey || event.ctrlKey || event.metaKey), preserveFocus: false }); + this.disposables.push(this.list.onOpen(e => { + const browserEvent = e.browserEvent; + + let openToSide = false; + let isSingleClick = false; + let isDoubleClick = false; + let isMiddleClick = false; + if (browserEvent instanceof MouseEvent) { + isSingleClick = browserEvent.detail === 1; + isDoubleClick = browserEvent.detail === 2; + isMiddleClick = browserEvent.button === 1 /* middle button */; + openToSide = this.list.useAltAsMultipleSelectionModifier ? (browserEvent.ctrlKey || browserEvent.metaKey) : browserEvent.altKey; + } + + const focused = this.list.getFocusedElements(); + const element = focused.length ? focused[0] : undefined; + if (element instanceof OpenEditor) { + if (isMiddleClick) { + const position = this.model.positionOfGroup(element.group); + this.editorService.closeEditor(position, element.editor).done(null, errors.onUnexpectedError); + } else { + this.openEditor(element, { preserveFocus: isSingleClick, pinned: isDoubleClick, sideBySide: openToSide }); } } })); @@ -268,21 +278,6 @@ export class OpenEditorsView extends ViewsViewletPanel { return -1; } - private onMouseClick(event: IListMouseEvent, isDoubleClick: boolean): void { - const element = event.element; - if (!(element instanceof OpenEditor)) { - return; - } - - if (event.browserEvent && event.browserEvent.button === 1 /* Middle Button */) { - const position = this.model.positionOfGroup(element.group); - this.editorService.closeEditor(position, element.editor).done(null, errors.onUnexpectedError); - } else { - const sideBySide = useAltAsMultipleSelectionModifier(this.configurationService) ? event.browserEvent.altKey : (event.browserEvent.ctrlKey || event.browserEvent.metaKey); - this.openEditor(element, { preserveFocus: !isDoubleClick, pinned: isDoubleClick, sideBySide }); - } - } - private openEditor(element: OpenEditor, options: { preserveFocus: boolean; pinned: boolean; sideBySide: boolean; }): void { if (element) { /* __GDPR__ diff --git a/src/vs/workbench/parts/markers/browser/markersPanel.ts b/src/vs/workbench/parts/markers/browser/markersPanel.ts index 6cd0676b79c..aa2b09d7da8 100644 --- a/src/vs/workbench/parts/markers/browser/markersPanel.ts +++ b/src/vs/workbench/parts/markers/browser/markersPanel.ts @@ -27,11 +27,10 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur import Messages from 'vs/workbench/parts/markers/common/messages'; import { RangeHighlightDecorations } from 'vs/workbench/browser/parts/editor/rangeDecorations'; import { IThemeService } from 'vs/platform/theme/common/themeService'; -import FileResultsNavigation from 'vs/workbench/parts/files/browser/fileResultsNavigation'; import { debounceEvent } from 'vs/base/common/event'; import { SimpleFileResourceDragAndDrop } from 'vs/base/parts/tree/browser/treeDnd'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; -import { WorkbenchTree } from 'vs/platform/list/browser/listService'; +import ResourceResultsNavigation, { WorkbenchTree } from 'vs/platform/list/browser/listService'; import { IMarkersWorkbenchService } from 'vs/workbench/parts/markers/common/markers'; export class MarkersPanel extends Panel { @@ -206,14 +205,13 @@ export class MarkersPanel extends Panel { }, { indentPixels: 0, twistiePixels: 20, - ariaLabel: Messages.MARKERS_PANEL_ARIA_LABEL_PROBLEMS_TREE, - keyboardSupport: false + ariaLabel: Messages.MARKERS_PANEL_ARIA_LABEL_PROBLEMS_TREE }); Constants.MarkerFocusContextKey.bindTo(this.tree.contextKeyService); - const fileResultsNavigation = this._register(new FileResultsNavigation(this.tree, { openOnFocus: true })); - this._register(debounceEvent(fileResultsNavigation.openFile, (last, event) => event, 75, true)(options => { + const fileResultsNavigation = this._register(new ResourceResultsNavigation(this.tree, { openOnFocus: true })); + this._register(debounceEvent(fileResultsNavigation.openResource, (last, event) => event, 75, true)(options => { this.openFileAtElement(options.element, options.editorOptions.preserveFocus, options.sideBySide, options.editorOptions.pinned); })); } diff --git a/src/vs/workbench/parts/markers/browser/markersTreeController.ts b/src/vs/workbench/parts/markers/browser/markersTreeController.ts index 07e688331c8..a460210a923 100644 --- a/src/vs/workbench/parts/markers/browser/markersTreeController.ts +++ b/src/vs/workbench/parts/markers/browser/markersTreeController.ts @@ -14,16 +14,18 @@ import { IMenuService, MenuId } from 'vs/platform/actions/common/actions'; import { IAction } from 'vs/base/common/actions'; import { ActionItem, Separator } from 'vs/base/browser/ui/actionbar/actionbar'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; -import { WorkbenchTree } from 'vs/platform/list/browser/listService'; +import { WorkbenchTree, WorkbenchTreeController } from 'vs/platform/list/browser/listService'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -export class Controller extends treedefaults.DefaultController { +export class Controller extends WorkbenchTreeController { constructor( @IContextMenuService private contextMenuService: IContextMenuService, @IMenuService private menuService: IMenuService, - @IKeybindingService private _keybindingService: IKeybindingService + @IKeybindingService private _keybindingService: IKeybindingService, + @IConfigurationService configurationService: IConfigurationService ) { - super({ clickBehavior: treedefaults.ClickBehavior.ON_MOUSE_DOWN, keyboardSupport: false }); + super({ clickBehavior: treedefaults.ClickBehavior.ON_MOUSE_DOWN, keyboardSupport: false }, configurationService); } protected onLeftClick(tree: tree.ITree, element: any, event: mouse.IMouseEvent): boolean { diff --git a/src/vs/workbench/parts/preferences/browser/keybindingsEditor.ts b/src/vs/workbench/parts/preferences/browser/keybindingsEditor.ts index 4171d03edc2..3fd4c0eac5e 100644 --- a/src/vs/workbench/parts/preferences/browser/keybindingsEditor.ts +++ b/src/vs/workbench/parts/preferences/browser/keybindingsEditor.ts @@ -333,7 +333,7 @@ export class KeybindingsEditor extends BaseEditor implements IKeybindingsEditor this.keybindingsListContainer = DOM.append(parent, $('.keybindings-list-container')); this.keybindingsList = this._register(this.instantiationService.createInstance(WorkbenchList, this.keybindingsListContainer, new Delegate(), [new KeybindingHeaderRenderer(), new KeybindingItemRenderer(this, this.keybindingsService)], - { identityProvider: e => e.id, keyboardSupport: false, mouseSupport: true, ariaLabel: localize('keybindingsLabel', "Keybindings") })); + { identityProvider: e => e.id, mouseSupport: true, ariaLabel: localize('keybindingsLabel', "Keybindings") })); this._register(this.keybindingsList.onContextMenu(e => this.onContextMenu(e))); this._register(this.keybindingsList.onFocusChange(e => this.onFocusChange(e))); this._register(this.keybindingsList.onDidFocus(() => { diff --git a/src/vs/workbench/parts/scm/electron-browser/scmViewlet.ts b/src/vs/workbench/parts/scm/electron-browser/scmViewlet.ts index dfdeb0359ed..9cfcc4ed51b 100644 --- a/src/vs/workbench/parts/scm/electron-browser/scmViewlet.ts +++ b/src/vs/workbench/parts/scm/electron-browser/scmViewlet.ts @@ -854,8 +854,7 @@ export class RepositoryPanel extends ViewletPanel { ]; this.list = this.instantiationService.createInstance(WorkbenchList, this.listContainer, delegate, renderers, { - identityProvider: scmResourceIdentityProvider, - keyboardSupport: false + identityProvider: scmResourceIdentityProvider }); chain(this.list.onOpen) @@ -941,6 +940,7 @@ export class RepositoryPanel extends ViewletPanel { } this.editorGroupService.pinEditor(activeEditor.position, activeEditorInput); + activeEditor.focus(); } private onListContextMenu(e: IListContextMenuEvent): void { diff --git a/src/vs/workbench/parts/search/browser/searchViewlet.ts b/src/vs/workbench/parts/search/browser/searchViewlet.ts index 333c9d7dc94..25cf4bc34b6 100644 --- a/src/vs/workbench/parts/search/browser/searchViewlet.ts +++ b/src/vs/workbench/parts/search/browser/searchViewlet.ts @@ -53,12 +53,11 @@ import { OpenFolderAction, OpenFileFolderAction } from 'vs/workbench/browser/act import * as Constants from 'vs/workbench/parts/search/common/constants'; import { IThemeService, ITheme, ICssStyleCollector, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; import { editorFindMatchHighlight, diffInserted, diffRemoved, diffInsertedOutline, diffRemovedOutline, activeContrastBorder } from 'vs/platform/theme/common/colorRegistry'; -import FileResultsNavigation from 'vs/workbench/parts/files/browser/fileResultsNavigation'; import { getOutOfWorkspaceEditorResources } from 'vs/workbench/parts/search/common/search'; import { PreferencesEditor } from 'vs/workbench/parts/preferences/browser/preferencesEditor'; import { SimpleFileResourceDragAndDrop } from 'vs/base/parts/tree/browser/treeDnd'; import { isDiffEditor, isCodeEditor, ICodeEditor } from 'vs/editor/browser/editorBrowser'; -import { WorkbenchTree } from 'vs/platform/list/browser/listService'; +import ResourceResultsNavigation, { WorkbenchTree } from 'vs/platform/list/browser/listService'; import { IEditorOptions } from 'vs/editor/common/config/editorOptions'; export class SearchViewlet extends Viewlet { @@ -84,7 +83,7 @@ export class SearchViewlet extends Viewlet { private searching: boolean; private actions: SearchAction[] = []; - private tree: ITree; + private tree: WorkbenchTree; private viewletSettings: any; private messages: Builder; private searchWidgetsContainer: Builder; @@ -505,15 +504,14 @@ export class SearchViewlet extends Viewlet { accessibilityProvider: this.instantiationService.createInstance(SearchAccessibilityProvider), dnd }, { - ariaLabel: nls.localize('treeAriaLabel', "Search Results"), - keyboardSupport: false + ariaLabel: nls.localize('treeAriaLabel', "Search Results") }); this.tree.setInput(this.viewModel.searchResult); this.toUnbind.push(renderer); - const fileResultsNavigation = this._register(new FileResultsNavigation(this.tree, { openOnFocus: true })); - this._register(debounceEvent(fileResultsNavigation.openFile, (last, event) => event, 75, true)(options => { + const fileResultsNavigation = this._register(new ResourceResultsNavigation(this.tree, { openOnFocus: true })); + this._register(debounceEvent(fileResultsNavigation.openResource, (last, event) => event, 75, true)(options => { if (options.element instanceof Match) { let selectedMatch: Match = options.element; if (this.currentSelectedFileMatch) {