diff --git a/src/vs/base/browser/ui/tree/abstractTree.ts b/src/vs/base/browser/ui/tree/abstractTree.ts index 4430f83c849..d47a7ac845e 100644 --- a/src/vs/base/browser/ui/tree/abstractTree.ts +++ b/src/vs/base/browser/ui/tree/abstractTree.ts @@ -6,15 +6,14 @@ import 'vs/css!./tree'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { IListOptions, List, IIdentityProvider, IMultipleSelectionController, IListStyles } from 'vs/base/browser/ui/list/listWidget'; -import { IVirtualDelegate, IRenderer, IListMouseEvent } from 'vs/base/browser/ui/list/list'; +import { IVirtualDelegate, IRenderer, IListMouseEvent, IListEvent } from 'vs/base/browser/ui/list/list'; import { append, $ } from 'vs/base/browser/dom'; -import { Event, Relay, chain, mapEvent } from 'vs/base/common/event'; +import { Event, Relay, chain } from 'vs/base/common/event'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { KeyCode } from 'vs/base/common/keyCodes'; import { ITreeModel, ITreeNode, ITreeRenderer } from 'vs/base/browser/ui/tree/tree'; import { ISpliceable } from 'vs/base/common/sequence'; import { IIndexTreeModelOptions } from 'vs/base/browser/ui/tree/indexTreeModel'; -import { memoize } from 'vs/base/common/decorators'; export function createComposedTreeListOptions(options?: IListOptions): IListOptions { if (!options) { @@ -161,6 +160,7 @@ function isInputElement(e: HTMLElement): boolean { } export interface ITreeOptions extends IListOptions, IIndexTreeModelOptions { } +export interface ITreeEvent extends IListEvent> { } export abstract class AbstractTree implements IDisposable { @@ -170,14 +170,8 @@ export abstract class AbstractTree implements IDisposable readonly onDidChangeCollapseState: Event>; readonly onDidChangeRenderNodeCount: Event>; - - @memoize get onDidChangeFocus(): Event { - return mapEvent(this.view.onFocusChange, e => e.elements.map(e => e.element)); - } - - @memoize get onDidChangeSelection(): Event { - return mapEvent(this.view.onSelectionChange, e => e.elements.map(e => e.element)); - } + readonly onDidChangeFocus: Event>; + readonly onDidChangeSelection: Event>; get onDidFocus(): Event { return this.view.onDidFocus; } get onDidBlur(): Event { return this.view.onDidBlur; } @@ -196,6 +190,9 @@ export abstract class AbstractTree implements IDisposable this.disposables.push(...treeRenderers); this.view = new List(container, treeDelegate, treeRenderers, createComposedTreeListOptions>(options)); + this.onDidChangeFocus = this.view.onFocusChange; + this.onDidChangeSelection = this.view.onSelectionChange; + this.model = this.createModel(this.view, options); onDidChangeCollapseStateRelay.input = this.model.onDidChangeCollapseState; this.onDidChangeCollapseState = this.model.onDidChangeCollapseState; @@ -270,9 +267,9 @@ export abstract class AbstractTree implements IDisposable this.model.refilter(); } - setSelection(elements: TRef[]): void { + setSelection(elements: TRef[], browserEvent?: UIEvent): void { const indexes = elements.map(e => this.model.getListIndex(e)); - this.view.setSelection(indexes); + this.view.setSelection(indexes, browserEvent); } getSelection(): T[] { diff --git a/src/vs/platform/list/browser/listService.ts b/src/vs/platform/list/browser/listService.ts index 75c937010bb..cd66bf3e9d9 100644 --- a/src/vs/platform/list/browser/listService.ts +++ b/src/vs/platform/list/browser/listService.ts @@ -33,7 +33,7 @@ import { attachInputBoxStyler, attachListStyler, computeStyles, defaultListStyle import { IThemeService } from 'vs/platform/theme/common/themeService'; import { InputFocusedContextKey } from 'vs/platform/workbench/common/contextkeys'; import { ObjectTree } from 'vs/base/browser/ui/tree/objectTree'; -import { ITreeOptions as ITreeOptions2 } from 'vs/base/browser/ui/tree/abstractTree'; +import { ITreeOptions as ITreeOptions2, ITreeEvent } from 'vs/base/browser/ui/tree/abstractTree'; import { ITreeRenderer } from 'vs/base/browser/ui/tree/tree'; export type ListWidget = List | PagedList | ITree | ObjectTree; @@ -565,6 +565,84 @@ export class TreeResourceNavigator extends Disposable { } } +export interface IOpenEvent { + editorOptions: IEditorOptions; + sideBySide: boolean; + element: T; +} + +export interface IResourceResultsNavigationOptions { + openOnFocus: boolean; +} + +export class ObjectTreeResourceNavigator extends Disposable { + + private readonly _openResource: Emitter> = new Emitter>(); + readonly openResource: Event> = this._openResource.event; + + constructor(private tree: WorkbenchObjectTree, 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(e: ITreeEvent): void { + const focus = this.tree.getFocus(); + + this.tree.setSelection(focus, e.browserEvent); + + const isMouseEvent = e.browserEvent instanceof MouseEvent; + const isDoubleClick = isMouseEvent && e.browserEvent && e.browserEvent.detail === 2; + + if (!isMouseEvent || this.tree.openOnSingleClick || isDoubleClick) { + this._openResource.fire({ + editorOptions: { + preserveFocus: true, + pinned: false, + revealIfVisible: true + }, + sideBySide: false, + element: focus[0] + }); + } + } + + private onSelection(e: ITreeEvent): void { + const isMouseEvent = e.browserEvent && e.browserEvent instanceof MouseEvent; + if (!isMouseEvent || this.tree.openOnSingleClick) { + return; + } + + const isDoubleClick = isMouseEvent && e.browserEvent && e.browserEvent.detail === 2; + + if (!isMouseEvent || this.tree.openOnSingleClick || isDoubleClick) { + if (isDoubleClick && e.browserEvent) { + e.browserEvent.preventDefault(); // focus moves to editor, we need to prevent default + } + + const sideBySide = e.browserEvent && e.browserEvent instanceof KeyboardEvent && (e.browserEvent.ctrlKey || e.browserEvent.metaKey || e.browserEvent.altKey); + + this._openResource.fire({ + editorOptions: { + preserveFocus: isDoubleClick, + pinned: isDoubleClick, + revealIfVisible: true + }, + sideBySide, + element: this.tree.getSelection()[0] + }); + } + } +} + export interface IHighlighter { getHighlights(tree: ITree, element: any, pattern: string): FuzzyScore; getHighlightsStorageKey?(element: any): any; @@ -810,6 +888,7 @@ export class WorkbenchObjectTree, TFilterData = void> private hasDoubleSelection: IContextKey; private hasMultiSelection: IContextKey; + private _openOnSingleClick: boolean; private _useAltAsMultipleSelectionModifier: boolean; constructor( @@ -820,7 +899,7 @@ export class WorkbenchObjectTree, TFilterData = void> @IContextKeyService contextKeyService: IContextKeyService, @IListService listService: IListService, @IThemeService themeService: IThemeService, - @IConfigurationService private configurationService: IConfigurationService + @IConfigurationService configurationService: IConfigurationService ) { super(container, delegate, renderers, { keyboardSupport: false, @@ -839,9 +918,10 @@ export class WorkbenchObjectTree, TFilterData = void> this.hasDoubleSelection = WorkbenchListDoubleSelection.bindTo(this.contextKeyService); this.hasMultiSelection = WorkbenchListMultiSelection.bindTo(this.contextKeyService); + this._openOnSingleClick = useSingleClickToOpen(configurationService); this._useAltAsMultipleSelectionModifier = useAltAsMultipleSelectionModifier(configurationService); - this.disposables.push(combinedDisposable([ + this.disposables.push( this.contextKeyService, (listService as ListService).register(this), attachListStyler(this, themeService), @@ -858,18 +938,21 @@ export class WorkbenchObjectTree, TFilterData = void> const focus = this.getFocus(); this.hasSelectionOrFocus.set(selection.length > 0 || focus.length > 0); - }) - ])); + }), + configurationService.onDidChangeConfiguration(e => { + if (e.affectsConfiguration(openModeSettingKey)) { + this._openOnSingleClick = useSingleClickToOpen(configurationService); + } - this.registerListeners(); + if (e.affectsConfiguration(multiSelectModifierSettingKey)) { + this._useAltAsMultipleSelectionModifier = useAltAsMultipleSelectionModifier(configurationService); + } + }) + ); } - private registerListeners(): void { - this.disposables.push(this.configurationService.onDidChangeConfiguration(e => { - if (e.affectsConfiguration(multiSelectModifierSettingKey)) { - this._useAltAsMultipleSelectionModifier = useAltAsMultipleSelectionModifier(this.configurationService); - } - })); + get openOnSingleClick(): boolean { + return this._openOnSingleClick; } get useAltAsMultipleSelectionModifier(): boolean { diff --git a/src/vs/workbench/parts/markers/electron-browser/markersPanel.ts b/src/vs/workbench/parts/markers/electron-browser/markersPanel.ts index 6c06bb14f9a..bdeacd24cc3 100644 --- a/src/vs/workbench/parts/markers/electron-browser/markersPanel.ts +++ b/src/vs/workbench/parts/markers/electron-browser/markersPanel.ts @@ -30,7 +30,7 @@ import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/c import { Iterator } from 'vs/base/common/iterator'; import { ITreeElement, ITreeNode } from 'vs/base/browser/ui/tree/tree'; import { debounceEvent, Relay } from 'vs/base/common/event'; -import { WorkbenchObjectTree } from 'vs/platform/list/browser/listService'; +import { WorkbenchObjectTree, ObjectTreeResourceNavigator } from 'vs/platform/list/browser/listService'; import { FilterOptions } from 'vs/workbench/parts/markers/electron-browser/markersFilterOptions'; import { IExpression, getEmptyExpression } from 'vs/base/common/glob'; import { mixin, deepClone } from 'vs/base/common/objects'; @@ -325,10 +325,10 @@ export class MarkersPanel extends Panel { // relatedInformationFocusContextKey.set(false); // })); - // const markersNavigator = this._register(new TreeResourceNavigator(this.tree, { openOnFocus: true })); - // this._register(debounceEvent(markersNavigator.openResource, (last, event) => event, 75, true)(options => { - // this.openFileAtElement(options.element, options.editorOptions.preserveFocus, options.sideBySide, options.editorOptions.pinned); - // })); + const markersNavigator = this._register(new ObjectTreeResourceNavigator(this.tree, { openOnFocus: true })); + this._register(debounceEvent(markersNavigator.openResource, (last, event) => event, 75, true)(options => { + this.openFileAtElement(options.element, options.editorOptions.preserveFocus, options.sideBySide, options.editorOptions.pinned); + })); } // TODO@joao diff --git a/src/vs/workbench/parts/markers/electron-browser/markersTreeController.ts b/src/vs/workbench/parts/markers/electron-browser/markersTreeController.ts index 4ec0d2fb1ac..88accb29a87 100644 --- a/src/vs/workbench/parts/markers/electron-browser/markersTreeController.ts +++ b/src/vs/workbench/parts/markers/electron-browser/markersTreeController.ts @@ -61,8 +61,9 @@ export class Controller extends WorkbenchTreeController { return false; } + // TODO@Joao public onContextMenu(tree: WorkbenchTree, element: any, event: tree.ContextMenuEvent): boolean { - tree.setFocus(element, { preventOpenOnFocus: true }); + tree.setFocus(element/* , { preventOpenOnFocus: true } */); const anchor = { x: event.posx, y: event.posy }; this.contextMenuService.showContextMenu({