提交 4a01f5af 编写于 作者: A Andre Weinand

Merge commit 'e09d5fc5' into aweinand/debugAdapterExecutable

......@@ -17,6 +17,12 @@ export interface IRenderer<TElement, TTemplateData> {
disposeTemplate(templateData: TTemplateData): void;
}
export interface IListOpenEvent<T> {
elements: T[];
indexes: number[];
browserEvent?: UIEvent;
}
export interface IListEvent<T> {
elements: T[];
indexes: number[];
......
......@@ -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<T> {
return mapEvent(this.list.onFocusChange, ({ elements, indexes }) => ({ elements: elements.map(e => this._model.get(e)), indexes }));
}
get onOpen(): Event<IListOpenEvent<T>> {
return mapEvent(this.list.onOpen, ({ elements, indexes, browserEvent }) => ({ elements: elements.map(e => this._model.get(e)), indexes, browserEvent }));
}
get onSelectionChange(): Event<IListEvent<T>> {
return mapEvent(this.list.onSelectionChange, ({ elements, indexes }) => ({ elements: elements.map(e => this._model.get(e)), indexes }));
}
......@@ -126,8 +130,8 @@ export class PagedList<T> {
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 {
......
......@@ -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<T> implements IDisposable {
private disposables: IDisposable[];
private openController: IOpenController;
constructor(
private list: List<T>,
......@@ -270,6 +271,8 @@ class KeyboardController<T> 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<T> 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<T> implements IDisposable {
private multipleSelectionSupport: boolean;
private multipleSelectionController: IMultipleSelectionController<T> | undefined;
private multipleSelectionController: IMultipleSelectionController<T>;
private openController: IOpenController;
private didJustPressContextMenuKey: boolean = false;
private disposables: IDisposable[] = [];
......@@ -406,6 +417,8 @@ class MouseController<T> 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<T> 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<T> 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<T> {
isSelectionRangeChangeEvent(event: IListMouseEvent<T> | IListTouchEvent<T>): boolean;
}
export interface IOpenController {
shouldOpen(event: UIEvent): boolean;
}
export interface IListOptions<T> extends IListViewOptions, IListStyles {
identityProvider?: IIdentityProvider<T>;
ariaLabel?: string;
......@@ -533,6 +556,7 @@ export interface IListOptions<T> extends IListViewOptions, IListStyles {
verticalScrollMode?: ScrollbarVisibility;
multipleSelectionSupport?: boolean;
multipleSelectionController?: IMultipleSelectionController<T>;
openController?: IOpenController;
}
export interface IListStyles {
......@@ -708,9 +732,9 @@ export class List<T> implements ISpliceable<T>, IDisposable {
readonly onContextMenu: Event<IListContextMenuEvent<T>> = Event.None;
private _onOpen = new Emitter<number[]>();
@memoize get onOpen(): Event<IListEvent<T>> {
return mapEvent(this._onOpen.event, indexes => this.toListEvent({ indexes }));
private _onOpen = new Emitter<IListOpenEvent<T>>();
@memoize get onOpen(): Event<IListOpenEvent<T>> {
return this._onOpen.event;
}
private _onPin = new Emitter<number[]>();
......@@ -973,8 +997,8 @@ export class List<T> implements ISpliceable<T>, 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 {
......
......@@ -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' && (<mouse.IMouseEvent>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
......
......@@ -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<any>();
readonly onDidFocus: Event<any> = 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 = <tree.ITreeConfiguration>{
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);
......
......@@ -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<any> | PagedList<any> | ITree;
export const IListService = createDecorator<IListService>('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<T> implements IMultipleSelectionController<T> {
constructor(private configurationService: IConfigurationService) { }
......@@ -117,11 +125,39 @@ class MultipleSelectionController<T> implements IMultipleSelectionController<T>
}
}
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<T>(options: IListOptions<T>, configurationService: IConfigurationService): IListOptions<T> {
if (options.multipleSelectionSupport === true && !options.multipleSelectionController) {
options.multipleSelectionController = new MultipleSelectionController(configurationService);
}
options.openController = new OpenController(configurationService);
return options;
}
export class WorkbenchList<T> extends List<T> {
readonly contextKeyService: IContextKeyService;
private listDoubleSelection: IContextKey<boolean>;
private _useAltAsMultipleSelectionModifier: boolean;
constructor(
container: HTMLElement,
delegate: IDelegate<T>,
......@@ -130,31 +166,45 @@ export class WorkbenchList<T> extends List<T> {
@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<any>, 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<T> extends PagedList<T> {
readonly contextKeyService: IContextKeyService;
private disposable: IDisposable;
private disposables: IDisposable[] = [];
private _useAltAsMultipleSelectionModifier: boolean;
constructor(
container: HTMLElement,
......@@ -164,57 +214,134 @@ export class WorkbenchPagedList<T> extends PagedList<T> {
@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<any>, 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<boolean>;
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 {
this.disposables = dispose(this.disposables);
}
}
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 {
......@@ -222,6 +349,89 @@ export class WorkbenchTree extends Tree {
}
}
export interface IOpenResourceOptions {
editorOptions: IEditorOptions;
sideBySide: boolean;
element: any;
payload: any;
}
export interface IResourceResultsNavigationOptions {
openOnFocus: boolean;
}
export class TreeResourceNavigator extends Disposable {
private _openResource: Emitter<IOpenResourceOptions> = new Emitter<IOpenResourceOptions>();
public readonly openResource: Event<IOpenResourceOptions> = 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<IConfigurationRegistry>(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. For parents with children in trees, this setting will control if a single click expands the parent or a double click. Note that some trees and lists might chose to ignore this setting if it is not applicable. ")
}
}
});
\ No newline at end of file
......@@ -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)
......
......@@ -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 {
......
......@@ -270,17 +270,17 @@ function registerEditorCommands() {
weight: KeybindingsRegistry.WEIGHT.workbenchContrib(),
when: void 0,
primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.KEY_U),
handler: (accessor, resource: URI, context: IEditorIdentifier | IEditorCommandsContext) => {
handler: (accessor, resource: URI, context: IEditorCommandsContext) => {
const editorGroupService = accessor.get(IEditorGroupService);
const model = editorGroupService.getStacksModel();
const editorService = accessor.get(IWorkbenchEditorService);
const contexts = getMultiSelectedEditorContexts(toEditorIdentifier(context, editorGroupService), accessor.get(IListService));
const contexts = getMultiSelectedEditorContexts(context, accessor.get(IListService));
let positionOne: { unmodifiedOnly: boolean } = void 0;
let positionTwo: { unmodifiedOnly: boolean } = void 0;
let positionThree: { unmodifiedOnly: boolean } = void 0;
contexts.forEach(c => {
switch (model.positionOfGroup(c.group)) {
switch (model.positionOfGroup(model.getGroup(c.groupId))) {
case Position.ONE: positionOne = { unmodifiedOnly: true }; break;
case Position.TWO: positionTwo = { unmodifiedOnly: true }; break;
case Position.THREE: positionThree = { unmodifiedOnly: true }; break;
......@@ -296,14 +296,15 @@ function registerEditorCommands() {
weight: KeybindingsRegistry.WEIGHT.workbenchContrib(),
when: void 0,
primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.KEY_W),
handler: (accessor, resource: URI, context: IEditorIdentifier | IEditorCommandsContext) => {
handler: (accessor, resource: URI, context: IEditorCommandsContext) => {
const editorGroupService = accessor.get(IEditorGroupService);
const editorService = accessor.get(IWorkbenchEditorService);
const contexts = getMultiSelectedEditorContexts(toEditorIdentifier(context, editorGroupService), accessor.get(IListService));
const distinctGroups = distinct(contexts.map(c => c.group));
const contexts = getMultiSelectedEditorContexts(context, accessor.get(IListService));
const distinctGroupIds = distinct(contexts.map(c => c.groupId));
const model = editorGroupService.getStacksModel();
if (distinctGroups.length) {
return editorService.closeEditors(distinctGroups.map(g => editorGroupService.getStacksModel().positionOfGroup(g)));
if (distinctGroupIds.length) {
return editorService.closeEditors(distinctGroupIds.map(gid => model.positionOfGroup(model.getGroup(gid))));
}
const activeEditor = editorService.getActiveEditor();
if (activeEditor) {
......@@ -320,21 +321,23 @@ function registerEditorCommands() {
when: void 0,
primary: KeyMod.CtrlCmd | KeyCode.KEY_W,
win: { primary: KeyMod.CtrlCmd | KeyCode.F4, secondary: [KeyMod.CtrlCmd | KeyCode.KEY_W] },
handler: (accessor, resource: URI, context: IEditorIdentifier | IEditorCommandsContext) => {
handler: (accessor, resource: URI, context: IEditorCommandsContext) => {
const editorGroupService = accessor.get(IEditorGroupService);
const editorService = accessor.get(IWorkbenchEditorService);
const contexts = getMultiSelectedEditorContexts(toEditorIdentifier(context, editorGroupService), accessor.get(IListService));
const groups = distinct(contexts.map(context => context.group));
const contexts = getMultiSelectedEditorContexts(context, accessor.get(IListService));
const groupIds = distinct(contexts.map(context => context.groupId));
const model = editorGroupService.getStacksModel();
const editorsToClose = new Map<Position, IEditorInput[]>();
groups.forEach(group => {
const position = editorGroupService.getStacksModel().positionOfGroup(group);
groupIds.forEach(groupId => {
const group = model.getGroup(groupId);
const position = model.positionOfGroup(group);
if (position >= 0) {
editorsToClose.set(position, contexts.map(c => {
if (group === c.group) {
let input = c ? c.editor : void 0;
if (c && groupId === c.groupId) {
let input = group.getEditor(c.editorIndex);
if (!input) {
// Get Top Editor at Position
......@@ -373,24 +376,26 @@ function registerEditorCommands() {
when: void 0,
primary: void 0,
mac: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KEY_T },
handler: (accessor, resource: URI, context: IEditorIdentifier | IEditorCommandsContext) => {
handler: (accessor, resource: URI, context: IEditorCommandsContext) => {
const editorGroupService = accessor.get(IEditorGroupService);
const editorService = accessor.get(IWorkbenchEditorService);
const contexts = getMultiSelectedEditorContexts(toEditorIdentifier(context, editorGroupService), accessor.get(IListService));
const groups = distinct(contexts.map(context => context.group));
const contexts = getMultiSelectedEditorContexts(context, accessor.get(IListService));
const groupIds = distinct(contexts.map(context => context.groupId));
const editorsToClose = new Map<Position, IEditorInput[]>();
const model = editorGroupService.getStacksModel();
groups.forEach(group => {
groupIds.forEach(groupId => {
const group = model.getGroup(groupId);
const inputsToSkip = contexts.map(c => {
if (!!c.editor && c.group === group) {
return c.editor;
if (c.groupId === groupId) {
return group.getEditor(c.editorIndex);
}
return void 0;
}).filter(input => !!input);
const toClose = group.getEditors().filter(input => inputsToSkip.indexOf(input) === -1);
editorsToClose.set(editorGroupService.getStacksModel().positionOfGroup(group), toClose);
editorsToClose.set(model.positionOfGroup(group), toClose);
});
return editorService.closeEditors({
......@@ -406,7 +411,7 @@ function registerEditorCommands() {
weight: KeybindingsRegistry.WEIGHT.workbenchContrib(),
when: void 0,
primary: void 0,
handler: (accessor, resource: URI, context: IEditorIdentifier | IEditorCommandsContext) => {
handler: (accessor, resource: URI, context: IEditorCommandsContext) => {
const editorGroupService = accessor.get(IEditorGroupService);
const editorService = accessor.get(IWorkbenchEditorService);
......@@ -425,7 +430,7 @@ function registerEditorCommands() {
weight: KeybindingsRegistry.WEIGHT.workbenchContrib(),
when: void 0,
primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.Enter),
handler: (accessor, resource: URI, context: IEditorIdentifier | IEditorCommandsContext) => {
handler: (accessor, resource: URI, context: IEditorCommandsContext) => {
const editorGroupService = accessor.get(IEditorGroupService);
const editorService = accessor.get(IWorkbenchEditorService);
......@@ -489,12 +494,13 @@ function registerEditorCommands() {
});
}
function positionAndInput(editorGroupService: IEditorGroupService, editorService: IWorkbenchEditorService, context?: IEditorIdentifier | IEditorCommandsContext): { position: Position, input: IEditorInput } {
function positionAndInput(editorGroupService: IEditorGroupService, editorService: IWorkbenchEditorService, context?: IEditorCommandsContext): { position: Position, input: IEditorInput } {
// Resolve from context
const editorContext = toEditorIdentifier(context, editorGroupService);
let position = editorContext ? editorGroupService.getStacksModel().positionOfGroup(editorContext.group) : null;
let input = editorContext ? editorContext.editor : null;
const model = editorGroupService.getStacksModel();
const group = context ? model.getGroup(context.groupId) : undefined;
let position = group ? model.positionOfGroup(group) : undefined;
let input = group ? group.getEditor(context.editorIndex) : undefined;
// If position or input are not passed in take the position and input of the active editor.
const active = editorService.getActiveEditor();
......@@ -506,54 +512,28 @@ function positionAndInput(editorGroupService: IEditorGroupService, editorService
return { position, input };
}
export function getMultiSelectedEditorContexts(editorContext: IEditorIdentifier, listService: IListService): IEditorIdentifier[] {
const elementToContext = (element: IEditorIdentifier | EditorGroup) => element instanceof EditorGroup ? { group: element, editor: void 0 } : element;
export function getMultiSelectedEditorContexts(editorContext: IEditorCommandsContext, listService: IListService): IEditorCommandsContext[] {
const elementToContext = (element: IEditorIdentifier | EditorGroup) =>
element instanceof EditorGroup ? { groupId: element.id, editorIndex: undefined } : { groupId: element.group.id, editorIndex: element.group.indexOf(element.editor) };
// First check for a focused list to return the selected items from
const list = listService.lastFocusedList;
if (list instanceof List && list.isDOMFocused()) {
const selection = list.getSelectedElements();
const focus = list.getFocusedElements();
// Only respect selection if it contains focused element
if (focus.length && selection && selection.indexOf(focus[0]) >= 0) {
return list.getSelectedElements().filter(e => e instanceof EditorGroup || isEditorIdentifier(e)).map(elementToContext);
}
const focusedElements: (IEditorIdentifier | EditorGroup)[] = list.getFocusedElements();
// need to take into account when editor context is { group: group }
const focus = editorContext ? editorContext : focusedElements.length ? focusedElements.map(elementToContext)[0] : undefined;
if (focus) {
const selection: (IEditorIdentifier | EditorGroup)[] = list.getSelectedElements();
// Only respect selection if it contains focused element
if (selection && selection.some(s => s instanceof EditorGroup ? s.id === focus.groupId : s.group.id === focus.groupId && s.group.indexOf(s.editor) === focus.editorIndex)) {
return selection.map(elementToContext);
}
if (focus.length) {
return focus.filter(e => e instanceof EditorGroup || isEditorIdentifier(e)).map(elementToContext);
return [focus];
}
}
// Otherwise go with passed in context
return !!editorContext ? [editorContext] : [];
}
function isEditorIdentifier(object: any): object is IEditorIdentifier {
const identifier = object as IEditorIdentifier;
return identifier && !!identifier.group && !!identifier.editor;
}
function isEditorGroupContext(object: any): object is IEditorCommandsContext {
const context = object as IEditorCommandsContext;
return context && typeof context.groupId === 'number';
}
function toEditorIdentifier(object: IEditorIdentifier | IEditorCommandsContext, editorGroupService: IEditorGroupService): IEditorIdentifier {
if (isEditorIdentifier(object)) {
return object as IEditorIdentifier;
}
if (isEditorGroupContext(object)) {
const stacks = editorGroupService.getStacksModel();
const group = stacks.getGroup(object.groupId);
return {
group,
editor: typeof object.editorIndex === 'number' ? group.getEditor(object.editorIndex) : void 0
};
}
return void 0;
}
\ No newline at end of file
......@@ -635,7 +635,7 @@ export class TabsTitleControl extends TitleControl {
return void 0; // only for left mouse click
}
const { editor, position } = this.toTabContext(index);
const { editor, position } = this.getGroupPositionAndEditor(index);
if (!this.isTabActionBar(((e as GestureEvent).initialTarget || e.target || e.srcElement) as HTMLElement)) {
setTimeout(() => this.editorService.openEditor(editor, null, position).done(null, errors.onUnexpectedError)); // timeout to keep focus in editor after mouse up
}
......@@ -646,7 +646,7 @@ export class TabsTitleControl extends TitleControl {
const showContextMenu = (e: Event) => {
DOM.EventHelper.stop(e);
const { group, editor } = this.toTabContext(index);
const { group, editor } = this.getGroupPositionAndEditor(index);
this.onContextMenu({ group, editor }, e, tab);
};
......@@ -668,7 +668,7 @@ export class TabsTitleControl extends TitleControl {
tab.blur();
if (e.button === 1 /* Middle Button*/ && !this.isTabActionBar((e.target || e.srcElement) as HTMLElement)) {
this.closeEditorAction.run(this.toTabContext(index)).done(null, errors.onUnexpectedError);
this.closeEditorAction.run(this.getGroupPositionAndEditor(index)).done(null, errors.onUnexpectedError);
}
}));
......@@ -690,7 +690,7 @@ export class TabsTitleControl extends TitleControl {
const event = new StandardKeyboardEvent(e);
let handled = false;
const { group, position, editor } = this.toTabContext(index);
const { group, position, editor } = this.getGroupPositionAndEditor(index);
// Run action on Enter/Space
if (event.equals(KeyCode.Enter) || event.equals(KeyCode.Space)) {
......@@ -733,7 +733,7 @@ export class TabsTitleControl extends TitleControl {
disposables.push(DOM.addDisposableListener(tab, DOM.EventType.DBLCLICK, (e: MouseEvent) => {
DOM.EventHelper.stop(e);
const { group, editor } = this.toTabContext(index);
const { group, editor } = this.getGroupPositionAndEditor(index);
this.editorGroupService.pinEditor(group, editor);
}));
......@@ -741,14 +741,14 @@ export class TabsTitleControl extends TitleControl {
// Context menu
disposables.push(DOM.addDisposableListener(tab, DOM.EventType.CONTEXT_MENU, (e: Event) => {
DOM.EventHelper.stop(e, true);
const { group, editor } = this.toTabContext(index);
const { group, editor } = this.getGroupPositionAndEditor(index);
this.onContextMenu({ group, editor }, e, tab);
}, true /* use capture to fix https://github.com/Microsoft/vscode/issues/19145 */));
// Drag start
disposables.push(DOM.addDisposableListener(tab, DOM.EventType.DRAG_START, (e: DragEvent) => {
const { group, editor } = this.toTabContext(index);
const { group, editor } = this.getGroupPositionAndEditor(index);
this.onEditorDragStart({ editor, group });
e.dataTransfer.effectAllowed = 'copyMove';
......@@ -795,7 +795,7 @@ export class TabsTitleControl extends TitleControl {
let draggedEditorIsTab = false;
const draggedEditor = TabsTitleControl.getDraggedEditor();
if (draggedEditor) {
const { group, editor } = this.toTabContext(index);
const { group, editor } = this.getGroupPositionAndEditor(index);
if (draggedEditor.editor === editor && draggedEditor.group === group) {
draggedEditorIsTab = true;
}
......@@ -832,7 +832,7 @@ export class TabsTitleControl extends TitleControl {
DOM.removeClass(tab, 'dragged-over');
this.updateDropFeedback(tab, false, index);
const { group, position } = this.toTabContext(index);
const { group, position } = this.getGroupPositionAndEditor(index);
this.onDrop(e, group, position, index);
}));
......@@ -844,7 +844,7 @@ export class TabsTitleControl extends TitleControl {
return !!DOM.findParentWithClass(element, 'monaco-action-bar', 'tab');
}
private toTabContext(index: number): { group: IEditorGroup, position: Position, editor: IEditorInput } {
private getGroupPositionAndEditor(index: number): { group: IEditorGroup, position: Position, editor: IEditorInput } {
const group = this.context;
const position = this.stacks.positionOfGroup(group);
const editor = group.getEditor(index);
......@@ -907,7 +907,7 @@ class TabActionRunner extends ActionRunner {
return TPromise.as(void 0);
}
return super.run(action, { group, editor: group.getEditor(this.index) });
return super.run(action, { groupId: group.id, editorIndex: this.index });
}
}
......
......@@ -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<boolean>(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 {
......
......@@ -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');
......
......@@ -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<any> {
public openInEditor(editorService: IWorkbenchEditorService, preserveFocus?: boolean, sideBySide?: boolean, pinned?: boolean): TPromise<any> {
return !this.source.available ? TPromise.as(null) :
this.source.openInEditor(editorService, this.range, preserveFocus, sideBySide);
this.source.openInEditor(editorService, this.range, preserveFocus, sideBySide, pinned);
}
}
......
......@@ -56,7 +56,7 @@ export class Source {
return this.uri.scheme === DEBUG_SCHEME;
}
public openInEditor(editorService: IWorkbenchEditorService, selection: IRange, preserveFocus?: boolean, sideBySide?: boolean): TPromise<any> {
public openInEditor(editorService: IWorkbenchEditorService, selection: IRange, preserveFocus?: boolean, sideBySide?: boolean, pinned?: boolean): TPromise<any> {
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);
}
......
......@@ -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);
}
......
......@@ -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);
}
......
......@@ -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 { TreeResourceNavigator, 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);
this.disposables.push(fileResultsNavigation);
this.disposables.push(fileResultsNavigation.openFile(e => {
const callstackNavigator = new TreeResourceNavigator(this.tree);
this.disposables.push(callstackNavigator);
this.disposables.push(callstackNavigator.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);
......
......@@ -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 {
......
......@@ -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, {
......
......@@ -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);
......
......@@ -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);
......
......@@ -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();
}
......
......@@ -526,8 +526,7 @@ export class ExtensionEditor extends BaseEditor {
controller
}, {
indentPixels: 40,
twistiePixels: 20,
keyboardSupport: false
twistiePixels: 20
});
tree.setInput(extensionDependencies);
......
......@@ -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();
}
......
/*---------------------------------------------------------------------------------------------
* 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<IOpenFileOptions> = new Emitter<IOpenFileOptions>();
public readonly openFile: Event<IOpenFileOptions> = 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
});
}
}
......@@ -12,6 +12,7 @@ import { FileStat, OpenEditor } from 'vs/workbench/parts/files/common/explorerMo
import { toResource } from 'vs/workbench/common/editor';
import { Tree } from 'vs/base/parts/tree/browser/treeImpl';
import { List } from 'vs/base/browser/ui/list/listWidget';
import { IFileStat } from 'vs/platform/files/common/files';
// Commands can get exeucted from a command pallete, from a context menu or from some list using a keybinding
// To cover all these cases we need to properly compute the resource on which the command is being executed
......@@ -38,18 +39,26 @@ export function getMultiSelectedResources(resource: URI, listService: IListServi
if (list && list.isDOMFocused()) {
// Explorer
if (list instanceof Tree) {
const focus = list.getFocus();
const selection = list.getSelection();
if (selection && selection.indexOf(focus) >= 0) {
return selection.map(fs => fs.resource);
const focus: IFileStat = list.getFocus();
// If the resource is passed it has to be a part of the returned context.
if (focus && (!URI.isUri(resource) || focus.resource.toString() === resource.toString())) {
const selection = list.getSelection();
// We only respect the selection if it contains the focused element.
if (selection && selection.indexOf(focus) >= 0) {
return selection.map(fs => fs.resource);
}
}
}
// Open editors view
if (list instanceof List) {
const focus = list.getFocusedElements();
const selection = list.getSelectedElements();
if (selection && focus.length && selection.indexOf(focus[0]) >= 0) {
return selection.filter(s => s instanceof OpenEditor).map((oe: OpenEditor) => oe.getResource());
// If the resource is passed it has to be a part of the returned context.
if (focus.length && (!URI.isUri(resource) || (focus[0] instanceof OpenEditor && focus[0].getResource().toString() === resource.toString()))) {
const selection = list.getSelectedElements();
// We only respect the selection if it contains the focused element.
if (selection && selection.indexOf(focus[0]) >= 0) {
return selection.filter(s => s instanceof OpenEditor).map((oe: OpenEditor) => oe.getResource());
}
}
}
}
......
......@@ -380,6 +380,10 @@ export class OpenEditor implements IEditorIdentifier {
return this._editor;
}
public get editorIndex() {
return this._group.indexOf(this.editor);
}
public get group() {
return this._group;
}
......
......@@ -12,7 +12,7 @@ import { TPromise } from 'vs/base/common/winjs.base';
import * as labels from 'vs/base/common/labels';
import URI from 'vs/base/common/uri';
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
import { toResource, IEditorIdentifier } from 'vs/workbench/common/editor';
import { toResource, IEditorCommandsContext } from 'vs/workbench/common/editor';
import { IWindowsService } from 'vs/platform/windows/common/windows';
import { ServicesAccessor, IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet';
......@@ -492,8 +492,9 @@ CommandsRegistry.registerCommand({
CommandsRegistry.registerCommand({
id: SAVE_ALL_IN_GROUP_COMMAND_ID,
handler: (accessor, resource: URI, editorContext: IEditorIdentifier) => {
handler: (accessor, resource: URI, editorContext: IEditorCommandsContext) => {
const contexts = getMultiSelectedEditorContexts(editorContext, accessor.get(IListService));
const editorGroupService = accessor.get(IEditorGroupService);
let saveAllArg: any;
if (!contexts.length) {
saveAllArg = true;
......@@ -501,7 +502,7 @@ CommandsRegistry.registerCommand({
const fileService = accessor.get(IFileService);
saveAllArg = [];
contexts.forEach(context => {
const editorGroup = context.group;
const editorGroup = editorGroupService.getStacksModel().getGroup(context.groupId);
editorGroup.getEditors().forEach(editor => {
const resource = toResource(editor, { supportSideBySide: true });
if (resource && (resource.scheme === 'untitled' || fileService.canHandleResource(resource))) {
......
......@@ -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
......
......@@ -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 });
......
......@@ -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<OpenEditor | IEditorGroup>, 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__
......@@ -311,7 +306,7 @@ export class OpenEditorsView extends ViewsViewletPanel {
fillInActions(this.contributedContextMenu, { shouldForwardArgs: true, arg: element instanceof OpenEditor ? element.editor.getResource() : {} }, actions, this.contextMenuService);
return TPromise.as(actions);
},
getActionsContext: () => element instanceof OpenEditor ? { group: element.group, editor: element.editor } : { group: element }
getActionsContext: () => element instanceof OpenEditor ? { groupId: element.group.id, editorIndex: element.editorIndex } : { groupId: element.id }
});
}
......@@ -493,7 +488,7 @@ class EditorGroupRenderer implements IRenderer<IEditorGroup, IEditorGroupTemplat
renderElement(editorGroup: IEditorGroup, index: number, templateData: IEditorGroupTemplateData): void {
templateData.editorGroup = editorGroup;
templateData.name.textContent = editorGroup.label;
templateData.actionBar.context = { group: editorGroup };
templateData.actionBar.context = { groupId: editorGroup.id };
}
disposeTemplate(templateData: IEditorGroupTemplateData): void {
......@@ -597,7 +592,7 @@ class OpenEditorRenderer implements IRenderer<OpenEditor, IOpenEditorTemplateDat
extraClasses: ['open-editor'],
fileDecorations: this.configurationService.getValue<IFilesConfiguration>().explorer.decorations
});
templateData.actionBar.context = { group: editor.group, editor: editor.editor };
templateData.actionBar.context = { groupId: editor.group.id, editorIndex: editor.editorIndex };
}
disposeTemplate(templateData: IOpenEditorTemplateData): void {
......
......@@ -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 { TreeResourceNavigator, 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 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);
}));
}
......
......@@ -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 {
......
......@@ -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(() => {
......
......@@ -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<ISCMResourceGroup | ISCMResource>): void {
......
......@@ -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 { TreeResourceNavigator, 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 searchResultsNavigator = this._register(new TreeResourceNavigator(this.tree, { openOnFocus: true }));
this._register(debounceEvent(searchResultsNavigator.openResource, (last, event) => event, 75, true)(options => {
if (options.element instanceof Match) {
let selectedMatch: Match = options.element;
if (this.currentSelectedFileMatch) {
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册