Refresh master with initial release/0.24 snapshot (#332)
* Initial port of release/0.24 source code * Fix additional headers * Fix a typo in launch.json
@@ -6,75 +6,22 @@
|
||||
'use strict';
|
||||
|
||||
import 'vs/css!./media/activityaction';
|
||||
import nls = require('vs/nls');
|
||||
import DOM = require('vs/base/browser/dom');
|
||||
import { EventType as TouchEventType, GestureEvent } from 'vs/base/browser/touch';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { Builder, $ } from 'vs/base/browser/builder';
|
||||
import { DelayedDragHandler } from 'vs/base/browser/dnd';
|
||||
import { Action } from 'vs/base/common/actions';
|
||||
import { BaseActionItem, Separator, IBaseActionItemOptions } from 'vs/base/browser/ui/actionbar/actionbar';
|
||||
import { IActivityBarService, ProgressBadge, TextBadge, NumberBadge, IconBadge, IBadge } from 'vs/workbench/services/activity/common/activityBarService';
|
||||
import Event, { Emitter } from 'vs/base/common/event';
|
||||
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
|
||||
import { ICommandService } from 'vs/platform/commands/common/commands';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
||||
import { ViewletDescriptor } from 'vs/workbench/browser/viewlet';
|
||||
import { IActivity, IGlobalActivity } from 'vs/workbench/browser/activity';
|
||||
import { IActivity, IGlobalActivity } from 'vs/workbench/common/activity';
|
||||
import { dispose } from 'vs/base/common/lifecycle';
|
||||
import { IViewletService, } from 'vs/workbench/services/viewlet/browser/viewlet';
|
||||
import { IPartService, Parts } from 'vs/workbench/services/part/common/partService';
|
||||
import { IThemeService, ITheme, registerThemingParticipant, ICssStyleCollector } from 'vs/platform/theme/common/themeService';
|
||||
import { ACTIVITY_BAR_BADGE_FOREGROUND, ACTIVITY_BAR_BADGE_BACKGROUND, ACTIVITY_BAR_DRAG_AND_DROP_BACKGROUND, ACTIVITY_BAR_FOREGROUND } from 'vs/workbench/common/theme';
|
||||
import { contrastBorder, activeContrastBorder, focusBorder } from 'vs/platform/theme/common/colorRegistry';
|
||||
import { activeContrastBorder, focusBorder } from 'vs/platform/theme/common/colorRegistry';
|
||||
import { StandardMouseEvent } from 'vs/base/browser/mouseEvent';
|
||||
import { KeyCode } from 'vs/base/common/keyCodes';
|
||||
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
|
||||
|
||||
export interface IViewletActivity {
|
||||
badge: IBadge;
|
||||
clazz: string;
|
||||
}
|
||||
|
||||
export class ActivityAction extends Action {
|
||||
private badge: IBadge;
|
||||
private _onDidChangeBadge = new Emitter<this>();
|
||||
|
||||
constructor(private _activity: IActivity) {
|
||||
super(_activity.id, _activity.name, _activity.cssClass);
|
||||
|
||||
this.badge = null;
|
||||
}
|
||||
|
||||
public get activity(): IActivity {
|
||||
return this._activity;
|
||||
}
|
||||
|
||||
public get onDidChangeBadge(): Event<this> {
|
||||
return this._onDidChangeBadge.event;
|
||||
}
|
||||
|
||||
public activate(): void {
|
||||
if (!this.checked) {
|
||||
this._setChecked(true);
|
||||
}
|
||||
}
|
||||
|
||||
public deactivate(): void {
|
||||
if (this.checked) {
|
||||
this._setChecked(false);
|
||||
}
|
||||
}
|
||||
|
||||
public getBadge(): IBadge {
|
||||
return this.badge;
|
||||
}
|
||||
|
||||
public setBadge(badge: IBadge): void {
|
||||
this.badge = badge;
|
||||
this._onDidChangeBadge.fire(this);
|
||||
}
|
||||
}
|
||||
import { ActivityAction, ActivityActionItem, ICompositeBarColors } from 'vs/workbench/browser/parts/compositebar/compositeBarActions';
|
||||
|
||||
export class ViewletActivityAction extends ActivityAction {
|
||||
|
||||
@@ -83,14 +30,14 @@ export class ViewletActivityAction extends ActivityAction {
|
||||
private lastRun: number = 0;
|
||||
|
||||
constructor(
|
||||
private viewlet: ViewletDescriptor,
|
||||
activity: IActivity,
|
||||
@IViewletService private viewletService: IViewletService,
|
||||
@IPartService private partService: IPartService
|
||||
) {
|
||||
super(viewlet);
|
||||
super(activity);
|
||||
}
|
||||
|
||||
public run(event): TPromise<any> {
|
||||
public run(event: any): TPromise<any> {
|
||||
if (event instanceof MouseEvent && event.button === 2) {
|
||||
return TPromise.as(false); // do not run on right click
|
||||
}
|
||||
@@ -106,483 +53,15 @@ export class ViewletActivityAction extends ActivityAction {
|
||||
const activeViewlet = this.viewletService.getActiveViewlet();
|
||||
|
||||
// Hide sidebar if selected viewlet already visible
|
||||
if (sideBarVisible && activeViewlet && activeViewlet.getId() === this.viewlet.id) {
|
||||
if (sideBarVisible && activeViewlet && activeViewlet.getId() === this.activity.id) {
|
||||
return this.partService.setSideBarHidden(true);
|
||||
}
|
||||
|
||||
return this.viewletService.openViewlet(this.viewlet.id, true)
|
||||
.then(() => this.activate());
|
||||
return this.viewletService.openViewlet(this.activity.id, true).then(() => this.activate());
|
||||
}
|
||||
}
|
||||
|
||||
export class ActivityActionItem extends BaseActionItem {
|
||||
protected $container: Builder;
|
||||
protected $label: Builder;
|
||||
protected $badge: Builder;
|
||||
|
||||
private $badgeContent: Builder;
|
||||
private mouseUpTimeout: number;
|
||||
|
||||
constructor(
|
||||
action: ActivityAction,
|
||||
options: IBaseActionItemOptions,
|
||||
@IThemeService protected themeService: IThemeService
|
||||
) {
|
||||
super(null, action, options);
|
||||
|
||||
this.themeService.onThemeChange(this.onThemeChange, this, this._callOnDispose);
|
||||
action.onDidChangeBadge(this.handleBadgeChangeEvenet, this, this._callOnDispose);
|
||||
}
|
||||
|
||||
protected get activity(): IActivity {
|
||||
return (this._action as ActivityAction).activity;
|
||||
}
|
||||
|
||||
protected updateStyles(): void {
|
||||
const theme = this.themeService.getTheme();
|
||||
|
||||
// Label
|
||||
if (this.$label) {
|
||||
const background = theme.getColor(ACTIVITY_BAR_FOREGROUND);
|
||||
|
||||
this.$label.style('background-color', background ? background.toString() : null);
|
||||
}
|
||||
|
||||
// Badge
|
||||
if (this.$badgeContent) {
|
||||
const badgeForeground = theme.getColor(ACTIVITY_BAR_BADGE_FOREGROUND);
|
||||
const badgeBackground = theme.getColor(ACTIVITY_BAR_BADGE_BACKGROUND);
|
||||
const contrastBorderColor = theme.getColor(contrastBorder);
|
||||
|
||||
this.$badgeContent.style('color', badgeForeground ? badgeForeground.toString() : null);
|
||||
this.$badgeContent.style('background-color', badgeBackground ? badgeBackground.toString() : null);
|
||||
|
||||
this.$badgeContent.style('border-style', contrastBorderColor ? 'solid' : null);
|
||||
this.$badgeContent.style('border-width', contrastBorderColor ? '1px' : null);
|
||||
this.$badgeContent.style('border-color', contrastBorderColor ? contrastBorderColor.toString() : null);
|
||||
}
|
||||
}
|
||||
|
||||
public render(container: HTMLElement): void {
|
||||
super.render(container);
|
||||
|
||||
// Make the container tab-able for keyboard navigation
|
||||
this.$container = $(container).attr({
|
||||
tabIndex: '0',
|
||||
role: 'button',
|
||||
title: this.activity.name
|
||||
});
|
||||
|
||||
// Try hard to prevent keyboard only focus feedback when using mouse
|
||||
this.$container.on(DOM.EventType.MOUSE_DOWN, () => {
|
||||
this.$container.addClass('clicked');
|
||||
});
|
||||
|
||||
this.$container.on(DOM.EventType.MOUSE_UP, () => {
|
||||
if (this.mouseUpTimeout) {
|
||||
clearTimeout(this.mouseUpTimeout);
|
||||
}
|
||||
|
||||
this.mouseUpTimeout = setTimeout(() => {
|
||||
this.$container.removeClass('clicked');
|
||||
}, 800); // delayed to prevent focus feedback from showing on mouse up
|
||||
});
|
||||
|
||||
// Label
|
||||
this.$label = $('a.action-label').appendTo(this.builder);
|
||||
if (this.activity.cssClass) {
|
||||
this.$label.addClass(this.activity.cssClass);
|
||||
}
|
||||
|
||||
this.$badge = this.builder.clone().div({ 'class': 'badge' }, (badge: Builder) => {
|
||||
this.$badgeContent = badge.div({ 'class': 'badge-content' });
|
||||
});
|
||||
|
||||
this.$badge.hide();
|
||||
|
||||
this.updateStyles();
|
||||
}
|
||||
|
||||
private onThemeChange(theme: ITheme): void {
|
||||
this.updateStyles();
|
||||
}
|
||||
|
||||
public setBadge(badge: IBadge): void {
|
||||
this.updateBadge(badge);
|
||||
}
|
||||
|
||||
protected updateBadge(badge: IBadge): void {
|
||||
this.$badgeContent.empty();
|
||||
this.$badge.hide();
|
||||
|
||||
if (badge) {
|
||||
|
||||
// Number
|
||||
if (badge instanceof NumberBadge) {
|
||||
if (badge.number) {
|
||||
this.$badgeContent.text(badge.number > 99 ? '99+' : badge.number.toString());
|
||||
this.$badge.show();
|
||||
}
|
||||
}
|
||||
|
||||
// Text
|
||||
else if (badge instanceof TextBadge) {
|
||||
this.$badgeContent.text(badge.text);
|
||||
this.$badge.show();
|
||||
}
|
||||
|
||||
// Text
|
||||
else if (badge instanceof IconBadge) {
|
||||
this.$badge.show();
|
||||
}
|
||||
|
||||
// Progress
|
||||
else if (badge instanceof ProgressBadge) {
|
||||
this.$badge.show();
|
||||
}
|
||||
|
||||
const description = badge.getDescription();
|
||||
this.$label.attr('aria-label', `${this.activity.name} - ${description}`);
|
||||
this.$label.title(description);
|
||||
}
|
||||
}
|
||||
|
||||
private handleBadgeChangeEvenet(): void {
|
||||
const action = this.getAction();
|
||||
if (action instanceof ActivityAction) {
|
||||
this.updateBadge(action.getBadge());
|
||||
}
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
super.dispose();
|
||||
|
||||
if (this.mouseUpTimeout) {
|
||||
clearTimeout(this.mouseUpTimeout);
|
||||
}
|
||||
|
||||
this.$badge.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
export class ViewletActionItem extends ActivityActionItem {
|
||||
|
||||
private static manageExtensionAction: ManageExtensionAction;
|
||||
private static toggleViewletPinnedAction: ToggleViewletPinnedAction;
|
||||
private static draggedViewlet: ViewletDescriptor;
|
||||
|
||||
private _keybinding: string;
|
||||
private cssClass: string;
|
||||
|
||||
constructor(
|
||||
private action: ViewletActivityAction,
|
||||
@IContextMenuService private contextMenuService: IContextMenuService,
|
||||
@IActivityBarService private activityBarService: IActivityBarService,
|
||||
@IKeybindingService private keybindingService: IKeybindingService,
|
||||
@IInstantiationService instantiationService: IInstantiationService,
|
||||
@IThemeService themeService: IThemeService
|
||||
) {
|
||||
super(action, { draggable: true }, themeService);
|
||||
|
||||
this.cssClass = action.class;
|
||||
this._keybinding = this.getKeybindingLabel(this.viewlet.id);
|
||||
|
||||
if (!ViewletActionItem.manageExtensionAction) {
|
||||
ViewletActionItem.manageExtensionAction = instantiationService.createInstance(ManageExtensionAction);
|
||||
}
|
||||
|
||||
if (!ViewletActionItem.toggleViewletPinnedAction) {
|
||||
ViewletActionItem.toggleViewletPinnedAction = instantiationService.createInstance(ToggleViewletPinnedAction, void 0);
|
||||
}
|
||||
}
|
||||
|
||||
private get viewlet(): ViewletDescriptor {
|
||||
return this.action.activity as ViewletDescriptor;
|
||||
}
|
||||
|
||||
private getKeybindingLabel(id: string): string {
|
||||
const kb = this.keybindingService.lookupKeybinding(id);
|
||||
if (kb) {
|
||||
return kb.getLabel();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public render(container: HTMLElement): void {
|
||||
super.render(container);
|
||||
|
||||
this.$container.on('contextmenu', e => {
|
||||
DOM.EventHelper.stop(e, true);
|
||||
|
||||
this.showContextMenu(container);
|
||||
});
|
||||
|
||||
// Allow to drag
|
||||
this.$container.on(DOM.EventType.DRAG_START, (e: DragEvent) => {
|
||||
e.dataTransfer.effectAllowed = 'move';
|
||||
this.setDraggedViewlet(this.viewlet);
|
||||
|
||||
// Trigger the action even on drag start to prevent clicks from failing that started a drag
|
||||
if (!this.getAction().checked) {
|
||||
this.getAction().run();
|
||||
}
|
||||
});
|
||||
|
||||
// Drag enter
|
||||
let counter = 0; // see https://github.com/Microsoft/vscode/issues/14470
|
||||
this.$container.on(DOM.EventType.DRAG_ENTER, (e: DragEvent) => {
|
||||
const draggedViewlet = ViewletActionItem.getDraggedViewlet();
|
||||
if (draggedViewlet && draggedViewlet.id !== this.viewlet.id) {
|
||||
counter++;
|
||||
this.updateFromDragging(container, true);
|
||||
}
|
||||
});
|
||||
|
||||
// Drag leave
|
||||
this.$container.on(DOM.EventType.DRAG_LEAVE, (e: DragEvent) => {
|
||||
const draggedViewlet = ViewletActionItem.getDraggedViewlet();
|
||||
if (draggedViewlet) {
|
||||
counter--;
|
||||
if (counter === 0) {
|
||||
this.updateFromDragging(container, false);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Drag end
|
||||
this.$container.on(DOM.EventType.DRAG_END, (e: DragEvent) => {
|
||||
const draggedViewlet = ViewletActionItem.getDraggedViewlet();
|
||||
if (draggedViewlet) {
|
||||
counter = 0;
|
||||
this.updateFromDragging(container, false);
|
||||
|
||||
ViewletActionItem.clearDraggedViewlet();
|
||||
}
|
||||
});
|
||||
|
||||
// Drop
|
||||
this.$container.on(DOM.EventType.DROP, (e: DragEvent) => {
|
||||
DOM.EventHelper.stop(e, true);
|
||||
|
||||
const draggedViewlet = ViewletActionItem.getDraggedViewlet();
|
||||
if (draggedViewlet && draggedViewlet.id !== this.viewlet.id) {
|
||||
this.updateFromDragging(container, false);
|
||||
ViewletActionItem.clearDraggedViewlet();
|
||||
|
||||
this.activityBarService.move(draggedViewlet.id, this.viewlet.id);
|
||||
}
|
||||
});
|
||||
|
||||
// Keybinding
|
||||
this.keybinding = this._keybinding; // force update
|
||||
|
||||
// Activate on drag over to reveal targets
|
||||
[this.$badge, this.$label].forEach(b => new DelayedDragHandler(b.getHTMLElement(), () => {
|
||||
if (!ViewletActionItem.getDraggedViewlet() && !this.getAction().checked) {
|
||||
this.getAction().run();
|
||||
}
|
||||
}));
|
||||
|
||||
this.updateStyles();
|
||||
}
|
||||
|
||||
private updateFromDragging(element: HTMLElement, isDragging: boolean): void {
|
||||
const theme = this.themeService.getTheme();
|
||||
const dragBackground = theme.getColor(ACTIVITY_BAR_DRAG_AND_DROP_BACKGROUND);
|
||||
|
||||
element.style.backgroundColor = isDragging && dragBackground ? dragBackground.toString() : null;
|
||||
}
|
||||
|
||||
public static getDraggedViewlet(): ViewletDescriptor {
|
||||
return ViewletActionItem.draggedViewlet;
|
||||
}
|
||||
|
||||
private setDraggedViewlet(viewlet: ViewletDescriptor): void {
|
||||
ViewletActionItem.draggedViewlet = viewlet;
|
||||
}
|
||||
|
||||
public static clearDraggedViewlet(): void {
|
||||
ViewletActionItem.draggedViewlet = void 0;
|
||||
}
|
||||
|
||||
private showContextMenu(container: HTMLElement): void {
|
||||
const actions: Action[] = [ViewletActionItem.toggleViewletPinnedAction];
|
||||
if (this.viewlet.extensionId) {
|
||||
actions.push(new Separator());
|
||||
actions.push(ViewletActionItem.manageExtensionAction);
|
||||
}
|
||||
|
||||
const isPinned = this.activityBarService.isPinned(this.viewlet.id);
|
||||
if (isPinned) {
|
||||
ViewletActionItem.toggleViewletPinnedAction.label = nls.localize('removeFromActivityBar', "Remove from Activity Bar");
|
||||
} else {
|
||||
ViewletActionItem.toggleViewletPinnedAction.label = nls.localize('keepInActivityBar', "Keep in Activity Bar");
|
||||
}
|
||||
|
||||
this.contextMenuService.showContextMenu({
|
||||
getAnchor: () => container,
|
||||
getActionsContext: () => this.viewlet,
|
||||
getActions: () => TPromise.as(actions)
|
||||
});
|
||||
}
|
||||
|
||||
public focus(): void {
|
||||
this.$container.domFocus();
|
||||
}
|
||||
|
||||
public set keybinding(keybinding: string) {
|
||||
this._keybinding = keybinding;
|
||||
|
||||
if (!this.$label) {
|
||||
return;
|
||||
}
|
||||
|
||||
let title: string;
|
||||
if (keybinding) {
|
||||
title = nls.localize('titleKeybinding', "{0} ({1})", this.activity.name, keybinding);
|
||||
} else {
|
||||
title = this.activity.name;
|
||||
}
|
||||
|
||||
this.$label.title(title);
|
||||
this.$badge.title(title);
|
||||
this.$container.title(title);
|
||||
}
|
||||
|
||||
protected _updateClass(): void {
|
||||
if (this.cssClass) {
|
||||
this.$badge.removeClass(this.cssClass);
|
||||
}
|
||||
|
||||
this.cssClass = this.getAction().class;
|
||||
this.$badge.addClass(this.cssClass);
|
||||
}
|
||||
|
||||
protected _updateChecked(): void {
|
||||
if (this.getAction().checked) {
|
||||
this.$container.addClass('checked');
|
||||
} else {
|
||||
this.$container.removeClass('checked');
|
||||
}
|
||||
}
|
||||
|
||||
protected _updateEnabled(): void {
|
||||
if (this.getAction().enabled) {
|
||||
this.builder.removeClass('disabled');
|
||||
} else {
|
||||
this.builder.addClass('disabled');
|
||||
}
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
super.dispose();
|
||||
|
||||
ViewletActionItem.clearDraggedViewlet();
|
||||
|
||||
this.$label.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
export class ViewletOverflowActivityAction extends ActivityAction {
|
||||
|
||||
constructor(
|
||||
private showMenu: () => void
|
||||
) {
|
||||
super({
|
||||
id: 'activitybar.additionalViewlets.action',
|
||||
name: nls.localize('additionalViews', "Additional Views"),
|
||||
cssClass: 'toggle-more'
|
||||
});
|
||||
}
|
||||
|
||||
public run(event): TPromise<any> {
|
||||
this.showMenu();
|
||||
|
||||
return TPromise.as(true);
|
||||
}
|
||||
}
|
||||
|
||||
export class ViewletOverflowActivityActionItem extends ActivityActionItem {
|
||||
private name: string;
|
||||
private cssClass: string;
|
||||
private actions: OpenViewletAction[];
|
||||
|
||||
constructor(
|
||||
action: ActivityAction,
|
||||
private getOverflowingViewlets: () => ViewletDescriptor[],
|
||||
private getBadge: (viewlet: ViewletDescriptor) => IBadge,
|
||||
@IInstantiationService private instantiationService: IInstantiationService,
|
||||
@IViewletService private viewletService: IViewletService,
|
||||
@IContextMenuService private contextMenuService: IContextMenuService,
|
||||
@IThemeService themeService: IThemeService
|
||||
) {
|
||||
super(action, null, themeService);
|
||||
|
||||
this.cssClass = action.class;
|
||||
this.name = action.label;
|
||||
}
|
||||
|
||||
public showMenu(): void {
|
||||
if (this.actions) {
|
||||
dispose(this.actions);
|
||||
}
|
||||
|
||||
this.actions = this.getActions();
|
||||
|
||||
this.contextMenuService.showContextMenu({
|
||||
getAnchor: () => this.builder.getHTMLElement(),
|
||||
getActions: () => TPromise.as(this.actions),
|
||||
onHide: () => dispose(this.actions)
|
||||
});
|
||||
}
|
||||
|
||||
private getActions(): OpenViewletAction[] {
|
||||
const activeViewlet = this.viewletService.getActiveViewlet();
|
||||
|
||||
return this.getOverflowingViewlets().map(viewlet => {
|
||||
const action = this.instantiationService.createInstance(OpenViewletAction, viewlet);
|
||||
action.radio = activeViewlet && activeViewlet.getId() === action.id;
|
||||
|
||||
const badge = this.getBadge(action.viewlet);
|
||||
let suffix: string | number;
|
||||
if (badge instanceof NumberBadge) {
|
||||
suffix = badge.number;
|
||||
} else if (badge instanceof TextBadge) {
|
||||
suffix = badge.text;
|
||||
}
|
||||
|
||||
if (suffix) {
|
||||
action.label = nls.localize('numberBadge', "{0} ({1})", action.viewlet.name, suffix);
|
||||
} else {
|
||||
action.label = action.viewlet.name;
|
||||
}
|
||||
|
||||
return action;
|
||||
});
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
super.dispose();
|
||||
|
||||
this.actions = dispose(this.actions);
|
||||
}
|
||||
}
|
||||
|
||||
class ManageExtensionAction extends Action {
|
||||
|
||||
constructor(
|
||||
@ICommandService private commandService: ICommandService
|
||||
) {
|
||||
super('activitybar.manage.extension', nls.localize('manageExtension', "Manage Extension"));
|
||||
}
|
||||
|
||||
public run(viewlet: ViewletDescriptor): TPromise<any> {
|
||||
return this.commandService.executeCommand('_extensions.manage', viewlet.extensionId);
|
||||
}
|
||||
}
|
||||
|
||||
class OpenViewletAction extends Action {
|
||||
export class ToggleViewletAction extends Action {
|
||||
|
||||
constructor(
|
||||
private _viewlet: ViewletDescriptor,
|
||||
@@ -592,44 +71,16 @@ class OpenViewletAction extends Action {
|
||||
super(_viewlet.id, _viewlet.name);
|
||||
}
|
||||
|
||||
public get viewlet(): ViewletDescriptor {
|
||||
return this._viewlet;
|
||||
}
|
||||
|
||||
public run(): TPromise<any> {
|
||||
const sideBarVisible = this.partService.isVisible(Parts.SIDEBAR_PART);
|
||||
const activeViewlet = this.viewletService.getActiveViewlet();
|
||||
|
||||
// Hide sidebar if selected viewlet already visible
|
||||
if (sideBarVisible && activeViewlet && activeViewlet.getId() === this.viewlet.id) {
|
||||
if (sideBarVisible && activeViewlet && activeViewlet.getId() === this._viewlet.id) {
|
||||
return this.partService.setSideBarHidden(true);
|
||||
}
|
||||
|
||||
return this.viewletService.openViewlet(this.viewlet.id, true);
|
||||
}
|
||||
}
|
||||
|
||||
export class ToggleViewletPinnedAction extends Action {
|
||||
|
||||
constructor(
|
||||
private viewlet: ViewletDescriptor,
|
||||
@IActivityBarService private activityBarService: IActivityBarService
|
||||
) {
|
||||
super('activitybar.show.toggleViewletPinned', viewlet ? viewlet.name : nls.localize('toggle', "Toggle View Pinned"));
|
||||
|
||||
this.checked = this.viewlet && this.activityBarService.isPinned(this.viewlet.id);
|
||||
}
|
||||
|
||||
public run(context?: ViewletDescriptor): TPromise<any> {
|
||||
const viewlet = this.viewlet || context;
|
||||
|
||||
if (this.activityBarService.isPinned(viewlet.id)) {
|
||||
this.activityBarService.unpin(viewlet.id);
|
||||
} else {
|
||||
this.activityBarService.pin(viewlet.id);
|
||||
}
|
||||
|
||||
return TPromise.as(true);
|
||||
return this.viewletService.openViewlet(this._viewlet.id, true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -644,10 +95,11 @@ export class GlobalActivityActionItem extends ActivityActionItem {
|
||||
|
||||
constructor(
|
||||
action: GlobalActivityAction,
|
||||
colors: ICompositeBarColors,
|
||||
@IThemeService themeService: IThemeService,
|
||||
@IContextMenuService protected contextMenuService: IContextMenuService
|
||||
) {
|
||||
super(action, { draggable: false }, themeService);
|
||||
super(action, { draggable: false, colors, icon: true }, themeService);
|
||||
}
|
||||
|
||||
public render(container: HTMLElement): void {
|
||||
@@ -670,6 +122,13 @@ export class GlobalActivityActionItem extends ActivityActionItem {
|
||||
this.showContextMenu(this.$container.getHTMLElement());
|
||||
}
|
||||
});
|
||||
|
||||
this.$container.on(TouchEventType.Tap, (e: GestureEvent) => {
|
||||
DOM.EventHelper.stop(e, true);
|
||||
|
||||
const event = new StandardMouseEvent(e);
|
||||
this.showContextMenu({ x: event.posx, y: event.posy });
|
||||
});
|
||||
}
|
||||
|
||||
private showContextMenu(location: HTMLElement | { x: number, y: number }): void {
|
||||
|
||||
@@ -8,37 +8,40 @@
|
||||
import 'vs/css!./media/activitybarpart';
|
||||
import nls = require('vs/nls');
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import DOM = require('vs/base/browser/dom');
|
||||
import * as arrays from 'vs/base/common/arrays';
|
||||
import { illegalArgument } from 'vs/base/common/errors';
|
||||
import { Builder, $, Dimension } from 'vs/base/browser/builder';
|
||||
import { Action } from 'vs/base/common/actions';
|
||||
import { ActionsOrientation, ActionBar, IActionItem, Separator } from 'vs/base/browser/ui/actionbar/actionbar';
|
||||
import { ViewletDescriptor } from 'vs/workbench/browser/viewlet';
|
||||
import { GlobalActivityExtensions, IGlobalActivityRegistry } from 'vs/workbench/browser/activity';
|
||||
import { ActionsOrientation, ActionBar, Separator } from 'vs/base/browser/ui/actionbar/actionbar';
|
||||
import { GlobalActivityExtensions, IGlobalActivityRegistry } from 'vs/workbench/common/activity';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { Part } from 'vs/workbench/browser/part';
|
||||
import { IViewlet } from 'vs/workbench/common/viewlet';
|
||||
import { ToggleViewletPinnedAction, ViewletActivityAction, ActivityAction, GlobalActivityActionItem, ViewletActionItem, ViewletOverflowActivityAction, ViewletOverflowActivityActionItem, GlobalActivityAction, IViewletActivity } from 'vs/workbench/browser/parts/activitybar/activitybarActions';
|
||||
import { GlobalActivityActionItem, GlobalActivityAction, ViewletActivityAction, ToggleViewletAction } from 'vs/workbench/browser/parts/activitybar/activitybarActions';
|
||||
import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet';
|
||||
import { IActivityBarService, IBadge } from 'vs/workbench/services/activity/common/activityBarService';
|
||||
import { IBadge } from 'vs/workbench/services/activity/common/activity';
|
||||
import { IPartService, Position as SideBarPosition } from 'vs/workbench/services/part/common/partService';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IExtensionService } from 'vs/platform/extensions/common/extensions';
|
||||
import { IStorageService } from 'vs/platform/storage/common/storage';
|
||||
import { Scope as MementoScope } from 'vs/workbench/common/memento';
|
||||
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
|
||||
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
|
||||
import { StandardMouseEvent } from 'vs/base/browser/mouseEvent';
|
||||
import { dispose, IDisposable, toDisposable } from 'vs/base/common/lifecycle';
|
||||
import { ToggleActivityBarVisibilityAction } from 'vs/workbench/browser/actions/toggleActivityBarVisibility';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import { ACTIVITY_BAR_BACKGROUND, ACTIVITY_BAR_BORDER } from 'vs/workbench/common/theme';
|
||||
import { ACTIVITY_BAR_BACKGROUND, ACTIVITY_BAR_BORDER, ACTIVITY_BAR_FOREGROUND, ACTIVITY_BAR_BADGE_BACKGROUND, ACTIVITY_BAR_BADGE_FOREGROUND, ACTIVITY_BAR_DRAG_AND_DROP_BACKGROUND } from 'vs/workbench/common/theme';
|
||||
import { contrastBorder } from 'vs/platform/theme/common/colorRegistry';
|
||||
import { CompositeBar } from 'vs/workbench/browser/parts/compositebar/compositeBar';
|
||||
import { ToggleCompositePinnedAction } from 'vs/workbench/browser/parts/compositebar/compositeBarActions';
|
||||
import { ViewletDescriptor } from 'vs/workbench/browser/viewlet';
|
||||
|
||||
export class ActivitybarPart extends Part implements IActivityBarService {
|
||||
export class ActivitybarPart extends Part {
|
||||
|
||||
private static readonly ACTIVITY_ACTION_HEIGHT = 50;
|
||||
private static readonly PINNED_VIEWLETS = 'workbench.activity.pinnedViewlets';
|
||||
private static COLORS = {
|
||||
backgroundColor: ACTIVITY_BAR_FOREGROUND,
|
||||
badgeBackground: ACTIVITY_BAR_BADGE_BACKGROUND,
|
||||
badgeForeground: ACTIVITY_BAR_BADGE_FOREGROUND,
|
||||
dragAndDropBackground: ACTIVITY_BAR_DRAG_AND_DROP_BACKGROUND
|
||||
};
|
||||
|
||||
public _serviceBrand: any;
|
||||
|
||||
@@ -47,17 +50,7 @@ export class ActivitybarPart extends Part implements IActivityBarService {
|
||||
private globalActionBar: ActionBar;
|
||||
private globalActivityIdToActions: { [globalActivityId: string]: GlobalActivityAction; };
|
||||
|
||||
private viewletSwitcherBar: ActionBar;
|
||||
private viewletOverflowAction: ViewletOverflowActivityAction;
|
||||
private viewletOverflowActionItem: ViewletOverflowActivityActionItem;
|
||||
|
||||
private viewletIdToActions: { [viewletId: string]: ActivityAction; };
|
||||
private viewletIdToActionItems: { [viewletId: string]: IActionItem; };
|
||||
private viewletIdToActivityStack: { [viewletId: string]: IViewletActivity[]; };
|
||||
|
||||
private memento: object;
|
||||
private pinnedViewlets: string[];
|
||||
private activeUnpinnedViewlet: ViewletDescriptor;
|
||||
private compositeBar: CompositeBar;
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
@@ -72,59 +65,54 @@ export class ActivitybarPart extends Part implements IActivityBarService {
|
||||
super(id, { hasTitle: false }, themeService);
|
||||
|
||||
this.globalActivityIdToActions = Object.create(null);
|
||||
this.compositeBar = this.instantiationService.createInstance(CompositeBar, {
|
||||
icon: true,
|
||||
storageId: ActivitybarPart.PINNED_VIEWLETS,
|
||||
orientation: ActionsOrientation.VERTICAL,
|
||||
composites: this.getViewlets(), // {{SQL CARBON EDIT}}
|
||||
openComposite: (compositeId: string) => this.viewletService.openViewlet(compositeId, true),
|
||||
getActivityAction: (compositeId: string) => this.instantiationService.createInstance(ViewletActivityAction, this.viewletService.getViewlet(compositeId)),
|
||||
getCompositePinnedAction: (compositeId: string) => new ToggleCompositePinnedAction(this.viewletService.getViewlet(compositeId), this.compositeBar),
|
||||
getOnCompositeClickAction: (compositeId: string) => this.instantiationService.createInstance(ToggleViewletAction, this.viewletService.getViewlet(compositeId)),
|
||||
getDefaultCompositeId: () => this.viewletService.getDefaultViewletId(),
|
||||
hidePart: () => this.partService.setSideBarHidden(true),
|
||||
colors: ActivitybarPart.COLORS,
|
||||
overflowActionSize: 50
|
||||
});
|
||||
this.registerListeners();
|
||||
}
|
||||
|
||||
this.viewletIdToActionItems = Object.create(null);
|
||||
this.viewletIdToActions = Object.create(null);
|
||||
this.viewletIdToActivityStack = Object.create(null);
|
||||
// {{SQL CARBON EDIT}}
|
||||
private getViewlets(): ViewletDescriptor[] {
|
||||
const pinnedViewlets = JSON.parse(this.storageService.get(ActivitybarPart.PINNED_VIEWLETS, StorageScope.GLOBAL, null)) as string[];
|
||||
const allViewLets = this.viewletService.getViewlets();
|
||||
|
||||
this.memento = this.getMemento(this.storageService, MementoScope.GLOBAL);
|
||||
|
||||
const pinnedViewlets = this.memento[ActivitybarPart.PINNED_VIEWLETS] as string[];
|
||||
|
||||
if (pinnedViewlets) {
|
||||
this.pinnedViewlets = pinnedViewlets
|
||||
// TODO@Ben: Migrate git => scm viewlet
|
||||
.map(id => id === 'workbench.view.git' ? 'workbench.view.scm' : id)
|
||||
.filter(arrays.uniqueFilter<string>(str => str));
|
||||
} else {
|
||||
this.pinnedViewlets = this.viewletService.getViewlets().map(v => v.id);
|
||||
if (!pinnedViewlets) {
|
||||
return allViewLets.filter(viewlet => viewlet.id !== 'workbench.view.extensions') ;
|
||||
}
|
||||
|
||||
this.registerListeners();
|
||||
return allViewLets;
|
||||
}
|
||||
|
||||
private registerListeners(): void {
|
||||
|
||||
// Activate viewlet action on opening of a viewlet
|
||||
this.toUnbind.push(this.viewletService.onDidViewletOpen(viewlet => this.onDidViewletOpen(viewlet)));
|
||||
this.toUnbind.push(this.viewletService.onDidViewletOpen(viewlet => this.compositeBar.activateComposite(viewlet.getId())));
|
||||
|
||||
// Deactivate viewlet action on close
|
||||
this.toUnbind.push(this.viewletService.onDidViewletClose(viewlet => this.onDidViewletClose(viewlet)));
|
||||
this.toUnbind.push(this.viewletService.onDidViewletClose(viewlet => this.compositeBar.deactivateComposite(viewlet.getId())));
|
||||
this.toUnbind.push(this.compositeBar.onDidContextMenu(e => this.showContextMenu(e)));
|
||||
}
|
||||
|
||||
private onDidViewletOpen(viewlet: IViewlet): void {
|
||||
const id = viewlet.getId();
|
||||
|
||||
if (this.viewletIdToActions[id]) {
|
||||
this.viewletIdToActions[id].activate();
|
||||
public showActivity(viewletOrActionId: string, badge: IBadge, clazz?: string): IDisposable {
|
||||
if (this.viewletService.getViewlet(viewletOrActionId)) {
|
||||
return this.compositeBar.showActivity(viewletOrActionId, badge, clazz);
|
||||
}
|
||||
|
||||
const activeUnpinnedViewletShouldClose = this.activeUnpinnedViewlet && this.activeUnpinnedViewlet.id !== viewlet.getId();
|
||||
const activeUnpinnedViewletShouldShow = !this.getPinnedViewlets().some(v => v.id === viewlet.getId());
|
||||
if (activeUnpinnedViewletShouldShow || activeUnpinnedViewletShouldClose) {
|
||||
this.updateViewletSwitcher();
|
||||
}
|
||||
return this.showGlobalActivity(viewletOrActionId, badge);
|
||||
}
|
||||
|
||||
private onDidViewletClose(viewlet: IViewlet): void {
|
||||
const id = viewlet.getId();
|
||||
|
||||
if (this.viewletIdToActions[id]) {
|
||||
this.viewletIdToActions[id].deactivate();
|
||||
}
|
||||
}
|
||||
|
||||
public showGlobalActivity(globalActivityId: string, badge: IBadge): IDisposable {
|
||||
private showGlobalActivity(globalActivityId: string, badge: IBadge): IDisposable {
|
||||
if (!badge) {
|
||||
throw illegalArgument('badge');
|
||||
}
|
||||
@@ -139,87 +127,15 @@ export class ActivitybarPart extends Part implements IActivityBarService {
|
||||
return toDisposable(() => action.setBadge(undefined));
|
||||
}
|
||||
|
||||
public showActivity(viewletId: string, badge: IBadge, clazz?: string): IDisposable {
|
||||
if (!badge) {
|
||||
throw illegalArgument('badge');
|
||||
}
|
||||
|
||||
const activity = <IViewletActivity>{ badge, clazz };
|
||||
const stack = this.viewletIdToActivityStack[viewletId] || (this.viewletIdToActivityStack[viewletId] = []);
|
||||
stack.unshift(activity);
|
||||
|
||||
this.updateActivity(viewletId);
|
||||
|
||||
return {
|
||||
dispose: () => {
|
||||
const stack = this.viewletIdToActivityStack[viewletId];
|
||||
if (!stack) {
|
||||
return;
|
||||
}
|
||||
const idx = stack.indexOf(activity);
|
||||
if (idx < 0) {
|
||||
return;
|
||||
}
|
||||
stack.splice(idx, 1);
|
||||
if (stack.length === 0) {
|
||||
delete this.viewletIdToActivityStack[viewletId];
|
||||
}
|
||||
this.updateActivity(viewletId);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private updateActivity(viewletId: string) {
|
||||
const action = this.viewletIdToActions[viewletId];
|
||||
if (!action) {
|
||||
return;
|
||||
}
|
||||
const stack = this.viewletIdToActivityStack[viewletId];
|
||||
if (!stack || !stack.length) {
|
||||
// reset
|
||||
action.setBadge(undefined);
|
||||
|
||||
} else {
|
||||
// update
|
||||
const [{ badge, clazz }] = stack;
|
||||
action.setBadge(badge);
|
||||
if (clazz) {
|
||||
action.class = clazz;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public createContentArea(parent: Builder): Builder {
|
||||
const $el = $(parent);
|
||||
const $result = $('.content').appendTo($el);
|
||||
|
||||
// Top Actionbar with action items for each viewlet action
|
||||
this.createViewletSwitcher($result.clone());
|
||||
this.compositeBar.create($result.getHTMLElement());
|
||||
|
||||
// Top Actionbar with action items for each viewlet action
|
||||
this.createGlobalActivityActionBar($result.getHTMLElement());
|
||||
|
||||
// Contextmenu for viewlets
|
||||
$(parent).on('contextmenu', (e: MouseEvent) => {
|
||||
DOM.EventHelper.stop(e, true);
|
||||
|
||||
this.showContextMenu(e);
|
||||
}, this.toUnbind);
|
||||
|
||||
// Allow to drop at the end to move viewlet to the end
|
||||
$(parent).on(DOM.EventType.DROP, (e: DragEvent) => {
|
||||
const draggedViewlet = ViewletActionItem.getDraggedViewlet();
|
||||
if (draggedViewlet) {
|
||||
DOM.EventHelper.stop(e, true);
|
||||
|
||||
ViewletActionItem.clearDraggedViewlet();
|
||||
|
||||
const targetId = this.pinnedViewlets[this.pinnedViewlets.length - 1];
|
||||
if (targetId !== draggedViewlet.id) {
|
||||
this.move(draggedViewlet.id, this.pinnedViewlets[this.pinnedViewlets.length - 1]);
|
||||
}
|
||||
}
|
||||
});
|
||||
this.createGlobalActivityActionBar($('.global-activity').appendTo($result).getHTMLElement());
|
||||
|
||||
return $result;
|
||||
}
|
||||
@@ -246,31 +162,17 @@ export class ActivitybarPart extends Part implements IActivityBarService {
|
||||
private showContextMenu(e: MouseEvent): void {
|
||||
const event = new StandardMouseEvent(e);
|
||||
|
||||
const actions: Action[] = this.viewletService.getViewlets().map(viewlet => this.instantiationService.createInstance(ToggleViewletPinnedAction, viewlet));
|
||||
const actions: Action[] = this.viewletService.getViewlets().map(viewlet => this.instantiationService.createInstance(ToggleCompositePinnedAction, viewlet, this.compositeBar));
|
||||
actions.push(new Separator());
|
||||
actions.push(this.instantiationService.createInstance(ToggleActivityBarVisibilityAction, ToggleActivityBarVisibilityAction.ID, nls.localize('hideActivitBar', "Hide Activity Bar")));
|
||||
|
||||
this.contextMenuService.showContextMenu({
|
||||
getAnchor: () => { return { x: event.posx + 1, y: event.posy }; },
|
||||
getAnchor: () => { return { x: event.posx, y: event.posy }; },
|
||||
getActions: () => TPromise.as(actions),
|
||||
onHide: () => dispose(actions)
|
||||
});
|
||||
}
|
||||
|
||||
private createViewletSwitcher(div: Builder): void {
|
||||
this.viewletSwitcherBar = new ActionBar(div, {
|
||||
actionItemProvider: (action: Action) => action instanceof ViewletOverflowActivityAction ? this.viewletOverflowActionItem : this.viewletIdToActionItems[action.id],
|
||||
orientation: ActionsOrientation.VERTICAL,
|
||||
ariaLabel: nls.localize('activityBarAriaLabel', "Active View Switcher"),
|
||||
animated: false
|
||||
});
|
||||
|
||||
this.updateViewletSwitcher();
|
||||
|
||||
// Update viewlet switcher when external viewlets become ready
|
||||
this.extensionService.onReady().then(() => this.updateViewletSwitcher());
|
||||
}
|
||||
|
||||
private createGlobalActivityActionBar(container: HTMLElement): void {
|
||||
const activityRegistry = Registry.as<IGlobalActivityRegistry>(GlobalActivityExtensions);
|
||||
const descriptors = activityRegistry.getActivities();
|
||||
@@ -279,7 +181,7 @@ export class ActivitybarPart extends Part implements IActivityBarService {
|
||||
.map(a => new GlobalActivityAction(a));
|
||||
|
||||
this.globalActionBar = new ActionBar(container, {
|
||||
actionItemProvider: a => this.instantiationService.createInstance(GlobalActivityActionItem, a),
|
||||
actionItemProvider: a => this.instantiationService.createInstance(GlobalActivityActionItem, a, ActivitybarPart.COLORS),
|
||||
orientation: ActionsOrientation.VERTICAL,
|
||||
ariaLabel: nls.localize('globalActions', "Global Actions"),
|
||||
animated: false
|
||||
@@ -291,234 +193,8 @@ export class ActivitybarPart extends Part implements IActivityBarService {
|
||||
});
|
||||
}
|
||||
|
||||
private updateViewletSwitcher() {
|
||||
if (!this.viewletSwitcherBar) {
|
||||
return; // We have not been rendered yet so there is nothing to update.
|
||||
}
|
||||
|
||||
let viewletsToShow = this.getPinnedViewlets();
|
||||
|
||||
// Always show the active viewlet even if it is marked to be hidden
|
||||
const activeViewlet = this.viewletService.getActiveViewlet();
|
||||
if (activeViewlet && !viewletsToShow.some(viewlet => viewlet.id === activeViewlet.getId())) {
|
||||
this.activeUnpinnedViewlet = this.viewletService.getViewlet(activeViewlet.getId());
|
||||
viewletsToShow.push(this.activeUnpinnedViewlet);
|
||||
} else {
|
||||
this.activeUnpinnedViewlet = void 0;
|
||||
}
|
||||
|
||||
// Ensure we are not showing more viewlets than we have height for
|
||||
let overflows = false;
|
||||
if (this.dimension) {
|
||||
let availableHeight = this.dimension.height;
|
||||
if (this.globalActionBar) {
|
||||
availableHeight -= (this.globalActionBar.items.length * ActivitybarPart.ACTIVITY_ACTION_HEIGHT); // adjust for global actions showing
|
||||
}
|
||||
|
||||
const maxVisible = Math.floor(availableHeight / ActivitybarPart.ACTIVITY_ACTION_HEIGHT);
|
||||
overflows = viewletsToShow.length > maxVisible;
|
||||
|
||||
if (overflows) {
|
||||
viewletsToShow = viewletsToShow.slice(0, maxVisible - 1 /* make room for overflow action */);
|
||||
}
|
||||
}
|
||||
|
||||
const visibleViewlets = Object.keys(this.viewletIdToActions);
|
||||
const visibleViewletsChange = !arrays.equals(viewletsToShow.map(viewlet => viewlet.id), visibleViewlets);
|
||||
|
||||
// Pull out overflow action if there is a viewlet change so that we can add it to the end later
|
||||
if (this.viewletOverflowAction && visibleViewletsChange) {
|
||||
this.viewletSwitcherBar.pull(this.viewletSwitcherBar.length() - 1);
|
||||
|
||||
this.viewletOverflowAction.dispose();
|
||||
this.viewletOverflowAction = null;
|
||||
|
||||
this.viewletOverflowActionItem.dispose();
|
||||
this.viewletOverflowActionItem = null;
|
||||
}
|
||||
|
||||
// Pull out viewlets that overflow or got hidden
|
||||
const viewletIdsToShow = viewletsToShow.map(v => v.id);
|
||||
visibleViewlets.forEach(viewletId => {
|
||||
if (viewletIdsToShow.indexOf(viewletId) === -1) {
|
||||
this.pullViewlet(viewletId);
|
||||
}
|
||||
});
|
||||
|
||||
// Built actions for viewlets to show
|
||||
const newViewletsToShow = viewletsToShow
|
||||
.filter(viewlet => !this.viewletIdToActions[viewlet.id])
|
||||
.map(viewlet => this.toAction(viewlet));
|
||||
|
||||
// Update when we have new viewlets to show
|
||||
if (newViewletsToShow.length) {
|
||||
|
||||
// Add to viewlet switcher
|
||||
this.viewletSwitcherBar.push(newViewletsToShow, { label: true, icon: true });
|
||||
|
||||
// Make sure to activate the active one
|
||||
const activeViewlet = this.viewletService.getActiveViewlet();
|
||||
if (activeViewlet) {
|
||||
const activeViewletEntry = this.viewletIdToActions[activeViewlet.getId()];
|
||||
if (activeViewletEntry) {
|
||||
activeViewletEntry.activate();
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure to restore activity
|
||||
Object.keys(this.viewletIdToActions).forEach(viewletId => {
|
||||
this.updateActivity(viewletId);
|
||||
});
|
||||
}
|
||||
|
||||
// Add overflow action as needed
|
||||
if (visibleViewletsChange && overflows) {
|
||||
this.viewletOverflowAction = this.instantiationService.createInstance(ViewletOverflowActivityAction, () => this.viewletOverflowActionItem.showMenu());
|
||||
this.viewletOverflowActionItem = this.instantiationService.createInstance(ViewletOverflowActivityActionItem, this.viewletOverflowAction, () => this.getOverflowingViewlets(), (viewlet: ViewletDescriptor) => this.viewletIdToActivityStack[viewlet.id] && this.viewletIdToActivityStack[viewlet.id][0].badge);
|
||||
|
||||
this.viewletSwitcherBar.push(this.viewletOverflowAction, { label: true, icon: true });
|
||||
}
|
||||
}
|
||||
|
||||
private getOverflowingViewlets(): ViewletDescriptor[] {
|
||||
const viewlets = this.getPinnedViewlets();
|
||||
if (this.activeUnpinnedViewlet) {
|
||||
viewlets.push(this.activeUnpinnedViewlet);
|
||||
}
|
||||
const visibleViewlets = Object.keys(this.viewletIdToActions);
|
||||
|
||||
return viewlets.filter(viewlet => visibleViewlets.indexOf(viewlet.id) === -1);
|
||||
}
|
||||
|
||||
private getVisibleViewlets(): ViewletDescriptor[] {
|
||||
const viewlets = this.viewletService.getViewlets();
|
||||
const visibleViewlets = Object.keys(this.viewletIdToActions);
|
||||
|
||||
return viewlets.filter(viewlet => visibleViewlets.indexOf(viewlet.id) >= 0);
|
||||
}
|
||||
|
||||
private getPinnedViewlets(): ViewletDescriptor[] {
|
||||
return this.pinnedViewlets.map(viewletId => this.viewletService.getViewlet(viewletId)).filter(v => !!v); // ensure to remove those that might no longer exist
|
||||
}
|
||||
|
||||
private pullViewlet(viewletId: string): void {
|
||||
const index = Object.keys(this.viewletIdToActions).indexOf(viewletId);
|
||||
if (index >= 0) {
|
||||
this.viewletSwitcherBar.pull(index);
|
||||
|
||||
const action = this.viewletIdToActions[viewletId];
|
||||
action.dispose();
|
||||
delete this.viewletIdToActions[viewletId];
|
||||
|
||||
const actionItem = this.viewletIdToActionItems[action.id];
|
||||
actionItem.dispose();
|
||||
delete this.viewletIdToActionItems[action.id];
|
||||
}
|
||||
}
|
||||
|
||||
private toAction(viewlet: ViewletDescriptor): ActivityAction {
|
||||
const action = this.instantiationService.createInstance(ViewletActivityAction, viewlet);
|
||||
|
||||
this.viewletIdToActionItems[action.id] = this.instantiationService.createInstance(ViewletActionItem, action);
|
||||
this.viewletIdToActions[viewlet.id] = action;
|
||||
|
||||
return action;
|
||||
}
|
||||
|
||||
public getPinned(): string[] {
|
||||
return this.pinnedViewlets;
|
||||
}
|
||||
|
||||
public unpin(viewletId: string): void {
|
||||
if (!this.isPinned(viewletId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const activeViewlet = this.viewletService.getActiveViewlet();
|
||||
const defaultViewletId = this.viewletService.getDefaultViewletId();
|
||||
const visibleViewlets = this.getVisibleViewlets();
|
||||
|
||||
let unpinPromise: TPromise<any>;
|
||||
|
||||
// Case: viewlet is not the active one or the active one is a different one
|
||||
// Solv: we do nothing
|
||||
if (!activeViewlet || activeViewlet.getId() !== viewletId) {
|
||||
unpinPromise = TPromise.as(null);
|
||||
}
|
||||
|
||||
// Case: viewlet is not the default viewlet and default viewlet is still showing
|
||||
// Solv: we open the default viewlet
|
||||
else if (defaultViewletId !== viewletId && this.isPinned(defaultViewletId)) {
|
||||
unpinPromise = this.viewletService.openViewlet(defaultViewletId, true);
|
||||
}
|
||||
|
||||
// Case: we closed the last visible viewlet
|
||||
// Solv: we hide the sidebar
|
||||
else if (visibleViewlets.length === 1) {
|
||||
unpinPromise = this.partService.setSideBarHidden(true);
|
||||
}
|
||||
|
||||
// Case: we closed the default viewlet
|
||||
// Solv: we open the next visible viewlet from top
|
||||
else {
|
||||
unpinPromise = this.viewletService.openViewlet(visibleViewlets.filter(viewlet => viewlet.id !== viewletId)[0].id, true);
|
||||
}
|
||||
|
||||
unpinPromise.then(() => {
|
||||
|
||||
// then remove from pinned and update switcher
|
||||
const index = this.pinnedViewlets.indexOf(viewletId);
|
||||
this.pinnedViewlets.splice(index, 1);
|
||||
|
||||
this.updateViewletSwitcher();
|
||||
});
|
||||
}
|
||||
|
||||
public isPinned(viewletId: string): boolean {
|
||||
return this.pinnedViewlets.indexOf(viewletId) >= 0;
|
||||
}
|
||||
|
||||
public pin(viewletId: string, update = true): void {
|
||||
if (this.isPinned(viewletId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// first open that viewlet
|
||||
this.viewletService.openViewlet(viewletId, true).then(() => {
|
||||
|
||||
// then update
|
||||
this.pinnedViewlets.push(viewletId);
|
||||
this.pinnedViewlets = arrays.distinct(this.pinnedViewlets);
|
||||
|
||||
if (update) {
|
||||
this.updateViewletSwitcher();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public move(viewletId: string, toViewletId: string): void {
|
||||
|
||||
// Make sure a moved viewlet gets pinned
|
||||
if (!this.isPinned(viewletId)) {
|
||||
this.pin(viewletId, false /* defer update, we take care of it */);
|
||||
}
|
||||
|
||||
const fromIndex = this.pinnedViewlets.indexOf(viewletId);
|
||||
const toIndex = this.pinnedViewlets.indexOf(toViewletId);
|
||||
|
||||
this.pinnedViewlets.splice(fromIndex, 1);
|
||||
this.pinnedViewlets.splice(toIndex, 0, viewletId);
|
||||
|
||||
// Clear viewlets that are impacted by the move
|
||||
const visibleViewlets = Object.keys(this.viewletIdToActions);
|
||||
for (let i = Math.min(fromIndex, toIndex); i < visibleViewlets.length; i++) {
|
||||
this.pullViewlet(visibleViewlets[i]);
|
||||
}
|
||||
|
||||
// timeout helps to prevent artifacts from showing up
|
||||
setTimeout(() => {
|
||||
this.updateViewletSwitcher();
|
||||
}, 0);
|
||||
return this.viewletService.getViewlets().map(v => v.id).filter(id => this.compositeBar.isPinned(id));;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -531,16 +207,20 @@ export class ActivitybarPart extends Part implements IActivityBarService {
|
||||
|
||||
this.dimension = sizes[1];
|
||||
|
||||
// Update switcher to handle overflow issues
|
||||
this.updateViewletSwitcher();
|
||||
let availableHeight = this.dimension.height;
|
||||
if (this.globalActionBar) {
|
||||
// adjust height for global actions showing
|
||||
availableHeight -= (this.globalActionBar.items.length * this.globalActionBar.domNode.clientHeight);
|
||||
}
|
||||
this.compositeBar.layout(new Dimension(dimension.width, availableHeight));
|
||||
|
||||
return sizes;
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
if (this.viewletSwitcherBar) {
|
||||
this.viewletSwitcherBar.dispose();
|
||||
this.viewletSwitcherBar = null;
|
||||
if (this.compositeBar) {
|
||||
this.compositeBar.dispose();
|
||||
this.compositeBar = null;
|
||||
}
|
||||
|
||||
if (this.globalActionBar) {
|
||||
@@ -552,11 +232,10 @@ export class ActivitybarPart extends Part implements IActivityBarService {
|
||||
}
|
||||
|
||||
public shutdown(): void {
|
||||
|
||||
// Persist Hidden State
|
||||
this.memento[ActivitybarPart.PINNED_VIEWLETS] = this.pinnedViewlets;
|
||||
this.compositeBar.store();
|
||||
|
||||
// Pass to super
|
||||
super.shutdown();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,10 +41,6 @@
|
||||
right: 1px;
|
||||
}
|
||||
|
||||
.monaco-workbench > .activitybar > .content .monaco-action-bar .action-label.toggle-more {
|
||||
-webkit-mask: url('ellipsis-global.svg') no-repeat 50% 50%;
|
||||
}
|
||||
|
||||
.monaco-workbench > .activitybar > .content .monaco-action-bar .badge {
|
||||
position: absolute;
|
||||
top: 5px;
|
||||
|
||||
@@ -21,4 +21,8 @@
|
||||
|
||||
.monaco-workbench > .activitybar [tabindex="0"]:focus {
|
||||
outline: 0 !important; /* activity bar indicates focus custom */
|
||||
}
|
||||
|
||||
.monaco-workbench .activitybar > .content > .composite-bar > .monaco-action-bar .action-label.toggle-more {
|
||||
-webkit-mask: url('ellipsis-global.svg') no-repeat 50% 50%;
|
||||
}
|
||||
@@ -62,8 +62,7 @@ export abstract class CompositePart<T extends Composite> extends Part {
|
||||
private lastActiveCompositeId: string;
|
||||
private instantiatedComposites: Composite[];
|
||||
private titleLabel: ICompositeTitleLabel;
|
||||
private toolBar: ToolBar;
|
||||
private compositeLoaderPromises: { [compositeId: string]: TPromise<Composite>; };
|
||||
protected toolBar: ToolBar;
|
||||
private progressBar: ProgressBar;
|
||||
private contentAreaSize: Dimension;
|
||||
private telemetryActionsListener: IDisposable;
|
||||
@@ -75,7 +74,7 @@ export abstract class CompositePart<T extends Composite> extends Part {
|
||||
private messageService: IMessageService,
|
||||
private storageService: IStorageService,
|
||||
private telemetryService: ITelemetryService,
|
||||
private contextMenuService: IContextMenuService,
|
||||
protected contextMenuService: IContextMenuService,
|
||||
protected partService: IPartService,
|
||||
private keybindingService: IKeybindingService,
|
||||
protected instantiationService: IInstantiationService,
|
||||
@@ -98,7 +97,6 @@ export abstract class CompositePart<T extends Composite> extends Part {
|
||||
this.mapProgressServiceToComposite = {};
|
||||
this.activeComposite = null;
|
||||
this.instantiatedComposites = [];
|
||||
this.compositeLoaderPromises = {};
|
||||
this.lastActiveCompositeId = storageService.get(activeCompositeSettingsKey, StorageScope.WORKSPACE, this.defaultCompositeId);
|
||||
}
|
||||
|
||||
@@ -120,7 +118,7 @@ export abstract class CompositePart<T extends Composite> extends Part {
|
||||
private doOpenComposite(id: string, focus?: boolean): TPromise<Composite> {
|
||||
|
||||
// Use a generated token to avoid race conditions from long running promises
|
||||
let currentCompositeOpenToken = defaultGenerator.nextId();
|
||||
const currentCompositeOpenToken = defaultGenerator.nextId();
|
||||
this.currentCompositeOpenToken = currentCompositeOpenToken;
|
||||
|
||||
// Hide current
|
||||
@@ -137,32 +135,31 @@ export abstract class CompositePart<T extends Composite> extends Part {
|
||||
this.updateTitle(id);
|
||||
|
||||
// Create composite
|
||||
return this.createComposite(id, true).then(composite => {
|
||||
const composite = this.createComposite(id, true);
|
||||
|
||||
// Check if another composite opened meanwhile and return in that case
|
||||
if ((this.currentCompositeOpenToken !== currentCompositeOpenToken) || (this.activeComposite && this.activeComposite.getId() !== composite.getId())) {
|
||||
return TPromise.as(null);
|
||||
// Check if another composite opened meanwhile and return in that case
|
||||
if ((this.currentCompositeOpenToken !== currentCompositeOpenToken) || (this.activeComposite && this.activeComposite.getId() !== composite.getId())) {
|
||||
return TPromise.as(null);
|
||||
}
|
||||
|
||||
// Check if composite already visible and just focus in that case
|
||||
if (this.activeComposite && this.activeComposite.getId() === composite.getId()) {
|
||||
if (focus) {
|
||||
composite.focus();
|
||||
}
|
||||
|
||||
// Check if composite already visible and just focus in that case
|
||||
if (this.activeComposite && this.activeComposite.getId() === composite.getId()) {
|
||||
if (focus) {
|
||||
composite.focus();
|
||||
}
|
||||
// Fullfill promise with composite that is being opened
|
||||
return TPromise.as(composite);
|
||||
}
|
||||
|
||||
// Fullfill promise with composite that is being opened
|
||||
return TPromise.as(composite);
|
||||
// Show Composite and Focus
|
||||
return this.showComposite(composite).then(() => {
|
||||
if (focus) {
|
||||
composite.focus();
|
||||
}
|
||||
|
||||
// Show Composite and Focus
|
||||
return this.showComposite(composite).then(() => {
|
||||
if (focus) {
|
||||
composite.focus();
|
||||
}
|
||||
|
||||
// Fullfill promise with composite that is being opened
|
||||
return composite;
|
||||
});
|
||||
// Fullfill promise with composite that is being opened
|
||||
return composite;
|
||||
});
|
||||
}).then(composite => {
|
||||
if (composite) {
|
||||
@@ -173,46 +170,31 @@ export abstract class CompositePart<T extends Composite> extends Part {
|
||||
});
|
||||
}
|
||||
|
||||
protected createComposite(id: string, isActive?: boolean): TPromise<Composite> {
|
||||
protected createComposite(id: string, isActive?: boolean): Composite {
|
||||
|
||||
// Check if composite is already created
|
||||
for (let i = 0; i < this.instantiatedComposites.length; i++) {
|
||||
if (this.instantiatedComposites[i].getId() === id) {
|
||||
return TPromise.as(this.instantiatedComposites[i]);
|
||||
return this.instantiatedComposites[i];
|
||||
}
|
||||
}
|
||||
|
||||
// Instantiate composite from registry otherwise
|
||||
let compositeDescriptor = this.registry.getComposite(id);
|
||||
const compositeDescriptor = this.registry.getComposite(id);
|
||||
if (compositeDescriptor) {
|
||||
let loaderPromise = this.compositeLoaderPromises[id];
|
||||
if (!loaderPromise) {
|
||||
let progressService = this.instantiationService.createInstance(WorkbenchProgressService, this.progressBar, compositeDescriptor.id, isActive);
|
||||
let compositeInstantiationService = this.instantiationService.createChild(new ServiceCollection([IProgressService, progressService]));
|
||||
const progressService = this.instantiationService.createInstance(WorkbenchProgressService, this.progressBar, compositeDescriptor.id, isActive);
|
||||
const compositeInstantiationService = this.instantiationService.createChild(new ServiceCollection([IProgressService, progressService]));
|
||||
|
||||
loaderPromise = compositeInstantiationService.createInstance(compositeDescriptor).then((composite: Composite) => {
|
||||
this.mapProgressServiceToComposite[composite.getId()] = progressService;
|
||||
const composite = compositeDescriptor.instantiate(compositeInstantiationService);
|
||||
this.mapProgressServiceToComposite[composite.getId()] = progressService;
|
||||
|
||||
// Remember as Instantiated
|
||||
this.instantiatedComposites.push(composite);
|
||||
// Remember as Instantiated
|
||||
this.instantiatedComposites.push(composite);
|
||||
|
||||
// Register to title area update events from the composite
|
||||
this.instantiatedCompositeListeners.push(composite.onTitleAreaUpdate(() => this.onTitleAreaUpdate(composite.getId())));
|
||||
// Register to title area update events from the composite
|
||||
this.instantiatedCompositeListeners.push(composite.onTitleAreaUpdate(() => this.onTitleAreaUpdate(composite.getId())));
|
||||
|
||||
// Remove from Promises Cache since Loaded
|
||||
delete this.compositeLoaderPromises[id];
|
||||
|
||||
return composite;
|
||||
});
|
||||
|
||||
// Report progress for slow loading composites
|
||||
progressService.showWhile(loaderPromise, this.partService.isCreated() ? 800 : 3200 /* less ugly initial startup */);
|
||||
|
||||
// Add to Promise Cache until Loaded
|
||||
this.compositeLoaderPromises[id] = loaderPromise;
|
||||
}
|
||||
|
||||
return loaderPromise;
|
||||
return composite;
|
||||
}
|
||||
|
||||
throw new Error(strings.format('Unable to find composite with id {0}', id));
|
||||
@@ -260,7 +242,7 @@ export abstract class CompositePart<T extends Composite> extends Part {
|
||||
}
|
||||
|
||||
// Report progress for slow loading composites (but only if we did not create the composites before already)
|
||||
let progressService = this.mapProgressServiceToComposite[composite.getId()];
|
||||
const progressService = this.mapProgressServiceToComposite[composite.getId()];
|
||||
if (progressService && !compositeContainer) {
|
||||
this.mapProgressServiceToComposite[composite.getId()].showWhile(createCompositePromise, this.partService.isCreated() ? 800 : 3200 /* less ugly initial startup */);
|
||||
}
|
||||
@@ -281,7 +263,7 @@ export abstract class CompositePart<T extends Composite> extends Part {
|
||||
this.toolBar.actionRunner = composite.getActionRunner();
|
||||
|
||||
// Update title with composite title if it differs from descriptor
|
||||
let descriptor = this.registry.getComposite(composite.getId());
|
||||
const descriptor = this.registry.getComposite(composite.getId());
|
||||
if (descriptor && descriptor.name !== composite.getTitle()) {
|
||||
this.updateTitle(composite.getId(), composite.getTitle());
|
||||
}
|
||||
@@ -309,6 +291,12 @@ export abstract class CompositePart<T extends Composite> extends Part {
|
||||
|
||||
// Log in telemetry
|
||||
if (this.telemetryService) {
|
||||
/* __GDPR__
|
||||
"workbenchActionExecuted" : {
|
||||
"id" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
|
||||
"from": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
|
||||
}
|
||||
*/
|
||||
this.telemetryService.publicLog('workbenchActionExecuted', { id: e.action.id, from: this.nameForTelemetry });
|
||||
}
|
||||
});
|
||||
@@ -338,7 +326,7 @@ export abstract class CompositePart<T extends Composite> extends Part {
|
||||
this.updateTitle(this.activeComposite.getId(), this.activeComposite.getTitle());
|
||||
|
||||
// Actions
|
||||
let actionsBinding = this.collectCompositeActions(this.activeComposite);
|
||||
const actionsBinding = this.collectCompositeActions(this.activeComposite);
|
||||
this.mapActionsBindingToComposite[this.activeComposite.getId()] = actionsBinding;
|
||||
actionsBinding();
|
||||
}
|
||||
@@ -350,8 +338,8 @@ export abstract class CompositePart<T extends Composite> extends Part {
|
||||
}
|
||||
|
||||
private updateTitle(compositeId: string, compositeTitle?: string): void {
|
||||
let compositeDescriptor = this.registry.getComposite(compositeId);
|
||||
if (!compositeDescriptor) {
|
||||
const compositeDescriptor = this.registry.getComposite(compositeId);
|
||||
if (!compositeDescriptor || !this.titleLabel) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -369,15 +357,15 @@ export abstract class CompositePart<T extends Composite> extends Part {
|
||||
private collectCompositeActions(composite: Composite): () => void {
|
||||
|
||||
// From Composite
|
||||
let primaryActions: IAction[] = composite.getActions().slice(0);
|
||||
let secondaryActions: IAction[] = composite.getSecondaryActions().slice(0);
|
||||
const primaryActions: IAction[] = composite.getActions().slice(0);
|
||||
const secondaryActions: IAction[] = composite.getSecondaryActions().slice(0);
|
||||
|
||||
// From Part
|
||||
primaryActions.push(...this.getActions());
|
||||
secondaryActions.push(...this.getSecondaryActions());
|
||||
|
||||
// From Contributions
|
||||
let actionBarRegistry = Registry.as<IActionBarRegistry>(Extensions.Actionbar);
|
||||
const actionBarRegistry = Registry.as<IActionBarRegistry>(Extensions.Actionbar);
|
||||
primaryActions.push(...actionBarRegistry.getActionBarActionsForContext(this.actionContributionScope, composite));
|
||||
secondaryActions.push(...actionBarRegistry.getSecondaryActionBarActionsForContext(this.actionContributionScope, composite));
|
||||
|
||||
@@ -398,10 +386,10 @@ export abstract class CompositePart<T extends Composite> extends Part {
|
||||
return TPromise.as(null); // Nothing to do
|
||||
}
|
||||
|
||||
let composite = this.activeComposite;
|
||||
const composite = this.activeComposite;
|
||||
this.activeComposite = null;
|
||||
|
||||
let compositeContainer = this.mapCompositeToCompositeContainer[composite.getId()];
|
||||
const compositeContainer = this.mapCompositeToCompositeContainer[composite.getId()];
|
||||
|
||||
// Indicate to Composite
|
||||
return composite.setVisible(false).then(() => {
|
||||
@@ -424,7 +412,7 @@ export abstract class CompositePart<T extends Composite> extends Part {
|
||||
public createTitleArea(parent: Builder): Builder {
|
||||
|
||||
// Title Area Container
|
||||
let titleArea = $(parent).div({
|
||||
const titleArea = $(parent).div({
|
||||
'class': ['composite', 'title']
|
||||
});
|
||||
|
||||
@@ -480,7 +468,7 @@ export abstract class CompositePart<T extends Composite> extends Part {
|
||||
if (this.activeComposite) {
|
||||
const contextMenuActions = this.getTitleAreaContextMenuActions();
|
||||
if (contextMenuActions.length) {
|
||||
let anchor: { x: number, y: number } = { x: event.posx, y: event.posy };
|
||||
const anchor: { x: number, y: number } = { x: event.posx, y: event.posy };
|
||||
this.contextMenuService.showContextMenu({
|
||||
getAnchor: () => anchor,
|
||||
getActions: () => TPromise.as(contextMenuActions),
|
||||
@@ -506,7 +494,7 @@ export abstract class CompositePart<T extends Composite> extends Part {
|
||||
|
||||
// Check Registry
|
||||
if (!actionItem) {
|
||||
let actionBarRegistry = Registry.as<IActionBarRegistry>(Extensions.Actionbar);
|
||||
const actionBarRegistry = Registry.as<IActionBarRegistry>(Extensions.Actionbar);
|
||||
actionItem = actionBarRegistry.getActionItemForContext(this.actionContributionScope, ToolBarContext, action);
|
||||
}
|
||||
|
||||
@@ -542,7 +530,7 @@ export abstract class CompositePart<T extends Composite> extends Part {
|
||||
public layout(dimension: Dimension): Dimension[] {
|
||||
|
||||
// Pass to super
|
||||
let sizes = super.layout(dimension);
|
||||
const sizes = super.layout(dimension);
|
||||
|
||||
// Pass Contentsize to composite
|
||||
this.contentAreaSize = sizes[1];
|
||||
|
||||
459
src/vs/workbench/browser/parts/compositebar/compositeBar.ts
Normal file
@@ -0,0 +1,459 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import nls = require('vs/nls');
|
||||
import { Action } from 'vs/base/common/actions';
|
||||
import { illegalArgument } from 'vs/base/common/errors';
|
||||
import * as dom from 'vs/base/browser/dom';
|
||||
import * as arrays from 'vs/base/common/arrays';
|
||||
import { Dimension } from 'vs/base/browser/builder';
|
||||
import { dispose, IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { IBadge } from 'vs/workbench/services/activity/common/activity';
|
||||
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { ActionBar, IActionItem, ActionsOrientation } from 'vs/base/browser/ui/actionbar/actionbar';
|
||||
import Event, { Emitter } from 'vs/base/common/event';
|
||||
import { CompositeActionItem, CompositeOverflowActivityAction, ICompositeActivity, CompositeOverflowActivityActionItem, ActivityAction, ICompositeBar, ICompositeBarColors } from 'vs/workbench/browser/parts/compositebar/compositeBarActions';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
|
||||
export interface ICompositeBarOptions {
|
||||
icon: boolean;
|
||||
storageId: string;
|
||||
orientation: ActionsOrientation;
|
||||
composites: { id: string, name: string }[];
|
||||
colors: ICompositeBarColors;
|
||||
overflowActionSize: number;
|
||||
getActivityAction: (compositeId: string) => ActivityAction;
|
||||
getCompositePinnedAction: (compositeId: string) => Action;
|
||||
getOnCompositeClickAction: (compositeId: string) => Action;
|
||||
openComposite: (compositeId: string) => TPromise<any>;
|
||||
getDefaultCompositeId: () => string;
|
||||
hidePart: () => TPromise<any>;
|
||||
}
|
||||
|
||||
export class CompositeBar implements ICompositeBar {
|
||||
|
||||
private _onDidContextMenu: Emitter<MouseEvent>;
|
||||
|
||||
private dimension: Dimension;
|
||||
private toDispose: IDisposable[];
|
||||
|
||||
private compositeSwitcherBar: ActionBar;
|
||||
private compositeOverflowAction: CompositeOverflowActivityAction;
|
||||
private compositeOverflowActionItem: CompositeOverflowActivityActionItem;
|
||||
|
||||
private compositeIdToActions: { [compositeId: string]: ActivityAction; };
|
||||
private compositeIdToActionItems: { [compositeId: string]: IActionItem; };
|
||||
private compositeIdToActivityStack: { [compositeId: string]: ICompositeActivity[]; };
|
||||
private compositeSizeInBar: Map<string, number>;
|
||||
|
||||
private pinnedComposites: string[];
|
||||
private activeCompositeId: string;
|
||||
private activeUnpinnedCompositeId: string;
|
||||
|
||||
constructor(
|
||||
private options: ICompositeBarOptions,
|
||||
@IInstantiationService private instantiationService: IInstantiationService,
|
||||
@IStorageService private storageService: IStorageService,
|
||||
) {
|
||||
this.toDispose = [];
|
||||
this.compositeIdToActionItems = Object.create(null);
|
||||
this.compositeIdToActions = Object.create(null);
|
||||
this.compositeIdToActivityStack = Object.create(null);
|
||||
this.compositeSizeInBar = new Map<string, number>();
|
||||
|
||||
this._onDidContextMenu = new Emitter<MouseEvent>();
|
||||
|
||||
const pinnedComposites = JSON.parse(this.storageService.get(this.options.storageId, StorageScope.GLOBAL, null)) as string[];
|
||||
if (pinnedComposites) {
|
||||
this.pinnedComposites = pinnedComposites;
|
||||
} else {
|
||||
this.pinnedComposites = this.options.composites.map(c => c.id);
|
||||
}
|
||||
}
|
||||
|
||||
public get onDidContextMenu(): Event<MouseEvent> {
|
||||
return this._onDidContextMenu.event;
|
||||
}
|
||||
|
||||
public activateComposite(id: string): void {
|
||||
if (this.compositeIdToActions[id]) {
|
||||
this.compositeIdToActions[id].activate();
|
||||
}
|
||||
this.activeCompositeId = id;
|
||||
|
||||
const activeUnpinnedCompositeShouldClose = this.activeUnpinnedCompositeId && this.activeUnpinnedCompositeId !== id;
|
||||
const activeUnpinnedCompositeShouldShow = !this.pinnedComposites.some(pid => pid === id);
|
||||
if (activeUnpinnedCompositeShouldShow || activeUnpinnedCompositeShouldClose) {
|
||||
this.updateCompositeSwitcher();
|
||||
}
|
||||
}
|
||||
|
||||
public deactivateComposite(id: string): void {
|
||||
if (this.compositeIdToActions[id]) {
|
||||
this.compositeIdToActions[id].deactivate();
|
||||
}
|
||||
}
|
||||
|
||||
public showActivity(compositeId: string, badge: IBadge, clazz?: string): IDisposable {
|
||||
if (!badge) {
|
||||
throw illegalArgument('badge');
|
||||
}
|
||||
|
||||
const activity = <ICompositeActivity>{ badge, clazz };
|
||||
const stack = this.compositeIdToActivityStack[compositeId] || (this.compositeIdToActivityStack[compositeId] = []);
|
||||
stack.unshift(activity);
|
||||
|
||||
this.updateActivity(compositeId);
|
||||
|
||||
return {
|
||||
dispose: () => {
|
||||
const stack = this.compositeIdToActivityStack[compositeId];
|
||||
if (!stack) {
|
||||
return;
|
||||
}
|
||||
|
||||
const idx = stack.indexOf(activity);
|
||||
if (idx < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
stack.splice(idx, 1);
|
||||
if (stack.length === 0) {
|
||||
delete this.compositeIdToActivityStack[compositeId];
|
||||
}
|
||||
|
||||
this.updateActivity(compositeId);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private updateActivity(compositeId: string) {
|
||||
const action = this.compositeIdToActions[compositeId];
|
||||
if (!action) {
|
||||
return;
|
||||
}
|
||||
|
||||
const stack = this.compositeIdToActivityStack[compositeId];
|
||||
|
||||
// reset
|
||||
if (!stack || !stack.length) {
|
||||
action.setBadge(undefined);
|
||||
}
|
||||
|
||||
// update
|
||||
else {
|
||||
const [{ badge, clazz }] = stack;
|
||||
action.setBadge(badge);
|
||||
if (clazz) {
|
||||
action.class = clazz;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public create(parent: HTMLElement): HTMLElement {
|
||||
const actionBarDiv = parent.appendChild(dom.$('.composite-bar'));
|
||||
this.compositeSwitcherBar = new ActionBar(actionBarDiv, {
|
||||
actionItemProvider: (action: Action) => action instanceof CompositeOverflowActivityAction ? this.compositeOverflowActionItem : this.compositeIdToActionItems[action.id],
|
||||
orientation: this.options.orientation,
|
||||
ariaLabel: nls.localize('activityBarAriaLabel', "Active View Switcher"),
|
||||
animated: false,
|
||||
});
|
||||
|
||||
// Contextmenu for composites
|
||||
this.toDispose.push(dom.addDisposableListener(parent, dom.EventType.CONTEXT_MENU, (e: MouseEvent) => {
|
||||
dom.EventHelper.stop(e, true);
|
||||
this._onDidContextMenu.fire(e);
|
||||
}));
|
||||
|
||||
// Allow to drop at the end to move composites to the end
|
||||
this.toDispose.push(dom.addDisposableListener(parent, dom.EventType.DROP, (e: DragEvent) => {
|
||||
const draggedCompositeId = CompositeActionItem.getDraggedCompositeId();
|
||||
if (draggedCompositeId) {
|
||||
dom.EventHelper.stop(e, true);
|
||||
CompositeActionItem.clearDraggedComposite();
|
||||
|
||||
const targetId = this.pinnedComposites[this.pinnedComposites.length - 1];
|
||||
if (targetId !== draggedCompositeId) {
|
||||
this.move(draggedCompositeId, this.pinnedComposites[this.pinnedComposites.length - 1]);
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
return actionBarDiv;
|
||||
}
|
||||
|
||||
public getAction(compositeId): ActivityAction {
|
||||
return this.compositeIdToActions[compositeId];
|
||||
}
|
||||
|
||||
private updateCompositeSwitcher(): void {
|
||||
if (!this.compositeSwitcherBar || !this.dimension) {
|
||||
return; // We have not been rendered yet so there is nothing to update.
|
||||
}
|
||||
|
||||
let compositesToShow = this.pinnedComposites;
|
||||
|
||||
// Always show the active composite even if it is marked to be hidden
|
||||
if (this.activeCompositeId && !compositesToShow.some(id => id === this.activeCompositeId)) {
|
||||
this.activeUnpinnedCompositeId = this.activeCompositeId;
|
||||
compositesToShow = compositesToShow.concat(this.activeUnpinnedCompositeId);
|
||||
} else {
|
||||
this.activeUnpinnedCompositeId = void 0;
|
||||
}
|
||||
|
||||
// Ensure we are not showing more composites than we have height for
|
||||
let overflows = false;
|
||||
let maxVisible = compositesToShow.length;
|
||||
let size = 0;
|
||||
const limit = this.options.orientation === ActionsOrientation.VERTICAL ? this.dimension.height : this.dimension.width;
|
||||
for (let i = 0; i < compositesToShow.length && size <= limit; i++) {
|
||||
size += this.compositeSizeInBar.get(compositesToShow[i]);
|
||||
if (size > limit) {
|
||||
maxVisible = i;
|
||||
}
|
||||
}
|
||||
overflows = compositesToShow.length > maxVisible;
|
||||
|
||||
if (overflows) {
|
||||
size -= this.compositeSizeInBar.get(compositesToShow[maxVisible]);
|
||||
compositesToShow = compositesToShow.slice(0, maxVisible);
|
||||
}
|
||||
// Check if we need to make extra room for the overflow action
|
||||
if (overflows && (size + this.options.overflowActionSize > limit)) {
|
||||
compositesToShow.pop();
|
||||
}
|
||||
if (this.activeCompositeId && compositesToShow.length && compositesToShow.indexOf(this.activeCompositeId) === -1) {
|
||||
compositesToShow.pop();
|
||||
compositesToShow.push(this.activeCompositeId);
|
||||
}
|
||||
|
||||
const visibleComposites = Object.keys(this.compositeIdToActions);
|
||||
const visibleCompositesChange = !arrays.equals(compositesToShow, visibleComposites);
|
||||
|
||||
// Pull out overflow action if there is a composite change so that we can add it to the end later
|
||||
if (this.compositeOverflowAction && visibleCompositesChange) {
|
||||
this.compositeSwitcherBar.pull(this.compositeSwitcherBar.length() - 1);
|
||||
|
||||
this.compositeOverflowAction.dispose();
|
||||
this.compositeOverflowAction = null;
|
||||
|
||||
this.compositeOverflowActionItem.dispose();
|
||||
this.compositeOverflowActionItem = null;
|
||||
}
|
||||
|
||||
// Pull out composites that overflow, got hidden or changed position
|
||||
visibleComposites.forEach((compositeId, index) => {
|
||||
if (compositesToShow.indexOf(compositeId) !== index) {
|
||||
this.pullComposite(compositeId);
|
||||
}
|
||||
});
|
||||
|
||||
// Built actions for composites to show
|
||||
const newCompositesToShow = compositesToShow
|
||||
.filter(compositeId => !this.compositeIdToActions[compositeId])
|
||||
.map(compositeId => this.toAction(compositeId));
|
||||
|
||||
// Update when we have new composites to show
|
||||
if (newCompositesToShow.length) {
|
||||
|
||||
// Add to composite switcher
|
||||
this.compositeSwitcherBar.push(newCompositesToShow, { label: true, icon: this.options.icon });
|
||||
|
||||
// Make sure to activate the active one
|
||||
if (this.activeCompositeId) {
|
||||
const activeCompositeEntry = this.compositeIdToActions[this.activeCompositeId];
|
||||
if (activeCompositeEntry) {
|
||||
activeCompositeEntry.activate();
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure to restore activity
|
||||
Object.keys(this.compositeIdToActions).forEach(compositeId => {
|
||||
this.updateActivity(compositeId);
|
||||
});
|
||||
}
|
||||
|
||||
// Add overflow action as needed
|
||||
if ((visibleCompositesChange && overflows) || this.compositeSwitcherBar.length() === 0) {
|
||||
this.compositeOverflowAction = this.instantiationService.createInstance(CompositeOverflowActivityAction, () => this.compositeOverflowActionItem.showMenu());
|
||||
this.compositeOverflowActionItem = this.instantiationService.createInstance(
|
||||
CompositeOverflowActivityActionItem,
|
||||
this.compositeOverflowAction,
|
||||
() => this.getOverflowingComposites(),
|
||||
() => this.activeCompositeId,
|
||||
(compositeId: string) => this.compositeIdToActivityStack[compositeId] && this.compositeIdToActivityStack[compositeId][0].badge,
|
||||
this.options.getOnCompositeClickAction,
|
||||
this.options.colors
|
||||
);
|
||||
|
||||
this.compositeSwitcherBar.push(this.compositeOverflowAction, { label: false, icon: true });
|
||||
}
|
||||
}
|
||||
|
||||
private getOverflowingComposites(): { id: string, name: string }[] {
|
||||
let overflowingIds = this.pinnedComposites;
|
||||
if (this.activeUnpinnedCompositeId) {
|
||||
overflowingIds = overflowingIds.concat(this.activeUnpinnedCompositeId);
|
||||
}
|
||||
const visibleComposites = Object.keys(this.compositeIdToActions);
|
||||
|
||||
overflowingIds = overflowingIds.filter(compositeId => visibleComposites.indexOf(compositeId) === -1);
|
||||
return this.options.composites.filter(c => overflowingIds.indexOf(c.id) !== -1);
|
||||
}
|
||||
|
||||
private getVisibleComposites(): string[] {
|
||||
return Object.keys(this.compositeIdToActions);
|
||||
}
|
||||
|
||||
private pullComposite(compositeId: string): void {
|
||||
const index = Object.keys(this.compositeIdToActions).indexOf(compositeId);
|
||||
if (index >= 0) {
|
||||
this.compositeSwitcherBar.pull(index);
|
||||
|
||||
const action = this.compositeIdToActions[compositeId];
|
||||
action.dispose();
|
||||
delete this.compositeIdToActions[compositeId];
|
||||
|
||||
const actionItem = this.compositeIdToActionItems[action.id];
|
||||
actionItem.dispose();
|
||||
delete this.compositeIdToActionItems[action.id];
|
||||
}
|
||||
}
|
||||
|
||||
private toAction(compositeId: string): ActivityAction {
|
||||
if (this.compositeIdToActions[compositeId]) {
|
||||
return this.compositeIdToActions[compositeId];
|
||||
}
|
||||
|
||||
const compositeActivityAction = this.options.getActivityAction(compositeId);
|
||||
const pinnedAction = this.options.getCompositePinnedAction(compositeId);
|
||||
this.compositeIdToActionItems[compositeId] = this.instantiationService.createInstance(CompositeActionItem, compositeActivityAction, pinnedAction, this.options.colors, this.options.icon, this);
|
||||
this.compositeIdToActions[compositeId] = compositeActivityAction;
|
||||
|
||||
return compositeActivityAction;
|
||||
}
|
||||
|
||||
public unpin(compositeId: string): void {
|
||||
if (!this.isPinned(compositeId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const defaultCompositeId = this.options.getDefaultCompositeId();
|
||||
const visibleComposites = this.getVisibleComposites();
|
||||
|
||||
let unpinPromise: TPromise<any>;
|
||||
|
||||
// Case: composite is not the active one or the active one is a different one
|
||||
// Solv: we do nothing
|
||||
if (!this.activeCompositeId || this.activeCompositeId !== compositeId) {
|
||||
unpinPromise = TPromise.as(null);
|
||||
}
|
||||
|
||||
// Case: composite is not the default composite and default composite is still showing
|
||||
// Solv: we open the default composite
|
||||
else if (defaultCompositeId !== compositeId && this.isPinned(defaultCompositeId)) {
|
||||
unpinPromise = this.options.openComposite(defaultCompositeId);
|
||||
}
|
||||
|
||||
// Case: we closed the last visible composite
|
||||
// Solv: we hide the part
|
||||
else if (visibleComposites.length === 1) {
|
||||
unpinPromise = this.options.hidePart();
|
||||
}
|
||||
|
||||
// Case: we closed the default composite
|
||||
// Solv: we open the next visible composite from top
|
||||
else {
|
||||
unpinPromise = this.options.openComposite(visibleComposites.filter(cid => cid !== compositeId)[0]);
|
||||
}
|
||||
|
||||
unpinPromise.then(() => {
|
||||
// then remove from pinned and update switcher
|
||||
const index = this.pinnedComposites.indexOf(compositeId);
|
||||
this.pinnedComposites.splice(index, 1);
|
||||
|
||||
this.updateCompositeSwitcher();
|
||||
});
|
||||
}
|
||||
|
||||
public isPinned(compositeId: string): boolean {
|
||||
return this.pinnedComposites.indexOf(compositeId) >= 0;
|
||||
}
|
||||
|
||||
public pin(compositeId: string, update = true): void {
|
||||
if (this.isPinned(compositeId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.options.openComposite(compositeId).then(() => {
|
||||
this.pinnedComposites.push(compositeId);
|
||||
this.pinnedComposites = arrays.distinct(this.pinnedComposites);
|
||||
|
||||
if (update) {
|
||||
this.updateCompositeSwitcher();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public move(compositeId: string, toCompositeId: string): void {
|
||||
// Make sure both composites are known to this composite bar
|
||||
if (this.options.composites.filter(c => c.id === compositeId || c.id === toCompositeId).length !== 2) {
|
||||
return;
|
||||
}
|
||||
// Make sure a moved composite gets pinned
|
||||
if (!this.isPinned(compositeId)) {
|
||||
this.pin(compositeId, false /* defer update, we take care of it */);
|
||||
}
|
||||
|
||||
const fromIndex = this.pinnedComposites.indexOf(compositeId);
|
||||
const toIndex = this.pinnedComposites.indexOf(toCompositeId);
|
||||
|
||||
this.pinnedComposites.splice(fromIndex, 1);
|
||||
this.pinnedComposites.splice(toIndex, 0, compositeId);
|
||||
|
||||
// Clear composites that are impacted by the move
|
||||
const visibleComposites = Object.keys(this.compositeIdToActions);
|
||||
for (let i = Math.min(fromIndex, toIndex); i < visibleComposites.length; i++) {
|
||||
this.pullComposite(visibleComposites[i]);
|
||||
}
|
||||
|
||||
// timeout helps to prevent artifacts from showing up
|
||||
setTimeout(() => {
|
||||
this.updateCompositeSwitcher();
|
||||
}, 0);
|
||||
}
|
||||
|
||||
public layout(dimension: Dimension): void {
|
||||
this.dimension = dimension;
|
||||
if (dimension.height === 0 || dimension.width === 0) {
|
||||
// Do not layout if not visible. Otherwise the size measurment would be computed wrongly
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.compositeSizeInBar.size === 0) {
|
||||
// Compute size of each composite by getting the size from the css renderer
|
||||
// Size is later used for overflow computation
|
||||
this.compositeSwitcherBar.clear();
|
||||
this.compositeSwitcherBar.push(this.options.composites.map(c => this.options.getActivityAction(c.id)));
|
||||
this.options.composites.map((c, index) => this.compositeSizeInBar.set(c.id, this.options.orientation === ActionsOrientation.VERTICAL
|
||||
? this.compositeSwitcherBar.getHeight(index)
|
||||
: this.compositeSwitcherBar.getWidth(index)
|
||||
));
|
||||
this.compositeSwitcherBar.clear();
|
||||
}
|
||||
this.updateCompositeSwitcher();
|
||||
}
|
||||
|
||||
public store(): void {
|
||||
this.storageService.store(this.options.storageId, JSON.stringify(this.pinnedComposites), StorageScope.GLOBAL);
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
this.toDispose = dispose(this.toDispose);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,605 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import nls = require('vs/nls');
|
||||
import { Action } from 'vs/base/common/actions';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import * as dom from 'vs/base/browser/dom';
|
||||
import { Builder, $ } from 'vs/base/browser/builder';
|
||||
import { BaseActionItem, IBaseActionItemOptions, Separator } from 'vs/base/browser/ui/actionbar/actionbar';
|
||||
import { ICommandService } from 'vs/platform/commands/common/commands';
|
||||
import { dispose } from 'vs/base/common/lifecycle';
|
||||
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
|
||||
import { IThemeService, ITheme } from 'vs/platform/theme/common/themeService';
|
||||
import { TextBadge, NumberBadge, IBadge, IconBadge, ProgressBadge } from 'vs/workbench/services/activity/common/activity';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { contrastBorder } from 'vs/platform/theme/common/colorRegistry';
|
||||
import { DelayedDragHandler } from 'vs/base/browser/dnd';
|
||||
import { IActivity } from 'vs/workbench/common/activity';
|
||||
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
||||
import Event, { Emitter } from 'vs/base/common/event';
|
||||
|
||||
export interface ICompositeActivity {
|
||||
badge: IBadge;
|
||||
clazz: string;
|
||||
}
|
||||
|
||||
export interface ICompositeBar {
|
||||
/**
|
||||
* Unpins a composite from the composite bar.
|
||||
*/
|
||||
unpin(compositeId: string): void;
|
||||
|
||||
/**
|
||||
* Pin a composite inside the composite bar.
|
||||
*/
|
||||
pin(compositeId: string): void;
|
||||
|
||||
/**
|
||||
* Find out if a composite is pinned in the composite bar.
|
||||
*/
|
||||
isPinned(compositeId: string): boolean;
|
||||
|
||||
/**
|
||||
* Reorder composite ordering by moving a composite to the location of another composite.
|
||||
*/
|
||||
move(compositeId: string, tocompositeId: string): void;
|
||||
}
|
||||
|
||||
export class ActivityAction extends Action {
|
||||
private badge: IBadge;
|
||||
private _onDidChangeBadge = new Emitter<this>();
|
||||
|
||||
constructor(private _activity: IActivity) {
|
||||
super(_activity.id, _activity.name, _activity.cssClass);
|
||||
|
||||
this.badge = null;
|
||||
}
|
||||
|
||||
public get activity(): IActivity {
|
||||
return this._activity;
|
||||
}
|
||||
|
||||
public get onDidChangeBadge(): Event<this> {
|
||||
return this._onDidChangeBadge.event;
|
||||
}
|
||||
|
||||
public activate(): void {
|
||||
if (!this.checked) {
|
||||
this._setChecked(true);
|
||||
}
|
||||
}
|
||||
|
||||
public deactivate(): void {
|
||||
if (this.checked) {
|
||||
this._setChecked(false);
|
||||
}
|
||||
}
|
||||
|
||||
public getBadge(): IBadge {
|
||||
return this.badge;
|
||||
}
|
||||
|
||||
public setBadge(badge: IBadge): void {
|
||||
this.badge = badge;
|
||||
this._onDidChangeBadge.fire(this);
|
||||
}
|
||||
}
|
||||
|
||||
export interface ICompositeBarColors {
|
||||
backgroundColor: string;
|
||||
badgeBackground: string;
|
||||
badgeForeground: string;
|
||||
dragAndDropBackground: string;
|
||||
}
|
||||
|
||||
export interface IActivityActionItemOptions extends IBaseActionItemOptions {
|
||||
icon?: boolean;
|
||||
colors: ICompositeBarColors;
|
||||
}
|
||||
|
||||
export class ActivityActionItem extends BaseActionItem {
|
||||
protected $container: Builder;
|
||||
protected $label: Builder;
|
||||
protected $badge: Builder;
|
||||
protected options: IActivityActionItemOptions;
|
||||
|
||||
private $badgeContent: Builder;
|
||||
private mouseUpTimeout: number;
|
||||
|
||||
constructor(
|
||||
action: ActivityAction,
|
||||
options: IActivityActionItemOptions,
|
||||
@IThemeService protected themeService: IThemeService
|
||||
) {
|
||||
super(null, action, options);
|
||||
|
||||
this.themeService.onThemeChange(this.onThemeChange, this, this._callOnDispose);
|
||||
action.onDidChangeBadge(this.handleBadgeChangeEvenet, this, this._callOnDispose);
|
||||
}
|
||||
|
||||
protected get activity(): IActivity {
|
||||
return (this._action as ActivityAction).activity;
|
||||
}
|
||||
|
||||
protected updateStyles(): void {
|
||||
const theme = this.themeService.getTheme();
|
||||
|
||||
// Label
|
||||
if (this.$label && this.options.icon) {
|
||||
const background = theme.getColor(this.options.colors.backgroundColor);
|
||||
|
||||
this.$label.style('background-color', background ? background.toString() : null);
|
||||
}
|
||||
|
||||
// Badge
|
||||
if (this.$badgeContent) {
|
||||
const badgeForeground = theme.getColor(this.options.colors.badgeForeground);
|
||||
const badgeBackground = theme.getColor(this.options.colors.badgeBackground);
|
||||
const contrastBorderColor = theme.getColor(contrastBorder);
|
||||
|
||||
this.$badgeContent.style('color', badgeForeground ? badgeForeground.toString() : null);
|
||||
this.$badgeContent.style('background-color', badgeBackground ? badgeBackground.toString() : null);
|
||||
|
||||
this.$badgeContent.style('border-style', contrastBorderColor ? 'solid' : null);
|
||||
this.$badgeContent.style('border-width', contrastBorderColor ? '1px' : null);
|
||||
this.$badgeContent.style('border-color', contrastBorderColor ? contrastBorderColor.toString() : null);
|
||||
}
|
||||
}
|
||||
|
||||
public render(container: HTMLElement): void {
|
||||
super.render(container);
|
||||
|
||||
// Make the container tab-able for keyboard navigation
|
||||
this.$container = $(container).attr({
|
||||
tabIndex: '0',
|
||||
role: 'button',
|
||||
title: this.activity.name
|
||||
});
|
||||
|
||||
// Try hard to prevent keyboard only focus feedback when using mouse
|
||||
this.$container.on(dom.EventType.MOUSE_DOWN, () => {
|
||||
this.$container.addClass('clicked');
|
||||
});
|
||||
|
||||
this.$container.on(dom.EventType.MOUSE_UP, () => {
|
||||
if (this.mouseUpTimeout) {
|
||||
clearTimeout(this.mouseUpTimeout);
|
||||
}
|
||||
|
||||
this.mouseUpTimeout = setTimeout(() => {
|
||||
this.$container.removeClass('clicked');
|
||||
}, 800); // delayed to prevent focus feedback from showing on mouse up
|
||||
});
|
||||
|
||||
// Label
|
||||
this.$label = $('a.action-label').appendTo(this.builder);
|
||||
if (this.activity.cssClass) {
|
||||
this.$label.addClass(this.activity.cssClass);
|
||||
}
|
||||
if (!this.options.icon) {
|
||||
this.$label.text(this.getAction().label);
|
||||
}
|
||||
|
||||
this.$badge = this.builder.clone().div({ 'class': 'badge' }, (badge: Builder) => {
|
||||
this.$badgeContent = badge.div({ 'class': 'badge-content' });
|
||||
});
|
||||
|
||||
this.$badge.hide();
|
||||
|
||||
this.updateStyles();
|
||||
}
|
||||
|
||||
private onThemeChange(theme: ITheme): void {
|
||||
this.updateStyles();
|
||||
}
|
||||
|
||||
public setBadge(badge: IBadge): void {
|
||||
this.updateBadge(badge);
|
||||
}
|
||||
|
||||
protected updateBadge(badge: IBadge): void {
|
||||
this.$badgeContent.empty();
|
||||
this.$badge.hide();
|
||||
|
||||
if (badge) {
|
||||
|
||||
// Number
|
||||
if (badge instanceof NumberBadge) {
|
||||
if (badge.number) {
|
||||
this.$badgeContent.text(badge.number > 99 ? '99+' : badge.number.toString());
|
||||
this.$badge.show();
|
||||
}
|
||||
}
|
||||
|
||||
// Text
|
||||
else if (badge instanceof TextBadge) {
|
||||
this.$badgeContent.text(badge.text);
|
||||
this.$badge.show();
|
||||
}
|
||||
|
||||
// Text
|
||||
else if (badge instanceof IconBadge) {
|
||||
this.$badge.show();
|
||||
}
|
||||
|
||||
// Progress
|
||||
else if (badge instanceof ProgressBadge) {
|
||||
this.$badge.show();
|
||||
}
|
||||
}
|
||||
|
||||
// Title
|
||||
let title: string;
|
||||
if (badge && badge.getDescription()) {
|
||||
if (this.activity.name) {
|
||||
title = nls.localize('badgeTitle', "{0} - {1}", this.activity.name, badge.getDescription());
|
||||
} else {
|
||||
title = badge.getDescription();
|
||||
}
|
||||
} else {
|
||||
title = this.activity.name;
|
||||
}
|
||||
|
||||
[this.$label, this.$badge, this.$container].forEach(b => {
|
||||
if (b) {
|
||||
b.attr('aria-label', title);
|
||||
b.title(title);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private handleBadgeChangeEvenet(): void {
|
||||
const action = this.getAction();
|
||||
if (action instanceof ActivityAction) {
|
||||
this.updateBadge(action.getBadge());
|
||||
}
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
super.dispose();
|
||||
|
||||
if (this.mouseUpTimeout) {
|
||||
clearTimeout(this.mouseUpTimeout);
|
||||
}
|
||||
|
||||
this.$badge.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
export class CompositeOverflowActivityAction extends ActivityAction {
|
||||
|
||||
constructor(
|
||||
private showMenu: () => void
|
||||
) {
|
||||
super({
|
||||
id: 'additionalComposites.action',
|
||||
name: nls.localize('additionalViews', "Additional Views"),
|
||||
cssClass: 'toggle-more'
|
||||
});
|
||||
}
|
||||
|
||||
public run(event: any): TPromise<any> {
|
||||
this.showMenu();
|
||||
|
||||
return TPromise.as(true);
|
||||
}
|
||||
}
|
||||
|
||||
export class CompositeOverflowActivityActionItem extends ActivityActionItem {
|
||||
private name: string;
|
||||
private cssClass: string;
|
||||
private actions: Action[];
|
||||
|
||||
constructor(
|
||||
action: ActivityAction,
|
||||
private getOverflowingComposites: () => { id: string, name: string }[],
|
||||
private getActiveCompositeId: () => string,
|
||||
private getBadge: (compositeId: string) => IBadge,
|
||||
private getCompositeOpenAction: (compositeId: string) => Action,
|
||||
colors: ICompositeBarColors,
|
||||
@IInstantiationService private instantiationService: IInstantiationService,
|
||||
@IContextMenuService private contextMenuService: IContextMenuService,
|
||||
@IThemeService themeService: IThemeService
|
||||
) {
|
||||
super(action, { icon: true, colors }, themeService);
|
||||
|
||||
this.cssClass = action.class;
|
||||
this.name = action.label;
|
||||
}
|
||||
|
||||
public showMenu(): void {
|
||||
if (this.actions) {
|
||||
dispose(this.actions);
|
||||
}
|
||||
|
||||
this.actions = this.getActions();
|
||||
|
||||
this.contextMenuService.showContextMenu({
|
||||
getAnchor: () => this.builder.getHTMLElement(),
|
||||
getActions: () => TPromise.as(this.actions),
|
||||
onHide: () => dispose(this.actions)
|
||||
});
|
||||
}
|
||||
|
||||
private getActions(): Action[] {
|
||||
return this.getOverflowingComposites().map(composite => {
|
||||
const action = this.getCompositeOpenAction(composite.id);
|
||||
action.radio = this.getActiveCompositeId() === action.id;
|
||||
|
||||
const badge = this.getBadge(composite.id);
|
||||
let suffix: string | number;
|
||||
if (badge instanceof NumberBadge) {
|
||||
suffix = badge.number;
|
||||
} else if (badge instanceof TextBadge) {
|
||||
suffix = badge.text;
|
||||
}
|
||||
|
||||
if (suffix) {
|
||||
action.label = nls.localize('numberBadge', "{0} ({1})", composite.name, suffix);
|
||||
} else {
|
||||
action.label = composite.name;
|
||||
}
|
||||
|
||||
return action;
|
||||
});
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
super.dispose();
|
||||
|
||||
this.actions = dispose(this.actions);
|
||||
}
|
||||
}
|
||||
|
||||
class ManageExtensionAction extends Action {
|
||||
|
||||
constructor(
|
||||
@ICommandService private commandService: ICommandService
|
||||
) {
|
||||
super('activitybar.manage.extension', nls.localize('manageExtension', "Manage Extension"));
|
||||
}
|
||||
|
||||
public run(id: string): TPromise<any> {
|
||||
return this.commandService.executeCommand('_extensions.manage', id);
|
||||
}
|
||||
}
|
||||
|
||||
export class CompositeActionItem extends ActivityActionItem {
|
||||
|
||||
private static manageExtensionAction: ManageExtensionAction;
|
||||
private static draggedCompositeId: string;
|
||||
|
||||
private compositeActivity: IActivity;
|
||||
private cssClass: string;
|
||||
|
||||
constructor(
|
||||
private compositeActivityAction: ActivityAction,
|
||||
private toggleCompositePinnedAction: Action,
|
||||
colors: ICompositeBarColors,
|
||||
icon: boolean,
|
||||
private compositeBar: ICompositeBar,
|
||||
@IContextMenuService private contextMenuService: IContextMenuService,
|
||||
@IKeybindingService private keybindingService: IKeybindingService,
|
||||
@IInstantiationService instantiationService: IInstantiationService,
|
||||
@IThemeService themeService: IThemeService
|
||||
) {
|
||||
super(compositeActivityAction, { draggable: true, colors, icon }, themeService);
|
||||
|
||||
this.cssClass = compositeActivityAction.class;
|
||||
|
||||
if (!CompositeActionItem.manageExtensionAction) {
|
||||
CompositeActionItem.manageExtensionAction = instantiationService.createInstance(ManageExtensionAction);
|
||||
}
|
||||
}
|
||||
|
||||
protected get activity(): IActivity {
|
||||
if (!this.compositeActivity) {
|
||||
let activityName: string;
|
||||
const keybinding = this.getKeybindingLabel(this.compositeActivityAction.activity.keybindingId);
|
||||
if (keybinding) {
|
||||
activityName = nls.localize('titleKeybinding', "{0} ({1})", this.compositeActivityAction.activity.name, keybinding);
|
||||
} else {
|
||||
activityName = this.compositeActivityAction.activity.name;
|
||||
}
|
||||
|
||||
this.compositeActivity = {
|
||||
id: this.compositeActivityAction.activity.id,
|
||||
cssClass: this.cssClass,
|
||||
name: activityName
|
||||
};
|
||||
}
|
||||
|
||||
return this.compositeActivity;
|
||||
}
|
||||
|
||||
private getKeybindingLabel(id: string): string {
|
||||
const kb = this.keybindingService.lookupKeybinding(id);
|
||||
if (kb) {
|
||||
return kb.getLabel();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public render(container: HTMLElement): void {
|
||||
super.render(container);
|
||||
|
||||
this.$container.on('contextmenu', e => {
|
||||
dom.EventHelper.stop(e, true);
|
||||
|
||||
this.showContextMenu(container);
|
||||
});
|
||||
|
||||
// Allow to drag
|
||||
this.$container.on(dom.EventType.DRAG_START, (e: DragEvent) => {
|
||||
e.dataTransfer.effectAllowed = 'move';
|
||||
this.setDraggedComposite(this.activity.id);
|
||||
|
||||
// Trigger the action even on drag start to prevent clicks from failing that started a drag
|
||||
if (!this.getAction().checked) {
|
||||
this.getAction().run();
|
||||
}
|
||||
});
|
||||
|
||||
// Drag enter
|
||||
let counter = 0; // see https://github.com/Microsoft/vscode/issues/14470
|
||||
this.$container.on(dom.EventType.DRAG_ENTER, (e: DragEvent) => {
|
||||
const draggedCompositeId = CompositeActionItem.getDraggedCompositeId();
|
||||
if (draggedCompositeId && draggedCompositeId !== this.activity.id) {
|
||||
counter++;
|
||||
this.updateFromDragging(container, true);
|
||||
}
|
||||
});
|
||||
|
||||
// Drag leave
|
||||
this.$container.on(dom.EventType.DRAG_LEAVE, (e: DragEvent) => {
|
||||
const draggedCompositeId = CompositeActionItem.getDraggedCompositeId();
|
||||
if (draggedCompositeId) {
|
||||
counter--;
|
||||
if (counter === 0) {
|
||||
this.updateFromDragging(container, false);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Drag end
|
||||
this.$container.on(dom.EventType.DRAG_END, (e: DragEvent) => {
|
||||
const draggedCompositeId = CompositeActionItem.getDraggedCompositeId();
|
||||
if (draggedCompositeId) {
|
||||
counter = 0;
|
||||
this.updateFromDragging(container, false);
|
||||
|
||||
CompositeActionItem.clearDraggedComposite();
|
||||
}
|
||||
});
|
||||
|
||||
// Drop
|
||||
this.$container.on(dom.EventType.DROP, (e: DragEvent) => {
|
||||
dom.EventHelper.stop(e, true);
|
||||
|
||||
const draggedCompositeId = CompositeActionItem.getDraggedCompositeId();
|
||||
if (draggedCompositeId && draggedCompositeId !== this.activity.id) {
|
||||
this.updateFromDragging(container, false);
|
||||
CompositeActionItem.clearDraggedComposite();
|
||||
|
||||
this.compositeBar.move(draggedCompositeId, this.activity.id);
|
||||
}
|
||||
});
|
||||
|
||||
// Activate on drag over to reveal targets
|
||||
[this.$badge, this.$label].forEach(b => new DelayedDragHandler(b.getHTMLElement(), () => {
|
||||
if (!CompositeActionItem.getDraggedCompositeId() && !this.getAction().checked) {
|
||||
this.getAction().run();
|
||||
}
|
||||
}));
|
||||
|
||||
this.updateStyles();
|
||||
}
|
||||
|
||||
private updateFromDragging(element: HTMLElement, isDragging: boolean): void {
|
||||
const theme = this.themeService.getTheme();
|
||||
const dragBackground = theme.getColor(this.options.colors.dragAndDropBackground);
|
||||
|
||||
element.style.backgroundColor = isDragging && dragBackground ? dragBackground.toString() : null;
|
||||
}
|
||||
|
||||
public static getDraggedCompositeId(): string {
|
||||
return CompositeActionItem.draggedCompositeId;
|
||||
}
|
||||
|
||||
private setDraggedComposite(compositeId: string): void {
|
||||
CompositeActionItem.draggedCompositeId = compositeId;
|
||||
}
|
||||
|
||||
public static clearDraggedComposite(): void {
|
||||
CompositeActionItem.draggedCompositeId = void 0;
|
||||
}
|
||||
|
||||
private showContextMenu(container: HTMLElement): void {
|
||||
const actions: Action[] = [this.toggleCompositePinnedAction];
|
||||
if ((<any>this.compositeActivityAction.activity).extensionId) {
|
||||
actions.push(new Separator());
|
||||
actions.push(CompositeActionItem.manageExtensionAction);
|
||||
}
|
||||
|
||||
const isPinned = this.compositeBar.isPinned(this.activity.id);
|
||||
if (isPinned) {
|
||||
this.toggleCompositePinnedAction.label = nls.localize('hide', "Hide");
|
||||
this.toggleCompositePinnedAction.checked = false;
|
||||
} else {
|
||||
this.toggleCompositePinnedAction.label = nls.localize('keep', "Keep");
|
||||
}
|
||||
|
||||
this.contextMenuService.showContextMenu({
|
||||
getAnchor: () => container,
|
||||
getActionsContext: () => this.activity.id,
|
||||
getActions: () => TPromise.as(actions)
|
||||
});
|
||||
}
|
||||
|
||||
public focus(): void {
|
||||
this.$container.domFocus();
|
||||
}
|
||||
|
||||
protected _updateClass(): void {
|
||||
if (this.cssClass) {
|
||||
this.$badge.removeClass(this.cssClass);
|
||||
}
|
||||
|
||||
this.cssClass = this.getAction().class;
|
||||
this.$badge.addClass(this.cssClass);
|
||||
}
|
||||
|
||||
protected _updateChecked(): void {
|
||||
if (this.getAction().checked) {
|
||||
this.$container.addClass('checked');
|
||||
} else {
|
||||
this.$container.removeClass('checked');
|
||||
}
|
||||
}
|
||||
|
||||
protected _updateEnabled(): void {
|
||||
if (this.getAction().enabled) {
|
||||
this.builder.removeClass('disabled');
|
||||
} else {
|
||||
this.builder.addClass('disabled');
|
||||
}
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
super.dispose();
|
||||
|
||||
CompositeActionItem.clearDraggedComposite();
|
||||
|
||||
this.$label.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
export class ToggleCompositePinnedAction extends Action {
|
||||
|
||||
constructor(
|
||||
private activity: IActivity,
|
||||
private compositeBar: ICompositeBar
|
||||
) {
|
||||
super('show.toggleCompositePinned', activity ? activity.name : nls.localize('toggle', "Toggle View Pinned"));
|
||||
|
||||
this.checked = this.activity && this.compositeBar.isPinned(this.activity.id);
|
||||
}
|
||||
|
||||
public run(context: string): TPromise<any> {
|
||||
const id = this.activity ? this.activity.id : context;
|
||||
|
||||
if (this.compositeBar.isPinned(id)) {
|
||||
this.compositeBar.unpin(id);
|
||||
} else {
|
||||
this.compositeBar.pin(id);
|
||||
}
|
||||
|
||||
return TPromise.as(true);
|
||||
}
|
||||
}
|
||||
@@ -5,14 +5,10 @@
|
||||
'use strict';
|
||||
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import types = require('vs/base/common/types');
|
||||
import { Builder } from 'vs/base/browser/builder';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { Panel } from 'vs/workbench/browser/panel';
|
||||
import { EditorInput, EditorOptions, IEditorDescriptor, IEditorInputFactory, IEditorRegistry, Extensions, IFileInputFactory } from 'vs/workbench/common/editor';
|
||||
import { EditorInput, EditorOptions } from 'vs/workbench/common/editor';
|
||||
import { IEditor, Position } from 'vs/platform/editor/common/editor';
|
||||
import { IInstantiationService, IConstructorSignature0 } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { SyncDescriptor, AsyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
|
||||
@@ -123,183 +119,4 @@ export abstract class BaseEditor extends Panel implements IEditor {
|
||||
// Super Dispose
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A lightweight descriptor of an editor. The descriptor is deferred so that heavy editors
|
||||
* can load lazily in the workbench.
|
||||
*/
|
||||
export class EditorDescriptor extends AsyncDescriptor<BaseEditor> implements IEditorDescriptor {
|
||||
private id: string;
|
||||
private name: string;
|
||||
|
||||
constructor(id: string, name: string, moduleId: string, ctorName: string) {
|
||||
super(moduleId, ctorName);
|
||||
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public getId(): string {
|
||||
return this.id;
|
||||
}
|
||||
|
||||
public getName(): string {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
public describes(obj: any): boolean {
|
||||
return obj instanceof BaseEditor && (<BaseEditor>obj).getId() === this.id;
|
||||
}
|
||||
}
|
||||
|
||||
const INPUT_DESCRIPTORS_PROPERTY = '__$inputDescriptors';
|
||||
|
||||
class EditorRegistry implements IEditorRegistry {
|
||||
private editors: EditorDescriptor[];
|
||||
private instantiationService: IInstantiationService;
|
||||
private fileInputFactory: IFileInputFactory;
|
||||
private editorInputFactoryConstructors: { [editorInputId: string]: IConstructorSignature0<IEditorInputFactory> } = Object.create(null);
|
||||
private editorInputFactoryInstances: { [editorInputId: string]: IEditorInputFactory } = Object.create(null);
|
||||
|
||||
constructor() {
|
||||
this.editors = [];
|
||||
}
|
||||
|
||||
public setInstantiationService(service: IInstantiationService): void {
|
||||
this.instantiationService = service;
|
||||
|
||||
for (let key in this.editorInputFactoryConstructors) {
|
||||
const element = this.editorInputFactoryConstructors[key];
|
||||
this.createEditorInputFactory(key, element);
|
||||
}
|
||||
|
||||
this.editorInputFactoryConstructors = {};
|
||||
}
|
||||
|
||||
private createEditorInputFactory(editorInputId: string, ctor: IConstructorSignature0<IEditorInputFactory>): void {
|
||||
const instance = this.instantiationService.createInstance(ctor);
|
||||
this.editorInputFactoryInstances[editorInputId] = instance;
|
||||
}
|
||||
|
||||
public registerEditor(descriptor: EditorDescriptor, editorInputDescriptor: SyncDescriptor<EditorInput>): void;
|
||||
public registerEditor(descriptor: EditorDescriptor, editorInputDescriptor: SyncDescriptor<EditorInput>[]): void;
|
||||
public registerEditor(descriptor: EditorDescriptor, editorInputDescriptor: any): void {
|
||||
|
||||
// Support both non-array and array parameter
|
||||
let inputDescriptors: SyncDescriptor<EditorInput>[] = [];
|
||||
if (!types.isArray(editorInputDescriptor)) {
|
||||
inputDescriptors.push(editorInputDescriptor);
|
||||
} else {
|
||||
inputDescriptors = editorInputDescriptor;
|
||||
}
|
||||
|
||||
// Register (Support multiple Editors per Input)
|
||||
descriptor[INPUT_DESCRIPTORS_PROPERTY] = inputDescriptors;
|
||||
this.editors.push(descriptor);
|
||||
}
|
||||
|
||||
public getEditor(input: EditorInput): EditorDescriptor {
|
||||
const findEditorDescriptors = (input: EditorInput, byInstanceOf?: boolean): EditorDescriptor[] => {
|
||||
const matchingDescriptors: EditorDescriptor[] = [];
|
||||
|
||||
for (let i = 0; i < this.editors.length; i++) {
|
||||
const editor = this.editors[i];
|
||||
const inputDescriptors = <SyncDescriptor<EditorInput>[]>editor[INPUT_DESCRIPTORS_PROPERTY];
|
||||
for (let j = 0; j < inputDescriptors.length; j++) {
|
||||
const inputClass = inputDescriptors[j].ctor;
|
||||
|
||||
// Direct check on constructor type (ignores prototype chain)
|
||||
if (!byInstanceOf && input.constructor === inputClass) {
|
||||
matchingDescriptors.push(editor);
|
||||
break;
|
||||
}
|
||||
|
||||
// Normal instanceof check
|
||||
else if (byInstanceOf && input instanceof inputClass) {
|
||||
matchingDescriptors.push(editor);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If no descriptors found, continue search using instanceof and prototype chain
|
||||
if (!byInstanceOf && matchingDescriptors.length === 0) {
|
||||
return findEditorDescriptors(input, true);
|
||||
}
|
||||
|
||||
if (byInstanceOf) {
|
||||
return matchingDescriptors;
|
||||
}
|
||||
|
||||
return matchingDescriptors;
|
||||
};
|
||||
|
||||
const descriptors = findEditorDescriptors(input);
|
||||
if (descriptors && descriptors.length > 0) {
|
||||
|
||||
// Ask the input for its preferred Editor
|
||||
const preferredEditorId = input.getPreferredEditorId(descriptors.map(d => d.getId()));
|
||||
if (preferredEditorId) {
|
||||
return this.getEditorById(preferredEditorId);
|
||||
}
|
||||
|
||||
// Otherwise, first come first serve
|
||||
return descriptors[0];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public getEditorById(editorId: string): EditorDescriptor {
|
||||
for (let i = 0; i < this.editors.length; i++) {
|
||||
const editor = this.editors[i];
|
||||
if (editor.getId() === editorId) {
|
||||
return editor;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public getEditors(): EditorDescriptor[] {
|
||||
return this.editors.slice(0);
|
||||
}
|
||||
|
||||
public setEditors(editorsToSet: EditorDescriptor[]): void {
|
||||
this.editors = editorsToSet;
|
||||
}
|
||||
|
||||
public getEditorInputs(): any[] {
|
||||
const inputClasses: any[] = [];
|
||||
for (let i = 0; i < this.editors.length; i++) {
|
||||
const editor = this.editors[i];
|
||||
const editorInputDescriptors = <SyncDescriptor<EditorInput>[]>editor[INPUT_DESCRIPTORS_PROPERTY];
|
||||
inputClasses.push(...editorInputDescriptors.map(descriptor => descriptor.ctor));
|
||||
}
|
||||
|
||||
return inputClasses;
|
||||
}
|
||||
|
||||
public registerFileInputFactory(factory: IFileInputFactory): void {
|
||||
this.fileInputFactory = factory;
|
||||
}
|
||||
|
||||
public getFileInputFactory(): IFileInputFactory {
|
||||
return this.fileInputFactory;
|
||||
}
|
||||
|
||||
public registerEditorInputFactory(editorInputId: string, ctor: IConstructorSignature0<IEditorInputFactory>): void {
|
||||
if (!this.instantiationService) {
|
||||
this.editorInputFactoryConstructors[editorInputId] = ctor;
|
||||
} else {
|
||||
this.createEditorInputFactory(editorInputId, ctor);
|
||||
}
|
||||
}
|
||||
|
||||
public getEditorInputFactory(editorInputId: string): IEditorInputFactory {
|
||||
return this.editorInputFactoryInstances[editorInputId];
|
||||
}
|
||||
}
|
||||
|
||||
Registry.add(Extensions.Editors, new EditorRegistry());
|
||||
}
|
||||
@@ -64,48 +64,46 @@ export abstract class BaseBinaryResourceEditor extends BaseEditor {
|
||||
}
|
||||
|
||||
public setInput(input: EditorInput, options?: EditorOptions): TPromise<void> {
|
||||
const oldInput = this.input;
|
||||
super.setInput(input, options);
|
||||
|
||||
// Detect options
|
||||
// Return early for same input unless we force to open
|
||||
const forceOpen = options && options.forceOpen;
|
||||
|
||||
// Same Input
|
||||
if (!forceOpen && input.matches(oldInput)) {
|
||||
if (!forceOpen && input.matches(this.input)) {
|
||||
return TPromise.as<void>(null);
|
||||
}
|
||||
|
||||
// Different Input (Reload)
|
||||
return input.resolve(true).then((resolvedModel: EditorModel) => {
|
||||
// Otherwise set input and resolve
|
||||
return super.setInput(input, options).then(() => {
|
||||
return input.resolve(true).then((resolvedModel: EditorModel) => {
|
||||
|
||||
// Assert Model instance
|
||||
if (!(resolvedModel instanceof BinaryEditorModel)) {
|
||||
return TPromise.wrapError<void>(new Error('Unable to open file as binary'));
|
||||
}
|
||||
// Assert Model instance
|
||||
if (!(resolvedModel instanceof BinaryEditorModel)) {
|
||||
return TPromise.wrapError<void>(new Error('Unable to open file as binary'));
|
||||
}
|
||||
|
||||
// Assert that the current input is still the one we expect. This prevents a race condition when loading takes long and another input was set meanwhile
|
||||
if (!this.input || this.input !== input) {
|
||||
return null;
|
||||
}
|
||||
// Assert that the current input is still the one we expect. This prevents a race condition when loading takes long and another input was set meanwhile
|
||||
if (!this.input || this.input !== input) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Render Input
|
||||
const model = <BinaryEditorModel>resolvedModel;
|
||||
ResourceViewer.show(
|
||||
{ name: model.getName(), resource: model.getResource(), size: model.getSize(), etag: model.getETag() },
|
||||
this.binaryContainer,
|
||||
this.scrollbar,
|
||||
(resource: URI) => {
|
||||
this.windowsService.openExternal(resource.toString()).then(didOpen => {
|
||||
if (!didOpen) {
|
||||
return this.windowsService.showItemInFolder(resource.fsPath);
|
||||
}
|
||||
// Render Input
|
||||
const model = <BinaryEditorModel>resolvedModel;
|
||||
ResourceViewer.show(
|
||||
{ name: model.getName(), resource: model.getResource(), size: model.getSize(), etag: model.getETag() },
|
||||
this.binaryContainer,
|
||||
this.scrollbar,
|
||||
(resource: URI) => {
|
||||
this.windowsService.openExternal(resource.toString()).then(didOpen => {
|
||||
if (!didOpen) {
|
||||
return this.windowsService.showItemInFolder(resource.fsPath);
|
||||
}
|
||||
|
||||
return void 0;
|
||||
});
|
||||
},
|
||||
(meta) => this.handleMetadataChanged(meta));
|
||||
return void 0;
|
||||
});
|
||||
},
|
||||
(meta) => this.handleMetadataChanged(meta));
|
||||
|
||||
return TPromise.as<void>(null);
|
||||
return TPromise.as<void>(null);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -10,8 +10,8 @@ import URI from 'vs/base/common/uri';
|
||||
import { Action, IAction } from 'vs/base/common/actions';
|
||||
import { IEditorQuickOpenEntry, IQuickOpenRegistry, Extensions as QuickOpenExtensions, QuickOpenHandlerDescriptor } from 'vs/workbench/browser/quickopen';
|
||||
import { StatusbarItemDescriptor, StatusbarAlignment, IStatusbarRegistry, Extensions as StatusExtensions } from 'vs/workbench/browser/parts/statusbar/statusbar';
|
||||
import { EditorDescriptor } from 'vs/workbench/browser/parts/editor/baseEditor';
|
||||
import { EditorInput, IEditorRegistry, Extensions as EditorExtensions, IEditorInputFactory, SideBySideEditorInput } from 'vs/workbench/common/editor';
|
||||
import { IEditorRegistry, EditorDescriptor, Extensions as EditorExtensions } from 'vs/workbench/browser/editor';
|
||||
import { EditorInput, IEditorInputFactory, SideBySideEditorInput, IEditorInputFactoryRegistry, Extensions as EditorInputExtensions } from 'vs/workbench/common/editor';
|
||||
import { TextResourceEditor } from 'vs/workbench/browser/parts/editor/textResourceEditor';
|
||||
import { SideBySideEditor } from 'vs/workbench/browser/parts/editor/sideBySideEditor';
|
||||
import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput';
|
||||
@@ -22,15 +22,15 @@ import { TextDiffEditor } from 'vs/workbench/browser/parts/editor/textDiffEditor
|
||||
import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
|
||||
import { BinaryResourceDiffEditor } from 'vs/workbench/browser/parts/editor/binaryDiffEditor';
|
||||
import { ChangeEncodingAction, ChangeEOLAction, ChangeModeAction, EditorStatus } from 'vs/workbench/browser/parts/editor/editorStatus';
|
||||
import { IWorkbenchActionRegistry, Extensions as ActionExtensions } from 'vs/workbench/common/actionRegistry';
|
||||
import { IWorkbenchActionRegistry, Extensions as ActionExtensions } from 'vs/workbench/common/actions';
|
||||
import { Scope, IActionBarRegistry, Extensions as ActionBarExtensions, ActionBarContributor } from 'vs/workbench/browser/actions';
|
||||
import { SyncActionDescriptor } from 'vs/platform/actions/common/actions';
|
||||
import { SyncActionDescriptor, MenuRegistry, MenuId } from 'vs/platform/actions/common/actions';
|
||||
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
|
||||
import { KeyMod, KeyChord, KeyCode } from 'vs/base/common/keyCodes';
|
||||
import {
|
||||
CloseEditorsInGroupAction, CloseEditorsInOtherGroupsAction, CloseAllEditorsAction, MoveGroupLeftAction, MoveGroupRightAction, SplitEditorAction, JoinTwoGroupsAction, KeepEditorAction, CloseOtherEditorsInGroupAction, OpenToSideAction, RevertAndCloseEditorAction,
|
||||
NavigateBetweenGroupsAction, FocusActiveGroupAction, FocusFirstGroupAction, FocusSecondGroupAction, FocusThirdGroupAction, EvenGroupWidthsAction, MaximizeGroupAction, MinimizeOtherGroupsAction, FocusPreviousGroup, FocusNextGroup, ShowEditorsInGroupOneAction,
|
||||
toEditorQuickOpenEntry, CloseLeftEditorsInGroupAction, CloseRightEditorsInGroupAction, CloseUnmodifiedEditorsInGroupAction, OpenNextEditor, OpenPreviousEditor, NavigateBackwardsAction, NavigateForwardAction, ReopenClosedEditorAction, OpenPreviousRecentlyUsedEditorInGroupAction, NAVIGATE_IN_GROUP_ONE_PREFIX,
|
||||
toEditorQuickOpenEntry, CloseLeftEditorsInGroupAction, CloseRightEditorsInGroupAction, CloseUnmodifiedEditorsInGroupAction, OpenNextEditor, OpenPreviousEditor, NavigateBackwardsAction, NavigateForwardAction, NavigateLastAction, ReopenClosedEditorAction, OpenPreviousRecentlyUsedEditorInGroupAction, NAVIGATE_IN_GROUP_ONE_PREFIX,
|
||||
OpenPreviousEditorFromHistoryAction, ShowAllEditorsAction, NAVIGATE_ALL_EDITORS_GROUP_PREFIX, ClearEditorHistoryAction, ShowEditorsInGroupTwoAction, MoveEditorRightInGroupAction, OpenNextEditorInGroup, OpenPreviousEditorInGroup, OpenNextRecentlyUsedEditorAction, OpenPreviousRecentlyUsedEditorAction,
|
||||
NAVIGATE_IN_GROUP_TWO_PREFIX, ShowEditorsInGroupThreeAction, NAVIGATE_IN_GROUP_THREE_PREFIX, FocusLastEditorInStackAction, OpenNextRecentlyUsedEditorInGroupAction, MoveEditorToPreviousGroupAction, MoveEditorToNextGroupAction, MoveEditorLeftInGroupAction, ClearRecentFilesAction
|
||||
} from 'vs/workbench/browser/parts/editor/editorActions';
|
||||
@@ -39,14 +39,15 @@ import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/edi
|
||||
import { getQuickNavigateHandler, inQuickOpenContext } from 'vs/workbench/browser/parts/quickopen/quickopen';
|
||||
import { KeybindingsRegistry } from 'vs/platform/keybinding/common/keybindingsRegistry';
|
||||
import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { isMacintosh } from 'vs/base/common/platform';
|
||||
import { GroupOnePicker, GroupTwoPicker, GroupThreePicker, AllEditorsPicker } from 'vs/workbench/browser/parts/editor/editorPicker';
|
||||
|
||||
// Register String Editor
|
||||
Registry.as<IEditorRegistry>(EditorExtensions.Editors).registerEditor(
|
||||
new EditorDescriptor(
|
||||
TextResourceEditor,
|
||||
TextResourceEditor.ID,
|
||||
nls.localize('textEditor', "Text Editor"),
|
||||
'vs/workbench/browser/parts/editor/textResourceEditor',
|
||||
'TextResourceEditor'
|
||||
),
|
||||
[
|
||||
new SyncDescriptor(UntitledEditorInput),
|
||||
@@ -57,10 +58,9 @@ Registry.as<IEditorRegistry>(EditorExtensions.Editors).registerEditor(
|
||||
// Register Text Diff Editor
|
||||
Registry.as<IEditorRegistry>(EditorExtensions.Editors).registerEditor(
|
||||
new EditorDescriptor(
|
||||
TextDiffEditor,
|
||||
TextDiffEditor.ID,
|
||||
nls.localize('textDiffEditor', "Text Diff Editor"),
|
||||
'vs/workbench/browser/parts/editor/textDiffEditor',
|
||||
'TextDiffEditor'
|
||||
nls.localize('textDiffEditor', "Text Diff Editor")
|
||||
),
|
||||
[
|
||||
new SyncDescriptor(DiffEditorInput)
|
||||
@@ -70,10 +70,9 @@ Registry.as<IEditorRegistry>(EditorExtensions.Editors).registerEditor(
|
||||
// Register Binary Resource Diff Editor
|
||||
Registry.as<IEditorRegistry>(EditorExtensions.Editors).registerEditor(
|
||||
new EditorDescriptor(
|
||||
BinaryResourceDiffEditor,
|
||||
BinaryResourceDiffEditor.ID,
|
||||
nls.localize('binaryDiffEditor', "Binary Diff Editor"),
|
||||
'vs/workbench/browser/parts/editor/binaryDiffEditor',
|
||||
'BinaryResourceDiffEditor'
|
||||
nls.localize('binaryDiffEditor', "Binary Diff Editor")
|
||||
),
|
||||
[
|
||||
new SyncDescriptor(DiffEditorInput)
|
||||
@@ -82,10 +81,9 @@ Registry.as<IEditorRegistry>(EditorExtensions.Editors).registerEditor(
|
||||
|
||||
Registry.as<IEditorRegistry>(EditorExtensions.Editors).registerEditor(
|
||||
new EditorDescriptor(
|
||||
SideBySideEditor,
|
||||
SideBySideEditor.ID,
|
||||
nls.localize('sideBySideEditor', "Side by Side Editor"),
|
||||
'vs/workbench/browser/parts/editor/sideBySideEditor',
|
||||
'SideBySideEditor'
|
||||
nls.localize('sideBySideEditor', "Side by Side Editor")
|
||||
),
|
||||
[
|
||||
new SyncDescriptor(SideBySideEditorInput)
|
||||
@@ -115,7 +113,7 @@ class UntitledEditorInputFactory implements IEditorInputFactory {
|
||||
const untitledEditorInput = <UntitledEditorInput>editorInput;
|
||||
|
||||
// {{SQL CARBON EDIT}}
|
||||
if (!untitledEditorInput.getResource) {
|
||||
if (!untitledEditorInput.getResource()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -147,7 +145,7 @@ class UntitledEditorInputFactory implements IEditorInputFactory {
|
||||
}
|
||||
}
|
||||
|
||||
Registry.as<IEditorRegistry>(EditorExtensions.Editors).registerEditorInputFactory(UntitledEditorInput.ID, UntitledEditorInputFactory);
|
||||
Registry.as<IEditorInputFactoryRegistry>(EditorInputExtensions.EditorInputFactories).registerEditorInputFactory(UntitledEditorInput.ID, UntitledEditorInputFactory);
|
||||
|
||||
interface ISerializedSideBySideEditorInput {
|
||||
name: string;
|
||||
@@ -167,7 +165,7 @@ class SideBySideEditorInputFactory implements IEditorInputFactory {
|
||||
const input = <SideBySideEditorInput>editorInput;
|
||||
|
||||
if (input.details && input.master) {
|
||||
const registry = Registry.as<IEditorRegistry>(EditorExtensions.Editors);
|
||||
const registry = Registry.as<IEditorInputFactoryRegistry>(EditorInputExtensions.EditorInputFactories);
|
||||
const detailsInputFactory = registry.getEditorInputFactory(input.details.getTypeId());
|
||||
const masterInputFactory = registry.getEditorInputFactory(input.master.getTypeId());
|
||||
|
||||
@@ -194,7 +192,7 @@ class SideBySideEditorInputFactory implements IEditorInputFactory {
|
||||
public deserialize(instantiationService: IInstantiationService, serializedEditorInput: string): EditorInput {
|
||||
const deserialized: ISerializedSideBySideEditorInput = JSON.parse(serializedEditorInput);
|
||||
|
||||
const registry = Registry.as<IEditorRegistry>(EditorExtensions.Editors);
|
||||
const registry = Registry.as<IEditorInputFactoryRegistry>(EditorInputExtensions.EditorInputFactories);
|
||||
const detailsInputFactory = registry.getEditorInputFactory(deserialized.detailsTypeId);
|
||||
const masterInputFactory = registry.getEditorInputFactory(deserialized.masterTypeId);
|
||||
|
||||
@@ -211,7 +209,7 @@ class SideBySideEditorInputFactory implements IEditorInputFactory {
|
||||
}
|
||||
}
|
||||
|
||||
Registry.as<IEditorRegistry>(EditorExtensions.Editors).registerEditorInputFactory(SideBySideEditorInput.ID, SideBySideEditorInputFactory);
|
||||
Registry.as<IEditorInputFactoryRegistry>(EditorInputExtensions.EditorInputFactories).registerEditorInputFactory(SideBySideEditorInput.ID, SideBySideEditorInputFactory);
|
||||
|
||||
// Register Editor Status
|
||||
const statusBar = Registry.as<IStatusbarRegistry>(StatusExtensions.Statusbar);
|
||||
@@ -270,8 +268,8 @@ const editorPickerContext = ContextKeyExpr.and(inQuickOpenContext, ContextKeyExp
|
||||
|
||||
Registry.as<IQuickOpenRegistry>(QuickOpenExtensions.Quickopen).registerQuickOpenHandler(
|
||||
new QuickOpenHandlerDescriptor(
|
||||
'vs/workbench/browser/parts/editor/editorPicker',
|
||||
'GroupOnePicker',
|
||||
GroupOnePicker,
|
||||
GroupOnePicker.ID,
|
||||
NAVIGATE_IN_GROUP_ONE_PREFIX,
|
||||
editorPickerContextKey,
|
||||
[
|
||||
@@ -296,8 +294,8 @@ Registry.as<IQuickOpenRegistry>(QuickOpenExtensions.Quickopen).registerQuickOpen
|
||||
|
||||
Registry.as<IQuickOpenRegistry>(QuickOpenExtensions.Quickopen).registerQuickOpenHandler(
|
||||
new QuickOpenHandlerDescriptor(
|
||||
'vs/workbench/browser/parts/editor/editorPicker',
|
||||
'GroupTwoPicker',
|
||||
GroupTwoPicker,
|
||||
GroupTwoPicker.ID,
|
||||
NAVIGATE_IN_GROUP_TWO_PREFIX,
|
||||
editorPickerContextKey,
|
||||
[]
|
||||
@@ -306,8 +304,8 @@ Registry.as<IQuickOpenRegistry>(QuickOpenExtensions.Quickopen).registerQuickOpen
|
||||
|
||||
Registry.as<IQuickOpenRegistry>(QuickOpenExtensions.Quickopen).registerQuickOpenHandler(
|
||||
new QuickOpenHandlerDescriptor(
|
||||
'vs/workbench/browser/parts/editor/editorPicker',
|
||||
'GroupThreePicker',
|
||||
GroupThreePicker,
|
||||
GroupThreePicker.ID,
|
||||
NAVIGATE_IN_GROUP_THREE_PREFIX,
|
||||
editorPickerContextKey,
|
||||
[]
|
||||
@@ -316,8 +314,8 @@ Registry.as<IQuickOpenRegistry>(QuickOpenExtensions.Quickopen).registerQuickOpen
|
||||
|
||||
Registry.as<IQuickOpenRegistry>(QuickOpenExtensions.Quickopen).registerQuickOpenHandler(
|
||||
new QuickOpenHandlerDescriptor(
|
||||
'vs/workbench/browser/parts/editor/editorPicker',
|
||||
'AllEditorsPicker',
|
||||
AllEditorsPicker,
|
||||
AllEditorsPicker.ID,
|
||||
NAVIGATE_ALL_EDITORS_GROUP_PREFIX,
|
||||
editorPickerContextKey,
|
||||
[
|
||||
@@ -343,7 +341,7 @@ registry.registerWorkbenchAction(new SyncActionDescriptor(ShowEditorsInGroupThre
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(OpenNextEditor, OpenNextEditor.ID, OpenNextEditor.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.PageDown, mac: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.RightArrow, secondary: [KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.US_CLOSE_SQUARE_BRACKET] } }), 'View: Open Next Editor', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(OpenPreviousEditor, OpenPreviousEditor.ID, OpenPreviousEditor.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.PageUp, mac: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.LeftArrow, secondary: [KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.US_OPEN_SQUARE_BRACKET] } }), 'View: Open Previous Editor', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(ReopenClosedEditorAction, ReopenClosedEditorAction.ID, ReopenClosedEditorAction.LABEL, { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_T }), 'View: Reopen Closed Editor', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(ClearRecentFilesAction, ClearRecentFilesAction.ID, ClearRecentFilesAction.LABEL), 'View: Clear Recently Opened', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(ClearRecentFilesAction, ClearRecentFilesAction.ID, ClearRecentFilesAction.LABEL), 'File: Clear Recently Opened', nls.localize('file', "File"));
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(KeepEditorAction, KeepEditorAction.ID, KeepEditorAction.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.Enter) }), 'View: Keep Editor', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(CloseAllEditorsAction, CloseAllEditorsAction.ID, CloseAllEditorsAction.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.KEY_W) }), 'View: Close All Editors', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(CloseLeftEditorsInGroupAction, CloseLeftEditorsInGroupAction.ID, CloseLeftEditorsInGroupAction.LABEL), 'View: Close Editors to the Left', category);
|
||||
@@ -373,6 +371,7 @@ registry.registerWorkbenchAction(new SyncActionDescriptor(FocusPreviousGroup, Fo
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(FocusNextGroup, FocusNextGroup.ID, FocusNextGroup.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.RightArrow) }), 'View: Focus Next Group', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(NavigateForwardAction, NavigateForwardAction.ID, NavigateForwardAction.LABEL, { primary: null, win: { primary: KeyMod.Alt | KeyCode.RightArrow }, mac: { primary: KeyMod.WinCtrl | KeyMod.Shift | KeyCode.US_MINUS }, linux: { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.US_MINUS } }), 'Go Forward');
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(NavigateBackwardsAction, NavigateBackwardsAction.ID, NavigateBackwardsAction.LABEL, { primary: null, win: { primary: KeyMod.Alt | KeyCode.LeftArrow }, mac: { primary: KeyMod.WinCtrl | KeyCode.US_MINUS }, linux: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.US_MINUS } }), 'Go Back');
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(NavigateLastAction, NavigateLastAction.ID, NavigateLastAction.LABEL), 'Go Last');
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(OpenPreviousEditorFromHistoryAction, OpenPreviousEditorFromHistoryAction.ID, OpenPreviousEditorFromHistoryAction.LABEL), 'Open Previous Editor from History');
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(ClearEditorHistoryAction, ClearEditorHistoryAction.ID, ClearEditorHistoryAction.LABEL), 'Clear Editor History');
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(RevertAndCloseEditorAction, RevertAndCloseEditorAction.ID, RevertAndCloseEditorAction.LABEL), 'View: Revert and Close Editor', category);
|
||||
@@ -380,8 +379,8 @@ registry.registerWorkbenchAction(new SyncActionDescriptor(RevertAndCloseEditorAc
|
||||
// Register Editor Picker Actions including quick navigate support
|
||||
const openNextEditorKeybinding = { primary: KeyMod.CtrlCmd | KeyCode.Tab, mac: { primary: KeyMod.WinCtrl | KeyCode.Tab } };
|
||||
const openPreviousEditorKeybinding = { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.Tab, mac: { primary: KeyMod.WinCtrl | KeyMod.Shift | KeyCode.Tab } };
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(OpenNextRecentlyUsedEditorInGroupAction, OpenNextRecentlyUsedEditorInGroupAction.ID, OpenNextRecentlyUsedEditorInGroupAction.LABEL, openNextEditorKeybinding), 'Open Next Recently Used Editor in Group');
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(OpenPreviousRecentlyUsedEditorInGroupAction, OpenPreviousRecentlyUsedEditorInGroupAction.ID, OpenPreviousRecentlyUsedEditorInGroupAction.LABEL, openPreviousEditorKeybinding), 'Open Previous Recently Used Editor in Group');
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(OpenNextRecentlyUsedEditorInGroupAction, OpenNextRecentlyUsedEditorInGroupAction.ID, OpenNextRecentlyUsedEditorInGroupAction.LABEL, openNextEditorKeybinding), 'View: Open Next Recently Used Editor in Group', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(OpenPreviousRecentlyUsedEditorInGroupAction, OpenPreviousRecentlyUsedEditorInGroupAction.ID, OpenPreviousRecentlyUsedEditorInGroupAction.LABEL, openPreviousEditorKeybinding), 'View: Open Previous Recently Used Editor in Group', category);
|
||||
|
||||
const quickOpenNavigateNextInEditorPickerId = 'workbench.action.quickOpenNavigateNextInEditorPicker';
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
@@ -405,4 +404,17 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
|
||||
|
||||
// Editor Commands
|
||||
editorCommands.setup();
|
||||
editorCommands.setup();
|
||||
|
||||
// Touch Bar
|
||||
if (isMacintosh) {
|
||||
MenuRegistry.appendMenuItem(MenuId.TouchBarContext, {
|
||||
command: { id: NavigateBackwardsAction.ID, title: NavigateBackwardsAction.LABEL, iconPath: URI.parse(require.toUrl('vs/workbench/browser/parts/editor/media/back-tb.png')).fsPath },
|
||||
group: 'navigation'
|
||||
});
|
||||
|
||||
MenuRegistry.appendMenuItem(MenuId.TouchBarContext, {
|
||||
command: { id: NavigateForwardAction.ID, title: NavigateForwardAction.LABEL, iconPath: URI.parse(require.toUrl('vs/workbench/browser/parts/editor/media/forward-tb.png')).fsPath },
|
||||
group: 'navigation'
|
||||
});
|
||||
}
|
||||
@@ -9,7 +9,7 @@ import nls = require('vs/nls');
|
||||
import { Action } from 'vs/base/common/actions';
|
||||
import { mixin } from 'vs/base/common/objects';
|
||||
import { getCodeEditor } from 'vs/editor/common/services/codeEditorService';
|
||||
import { EditorInput, hasResource, TextEditorOptions, EditorOptions, IEditorIdentifier, IEditorContext, ActiveEditorMoveArguments, ActiveEditorMovePositioning, EditorCommands, ConfirmResult } from 'vs/workbench/common/editor';
|
||||
import { EditorInput, TextEditorOptions, EditorOptions, IEditorIdentifier, IEditorContext, ActiveEditorMoveArguments, ActiveEditorMovePositioning, EditorCommands, ConfirmResult } from 'vs/workbench/common/editor';
|
||||
import { QuickOpenEntryGroup } from 'vs/base/parts/quickopen/browser/quickOpenModel';
|
||||
import { EditorQuickOpenEntry, EditorQuickOpenEntryGroup, IEditorQuickOpenEntry, QuickOpenAction } from 'vs/workbench/browser/quickopen';
|
||||
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
@@ -255,16 +255,13 @@ export class FocusFirstGroupAction extends Action {
|
||||
|
||||
// Since no editor is currently opened, try to open last history entry to the target side
|
||||
const history = this.historyService.getHistory();
|
||||
for (let input of history) {
|
||||
|
||||
// For now only support to open files from history to the side
|
||||
if (history.length > 0) {
|
||||
const input = history[0];
|
||||
if (input instanceof EditorInput) {
|
||||
if (hasResource(input, { filter: ['file', 'untitled'] })) {
|
||||
return this.editorService.openEditor(input, null, Position.ONE);
|
||||
}
|
||||
} else {
|
||||
return this.editorService.openEditor(input as IResourceInput, Position.ONE);
|
||||
return this.editorService.openEditor(input, null, Position.ONE);
|
||||
}
|
||||
|
||||
return this.editorService.openEditor(input as IResourceInput, Position.ONE);
|
||||
}
|
||||
|
||||
return TPromise.as(true);
|
||||
@@ -327,15 +324,11 @@ export abstract class BaseFocusSideGroupAction extends Action {
|
||||
else if (referenceEditor) {
|
||||
const history = this.historyService.getHistory();
|
||||
for (let input of history) {
|
||||
|
||||
// For now only support to open files from history to the side
|
||||
if (input instanceof EditorInput) {
|
||||
if (hasResource(input, { filter: ['file', 'untitled'] })) {
|
||||
return this.editorService.openEditor(input, { pinned: true }, this.getTargetEditorSide());
|
||||
}
|
||||
} else {
|
||||
return this.editorService.openEditor({ resource: (input as IResourceInput).resource, options: { pinned: true } }, this.getTargetEditorSide());
|
||||
if (input instanceof EditorInput && input.supportsSplitEditor()) {
|
||||
return this.editorService.openEditor(input, { pinned: true }, this.getTargetEditorSide());
|
||||
}
|
||||
|
||||
return this.editorService.openEditor({ resource: (input as IResourceInput).resource, options: { pinned: true } }, this.getTargetEditorSide());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1116,6 +1109,22 @@ export class NavigateBackwardsAction extends Action {
|
||||
}
|
||||
}
|
||||
|
||||
export class NavigateLastAction extends Action {
|
||||
|
||||
public static ID = 'workbench.action.navigateLast';
|
||||
public static LABEL = nls.localize('navigateLast', "Go Last");
|
||||
|
||||
constructor(id: string, label: string, @IHistoryService private historyService: IHistoryService) {
|
||||
super(id, label);
|
||||
}
|
||||
|
||||
public run(): TPromise<any> {
|
||||
this.historyService.last();
|
||||
|
||||
return TPromise.as(null);
|
||||
}
|
||||
}
|
||||
|
||||
export class ReopenClosedEditorAction extends Action {
|
||||
|
||||
public static ID = 'workbench.action.reopenClosedEditor';
|
||||
|
||||
@@ -199,7 +199,7 @@ function handleCommandDeprecations(): void {
|
||||
};
|
||||
|
||||
Object.keys(mapDeprecatedCommands).forEach(deprecatedCommandId => {
|
||||
const newCommandId = mapDeprecatedCommands[deprecatedCommandId];
|
||||
const newCommandId: string = mapDeprecatedCommands[deprecatedCommandId];
|
||||
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
id: deprecatedCommandId,
|
||||
|
||||
@@ -20,7 +20,7 @@ import { RunOnceScheduler } from 'vs/base/common/async';
|
||||
import { isMacintosh } from 'vs/base/common/platform';
|
||||
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { Position, POSITIONS } from 'vs/platform/editor/common/editor';
|
||||
import { IEditorGroupService, ITabOptions, GroupArrangement, GroupOrientation } from 'vs/workbench/services/group/common/groupService';
|
||||
import { IEditorGroupService, IEditorTabOptions, GroupArrangement, GroupOrientation } from 'vs/workbench/services/group/common/groupService';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
|
||||
@@ -73,6 +73,7 @@ export interface IEditorGroupsControl {
|
||||
getInstantiationService(position: Position): IInstantiationService;
|
||||
getProgressBar(position: Position): ProgressBar;
|
||||
updateProgress(position: Position, state: ProgressState): void;
|
||||
updateTitleAreas(refreshActive?: boolean): void;
|
||||
|
||||
layout(dimension: Dimension): void;
|
||||
layout(position: Position): void;
|
||||
@@ -115,7 +116,7 @@ export class EditorGroupsControl extends Themable implements IEditorGroupsContro
|
||||
|
||||
private layoutVertically: boolean;
|
||||
|
||||
private tabOptions: ITabOptions;
|
||||
private tabOptions: IEditorTabOptions;
|
||||
|
||||
private silos: Builder[];
|
||||
private silosSize: number[];
|
||||
@@ -225,7 +226,7 @@ export class EditorGroupsControl extends Themable implements IEditorGroupsContro
|
||||
this.extensionService.onReady().then(() => this.onExtensionsReady());
|
||||
}
|
||||
|
||||
private updateTabOptions(tabOptions: ITabOptions, refresh?: boolean): void {
|
||||
private updateTabOptions(tabOptions: IEditorTabOptions, refresh?: boolean): void {
|
||||
const tabCloseButton = this.tabOptions ? this.tabOptions.tabCloseButton : 'right';
|
||||
this.tabOptions = tabOptions;
|
||||
|
||||
@@ -443,6 +444,9 @@ export class EditorGroupsControl extends Themable implements IEditorGroupsContro
|
||||
|
||||
// Log this fact in telemetry
|
||||
if (this.telemetryService) {
|
||||
/* __GDPR__
|
||||
"workbenchEditorMaximized" : {}
|
||||
*/
|
||||
this.telemetryService.publicLog('workbenchEditorMaximized');
|
||||
}
|
||||
|
||||
@@ -2077,6 +2081,32 @@ export class EditorGroupsControl extends Themable implements IEditorGroupsContro
|
||||
return silo ? silo.child().getProperty(key) : void 0;
|
||||
}
|
||||
|
||||
public updateTitleAreas(refreshActive?: boolean): void {
|
||||
POSITIONS.forEach(position => {
|
||||
const group = this.stacks.groupAt(position);
|
||||
if (!group) {
|
||||
return;
|
||||
}
|
||||
|
||||
const titleControl = this.getTitleAreaControl(position);
|
||||
if (!titleControl) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Make sure the active group is shown in the title
|
||||
// and refresh it if we are instructed to refresh it
|
||||
if (refreshActive && group.isActive) {
|
||||
titleControl.setContext(group);
|
||||
titleControl.refresh(true);
|
||||
}
|
||||
|
||||
// Otherwise, just refresh the toolbar
|
||||
else {
|
||||
titleControl.updateEditorActionsToolbar();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public updateProgress(position: Position, state: ProgressState): void {
|
||||
const progressbar = this.getProgressBar(position);
|
||||
if (!progressbar) {
|
||||
|
||||
@@ -20,17 +20,17 @@ import { getCodeEditor } from 'vs/editor/common/services/codeEditorService';
|
||||
import { toErrorMessage } from 'vs/base/common/errorMessage';
|
||||
import { Scope as MementoScope } from 'vs/workbench/common/memento';
|
||||
import { Part } from 'vs/workbench/browser/part';
|
||||
import { BaseEditor, EditorDescriptor } from 'vs/workbench/browser/parts/editor/baseEditor';
|
||||
import { IEditorRegistry, Extensions as EditorExtensions, EditorInput, EditorOptions, ConfirmResult, IWorkbenchEditorConfiguration, IEditorDescriptor, TextEditorOptions, SideBySideEditorInput, TextCompareEditorVisible, TEXT_DIFF_EDITOR_ID } from 'vs/workbench/common/editor';
|
||||
import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor';
|
||||
import { EditorInput, EditorOptions, ConfirmResult, IWorkbenchEditorConfiguration, TextEditorOptions, SideBySideEditorInput, TextCompareEditorVisible, TEXT_DIFF_EDITOR_ID, EditorOpeningEvent, IEditorOpeningEvent } from 'vs/workbench/common/editor';
|
||||
import { EditorGroupsControl, Rochade, IEditorGroupsControl, ProgressState } from 'vs/workbench/browser/parts/editor/editorGroupsControl';
|
||||
import { WorkbenchProgressService } from 'vs/workbench/services/progress/browser/progressService';
|
||||
import { IEditorGroupService, GroupOrientation, GroupArrangement, ITabOptions, IMoveOptions } from 'vs/workbench/services/group/common/groupService';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { IEditorPart } from 'vs/workbench/services/editor/browser/editorService';
|
||||
import { IEditorGroupService, GroupOrientation, GroupArrangement, IEditorTabOptions, IMoveOptions } from 'vs/workbench/services/group/common/groupService';
|
||||
import { IConfigurationService, IConfigurationChangeEvent } from 'vs/platform/configuration/common/configuration';
|
||||
import { IEditorPart } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { IPartService } from 'vs/workbench/services/part/common/partService';
|
||||
import { Position, POSITIONS, Direction } from 'vs/platform/editor/common/editor';
|
||||
import { Position, POSITIONS, Direction, IEditor } from 'vs/platform/editor/common/editor';
|
||||
import { IStorageService } from 'vs/platform/storage/common/storage';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
|
||||
import { IMessageService, IMessageWithAction, Severity } from 'vs/platform/message/common/message';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
@@ -44,10 +44,13 @@ import { EDITOR_GROUP_BACKGROUND } from 'vs/workbench/common/theme';
|
||||
import { createCSSRule } from 'vs/base/browser/dom';
|
||||
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
import { join } from 'vs/base/common/paths';
|
||||
import { isCommonCodeEditor } from 'vs/editor/common/editorCommon';
|
||||
import { IEditorDescriptor, IEditorRegistry, Extensions as EditorExtensions } from 'vs/workbench/browser/editor';
|
||||
|
||||
// {{SQL CARBON EDIT}}
|
||||
import { convertEditorInput } from 'sql/parts/common/customInputConverter';
|
||||
|
||||
|
||||
class ProgressMonitor {
|
||||
|
||||
constructor(private _token: number, private progressPromise: TPromise<void>) { }
|
||||
@@ -94,23 +97,23 @@ export class EditorPart extends Part implements IEditorPart, IEditorGroupService
|
||||
private editorGroupsControl: IEditorGroupsControl;
|
||||
private memento: object;
|
||||
private stacks: EditorStacksModel;
|
||||
private tabOptions: ITabOptions;
|
||||
private tabOptions: IEditorTabOptions;
|
||||
private forceHideTabs: boolean;
|
||||
private doNotFireTabOptionsChanged: boolean;
|
||||
private revealIfOpen: boolean;
|
||||
|
||||
private _onEditorsChanged: Emitter<void>;
|
||||
private _onEditorsMoved: Emitter<void>;
|
||||
private _onEditorOpening: Emitter<IEditorOpeningEvent>;
|
||||
private _onEditorGroupMoved: Emitter<void>;
|
||||
private _onEditorOpenFail: Emitter<EditorInput>;
|
||||
private _onGroupOrientationChanged: Emitter<void>;
|
||||
private _onTabOptionsChanged: Emitter<ITabOptions>;
|
||||
private _onTabOptionsChanged: Emitter<IEditorTabOptions>;
|
||||
|
||||
private textCompareEditorVisible: IContextKey<boolean>;
|
||||
|
||||
// The following data structures are partitioned into array of Position as provided by Services.POSITION array
|
||||
private visibleEditors: BaseEditor[];
|
||||
private instantiatedEditors: BaseEditor[][];
|
||||
private mapEditorInstantiationPromiseToEditor: { [editorId: string]: TPromise<BaseEditor>; }[];
|
||||
private editorOpenToken: number[];
|
||||
private pendingEditorInputsToClose: EditorIdentifier[];
|
||||
private pendingEditorInputCloseTimeout: number;
|
||||
@@ -134,10 +137,11 @@ export class EditorPart extends Part implements IEditorPart, IEditorGroupService
|
||||
super(id, { hasTitle: false }, themeService);
|
||||
|
||||
this._onEditorsChanged = new Emitter<void>();
|
||||
this._onEditorsMoved = new Emitter<void>();
|
||||
this._onEditorOpening = new Emitter<IEditorOpeningEvent>();
|
||||
this._onEditorGroupMoved = new Emitter<void>();
|
||||
this._onEditorOpenFail = new Emitter<EditorInput>();
|
||||
this._onGroupOrientationChanged = new Emitter<void>();
|
||||
this._onTabOptionsChanged = new Emitter<ITabOptions>();
|
||||
this._onTabOptionsChanged = new Emitter<IEditorTabOptions>();
|
||||
|
||||
this.visibleEditors = [];
|
||||
|
||||
@@ -145,8 +149,6 @@ export class EditorPart extends Part implements IEditorPart, IEditorGroupService
|
||||
|
||||
this.instantiatedEditors = arrays.fill(POSITIONS.length, () => []);
|
||||
|
||||
this.mapEditorInstantiationPromiseToEditor = arrays.fill(POSITIONS.length, () => Object.create(null));
|
||||
|
||||
this.pendingEditorInputsToClose = [];
|
||||
this.pendingEditorInputCloseTimeout = null;
|
||||
|
||||
@@ -162,18 +164,27 @@ export class EditorPart extends Part implements IEditorPart, IEditorGroupService
|
||||
previewEditors: editorConfig.enablePreview,
|
||||
showIcons: editorConfig.showIcons,
|
||||
showTabs: editorConfig.showTabs,
|
||||
tabCloseButton: editorConfig.tabCloseButton
|
||||
tabCloseButton: editorConfig.tabCloseButton,
|
||||
labelFormat: editorConfig.labelFormat,
|
||||
};
|
||||
|
||||
this.revealIfOpen = editorConfig.revealIfOpen;
|
||||
|
||||
/* __GDPR__
|
||||
"workbenchEditorConfiguration" : {
|
||||
"${include}": [
|
||||
"${IWorkbenchEditorConfiguration}"
|
||||
]
|
||||
}
|
||||
*/
|
||||
this.telemetryService.publicLog('workbenchEditorConfiguration', editorConfig);
|
||||
} else {
|
||||
this.tabOptions = {
|
||||
previewEditors: true,
|
||||
showIcons: false,
|
||||
showTabs: true,
|
||||
tabCloseButton: 'right'
|
||||
tabCloseButton: 'right',
|
||||
labelFormat: 'default',
|
||||
};
|
||||
|
||||
this.revealIfOpen = false;
|
||||
@@ -199,40 +210,44 @@ export class EditorPart extends Part implements IEditorPart, IEditorGroupService
|
||||
this.toUnbind.push(this.stacks.onEditorClosed(event => this.onEditorClosed(event)));
|
||||
this.toUnbind.push(this.stacks.onGroupOpened(event => this.onEditorGroupOpenedOrClosed()));
|
||||
this.toUnbind.push(this.stacks.onGroupClosed(event => this.onEditorGroupOpenedOrClosed()));
|
||||
this.toUnbind.push(this.configurationService.onDidUpdateConfiguration(e => this.onConfigurationUpdated(this.configurationService.getConfiguration<IWorkbenchEditorConfiguration>())));
|
||||
this.toUnbind.push(this.configurationService.onDidChangeConfiguration(e => this.onConfigurationUpdated(e)));
|
||||
}
|
||||
|
||||
private onEditorGroupOpenedOrClosed(): void {
|
||||
this.updateStyles();
|
||||
}
|
||||
|
||||
private onConfigurationUpdated(configuration: IWorkbenchEditorConfiguration): void {
|
||||
if (configuration && configuration.workbench && configuration.workbench.editor) {
|
||||
const editorConfig = configuration.workbench.editor;
|
||||
private onConfigurationUpdated(event: IConfigurationChangeEvent): void {
|
||||
if (event.affectsConfiguration('workbench.editor')) {
|
||||
const configuration = this.configurationService.getConfiguration<IWorkbenchEditorConfiguration>();
|
||||
if (configuration && configuration.workbench && configuration.workbench.editor) {
|
||||
const editorConfig = configuration.workbench.editor;
|
||||
|
||||
// Pin all preview editors of the user chose to disable preview
|
||||
const newPreviewEditors = editorConfig.enablePreview;
|
||||
if (this.tabOptions.previewEditors !== newPreviewEditors && !newPreviewEditors) {
|
||||
this.stacks.groups.forEach(group => {
|
||||
if (group.previewEditor) {
|
||||
this.pinEditor(group, group.previewEditor);
|
||||
}
|
||||
});
|
||||
// Pin all preview editors of the user chose to disable preview
|
||||
const newPreviewEditors = editorConfig.enablePreview;
|
||||
if (this.tabOptions.previewEditors !== newPreviewEditors && !newPreviewEditors) {
|
||||
this.stacks.groups.forEach(group => {
|
||||
if (group.previewEditor) {
|
||||
this.pinEditor(group, group.previewEditor);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const oldTabOptions = objects.clone(this.tabOptions);
|
||||
this.tabOptions = {
|
||||
previewEditors: newPreviewEditors,
|
||||
showIcons: editorConfig.showIcons,
|
||||
tabCloseButton: editorConfig.tabCloseButton,
|
||||
showTabs: this.forceHideTabs ? false : editorConfig.showTabs,
|
||||
labelFormat: editorConfig.labelFormat,
|
||||
};
|
||||
|
||||
if (!this.doNotFireTabOptionsChanged && !objects.equals(oldTabOptions, this.tabOptions)) {
|
||||
this._onTabOptionsChanged.fire(this.tabOptions);
|
||||
}
|
||||
|
||||
this.revealIfOpen = editorConfig.revealIfOpen;
|
||||
}
|
||||
|
||||
const oldTabOptions = objects.clone(this.tabOptions);
|
||||
this.tabOptions = {
|
||||
previewEditors: newPreviewEditors,
|
||||
showIcons: editorConfig.showIcons,
|
||||
tabCloseButton: editorConfig.tabCloseButton,
|
||||
showTabs: this.forceHideTabs ? false : editorConfig.showTabs
|
||||
};
|
||||
|
||||
if (!this.doNotFireTabOptionsChanged && !objects.equals(oldTabOptions, this.tabOptions)) {
|
||||
this._onTabOptionsChanged.fire(this.tabOptions);
|
||||
}
|
||||
|
||||
this.revealIfOpen = editorConfig.revealIfOpen;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -248,10 +263,24 @@ export class EditorPart extends Part implements IEditorPart, IEditorGroupService
|
||||
}
|
||||
|
||||
private onEditorOpened(identifier: EditorIdentifier): void {
|
||||
/* __GDPR__
|
||||
"editorOpened" : {
|
||||
"${include}": [
|
||||
"${EditorTelemetryDescriptor}"
|
||||
]
|
||||
}
|
||||
*/
|
||||
this.telemetryService.publicLog('editorOpened', identifier.editor.getTelemetryDescriptor());
|
||||
}
|
||||
|
||||
private onEditorClosed(event: EditorCloseEvent): void {
|
||||
/* __GDPR__
|
||||
"editorClosed" : {
|
||||
"${include}": [
|
||||
"${EditorTelemetryDescriptor}"
|
||||
]
|
||||
}
|
||||
*/
|
||||
this.telemetryService.publicLog('editorClosed', event.editor.getTelemetryDescriptor());
|
||||
}
|
||||
|
||||
@@ -270,8 +299,12 @@ export class EditorPart extends Part implements IEditorPart, IEditorGroupService
|
||||
return this._onEditorsChanged.event;
|
||||
}
|
||||
|
||||
public get onEditorsMoved(): Event<void> {
|
||||
return this._onEditorsMoved.event;
|
||||
public get onEditorOpening(): Event<IEditorOpeningEvent> {
|
||||
return this._onEditorOpening.event;
|
||||
}
|
||||
|
||||
public get onEditorGroupMoved(): Event<void> {
|
||||
return this._onEditorGroupMoved.event;
|
||||
}
|
||||
|
||||
public get onEditorOpenFail(): Event<EditorInput> {
|
||||
@@ -282,39 +315,52 @@ export class EditorPart extends Part implements IEditorPart, IEditorGroupService
|
||||
return this._onGroupOrientationChanged.event;
|
||||
}
|
||||
|
||||
public get onTabOptionsChanged(): Event<ITabOptions> {
|
||||
public get onTabOptionsChanged(): Event<IEditorTabOptions> {
|
||||
return this._onTabOptionsChanged.event;
|
||||
}
|
||||
|
||||
public getTabOptions(): ITabOptions {
|
||||
public getTabOptions(): IEditorTabOptions {
|
||||
return this.tabOptions;
|
||||
}
|
||||
|
||||
public openEditor(input: EditorInput, options?: EditorOptions, sideBySide?: boolean): TPromise<BaseEditor>;
|
||||
public openEditor(input: EditorInput, options?: EditorOptions, position?: Position, ratio?: number[]): TPromise<BaseEditor>;
|
||||
public openEditor(input: EditorInput, options?: EditorOptions, arg3?: any, ratio?: number[]): TPromise<BaseEditor> {
|
||||
|
||||
// Normalize some values
|
||||
if (!options) { options = null; }
|
||||
public openEditor(input: EditorInput, options?: EditorOptions, sideBySide?: boolean): TPromise<IEditor>;
|
||||
public openEditor(input: EditorInput, options?: EditorOptions, position?: Position, ratio?: number[]): TPromise<IEditor>;
|
||||
public openEditor(input: EditorInput, options?: EditorOptions, arg3?: any, ratio?: number[]): TPromise<IEditor> {
|
||||
if (!options) {
|
||||
options = null;
|
||||
}
|
||||
|
||||
// Determine position to open editor in (one, two, three)
|
||||
const position = this.findPosition(input, options, arg3, ratio);
|
||||
|
||||
// Some conditions under which we prevent the request
|
||||
if (
|
||||
!input || // no input
|
||||
position === null || // invalid position
|
||||
Object.keys(this.mapEditorInstantiationPromiseToEditor[position]).length > 0 || // pending editor load
|
||||
!this.editorGroupsControl || // too early
|
||||
this.editorGroupsControl.isDragging() // pending editor DND
|
||||
!input || // no input
|
||||
position === null || // invalid position
|
||||
!this.editorGroupsControl || // too early
|
||||
this.editorGroupsControl.isDragging() // pending editor DND
|
||||
) {
|
||||
return TPromise.as<BaseEditor>(null);
|
||||
}
|
||||
|
||||
// Editor opening event (can be prevented and overridden)
|
||||
const event = new EditorOpeningEvent(input, options, position);
|
||||
this._onEditorOpening.fire(event);
|
||||
const prevented = event.isPrevented();
|
||||
if (prevented) {
|
||||
return prevented();
|
||||
}
|
||||
|
||||
// {{SQL CARBON EDIT}}
|
||||
// Convert input into custom type if it's one of the ones we support
|
||||
input = convertEditorInput(input, options, this.instantiationService);
|
||||
|
||||
// Open through UI
|
||||
return this.doOpenEditor(position, input, options, ratio);
|
||||
}
|
||||
|
||||
private doOpenEditor(position: Position, input: EditorInput, options: EditorOptions, ratio: number[]): TPromise<BaseEditor> {
|
||||
|
||||
// We need an editor descriptor for the input
|
||||
const descriptor = Registry.as<IEditorRegistry>(EditorExtensions.Editors).getEditor(input);
|
||||
if (!descriptor) {
|
||||
@@ -323,15 +369,14 @@ export class EditorPart extends Part implements IEditorPart, IEditorGroupService
|
||||
|
||||
// Opened to the side
|
||||
if (position !== Position.ONE) {
|
||||
/* __GDPR__
|
||||
"workbenchSideEditorOpened" : {
|
||||
"position" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
|
||||
}
|
||||
*/
|
||||
this.telemetryService.publicLog('workbenchSideEditorOpened', { position: position });
|
||||
}
|
||||
|
||||
// Open through UI
|
||||
return this.doOpenEditor(position, descriptor, input, options, ratio);
|
||||
}
|
||||
|
||||
private doOpenEditor(position: Position, descriptor: IEditorDescriptor, input: EditorInput, options: EditorOptions, ratio: number[]): TPromise<BaseEditor> {
|
||||
|
||||
// Update stacks: We do this early on before the UI is there because we want our stacks model to have
|
||||
// a consistent view of the editor world and updating it later async after the UI is there will cause
|
||||
// issues (e.g. when a closeEditor call is made that expects the openEditor call to have updated the
|
||||
@@ -339,7 +384,7 @@ export class EditorPart extends Part implements IEditorPart, IEditorGroupService
|
||||
// This can however cause a race condition where the stacks model indicates the opened editor is there
|
||||
// while the UI is not yet ready. Clients have to deal with this fact and we have to make sure that the
|
||||
// stacks model gets updated if any of the UI updating fails with an error.
|
||||
const group = this.ensureGroup(position, !options || !options.preserveFocus);
|
||||
const [group, newGroupOpened] = this.ensureGroup(position, !options || !options.preserveFocus);
|
||||
const pinned = !this.tabOptions.previewEditors || (options && (options.pinned || typeof options.index === 'number')) || input.isDirty();
|
||||
|
||||
const active = (group.count === 0) || !options || !options.inactive;
|
||||
@@ -362,23 +407,34 @@ export class EditorPart extends Part implements IEditorPart, IEditorGroupService
|
||||
}));
|
||||
|
||||
// Show editor
|
||||
return this.doShowEditor(group, descriptor, input, options, ratio, monitor).then(editor => {
|
||||
if (!editor) {
|
||||
return TPromise.as<BaseEditor>(null); // canceled or other error
|
||||
}
|
||||
const editor = this.doShowEditor(group, descriptor, input, options, ratio, monitor);
|
||||
if (!editor) {
|
||||
return TPromise.as<BaseEditor>(null); // canceled or other error
|
||||
}
|
||||
|
||||
// Set input to editor
|
||||
return this.doSetInput(group, editor, input, options, monitor);
|
||||
});
|
||||
// Set input to editor
|
||||
const inputPromise = this.doSetInput(group, editor, input, options, monitor);
|
||||
|
||||
// A new active group got opened. Since this involves updating the title area controls to show
|
||||
// the new editor and actions we trigger a direct update of title controls from here to avoid
|
||||
// some UI flickering if we rely on the event handlers that all use schedulers.
|
||||
// The reason we can trigger this now is that after the input is set to the editor group, the
|
||||
// resource context is updated and the correct number of actions will be resolved from the title
|
||||
// area.
|
||||
if (newGroupOpened && this.stacks.isActive(group)) {
|
||||
this.editorGroupsControl.updateTitleAreas(true /* refresh new active group */);
|
||||
}
|
||||
|
||||
return inputPromise;
|
||||
}
|
||||
|
||||
private doShowEditor(group: EditorGroup, descriptor: IEditorDescriptor, input: EditorInput, options: EditorOptions, ratio: number[], monitor: ProgressMonitor): TPromise<BaseEditor> {
|
||||
const position = this.stacks.positionOfGroup(group);
|
||||
private doShowEditor(group: EditorGroup, descriptor: IEditorDescriptor, input: EditorInput, options: EditorOptions, ratio: number[], monitor: ProgressMonitor): BaseEditor {
|
||||
let position = this.stacks.positionOfGroup(group);
|
||||
const editorAtPosition = this.visibleEditors[position];
|
||||
|
||||
// Return early if the currently visible editor can handle the input
|
||||
if (editorAtPosition && descriptor.describes(editorAtPosition)) {
|
||||
return TPromise.as(editorAtPosition);
|
||||
return editorAtPosition;
|
||||
}
|
||||
|
||||
// Hide active one first
|
||||
@@ -387,107 +443,77 @@ export class EditorPart extends Part implements IEditorPart, IEditorGroupService
|
||||
}
|
||||
|
||||
// Create Editor
|
||||
return this.doCreateEditor(group, descriptor, monitor).then(editor => {
|
||||
const position = this.stacks.positionOfGroup(group); // might have changed due to a rochade meanwhile
|
||||
const editor = this.doCreateEditor(group, descriptor, monitor);
|
||||
position = this.stacks.positionOfGroup(group); // might have changed due to a rochade meanwhile
|
||||
|
||||
// Make sure that the user meanwhile did not open another editor or something went wrong
|
||||
if (!editor || !this.visibleEditors[position] || editor.getId() !== this.visibleEditors[position].getId()) {
|
||||
monitor.cancel();
|
||||
// Make sure that the user meanwhile did not open another editor or something went wrong
|
||||
if (!editor || !this.visibleEditors[position] || editor.getId() !== this.visibleEditors[position].getId()) {
|
||||
monitor.cancel();
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// Show in side by side control
|
||||
this.editorGroupsControl.show(editor, position, options && options.preserveFocus, ratio);
|
||||
|
||||
// Indicate to editor that it is now visible
|
||||
editor.setVisible(true, position);
|
||||
|
||||
// Update text compare editor visible context
|
||||
this.updateTextCompareEditorVisible();
|
||||
|
||||
// Make sure the editor is layed out
|
||||
this.editorGroupsControl.layout(position);
|
||||
|
||||
return editor;
|
||||
|
||||
}, e => {
|
||||
this.messageService.show(Severity.Error, types.isString(e) ? new Error(e) : e);
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
// Show in side by side control
|
||||
this.editorGroupsControl.show(editor, position, options && options.preserveFocus, ratio);
|
||||
|
||||
// Indicate to editor that it is now visible
|
||||
editor.setVisible(true, position);
|
||||
|
||||
// Update text compare editor visible context
|
||||
this.updateTextCompareEditorVisible();
|
||||
|
||||
// Make sure the editor is layed out
|
||||
this.editorGroupsControl.layout(position);
|
||||
|
||||
return editor;
|
||||
}
|
||||
|
||||
private doCreateEditor(group: EditorGroup, descriptor: IEditorDescriptor, monitor: ProgressMonitor): TPromise<BaseEditor> {
|
||||
private doCreateEditor(group: EditorGroup, descriptor: IEditorDescriptor, monitor: ProgressMonitor): BaseEditor {
|
||||
|
||||
// Instantiate editor
|
||||
return this.doInstantiateEditor(group, descriptor).then(editor => {
|
||||
const position = this.stacks.positionOfGroup(group); // might have changed due to a rochade meanwhile
|
||||
const editor = this.doInstantiateEditor(group, descriptor);
|
||||
const position = this.stacks.positionOfGroup(group); // might have changed due to a rochade meanwhile
|
||||
|
||||
// Make sure that the user meanwhile did not open another editor
|
||||
if (monitor.token !== this.editorOpenToken[position]) {
|
||||
monitor.cancel();
|
||||
// Make sure that the user meanwhile did not open another editor
|
||||
if (monitor.token !== this.editorOpenToken[position]) {
|
||||
monitor.cancel();
|
||||
|
||||
return null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// Remember Editor at position
|
||||
this.visibleEditors[position] = editor;
|
||||
// Remember Editor at position
|
||||
this.visibleEditors[position] = editor;
|
||||
|
||||
// Create editor as needed
|
||||
if (!editor.getContainer()) {
|
||||
editor.create($().div({
|
||||
'class': 'editor-container',
|
||||
'role': 'tabpanel',
|
||||
id: descriptor.getId()
|
||||
}));
|
||||
}
|
||||
// Create editor as needed
|
||||
if (!editor.getContainer()) {
|
||||
editor.create($().div({
|
||||
'class': 'editor-container',
|
||||
'role': 'tabpanel',
|
||||
id: descriptor.getId()
|
||||
}));
|
||||
}
|
||||
|
||||
return editor;
|
||||
});
|
||||
return editor;
|
||||
}
|
||||
|
||||
private doInstantiateEditor(group: EditorGroup, descriptor: IEditorDescriptor): TPromise<BaseEditor> {
|
||||
private doInstantiateEditor(group: EditorGroup, descriptor: IEditorDescriptor): BaseEditor {
|
||||
const position = this.stacks.positionOfGroup(group);
|
||||
|
||||
// Return early if already instantiated
|
||||
const instantiatedEditor = this.instantiatedEditors[position].filter(e => descriptor.describes(e))[0];
|
||||
if (instantiatedEditor) {
|
||||
return TPromise.as(instantiatedEditor);
|
||||
}
|
||||
|
||||
// Return early if editor is being instantiated at the same time from a previous call
|
||||
const pendingEditorInstantiate = this.mapEditorInstantiationPromiseToEditor[position][descriptor.getId()];
|
||||
if (pendingEditorInstantiate) {
|
||||
return pendingEditorInstantiate;
|
||||
return instantiatedEditor;
|
||||
}
|
||||
|
||||
// Otherwise instantiate
|
||||
const progressService = this.instantiationService.createInstance(WorkbenchProgressService, this.editorGroupsControl.getProgressBar(position), descriptor.getId(), true);
|
||||
const editorInstantiationService = this.editorGroupsControl.getInstantiationService(position).createChild(new ServiceCollection([IProgressService, progressService]));
|
||||
let loaded = false;
|
||||
|
||||
const onInstantiate = (arg: BaseEditor): TPromise<BaseEditor> => {
|
||||
const position = this.stacks.positionOfGroup(group); // might have changed due to a rochade meanwhile
|
||||
const editor = descriptor.instantiate(editorInstantiationService);
|
||||
|
||||
loaded = true;
|
||||
delete this.mapEditorInstantiationPromiseToEditor[position][descriptor.getId()];
|
||||
this.instantiatedEditors[position].push(editor);
|
||||
|
||||
this.instantiatedEditors[position].push(arg);
|
||||
|
||||
return TPromise.as(arg);
|
||||
};
|
||||
|
||||
const instantiateEditorPromise = editorInstantiationService.createInstance(<EditorDescriptor>descriptor).then(onInstantiate);
|
||||
|
||||
if (!loaded) {
|
||||
this.mapEditorInstantiationPromiseToEditor[position][descriptor.getId()] = instantiateEditorPromise;
|
||||
}
|
||||
|
||||
return instantiateEditorPromise.then(result => {
|
||||
progressService.dispose();
|
||||
|
||||
return result;
|
||||
});
|
||||
return editor;
|
||||
}
|
||||
|
||||
private doSetInput(group: EditorGroup, editor: BaseEditor, input: EditorInput, options: EditorOptions, monitor: ProgressMonitor): TPromise<BaseEditor> {
|
||||
@@ -641,6 +667,9 @@ export class EditorPart extends Part implements IEditorPart, IEditorGroupService
|
||||
// Explicitly trigger the focus changed handler because the side by side control will not trigger it unless
|
||||
// the user is actively changing focus with the mouse from left/top to right/bottom.
|
||||
this.onGroupFocusChanged();
|
||||
|
||||
// Update title area sync to avoid some flickering with actions
|
||||
this.editorGroupsControl.updateTitleAreas();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -667,7 +696,7 @@ export class EditorPart extends Part implements IEditorPart, IEditorGroupService
|
||||
|
||||
// Emit Editor move event
|
||||
if (rochade !== Rochade.NONE) {
|
||||
this._onEditorsMoved.fire();
|
||||
this._onEditorGroupMoved.fire();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -875,14 +904,13 @@ export class EditorPart extends Part implements IEditorPart, IEditorGroupService
|
||||
// Move data structures
|
||||
arrays.move(this.visibleEditors, fromPosition, toPosition);
|
||||
arrays.move(this.editorOpenToken, fromPosition, toPosition);
|
||||
arrays.move(this.mapEditorInstantiationPromiseToEditor, fromPosition, toPosition);
|
||||
arrays.move(this.instantiatedEditors, fromPosition, toPosition);
|
||||
|
||||
// Restore focus
|
||||
this.focusGroup(fromGroup);
|
||||
|
||||
// Events
|
||||
this._onEditorsMoved.fire();
|
||||
this._onEditorGroupMoved.fire();
|
||||
}
|
||||
|
||||
public moveEditor(input: EditorInput, from: EditorGroup, to: EditorGroup, moveOptions?: IMoveOptions): void;
|
||||
@@ -1020,7 +1048,7 @@ export class EditorPart extends Part implements IEditorPart, IEditorGroupService
|
||||
}
|
||||
}
|
||||
|
||||
public replaceEditors(editors: { toReplace: EditorInput, replaceWith: EditorInput, options?: EditorOptions }[], position?: Position): TPromise<BaseEditor[]> {
|
||||
public replaceEditors(editors: { toReplace: EditorInput, replaceWith: EditorInput, options?: EditorOptions }[], position?: Position): TPromise<IEditor[]> {
|
||||
const activeReplacements: IEditorReplacement[] = [];
|
||||
const hiddenReplacements: IEditorReplacement[] = [];
|
||||
|
||||
@@ -1079,9 +1107,9 @@ export class EditorPart extends Part implements IEditorPart, IEditorGroupService
|
||||
return res;
|
||||
}
|
||||
|
||||
public openEditors(editors: { input: EditorInput, position: Position, options?: EditorOptions }[]): TPromise<BaseEditor[]> {
|
||||
public openEditors(editors: { input: EditorInput, position: Position, options?: EditorOptions }[]): TPromise<IEditor[]> {
|
||||
if (!editors.length) {
|
||||
return TPromise.as<BaseEditor[]>([]);
|
||||
return TPromise.as<IEditor[]>([]);
|
||||
}
|
||||
|
||||
let activePosition: Position;
|
||||
@@ -1098,7 +1126,7 @@ export class EditorPart extends Part implements IEditorPart, IEditorGroupService
|
||||
return this.stacks.groups.some(g => g.count > 0);
|
||||
}
|
||||
|
||||
public restoreEditors(): TPromise<BaseEditor[]> {
|
||||
public restoreEditors(): TPromise<IEditor[]> {
|
||||
const editors = this.stacks.groups.map((group, index) => {
|
||||
return {
|
||||
input: group.activeEditor,
|
||||
@@ -1108,7 +1136,7 @@ export class EditorPart extends Part implements IEditorPart, IEditorGroupService
|
||||
});
|
||||
|
||||
if (!editors.length) {
|
||||
return TPromise.as<BaseEditor[]>([]);
|
||||
return TPromise.as<IEditor[]>([]);
|
||||
}
|
||||
|
||||
let activePosition: Position;
|
||||
@@ -1121,7 +1149,7 @@ export class EditorPart extends Part implements IEditorPart, IEditorGroupService
|
||||
return this.doOpenEditors(editors, activePosition, editorState && editorState.ratio);
|
||||
}
|
||||
|
||||
private doOpenEditors(editors: { input: EditorInput, position: Position, options?: EditorOptions }[], activePosition?: number, ratio?: number[]): TPromise<BaseEditor[]> {
|
||||
private doOpenEditors(editors: { input: EditorInput, position: Position, options?: EditorOptions }[], activePosition?: number, ratio?: number[]): TPromise<IEditor[]> {
|
||||
const positionOneEditors = editors.filter(e => e.position === Position.ONE);
|
||||
const positionTwoEditors = editors.filter(e => e.position === Position.TWO);
|
||||
const positionThreeEditors = editors.filter(e => e.position === Position.THREE);
|
||||
@@ -1168,7 +1196,7 @@ export class EditorPart extends Part implements IEditorPart, IEditorGroupService
|
||||
|
||||
// Open each input respecting the options. Since there can only be one active editor in each
|
||||
// position, we have to pick the first input from each position and add the others as inactive
|
||||
const promises: TPromise<BaseEditor>[] = [];
|
||||
const promises: TPromise<IEditor>[] = [];
|
||||
[positionOneEditors.shift(), positionTwoEditors.shift(), positionThreeEditors.shift()].forEach((editor, position) => {
|
||||
if (!editor) {
|
||||
return; // unused position
|
||||
@@ -1177,7 +1205,7 @@ export class EditorPart extends Part implements IEditorPart, IEditorGroupService
|
||||
const input = editor.input;
|
||||
|
||||
// Resolve editor options
|
||||
const preserveFocus = (activePosition !== position);
|
||||
const preserveFocus = (activePosition !== position && ratio && ratio.length > 0); // during restore, preserve focus to reduce flicker
|
||||
let options: EditorOptions;
|
||||
if (editor.options) {
|
||||
options = editor.options;
|
||||
@@ -1202,7 +1230,12 @@ export class EditorPart extends Part implements IEditorPart, IEditorGroupService
|
||||
[positionOneEditors, positionTwoEditors, positionThreeEditors].forEach((editors, index) => {
|
||||
const group = this.stacks.groupAt(index);
|
||||
if (group) {
|
||||
editors.forEach(editor => group.openEditor(editor.input, { pinned: true })); // group could be null if one openeditor call failed!
|
||||
|
||||
// Make sure we are keeping the order as the editors are passed to us. We have to set
|
||||
// an explicit index because otherwise we would put editors in the wrong order
|
||||
// (see https://github.com/Microsoft/vscode/issues/30364)
|
||||
const startingIndex = group.indexOf(group.activeEditor) + 1;
|
||||
editors.forEach((editor, offset) => group.openEditor(editor.input, { pinned: true, index: (startingIndex + offset) }));
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1298,6 +1331,20 @@ export class EditorPart extends Part implements IEditorPart, IEditorGroupService
|
||||
}
|
||||
}
|
||||
|
||||
public invokeWithinEditorContext<T>(fn: (accessor: ServicesAccessor) => T): T {
|
||||
const activeEditor = this.getActiveEditor();
|
||||
if (activeEditor) {
|
||||
const activeEditorControl = activeEditor.getControl();
|
||||
if (isCommonCodeEditor(activeEditorControl)) {
|
||||
return activeEditorControl.invokeWithinContext(fn);
|
||||
}
|
||||
|
||||
return this.editorGroupsControl.getInstantiationService(activeEditor.position).invokeFunction(fn);
|
||||
}
|
||||
|
||||
return this.instantiationService.invokeFunction(fn);
|
||||
}
|
||||
|
||||
public layout(dimension: Dimension): Dimension[] {
|
||||
|
||||
// Pass to super
|
||||
@@ -1337,7 +1384,8 @@ export class EditorPart extends Part implements IEditorPart, IEditorGroupService
|
||||
|
||||
// Emitters
|
||||
this._onEditorsChanged.dispose();
|
||||
this._onEditorsMoved.dispose();
|
||||
this._onEditorOpening.dispose();
|
||||
this._onEditorGroupMoved.dispose();
|
||||
this._onEditorOpenFail.dispose();
|
||||
|
||||
// Reset Tokens
|
||||
@@ -1493,7 +1541,6 @@ export class EditorPart extends Part implements IEditorPart, IEditorGroupService
|
||||
|
||||
this.doRochade(this.visibleEditors, from, to, null);
|
||||
this.doRochade(this.editorOpenToken, from, to, null);
|
||||
this.doRochade(this.mapEditorInstantiationPromiseToEditor, from, to, Object.create(null));
|
||||
this.doRochade(this.instantiatedEditors, from, to, []);
|
||||
}
|
||||
}
|
||||
@@ -1503,9 +1550,11 @@ export class EditorPart extends Part implements IEditorPart, IEditorGroupService
|
||||
array[from] = empty;
|
||||
}
|
||||
|
||||
private ensureGroup(position: Position, activate = true): EditorGroup {
|
||||
private ensureGroup(position: Position, activate = true): [EditorGroup, boolean /* new group opened */] {
|
||||
let newGroupOpened = false;
|
||||
let group = this.stacks.groupAt(position);
|
||||
if (!group) {
|
||||
newGroupOpened = true;
|
||||
|
||||
// Race condition: it could be that someone quickly opens editors one after
|
||||
// the other and we are asked to open an editor in position 2 before position
|
||||
@@ -1528,7 +1577,7 @@ export class EditorPart extends Part implements IEditorPart, IEditorGroupService
|
||||
this.stacks.setActive(group);
|
||||
}
|
||||
|
||||
return group;
|
||||
return [group, newGroupOpened];
|
||||
}
|
||||
|
||||
private modifyGroups(modification: () => void) {
|
||||
|
||||
@@ -7,14 +7,11 @@
|
||||
import 'vs/css!./media/editorpicker';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import nls = require('vs/nls');
|
||||
import labels = require('vs/base/common/labels');
|
||||
import URI from 'vs/base/common/uri';
|
||||
import errors = require('vs/base/common/errors');
|
||||
import strings = require('vs/base/common/strings');
|
||||
import { IIconLabelOptions } from 'vs/base/browser/ui/iconLabel/iconLabel';
|
||||
import { IAutoFocus, Mode, IEntryRunContext, IQuickNavigateConfiguration, IModel } from 'vs/base/parts/quickopen/common/quickOpen';
|
||||
import { QuickOpenModel, QuickOpenEntry, QuickOpenEntryGroup } from 'vs/base/parts/quickopen/browser/quickOpenModel';
|
||||
import scorer = require('vs/base/common/scorer');
|
||||
import { QuickOpenModel, QuickOpenEntry, QuickOpenEntryGroup, QuickOpenItemAccessor } from 'vs/base/parts/quickopen/browser/quickOpenModel';
|
||||
import { IModeService } from 'vs/editor/common/services/modeService';
|
||||
import { getIconClasses } from 'vs/workbench/browser/labels';
|
||||
import { IModelService } from 'vs/editor/common/services/modelService';
|
||||
@@ -25,6 +22,7 @@ import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/edi
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
|
||||
import { EditorInput, toResource, IEditorGroup, IEditorStacksModel } from 'vs/workbench/common/editor';
|
||||
import { compareItemsByScore, scoreItem, ScorerCache, prepareQuery } from 'vs/base/parts/quickopen/common/quickOpenScorer';
|
||||
|
||||
export class EditorPickerEntry extends QuickOpenEntryGroup {
|
||||
private stacks: IEditorStacksModel;
|
||||
@@ -89,7 +87,7 @@ export class EditorPickerEntry extends QuickOpenEntryGroup {
|
||||
}
|
||||
|
||||
export abstract class BaseEditorPicker extends QuickOpenHandler {
|
||||
private scorerCache: { [key: string]: number };
|
||||
private scorerCache: ScorerCache;
|
||||
|
||||
constructor(
|
||||
@IInstantiationService protected instantiationService: IInstantiationService,
|
||||
@@ -103,41 +101,38 @@ export abstract class BaseEditorPicker extends QuickOpenHandler {
|
||||
}
|
||||
|
||||
public getResults(searchValue: string): TPromise<QuickOpenModel> {
|
||||
searchValue = searchValue.trim();
|
||||
const normalizedSearchValueLowercase = strings.stripWildcards(searchValue).toLowerCase();
|
||||
|
||||
const editorEntries = this.getEditorEntries();
|
||||
if (!editorEntries.length) {
|
||||
return TPromise.as(null);
|
||||
}
|
||||
|
||||
const stacks = this.editorGroupService.getStacksModel();
|
||||
// Prepare search for scoring
|
||||
const query = prepareQuery(searchValue);
|
||||
|
||||
const entries = editorEntries.filter(e => {
|
||||
if (!searchValue) {
|
||||
if (!query.value) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const resource = e.getResource();
|
||||
const targetToMatch = resource ? labels.getPathLabel(e.getResource(), this.contextService) : e.getLabel();
|
||||
if (!scorer.matches(targetToMatch, normalizedSearchValueLowercase)) {
|
||||
const itemScore = scoreItem(e, query, true, QuickOpenItemAccessor, this.scorerCache);
|
||||
if (!itemScore.score) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const { labelHighlights, descriptionHighlights } = QuickOpenEntry.highlight(e, searchValue, true /* fuzzy highlight */);
|
||||
e.setHighlights(labelHighlights, descriptionHighlights);
|
||||
e.setHighlights(itemScore.labelMatch, itemScore.descriptionMatch);
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
// Sorting
|
||||
if (searchValue) {
|
||||
const stacks = this.editorGroupService.getStacksModel();
|
||||
if (query.value) {
|
||||
entries.sort((e1, e2) => {
|
||||
if (e1.group !== e2.group) {
|
||||
return stacks.positionOfGroup(e1.group) - stacks.positionOfGroup(e2.group);
|
||||
}
|
||||
|
||||
return QuickOpenEntry.compareByScore(e1, e2, searchValue, normalizedSearchValueLowercase, this.scorerCache);
|
||||
return compareItemsByScore(e1, e2, query, true, QuickOpenItemAccessor, this.scorerCache);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -220,6 +215,8 @@ export abstract class EditorGroupPicker extends BaseEditorPicker {
|
||||
|
||||
export class GroupOnePicker extends EditorGroupPicker {
|
||||
|
||||
public static readonly ID = 'workbench.picker.editors.one';
|
||||
|
||||
protected getPosition(): Position {
|
||||
return Position.ONE;
|
||||
}
|
||||
@@ -227,6 +224,8 @@ export class GroupOnePicker extends EditorGroupPicker {
|
||||
|
||||
export class GroupTwoPicker extends EditorGroupPicker {
|
||||
|
||||
public static readonly ID = 'workbench.picker.editors.two';
|
||||
|
||||
protected getPosition(): Position {
|
||||
return Position.TWO;
|
||||
}
|
||||
@@ -234,6 +233,8 @@ export class GroupTwoPicker extends EditorGroupPicker {
|
||||
|
||||
export class GroupThreePicker extends EditorGroupPicker {
|
||||
|
||||
public static readonly ID = 'workbench.picker.editors.three';
|
||||
|
||||
protected getPosition(): Position {
|
||||
return Position.THREE;
|
||||
}
|
||||
@@ -241,6 +242,8 @@ export class GroupThreePicker extends EditorGroupPicker {
|
||||
|
||||
export class AllEditorsPicker extends BaseEditorPicker {
|
||||
|
||||
public static readonly ID = 'workbench.picker.editors';
|
||||
|
||||
protected getEditorEntries(): EditorPickerEntry[] {
|
||||
const entries: EditorPickerEntry[] = [];
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
import 'vs/css!./media/editorstatus';
|
||||
import nls = require('vs/nls');
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { $, append, runAtThisOrScheduleAtNextAnimationFrame } from 'vs/base/browser/dom';
|
||||
import { $, append, runAtThisOrScheduleAtNextAnimationFrame, addDisposableListener } from 'vs/base/browser/dom';
|
||||
import strings = require('vs/base/common/strings');
|
||||
import paths = require('vs/base/common/paths');
|
||||
import types = require('vs/base/common/types');
|
||||
@@ -17,12 +17,12 @@ import errors = require('vs/base/common/errors');
|
||||
import { IStatusbarItem } from 'vs/workbench/browser/parts/statusbar/statusbar';
|
||||
import { Action } from 'vs/base/common/actions';
|
||||
import { language, LANGUAGE_DEFAULT, AccessibilitySupport } from 'vs/base/common/platform';
|
||||
import * as browser from 'vs/base/browser/browser';
|
||||
import { IMode } from 'vs/editor/common/modes';
|
||||
import { UntitledEditorInput } from 'vs/workbench/common/editor/untitledEditorInput';
|
||||
import { IFileEditorInput, EncodingMode, IEncodingSupport, toResource, SideBySideEditorInput } from 'vs/workbench/common/editor';
|
||||
import { IDisposable, combinedDisposable, dispose } from 'vs/base/common/lifecycle';
|
||||
import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService';
|
||||
import { IConfigurationEditingService, ConfigurationTarget } from 'vs/workbench/services/configuration/common/configurationEditing';
|
||||
import { IEditorAction, ICommonCodeEditor, EndOfLineSequence, IModel } from 'vs/editor/common/editorCommon';
|
||||
import { IModelLanguageChangedEvent, IModelOptionsChangedEvent } from 'vs/editor/common/model/textModelEvents';
|
||||
import { TrimTrailingWhitespaceAction } from 'vs/editor/contrib/linesOperations/common/linesOperations';
|
||||
@@ -33,7 +33,7 @@ import { IEditor as IBaseEditor, IEditorInput } from 'vs/platform/editor/common/
|
||||
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { IQuickOpenService, IPickOpenEntry, IFilePickOpenEntry } from 'vs/platform/quickOpen/common/quickOpen';
|
||||
import { IWorkspaceConfigurationService } from 'vs/workbench/services/configuration/common/configuration';
|
||||
import { SUPPORTED_ENCODINGS, IFileService } from 'vs/platform/files/common/files';
|
||||
import { SUPPORTED_ENCODINGS, IFileService, IFilesConfiguration, FILES_ASSOCIATIONS_CONFIG } from 'vs/platform/files/common/files';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IModeService } from 'vs/editor/common/services/modeService';
|
||||
import { IModelService } from 'vs/editor/common/services/modelService';
|
||||
@@ -43,13 +43,19 @@ import { TabFocus } from 'vs/editor/common/config/commonEditorConfig';
|
||||
import { ICommandService } from 'vs/platform/commands/common/commands';
|
||||
import { IExtensionGalleryService } from 'vs/platform/extensionManagement/common/extensionManagement';
|
||||
import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
|
||||
import { getCodeEditor as getEditorWidget } from 'vs/editor/common/services/codeEditorService';
|
||||
import { getCodeEditor as getEditorWidget, getCodeOrDiffEditor } from 'vs/editor/common/services/codeEditorService';
|
||||
import { ICursorPositionChangedEvent } from 'vs/editor/common/controller/cursorEvents';
|
||||
import { IConfigurationChangedEvent } from 'vs/editor/common/config/editorOptions';
|
||||
import { IConfigurationChangedEvent, IEditorOptions } from 'vs/editor/common/config/editorOptions';
|
||||
import { ITextResourceConfigurationService } from 'vs/editor/common/services/resourceConfiguration';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import { IContextViewService } from 'vs/platform/contextview/browser/contextView';
|
||||
import { attachStylerCallback } from 'vs/platform/theme/common/styler';
|
||||
import { widgetShadow, editorWidgetBackground } from 'vs/platform/theme/common/colorRegistry';
|
||||
|
||||
// TODO@Sandeep layer breaker
|
||||
// tslint:disable-next-line:import-patterns
|
||||
import { IPreferencesService } from 'vs/workbench/parts/preferences/common/preferences';
|
||||
import { ConfigurationTarget } from 'vs/platform/configuration/common/configuration'
|
||||
|
||||
// {{SQL CARBON EDIT}}
|
||||
import { QueryEditorService } from 'sql/parts/query/services/queryEditorService';
|
||||
@@ -233,7 +239,7 @@ const nlsMultiSelection = nls.localize('multiSelection', "{0} selections");
|
||||
const nlsEOLLF = nls.localize('endOfLineLineFeed', "LF");
|
||||
const nlsEOLCRLF = nls.localize('endOfLineCarriageReturnLineFeed', "CRLF");
|
||||
const nlsTabFocusMode = nls.localize('tabFocusModeEnabled', "Tab Moves Focus");
|
||||
const nlsScreenReaderDetected = nls.localize('screenReaderDetected', "Screen Reader Detected");
|
||||
const nlsScreenReaderDetected = nls.localize('screenReaderDetected', "Screen Reader Optimized");
|
||||
const nlsScreenReaderDetectedTitle = nls.localize('screenReaderDetectedExtra', "If you are not using a Screen Reader, please change the setting `editor.accessibilitySupport` to \"off\".");
|
||||
|
||||
function _setDisplay(el: HTMLElement, desiredValue: string): void {
|
||||
@@ -264,6 +270,7 @@ export class EditorStatus implements IStatusbarItem {
|
||||
private activeEditorListeners: IDisposable[];
|
||||
private delayedRender: IDisposable;
|
||||
private toRender: StateChange;
|
||||
private lastScreenReaderExplanation: ScreenReaderDetectedExplanation;
|
||||
|
||||
constructor(
|
||||
@IWorkbenchEditorService private editorService: IWorkbenchEditorService,
|
||||
@@ -272,11 +279,13 @@ export class EditorStatus implements IStatusbarItem {
|
||||
@IInstantiationService private instantiationService: IInstantiationService,
|
||||
@IUntitledEditorService private untitledEditorService: IUntitledEditorService,
|
||||
@IModeService private modeService: IModeService,
|
||||
@ITextFileService private textFileService: ITextFileService
|
||||
@ITextFileService private textFileService: ITextFileService,
|
||||
@IWorkspaceConfigurationService private readonly configurationService: IWorkspaceConfigurationService,
|
||||
) {
|
||||
this.toDispose = [];
|
||||
this.activeEditorListeners = [];
|
||||
this.state = new State();
|
||||
this.lastScreenReaderExplanation = null;
|
||||
}
|
||||
|
||||
public render(container: HTMLElement): IDisposable {
|
||||
@@ -291,6 +300,7 @@ export class EditorStatus implements IStatusbarItem {
|
||||
this.screenRedearModeElement = append(this.element, $('a.editor-status-screenreadermode.status-bar-info'));
|
||||
this.screenRedearModeElement.textContent = nlsScreenReaderDetected;
|
||||
this.screenRedearModeElement.title = nlsScreenReaderDetectedTitle;
|
||||
this.screenRedearModeElement.onclick = () => this.onScreenReaderModeClick();
|
||||
hide(this.screenRedearModeElement);
|
||||
|
||||
this.selectionElement = append(this.element, $('a.editor-status-selection'));
|
||||
@@ -472,6 +482,10 @@ export class EditorStatus implements IStatusbarItem {
|
||||
action.dispose();
|
||||
}
|
||||
|
||||
private onScreenReaderModeClick(): void {
|
||||
this.lastScreenReaderExplanation = this.instantiationService.createInstance(ScreenReaderDetectedExplanation, this.screenRedearModeElement);
|
||||
}
|
||||
|
||||
private onSelectionClick(): void {
|
||||
this.quickOpenService.show(':'); // "Go to line"
|
||||
}
|
||||
@@ -610,15 +624,35 @@ export class EditorStatus implements IStatusbarItem {
|
||||
this.updateState(update);
|
||||
}
|
||||
|
||||
private _promptedScreenReader: boolean = false;
|
||||
|
||||
private onScreenReaderModeChange(editorWidget: ICommonCodeEditor): void {
|
||||
let screenReaderMode = false;
|
||||
|
||||
// We only support text based editors
|
||||
if (editorWidget) {
|
||||
const screenReaderDetected = (browser.getAccessibilitySupport() === AccessibilitySupport.Enabled);
|
||||
if (screenReaderDetected) {
|
||||
const screenReaderConfiguration = this.configurationService.getConfiguration<IEditorOptions>('editor').accessibilitySupport;
|
||||
if (screenReaderConfiguration === 'auto') {
|
||||
// show explanation
|
||||
if (!this._promptedScreenReader) {
|
||||
this._promptedScreenReader = true;
|
||||
setTimeout(() => {
|
||||
this.onScreenReaderModeClick();
|
||||
}, 100);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
screenReaderMode = (editorWidget.getConfiguration().accessibilitySupport === AccessibilitySupport.Enabled);
|
||||
}
|
||||
|
||||
if (screenReaderMode === false && this.lastScreenReaderExplanation) {
|
||||
this.lastScreenReaderExplanation.hide();
|
||||
this.lastScreenReaderExplanation = null;
|
||||
}
|
||||
|
||||
this.updateState({ screenReaderMode: screenReaderMode });
|
||||
}
|
||||
|
||||
@@ -699,7 +733,7 @@ export class EditorStatus implements IStatusbarItem {
|
||||
private onResourceEncodingChange(resource: uri): void {
|
||||
const activeEditor = this.editorService.getActiveEditor();
|
||||
if (activeEditor) {
|
||||
const activeResource = toResource(activeEditor.input, { supportSideBySide: true, filter: ['file', 'untitled'] });
|
||||
const activeResource = toResource(activeEditor.input, { supportSideBySide: true });
|
||||
if (activeResource && activeResource.toString() === resource.toString()) {
|
||||
return this.onEncodingChange(<IBaseEditor>activeEditor); // only update if the encoding changed for the active resource
|
||||
}
|
||||
@@ -755,21 +789,18 @@ export class ChangeModeAction extends Action {
|
||||
public static ID = 'workbench.action.editor.changeLanguageMode';
|
||||
public static LABEL = nls.localize('changeMode', "Change Language Mode");
|
||||
|
||||
private static FILE_ASSOCIATION_KEY = 'files.associations';
|
||||
|
||||
constructor(
|
||||
actionId: string,
|
||||
actionLabel: string,
|
||||
@IModeService private modeService: IModeService,
|
||||
@IModelService private modelService: IModelService,
|
||||
@IWorkbenchEditorService private editorService: IWorkbenchEditorService,
|
||||
@IConfigurationEditingService private configurationEditingService: IConfigurationEditingService,
|
||||
@IWorkspaceConfigurationService private configurationService: IWorkspaceConfigurationService,
|
||||
@IQuickOpenService private quickOpenService: IQuickOpenService,
|
||||
@IPreferencesService private preferencesService: IPreferencesService,
|
||||
@IInstantiationService private instantiationService: IInstantiationService,
|
||||
@ICommandService private commandService: ICommandService,
|
||||
@IConfigurationEditingService private configurationEditService: IConfigurationEditingService
|
||||
@IUntitledEditorService private untitledEditorService: IUntitledEditorService
|
||||
) {
|
||||
super(actionId, actionLabel);
|
||||
}
|
||||
@@ -782,11 +813,16 @@ export class ChangeModeAction extends Action {
|
||||
}
|
||||
|
||||
const textModel = editorWidget.getModel();
|
||||
const fileResource = toResource(activeEditor.input, { supportSideBySide: true, filter: 'file' });
|
||||
const resource = toResource(activeEditor.input, { supportSideBySide: true });
|
||||
|
||||
let hasLanguageSupport = !!resource;
|
||||
if (resource.scheme === 'untitled' && !this.untitledEditorService.hasAssociatedFilePath(resource)) {
|
||||
hasLanguageSupport = false; // no configuration for untitled resources (e.g. "Untitled-1")
|
||||
}
|
||||
|
||||
// Compute mode
|
||||
let currentModeId: string;
|
||||
let modeId;
|
||||
let modeId: string;
|
||||
if (textModel) {
|
||||
modeId = textModel.getLanguageIdentifier().language;
|
||||
currentModeId = this.modeService.getLanguageName(modeId);
|
||||
@@ -821,7 +857,7 @@ export class ChangeModeAction extends Action {
|
||||
};
|
||||
});
|
||||
|
||||
if (fileResource) {
|
||||
if (hasLanguageSupport) {
|
||||
picks[0].separator = { border: true, label: nls.localize('languagesPicks', "languages (identifier)") };
|
||||
}
|
||||
|
||||
@@ -829,15 +865,14 @@ export class ChangeModeAction extends Action {
|
||||
let configureModeAssociations: IPickOpenEntry;
|
||||
let configureModeSettings: IPickOpenEntry;
|
||||
let galleryAction: Action;
|
||||
if (fileResource) {
|
||||
const ext = paths.extname(fileResource.fsPath) || paths.basename(fileResource.fsPath);
|
||||
if (hasLanguageSupport) {
|
||||
const ext = paths.extname(resource.fsPath) || paths.basename(resource.fsPath);
|
||||
|
||||
galleryAction = this.instantiationService.createInstance(ShowLanguageExtensionsAction, ext);
|
||||
if (galleryAction.enabled) {
|
||||
picks.unshift(galleryAction);
|
||||
}
|
||||
|
||||
|
||||
configureModeSettings = { label: nls.localize('configureModeSettings', "Configure '{0}' language based settings...", currentModeId) };
|
||||
picks.unshift(configureModeSettings);
|
||||
configureModeAssociations = { label: nls.localize('configureAssociationsExt', "Configure File Association for '{0}'...", ext) };
|
||||
@@ -848,7 +883,8 @@ export class ChangeModeAction extends Action {
|
||||
const autoDetectMode: IPickOpenEntry = {
|
||||
label: nls.localize('autoDetect', "Auto Detect")
|
||||
};
|
||||
if (fileResource) {
|
||||
|
||||
if (hasLanguageSupport) {
|
||||
picks.unshift(autoDetectMode);
|
||||
}
|
||||
|
||||
@@ -864,7 +900,7 @@ export class ChangeModeAction extends Action {
|
||||
|
||||
// User decided to permanently configure associations, return right after
|
||||
if (pick === configureModeAssociations) {
|
||||
this.configureFileAssociation(fileResource);
|
||||
this.configureFileAssociation(resource);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -876,36 +912,46 @@ export class ChangeModeAction extends Action {
|
||||
|
||||
// Change mode for active editor
|
||||
activeEditor = this.editorService.getActiveEditor();
|
||||
const editorWidget = getEditorWidget(activeEditor);
|
||||
if (editorWidget) {
|
||||
const models: IModel[] = [];
|
||||
|
||||
const textModel = editorWidget.getModel();
|
||||
if (textModel) {
|
||||
models.push(textModel);
|
||||
const codeOrDiffEditor = getCodeOrDiffEditor(activeEditor);
|
||||
const models: IModel[] = [];
|
||||
if (codeOrDiffEditor.codeEditor) {
|
||||
const codeEditorModel = codeOrDiffEditor.codeEditor.getModel();
|
||||
if (codeEditorModel) {
|
||||
models.push(codeEditorModel);
|
||||
}
|
||||
|
||||
// Find mode
|
||||
let mode: TPromise<IMode>;
|
||||
if (pick === autoDetectMode) {
|
||||
mode = this.modeService.getOrCreateModeByFilenameOrFirstLine(toResource(activeEditor.input, { supportSideBySide: true, filter: ['file', 'untitled'] }).fsPath, textModel.getLineContent(1));
|
||||
} else {
|
||||
mode = this.modeService.getOrCreateModeByLanguageName(pick.label);
|
||||
}
|
||||
if (codeOrDiffEditor.diffEditor) {
|
||||
const diffEditorModel = codeOrDiffEditor.diffEditor.getModel();
|
||||
if (diffEditorModel) {
|
||||
if (diffEditorModel.original) {
|
||||
models.push(diffEditorModel.original);
|
||||
}
|
||||
if (diffEditorModel.modified) {
|
||||
models.push(diffEditorModel.modified);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// {{SQL CARBON EDIT}}
|
||||
// Change mode
|
||||
models.forEach(textModel => {
|
||||
let self = this;
|
||||
mode.then((modeValue) => {
|
||||
QueryEditorService.sqlLanguageModeCheck(textModel, modeValue, activeEditor).then((newTextModel) => {
|
||||
if (newTextModel) {
|
||||
self.modelService.setMode(newTextModel, modeValue);
|
||||
}
|
||||
});
|
||||
// Find mode
|
||||
let mode: TPromise<IMode>;
|
||||
if (pick === autoDetectMode) {
|
||||
mode = this.modeService.getOrCreateModeByFilenameOrFirstLine(toResource(activeEditor.input, { supportSideBySide: true }).fsPath, textModel.getLineContent(1));
|
||||
} else {
|
||||
mode = this.modeService.getOrCreateModeByLanguageName(pick.label);
|
||||
}
|
||||
|
||||
// {{SQL CARBON EDIT}}
|
||||
// Change mode
|
||||
models.forEach(textModel => {
|
||||
let self = this;
|
||||
mode.then((modeValue) => {
|
||||
QueryEditorService.sqlLanguageModeCheck(textModel, modeValue, activeEditor).then((newTextModel) => {
|
||||
if (newTextModel) {
|
||||
self.modelService.setMode(newTextModel, modeValue);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -928,7 +974,7 @@ export class ChangeModeAction extends Action {
|
||||
TPromise.timeout(50 /* quick open is sensitive to being opened so soon after another */).done(() => {
|
||||
this.quickOpenService.pick(picks, { placeHolder: nls.localize('pickLanguageToConfigure', "Select Language Mode to Associate with '{0}'", extension || basename) }).done(language => {
|
||||
if (language) {
|
||||
const fileAssociationsConfig = this.configurationService.lookup(ChangeModeAction.FILE_ASSOCIATION_KEY);
|
||||
const fileAssociationsConfig = this.configurationService.inspect(FILES_ASSOCIATIONS_CONFIG);
|
||||
|
||||
let associationKey: string;
|
||||
if (extension && basename[0] !== '.') {
|
||||
@@ -951,8 +997,7 @@ export class ChangeModeAction extends Action {
|
||||
|
||||
currentAssociations[associationKey] = language.id;
|
||||
|
||||
// Write config
|
||||
this.configurationEditingService.writeConfiguration(target, { key: ChangeModeAction.FILE_ASSOCIATION_KEY, value: currentAssociations });
|
||||
this.configurationService.updateValue(FILES_ASSOCIATIONS_CONFIG, currentAssociations, target);
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -1070,7 +1115,7 @@ export class ChangeEncodingAction extends Action {
|
||||
actionLabel: string,
|
||||
@IWorkbenchEditorService private editorService: IWorkbenchEditorService,
|
||||
@IQuickOpenService private quickOpenService: IQuickOpenService,
|
||||
@IWorkspaceConfigurationService private configurationService: IWorkspaceConfigurationService,
|
||||
@ITextResourceConfigurationService private textResourceConfigurationService: ITextResourceConfigurationService,
|
||||
@IFileService private fileService: IFileService
|
||||
) {
|
||||
super(actionId, actionLabel);
|
||||
@@ -1112,19 +1157,22 @@ export class ChangeEncodingAction extends Action {
|
||||
return void 0;
|
||||
}
|
||||
|
||||
const resource = toResource(activeEditor.input, { filter: ['file', 'untitled'], supportSideBySide: true });
|
||||
const resource = toResource(activeEditor.input, { supportSideBySide: true });
|
||||
|
||||
return TPromise.timeout(50 /* quick open is sensitive to being opened so soon after another */)
|
||||
.then(() => {
|
||||
if (!resource || resource.scheme !== 'file') {
|
||||
return TPromise.as(null); // encoding detection only possible for file resources
|
||||
if (!resource || !this.fileService.canHandleResource(resource)) {
|
||||
return TPromise.as(null); // encoding detection only possible for resources the file service can handle
|
||||
}
|
||||
|
||||
return this.fileService.resolveContent(resource, { autoGuessEncoding: true, acceptTextOnly: true }).then(content => content.encoding, err => null);
|
||||
})
|
||||
.then((guessedEncoding: string) => {
|
||||
const isReopenWithEncoding = (action === reopenWithEncodingPick);
|
||||
const configuredEncoding = this.configurationService.lookup('files.encoding', { resource }).value;
|
||||
|
||||
const config = this.textResourceConfigurationService.getConfiguration(resource) as IFilesConfiguration;
|
||||
const configuredEncoding = config && config.files && config.files.encoding;
|
||||
|
||||
let directMatchIndex: number;
|
||||
let aliasMatchIndex: number;
|
||||
|
||||
@@ -1153,7 +1201,7 @@ export class ChangeEncodingAction extends Action {
|
||||
aliasMatchIndex = index;
|
||||
}
|
||||
|
||||
return { id: key, label: SUPPORTED_ENCODINGS[key].labelLong };
|
||||
return { id: key, label: SUPPORTED_ENCODINGS[key].labelLong, description: key };
|
||||
});
|
||||
|
||||
// If we have a guessed encoding, show it first unless it matches the configured encoding
|
||||
@@ -1178,3 +1226,107 @@ export class ChangeEncodingAction extends Action {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
class ScreenReaderDetectedExplanation {
|
||||
|
||||
private _isDisposed: boolean;
|
||||
private _toDispose: IDisposable[];
|
||||
|
||||
constructor(
|
||||
anchorElement: HTMLElement,
|
||||
@IThemeService private readonly themeService: IThemeService,
|
||||
@IContextViewService private readonly contextViewService: IContextViewService,
|
||||
@IWorkspaceConfigurationService private readonly configurationService: IWorkspaceConfigurationService,
|
||||
) {
|
||||
this._isDisposed = false;
|
||||
this._toDispose = [];
|
||||
|
||||
this.contextViewService.showContextView({
|
||||
getAnchor: () => anchorElement,
|
||||
|
||||
render: (container) => {
|
||||
return this.renderContents(container);
|
||||
},
|
||||
|
||||
onDOMEvent: (e, activeElement) => {
|
||||
},
|
||||
|
||||
onHide: () => {
|
||||
this.dispose();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
this._isDisposed = true;
|
||||
this._toDispose = dispose(this._toDispose);
|
||||
}
|
||||
|
||||
public hide(): void {
|
||||
if (this._isDisposed) {
|
||||
return;
|
||||
}
|
||||
this.contextViewService.hideContextView();
|
||||
}
|
||||
|
||||
protected renderContents(container: HTMLElement): IDisposable {
|
||||
const domNode = $('div.screen-reader-detected-explanation', {
|
||||
'aria-hidden': 'true'
|
||||
});
|
||||
|
||||
const title = $('h2.title', {}, nls.localize('screenReaderDetectedExplanation.title', "Screen Reader Optimized"));
|
||||
domNode.appendChild(title);
|
||||
|
||||
const closeBtn = $('div.cancel');
|
||||
this._toDispose.push(addDisposableListener(closeBtn, 'click', () => {
|
||||
this.contextViewService.hideContextView();
|
||||
}));
|
||||
domNode.appendChild(closeBtn);
|
||||
|
||||
const question = $('p.question', {}, nls.localize('screenReaderDetectedExplanation.question', "Are you using a screen reader to operate VS Code?"));
|
||||
domNode.appendChild(question);
|
||||
|
||||
const yesBtn = $('div.button', {}, nls.localize('screenReaderDetectedExplanation.answerYes', "Yes"));
|
||||
this._toDispose.push(addDisposableListener(yesBtn, 'click', () => {
|
||||
this.configurationService.updateValue('editor.accessibilitySupport', 'on', ConfigurationTarget.USER);
|
||||
this.contextViewService.hideContextView();
|
||||
}));
|
||||
domNode.appendChild(yesBtn);
|
||||
|
||||
const noBtn = $('div.button', {}, nls.localize('screenReaderDetectedExplanation.answerNo', "No"));
|
||||
this._toDispose.push(addDisposableListener(noBtn, 'click', () => {
|
||||
this.configurationService.updateValue('editor.accessibilitySupport', 'off', ConfigurationTarget.USER);
|
||||
this.contextViewService.hideContextView();
|
||||
}));
|
||||
domNode.appendChild(noBtn);
|
||||
|
||||
const clear = $('div');
|
||||
clear.style.clear = 'both';
|
||||
domNode.appendChild(clear);
|
||||
|
||||
const br = $('br');
|
||||
domNode.appendChild(br);
|
||||
|
||||
const hr = $('hr');
|
||||
domNode.appendChild(hr);
|
||||
|
||||
const explanation1 = $('p.body1', {}, nls.localize('screenReaderDetectedExplanation.body1', "VS Code is now optimized for usage with a screen reader."));
|
||||
domNode.appendChild(explanation1);
|
||||
|
||||
const explanation2 = $('p.body2', {}, nls.localize('screenReaderDetectedExplanation.body2', "Some editor features will have different behaviour: e.g. word wrapping, folding, etc."));
|
||||
domNode.appendChild(explanation2);
|
||||
|
||||
container.appendChild(domNode);
|
||||
|
||||
this._toDispose.push(attachStylerCallback(this.themeService, { widgetShadow, editorWidgetBackground }, colors => {
|
||||
domNode.style.backgroundColor = colors.editorWidgetBackground;
|
||||
if (colors.widgetShadow) {
|
||||
domNode.style.boxShadow = `0 2px 8px ${colors.widgetShadow}`;
|
||||
}
|
||||
}));
|
||||
|
||||
return {
|
||||
dispose: () => { this.dispose(); }
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
BIN
src/vs/workbench/browser/parts/editor/media/back-tb.png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="3 3 16 16" enable-background="new 3 3 16 16"><polygon fill="#C5C5C5" points="12.597,11.042 15.4,13.845 13.844,15.4 11.042,12.598 8.239,15.4 6.683,13.845 9.485,11.042 6.683,8.239 8.238,6.683 11.042,9.486 13.845,6.683 15.4,8.239"/></svg>
|
||||
|
After Width: | Height: | Size: 307 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="3 3 16 16" enable-background="new 3 3 16 16"><polygon fill="#424242" points="12.597,11.042 15.4,13.845 13.844,15.4 11.042,12.598 8.239,15.4 6.683,13.845 9.485,11.042 6.683,8.239 8.238,6.683 11.042,9.486 13.845,6.683 15.4,8.239"/></svg>
|
||||
|
After Width: | Height: | Size: 307 B |
@@ -18,7 +18,78 @@
|
||||
padding: 0 5px 0 5px;
|
||||
}
|
||||
|
||||
.monaco-workbench .editor-statusbar-item > .editor-status-metadata,
|
||||
.monaco-workbench > .part.statusbar > .statusbar-item > .editor-statusbar-item > a.editor-status-screenreadermode {
|
||||
.monaco-workbench .editor-statusbar-item > .editor-status-metadata {
|
||||
cursor: default !important;
|
||||
}
|
||||
|
||||
|
||||
.monaco-shell .screen-reader-detected-explanation {
|
||||
width: 420px;
|
||||
top: 30px;
|
||||
right: 6px;
|
||||
padding: 1em;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.monaco-shell .screen-reader-detected-explanation .cancel {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
margin: .5em 0 0;
|
||||
padding: .5em;
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.monaco-shell .screen-reader-detected-explanation h2 {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-weight: 400;
|
||||
font-size: 1.8em;
|
||||
}
|
||||
|
||||
.monaco-shell .screen-reader-detected-explanation p {
|
||||
font-size: 1.2em;
|
||||
}
|
||||
|
||||
.monaco-shell .screen-reader-detected-explanation p.question {
|
||||
font-size: 1.4em;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.monaco-shell .screen-reader-detected-explanation .button {
|
||||
color: white;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
background-color: #007ACC;
|
||||
padding-left: 12px;
|
||||
padding-right: 12px;
|
||||
border: 4px solid #007ACC;
|
||||
border-radius: 4px;
|
||||
float: left;
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.monaco-shell.vs .screen-reader-detected-explanation .cancel {
|
||||
background: url('close-big.svg') center center no-repeat;
|
||||
}
|
||||
.monaco-shell.vs .screen-reader-detected-explanation .cancel:hover {
|
||||
background-color: #eaeaea;
|
||||
}
|
||||
|
||||
.monaco-shell.vs-dark .screen-reader-detected-explanation .cancel,
|
||||
.monaco-shell.hc-black .screen-reader-detected-explanation .cancel {
|
||||
background: url('close-big-dark.svg') center center no-repeat;
|
||||
}
|
||||
.monaco-shell.vs-dark .screen-reader-detected-explanation .cancel:hover {
|
||||
background-color: rgba(30,30,30,0.8);
|
||||
}
|
||||
|
||||
.monaco-shell.hc-black .screen-reader-detected-explanation .cancel {
|
||||
opacity: 0.6;
|
||||
}
|
||||
.monaco-shell.hc-black .screen-reader-detected-explanation .cancel:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
BIN
src/vs/workbench/browser/parts/editor/media/forward-tb.png
Normal file
|
After Width: | Height: | Size: 1.6 KiB |
@@ -10,13 +10,15 @@ import errors = require('vs/base/common/errors');
|
||||
import { IEditorGroup, toResource } from 'vs/workbench/common/editor';
|
||||
import DOM = require('vs/base/browser/dom');
|
||||
import { TitleControl } from 'vs/workbench/browser/parts/editor/titleControl';
|
||||
import { EditorLabel } from 'vs/workbench/browser/labels';
|
||||
import { ResourceLabel } from 'vs/workbench/browser/labels';
|
||||
import { Verbosity } from 'vs/platform/editor/common/editor';
|
||||
import { TAB_ACTIVE_FOREGROUND, TAB_UNFOCUSED_ACTIVE_FOREGROUND } from 'vs/workbench/common/theme';
|
||||
import { EventType as TouchEventType, GestureEvent, Gesture } from 'vs/base/browser/touch';
|
||||
|
||||
export class NoTabsTitleControl extends TitleControl {
|
||||
private titleContainer: HTMLElement;
|
||||
private editorLabel: EditorLabel;
|
||||
private editorLabel: ResourceLabel;
|
||||
private titleTouchSupport: Gesture;
|
||||
|
||||
public setContext(group: IEditorGroup): void {
|
||||
super.setContext(group);
|
||||
@@ -29,17 +31,22 @@ export class NoTabsTitleControl extends TitleControl {
|
||||
|
||||
this.titleContainer = parent;
|
||||
|
||||
// Gesture Support
|
||||
this.titleTouchSupport = new Gesture(this.titleContainer);
|
||||
|
||||
// Pin on double click
|
||||
this.toUnbind.push(DOM.addDisposableListener(this.titleContainer, DOM.EventType.DBLCLICK, (e: MouseEvent) => this.onTitleDoubleClick(e)));
|
||||
|
||||
// Detect mouse click
|
||||
this.toUnbind.push(DOM.addDisposableListener(this.titleContainer, DOM.EventType.CLICK, (e: MouseEvent) => this.onTitleClick(e)));
|
||||
|
||||
// Detect touch
|
||||
this.toUnbind.push(DOM.addDisposableListener(this.titleContainer, TouchEventType.Tap, (e: GestureEvent) => this.onTitleClick(e)));
|
||||
|
||||
// Editor Label
|
||||
this.editorLabel = this.instantiationService.createInstance(EditorLabel, this.titleContainer, void 0);
|
||||
this.editorLabel = this.instantiationService.createInstance(ResourceLabel, this.titleContainer, void 0);
|
||||
this.toUnbind.push(this.editorLabel);
|
||||
this.toUnbind.push(DOM.addDisposableListener(this.editorLabel.labelElement, DOM.EventType.CLICK, (e: MouseEvent) => this.onTitleLabelClick(e)));
|
||||
this.toUnbind.push(DOM.addDisposableListener(this.editorLabel.descriptionElement, DOM.EventType.CLICK, (e: MouseEvent) => this.onTitleLabelClick(e)));
|
||||
this.toUnbind.push(this.editorLabel.onClick(e => this.onTitleLabelClick(e)));
|
||||
|
||||
// Right Actions Container
|
||||
const actionsContainer = document.createElement('div');
|
||||
@@ -51,6 +58,7 @@ export class NoTabsTitleControl extends TitleControl {
|
||||
|
||||
// Context Menu
|
||||
this.toUnbind.push(DOM.addDisposableListener(this.titleContainer, DOM.EventType.CONTEXT_MENU, (e: Event) => this.onContextMenu({ group: this.context, editor: this.context.activeEditor }, e, this.titleContainer)));
|
||||
this.toUnbind.push(DOM.addDisposableListener(this.titleContainer, TouchEventType.Contextmenu, (e: Event) => this.onContextMenu({ group: this.context, editor: this.context.activeEditor }, e, this.titleContainer)));
|
||||
}
|
||||
|
||||
private onTitleLabelClick(e: MouseEvent): void {
|
||||
@@ -71,7 +79,7 @@ export class NoTabsTitleControl extends TitleControl {
|
||||
this.editorGroupService.pinEditor(group, group.activeEditor);
|
||||
}
|
||||
|
||||
private onTitleClick(e: MouseEvent): void {
|
||||
private onTitleClick(e: MouseEvent | GestureEvent): void {
|
||||
if (!this.context) {
|
||||
return;
|
||||
}
|
||||
@@ -79,7 +87,7 @@ export class NoTabsTitleControl extends TitleControl {
|
||||
const group = this.context;
|
||||
|
||||
// Close editor on middle mouse click
|
||||
if (e.button === 1 /* Middle Button */) {
|
||||
if (e instanceof MouseEvent && e.button === 1 /* Middle Button */) {
|
||||
this.closeEditorAction.run({ group, editor: group.activeEditor }).done(null, errors.onUnexpectedError);
|
||||
}
|
||||
|
||||
@@ -119,7 +127,15 @@ export class NoTabsTitleControl extends TitleControl {
|
||||
// Editor Label
|
||||
const resource = toResource(editor, { supportSideBySide: true });
|
||||
const name = editor.getName() || '';
|
||||
const description = isActive ? (editor.getDescription() || '') : '';
|
||||
|
||||
const labelFormat = this.editorGroupService.getTabOptions().labelFormat;
|
||||
let description: string;
|
||||
if (labelFormat === 'default' && !isActive) {
|
||||
description = ''; // hide description when group is not active and style is 'default'
|
||||
} else {
|
||||
description = editor.getDescription(this.getVerbosity(labelFormat)) || '';
|
||||
}
|
||||
|
||||
let title = editor.getTitle(Verbosity.LONG);
|
||||
if (description === title) {
|
||||
title = ''; // dont repeat what is already shown
|
||||
@@ -135,4 +151,18 @@ export class NoTabsTitleControl extends TitleControl {
|
||||
// Update Editor Actions Toolbar
|
||||
this.updateEditorActionsToolbar();
|
||||
}
|
||||
|
||||
private getVerbosity(style: string): Verbosity {
|
||||
switch (style) {
|
||||
case 'short': return Verbosity.SHORT;
|
||||
case 'long': return Verbosity.LONG;
|
||||
default: return Verbosity.MEDIUM;
|
||||
}
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
super.dispose();
|
||||
|
||||
this.titleTouchSupport.dispose();
|
||||
}
|
||||
}
|
||||
@@ -4,13 +4,12 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import * as strings from 'vs/base/common/strings';
|
||||
import * as DOM from 'vs/base/browser/dom';
|
||||
import { Dimension, Builder } from 'vs/base/browser/builder';
|
||||
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { IEditorRegistry, Extensions as EditorExtensions, EditorInput, EditorOptions, SideBySideEditorInput } from 'vs/workbench/common/editor';
|
||||
import { BaseEditor, EditorDescriptor } from 'vs/workbench/browser/parts/editor/baseEditor';
|
||||
import { EditorInput, EditorOptions, SideBySideEditorInput } from 'vs/workbench/common/editor';
|
||||
import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor';
|
||||
import { IEditorControl, Position, IEditor } from 'vs/platform/editor/common/editor';
|
||||
import { VSash } from 'vs/base/browser/ui/sash/sash';
|
||||
|
||||
@@ -18,6 +17,7 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import { scrollbarShadow } from 'vs/platform/theme/common/colorRegistry';
|
||||
import { IEditorRegistry, Extensions as EditorExtensions } from 'vs/workbench/browser/editor';
|
||||
|
||||
export class SideBySideEditor extends BaseEditor {
|
||||
|
||||
@@ -110,7 +110,7 @@ export class SideBySideEditor extends BaseEditor {
|
||||
return this.detailsEditor;
|
||||
}
|
||||
|
||||
private updateInput(oldInput: SideBySideEditorInput, newInput: SideBySideEditorInput, options?: EditorOptions): TPromise<void> {
|
||||
private updateInput(oldInput: SideBySideEditorInput, newInput: SideBySideEditorInput, options?: EditorOptions): void {
|
||||
if (!newInput.matches(oldInput)) {
|
||||
if (oldInput) {
|
||||
this.disposeEditors();
|
||||
@@ -126,24 +126,21 @@ export class SideBySideEditor extends BaseEditor {
|
||||
}
|
||||
}
|
||||
|
||||
private setNewInput(newInput: SideBySideEditorInput, options?: EditorOptions): TPromise<void> {
|
||||
return TPromise.join([
|
||||
this._createEditor(<EditorInput>newInput.details, this.detailsEditorContainer),
|
||||
this._createEditor(<EditorInput>newInput.master, this.masterEditorContainer)
|
||||
]).then(result => this.onEditorsCreated(result[0], result[1], newInput.details, newInput.master, options));
|
||||
private setNewInput(newInput: SideBySideEditorInput, options?: EditorOptions): void {
|
||||
const detailsEditor = this._createEditor(<EditorInput>newInput.details, this.detailsEditorContainer);
|
||||
const masterEditor = this._createEditor(<EditorInput>newInput.master, this.masterEditorContainer);
|
||||
|
||||
this.onEditorsCreated(detailsEditor, masterEditor, newInput.details, newInput.master, options);
|
||||
}
|
||||
|
||||
private _createEditor(editorInput: EditorInput, container: HTMLElement): TPromise<BaseEditor> {
|
||||
private _createEditor(editorInput: EditorInput, container: HTMLElement): BaseEditor {
|
||||
const descriptor = Registry.as<IEditorRegistry>(EditorExtensions.Editors).getEditor(editorInput);
|
||||
if (!descriptor) {
|
||||
return TPromise.wrapError<BaseEditor>(new Error(strings.format('Can not find a registered editor for the input {0}', editorInput)));
|
||||
}
|
||||
return this.instantiationService.createInstance(<EditorDescriptor>descriptor)
|
||||
.then((editor: BaseEditor) => {
|
||||
editor.create(new Builder(container));
|
||||
editor.setVisible(this.isVisible(), this.position);
|
||||
return editor;
|
||||
});
|
||||
|
||||
const editor = descriptor.instantiate(this.instantiationService);
|
||||
editor.create(new Builder(container));
|
||||
editor.setVisible(this.isVisible(), this.position);
|
||||
|
||||
return editor;
|
||||
}
|
||||
|
||||
private onEditorsCreated(details: BaseEditor, master: BaseEditor, detailsInput: EditorInput, masterInput: EditorInput, options: EditorOptions): TPromise<void> {
|
||||
|
||||
@@ -17,10 +17,11 @@ import { ActionRunner, IAction } from 'vs/base/common/actions';
|
||||
import { Position, IEditorInput, Verbosity, IUntitledResourceInput } from 'vs/platform/editor/common/editor';
|
||||
import { IEditorGroup, toResource } from 'vs/workbench/common/editor';
|
||||
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
|
||||
import { EventType as TouchEventType, GestureEvent, Gesture } from 'vs/base/browser/touch';
|
||||
import { KeyCode } from 'vs/base/common/keyCodes';
|
||||
import { EditorLabel } from 'vs/workbench/browser/labels';
|
||||
import { ResourceLabel } from 'vs/workbench/browser/labels';
|
||||
import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar';
|
||||
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { IWorkbenchEditorService, DelegatingWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
|
||||
import { IEditorGroupService } from 'vs/workbench/services/group/common/groupService';
|
||||
import { IMessageService } from 'vs/platform/message/common/message';
|
||||
@@ -37,7 +38,6 @@ import { ScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElemen
|
||||
import { ScrollbarVisibility } from 'vs/base/common/scrollable';
|
||||
import { extractResources } from 'vs/base/browser/dnd';
|
||||
import { getOrSet } from 'vs/base/common/map';
|
||||
import { DelegatingWorkbenchEditorService } from 'vs/workbench/services/editor/browser/editorService';
|
||||
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
|
||||
import { IThemeService, registerThemingParticipant, ITheme, ICssStyleCollector } from 'vs/platform/theme/common/themeService';
|
||||
import { TAB_INACTIVE_BACKGROUND, TAB_ACTIVE_BACKGROUND, TAB_ACTIVE_FOREGROUND, TAB_INACTIVE_FOREGROUND, TAB_BORDER, EDITOR_DRAG_AND_DROP_BACKGROUND, TAB_UNFOCUSED_ACTIVE_FOREGROUND, TAB_UNFOCUSED_INACTIVE_FOREGROUND, TAB_UNFOCUSED_ACTIVE_BORDER, TAB_ACTIVE_BORDER } from 'vs/workbench/common/theme';
|
||||
@@ -47,16 +47,17 @@ import { IWorkspacesService } from 'vs/platform/workspaces/common/workspaces';
|
||||
|
||||
interface IEditorInputLabel {
|
||||
name: string;
|
||||
hasAmbiguousName?: boolean;
|
||||
description?: string;
|
||||
title?: string;
|
||||
}
|
||||
|
||||
type AugmentedLabel = IEditorInputLabel & { editor: IEditorInput };
|
||||
|
||||
export class TabsTitleControl extends TitleControl {
|
||||
private titleContainer: HTMLElement;
|
||||
private tabsContainer: HTMLElement;
|
||||
private activeTab: HTMLElement;
|
||||
private editorLabels: EditorLabel[];
|
||||
private editorLabels: ResourceLabel[];
|
||||
private scrollbar: ScrollableElement;
|
||||
private tabDisposeables: IDisposable[];
|
||||
private blockRevealActiveTab: boolean;
|
||||
@@ -264,7 +265,7 @@ export class TabsTitleControl extends TitleControl {
|
||||
|
||||
// Compute labels and protect against duplicates
|
||||
const editorsOfGroup = this.context.getEditors();
|
||||
const labels = this.getUniqueTabLabels(editorsOfGroup);
|
||||
const labels = this.getTabLabels(editorsOfGroup);
|
||||
|
||||
// Tab label and styles
|
||||
editorsOfGroup.forEach((editor, index) => {
|
||||
@@ -276,7 +277,7 @@ export class TabsTitleControl extends TitleControl {
|
||||
|
||||
const label = labels[index];
|
||||
const name = label.name;
|
||||
const description = label.hasAmbiguousName && label.description ? label.description : '';
|
||||
const description = label.description || '';
|
||||
const title = label.title || '';
|
||||
|
||||
// Container
|
||||
@@ -294,7 +295,8 @@ export class TabsTitleControl extends TitleControl {
|
||||
|
||||
// Label
|
||||
const tabLabel = this.editorLabels[index];
|
||||
tabLabel.setLabel({ name, description, resource: toResource(editor, { supportSideBySide: true }) }, { extraClasses: ['tab-label'], italic: !isPinned });
|
||||
// {{SQL CARBON EDIT}} -- add title in options passed
|
||||
tabLabel.setLabel({ name, description, resource: toResource(editor, { supportSideBySide: true }) }, { extraClasses: ['tab-label'], italic: !isPinned, title });
|
||||
|
||||
// Active state
|
||||
if (isTabActive) {
|
||||
@@ -338,56 +340,108 @@ export class TabsTitleControl extends TitleControl {
|
||||
this.layout();
|
||||
}
|
||||
|
||||
private getUniqueTabLabels(editors: IEditorInput[]): IEditorInputLabel[] {
|
||||
const labels: IEditorInputLabel[] = [];
|
||||
|
||||
const mapLabelToDuplicates = new Map<string, IEditorInputLabel[]>();
|
||||
const mapLabelAndDescriptionToDuplicates = new Map<string, IEditorInputLabel[]>();
|
||||
private getTabLabels(editors: IEditorInput[]): IEditorInputLabel[] {
|
||||
const labelFormat = this.editorGroupService.getTabOptions().labelFormat;
|
||||
const { verbosity, shortenDuplicates } = this.getLabelConfigFlags(labelFormat);
|
||||
|
||||
// Build labels and descriptions for each editor
|
||||
editors.forEach(editor => {
|
||||
const name = editor.getName();
|
||||
let description = editor.getDescription();
|
||||
if (mapLabelAndDescriptionToDuplicates.has(`${name}${description}`)) {
|
||||
description = editor.getDescription(true); // try verbose description if name+description already exists
|
||||
}
|
||||
const labels = editors.map(editor => ({
|
||||
editor,
|
||||
name: editor.getName(),
|
||||
description: editor.getDescription(verbosity),
|
||||
title: editor.getTitle(Verbosity.LONG)
|
||||
}));
|
||||
|
||||
const item: IEditorInputLabel = {
|
||||
name,
|
||||
description,
|
||||
title: editor.getTitle(Verbosity.LONG)
|
||||
};
|
||||
labels.push(item);
|
||||
|
||||
getOrSet(mapLabelToDuplicates, item.name, []).push(item);
|
||||
|
||||
if (typeof description === 'string') {
|
||||
getOrSet(mapLabelAndDescriptionToDuplicates, `${item.name}${item.description}`, []).push(item);
|
||||
}
|
||||
});
|
||||
|
||||
// Mark duplicates and shorten their descriptions
|
||||
mapLabelToDuplicates.forEach(duplicates => {
|
||||
if (duplicates.length > 1) {
|
||||
duplicates = duplicates.filter(d => {
|
||||
// we could have items with equal label and description. in that case it does not make much
|
||||
// sense to produce a shortened version of the label, so we ignore those kind of items
|
||||
return typeof d.description === 'string' && mapLabelAndDescriptionToDuplicates.get(`${d.name}${d.description}`).length === 1;
|
||||
});
|
||||
|
||||
if (duplicates.length > 1) {
|
||||
const shortenedDescriptions = shorten(duplicates.map(duplicate => duplicate.description));
|
||||
duplicates.forEach((duplicate, i) => {
|
||||
duplicate.description = shortenedDescriptions[i];
|
||||
duplicate.hasAmbiguousName = true;
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
// Shorten labels as needed
|
||||
if (shortenDuplicates) {
|
||||
this.shortenTabLabels(labels);
|
||||
}
|
||||
|
||||
return labels;
|
||||
}
|
||||
|
||||
private shortenTabLabels(labels: AugmentedLabel[]): void {
|
||||
|
||||
// Gather duplicate titles, while filtering out invalid descriptions
|
||||
const mapTitleToDuplicates = new Map<string, AugmentedLabel[]>();
|
||||
for (const label of labels) {
|
||||
if (typeof label.description === 'string') {
|
||||
getOrSet(mapTitleToDuplicates, label.name, []).push(label);
|
||||
} else {
|
||||
label.description = '';
|
||||
}
|
||||
}
|
||||
|
||||
// Identify duplicate titles and shorten descriptions
|
||||
mapTitleToDuplicates.forEach(duplicateTitles => {
|
||||
|
||||
// Remove description if the title isn't duplicated
|
||||
if (duplicateTitles.length === 1) {
|
||||
duplicateTitles[0].description = '';
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Identify duplicate descriptions
|
||||
const mapDescriptionToDuplicates = new Map<string, AugmentedLabel[]>();
|
||||
for (const label of duplicateTitles) {
|
||||
getOrSet(mapDescriptionToDuplicates, label.description, []).push(label);
|
||||
}
|
||||
|
||||
// For editors with duplicate descriptions, check whether any long descriptions differ
|
||||
let useLongDescriptions = false;
|
||||
mapDescriptionToDuplicates.forEach((duplicateDescriptions, name) => {
|
||||
if (!useLongDescriptions && duplicateDescriptions.length > 1) {
|
||||
const [first, ...rest] = duplicateDescriptions.map(({ editor }) => editor.getDescription(Verbosity.LONG));
|
||||
useLongDescriptions = rest.some(description => description !== first);
|
||||
}
|
||||
});
|
||||
|
||||
// If so, replace all descriptions with long descriptions
|
||||
if (useLongDescriptions) {
|
||||
mapDescriptionToDuplicates.clear();
|
||||
duplicateTitles.forEach(label => {
|
||||
label.description = label.editor.getDescription(Verbosity.LONG);
|
||||
getOrSet(mapDescriptionToDuplicates, label.description, []).push(label);
|
||||
});
|
||||
}
|
||||
|
||||
// Obtain final set of descriptions
|
||||
const descriptions: string[] = [];
|
||||
mapDescriptionToDuplicates.forEach((_, description) => descriptions.push(description));
|
||||
|
||||
// Remove description if all descriptions are identical
|
||||
if (descriptions.length === 1) {
|
||||
for (const label of mapDescriptionToDuplicates.get(descriptions[0])) {
|
||||
label.description = '';
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Shorten descriptions
|
||||
const shortenedDescriptions = shorten(descriptions);
|
||||
descriptions.forEach((description, i) => {
|
||||
for (const label of mapDescriptionToDuplicates.get(description)) {
|
||||
label.description = shortenedDescriptions[i];
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private getLabelConfigFlags(value: string) {
|
||||
switch (value) {
|
||||
case 'short':
|
||||
return { verbosity: Verbosity.SHORT, shortenDuplicates: false };
|
||||
case 'medium':
|
||||
return { verbosity: Verbosity.MEDIUM, shortenDuplicates: false };
|
||||
case 'long':
|
||||
return { verbosity: Verbosity.LONG, shortenDuplicates: false };
|
||||
default:
|
||||
return { verbosity: Verbosity.MEDIUM, shortenDuplicates: true };
|
||||
}
|
||||
}
|
||||
|
||||
protected doRefresh(): void {
|
||||
const group = this.context;
|
||||
const editor = group && group.activeEditor;
|
||||
@@ -451,8 +505,11 @@ export class TabsTitleControl extends TitleControl {
|
||||
tabContainer.setAttribute('role', 'presentation'); // cannot use role "tab" here due to https://github.com/Microsoft/vscode/issues/8659
|
||||
DOM.addClass(tabContainer, 'tab');
|
||||
|
||||
// Gesture Support
|
||||
const gestureSupport = new Gesture(tabContainer);
|
||||
|
||||
// Tab Editor Label
|
||||
const editorLabel = this.instantiationService.createInstance(EditorLabel, tabContainer, void 0);
|
||||
const editorLabel = this.instantiationService.createInstance(ResourceLabel, tabContainer, void 0);
|
||||
this.editorLabels.push(editorLabel);
|
||||
|
||||
// Tab Close
|
||||
@@ -466,7 +523,7 @@ export class TabsTitleControl extends TitleControl {
|
||||
// Eventing
|
||||
const disposable = this.hookTabListeners(tabContainer, index);
|
||||
|
||||
this.tabDisposeables.push(combinedDisposable([disposable, bar, editorLabel]));
|
||||
this.tabDisposeables.push(combinedDisposable([disposable, bar, editorLabel, gestureSupport]));
|
||||
|
||||
return tabContainer;
|
||||
}
|
||||
@@ -516,14 +573,42 @@ export class TabsTitleControl extends TitleControl {
|
||||
private hookTabListeners(tab: HTMLElement, index: number): IDisposable {
|
||||
const disposables: IDisposable[] = [];
|
||||
|
||||
// Open on Click
|
||||
disposables.push(DOM.addDisposableListener(tab, DOM.EventType.MOUSE_DOWN, (e: MouseEvent) => {
|
||||
const handleClickOrTouch = (e: MouseEvent | GestureEvent) => {
|
||||
tab.blur();
|
||||
|
||||
if (e instanceof MouseEvent && e.button !== 0) {
|
||||
if (e.button === 1) {
|
||||
return false; // required due to https://github.com/Microsoft/vscode/issues/16690
|
||||
}
|
||||
|
||||
return void 0; // only for left mouse click
|
||||
}
|
||||
|
||||
const { editor, position } = this.toTabContext(index);
|
||||
if (e.button === 0 /* Left Button */ && !this.isTabActionBar((e.target || e.srcElement) as HTMLElement)) {
|
||||
if (!this.isTabActionBar((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
|
||||
}
|
||||
|
||||
return void 0;
|
||||
};
|
||||
|
||||
const showContextMenu = (e: Event) => {
|
||||
DOM.EventHelper.stop(e);
|
||||
|
||||
const { group, editor } = this.toTabContext(index);
|
||||
|
||||
this.onContextMenu({ group, editor }, e, tab);
|
||||
};
|
||||
|
||||
// Open on Click
|
||||
disposables.push(DOM.addDisposableListener(tab, DOM.EventType.MOUSE_DOWN, (e: MouseEvent) => handleClickOrTouch(e)));
|
||||
|
||||
// Open on Touch
|
||||
disposables.push(DOM.addDisposableListener(tab, TouchEventType.Tap, (e: GestureEvent) => handleClickOrTouch(e)));
|
||||
|
||||
// Touch Scroll Support
|
||||
disposables.push(DOM.addDisposableListener(tab, TouchEventType.Change, (e: GestureEvent) => {
|
||||
this.tabsContainer.scrollLeft -= e.translationX;
|
||||
}));
|
||||
|
||||
// Close on mouse middle click
|
||||
@@ -540,14 +625,15 @@ export class TabsTitleControl extends TitleControl {
|
||||
disposables.push(DOM.addDisposableListener(tab, DOM.EventType.KEY_DOWN, (e: KeyboardEvent) => {
|
||||
const event = new StandardKeyboardEvent(e);
|
||||
if (event.shiftKey && event.keyCode === KeyCode.F10) {
|
||||
DOM.EventHelper.stop(e);
|
||||
|
||||
const { group, editor } = this.toTabContext(index);
|
||||
|
||||
this.onContextMenu({ group, editor }, e, tab);
|
||||
showContextMenu(e);
|
||||
}
|
||||
}));
|
||||
|
||||
// Context menu on touch context menu gesture
|
||||
disposables.push(DOM.addDisposableListener(tab, TouchEventType.Contextmenu, (e: GestureEvent) => {
|
||||
showContextMenu(e);
|
||||
}));
|
||||
|
||||
// Keyboard accessibility
|
||||
disposables.push(DOM.addDisposableListener(tab, DOM.EventType.KEY_UP, (e: KeyboardEvent) => {
|
||||
const event = new StandardKeyboardEvent(e);
|
||||
@@ -617,12 +703,15 @@ export class TabsTitleControl extends TitleControl {
|
||||
e.dataTransfer.effectAllowed = 'copyMove';
|
||||
|
||||
// Insert transfer accordingly
|
||||
const fileResource = toResource(editor, { supportSideBySide: true, filter: 'file' });
|
||||
if (fileResource) {
|
||||
const resource = fileResource.toString();
|
||||
e.dataTransfer.setData('URL', resource); // enables cross window DND of tabs
|
||||
e.dataTransfer.setData('DownloadURL', [MIME_BINARY, editor.getName(), resource].join(':')); // enables support to drag a tab as file to desktop
|
||||
const resource = toResource(editor, { supportSideBySide: true });
|
||||
if (resource) {
|
||||
const resourceStr = resource.toString();
|
||||
e.dataTransfer.setData('URL', resourceStr); // enables cross window DND of tabs
|
||||
e.dataTransfer.setData('text/plain', getPathLabel(resource)); // enables dropping tab resource path into text controls
|
||||
|
||||
if (resource.scheme === 'file') {
|
||||
e.dataTransfer.setData('DownloadURL', [MIME_BINARY, editor.getName(), resourceStr].join(':')); // enables support to drag a tab as file to desktop
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
@@ -635,7 +724,21 @@ export class TabsTitleControl extends TitleControl {
|
||||
// Drag over
|
||||
disposables.push(DOM.addDisposableListener(tab, DOM.EventType.DRAG_ENTER, (e: DragEvent) => {
|
||||
counter++;
|
||||
this.updateDropFeedback(tab, true, index);
|
||||
|
||||
// Find out if the currently dragged editor is this tab and in that
|
||||
// case we do not want to show any drop feedback
|
||||
let draggedEditorIsTab = false;
|
||||
const draggedEditor = TabsTitleControl.getDraggedEditor();
|
||||
if (draggedEditor) {
|
||||
const { group, editor } = this.toTabContext(index);
|
||||
if (draggedEditor.editor === editor && draggedEditor.group === group) {
|
||||
draggedEditorIsTab = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!draggedEditorIsTab) {
|
||||
this.updateDropFeedback(tab, true, index);
|
||||
}
|
||||
}));
|
||||
|
||||
// Drag leave
|
||||
|
||||
@@ -22,14 +22,13 @@ import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput';
|
||||
import { DiffNavigator } from 'vs/editor/browser/widget/diffNavigator';
|
||||
import { DiffEditorWidget } from 'vs/editor/browser/widget/diffEditorWidget';
|
||||
import { TextDiffEditorModel } from 'vs/workbench/common/editor/textDiffEditorModel';
|
||||
import { DelegatingWorkbenchEditorService } from 'vs/workbench/services/editor/browser/editorService';
|
||||
import { FileOperationError, FileOperationResult } from 'vs/platform/files/common/files';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { IStorageService } from 'vs/platform/storage/common/storage';
|
||||
import { ITextResourceConfigurationService } from 'vs/editor/common/services/resourceConfiguration';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
|
||||
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { IWorkbenchEditorService, DelegatingWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import { IEditorGroupService } from 'vs/workbench/services/group/common/groupService';
|
||||
import { IModeService } from 'vs/editor/common/services/modeService';
|
||||
@@ -112,16 +111,12 @@ export class TextDiffEditor extends BaseTextEditor {
|
||||
}
|
||||
|
||||
public setInput(input: EditorInput, options?: EditorOptions): TPromise<void> {
|
||||
const oldInput = this.input;
|
||||
super.setInput(input, options);
|
||||
|
||||
// Detect options
|
||||
// Return early for same input unless we force to open
|
||||
const forceOpen = options && options.forceOpen;
|
||||
if (!forceOpen && input.matches(this.input)) {
|
||||
|
||||
// Same Input
|
||||
if (!forceOpen && input.matches(oldInput)) {
|
||||
|
||||
// TextOptions (avoiding instanceof here for a reason, do not change!)
|
||||
// Still apply options if any (avoiding instanceof here for a reason, do not change!)
|
||||
const textOptions = <TextEditorOptions>options;
|
||||
if (textOptions && types.isFunction(textOptions.apply)) {
|
||||
textOptions.apply(<IDiffEditor>this.getControl(), ScrollType.Smooth);
|
||||
@@ -135,49 +130,51 @@ export class TextDiffEditor extends BaseTextEditor {
|
||||
this.diffNavigator.dispose();
|
||||
}
|
||||
|
||||
// Different Input (Reload)
|
||||
return input.resolve(true).then(resolvedModel => {
|
||||
// Set input and resolve
|
||||
return super.setInput(input, options).then(() => {
|
||||
return input.resolve(true).then(resolvedModel => {
|
||||
|
||||
// Assert Model Instance
|
||||
if (!(resolvedModel instanceof TextDiffEditorModel) && this.openAsBinary(input, options)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Assert that the current input is still the one we expect. This prevents a race condition when loading a diff takes long and another input was set meanwhile
|
||||
if (!this.input || this.input !== input) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Editor
|
||||
const diffEditor = <IDiffEditor>this.getControl();
|
||||
diffEditor.setModel((<TextDiffEditorModel>resolvedModel).textDiffEditorModel);
|
||||
|
||||
// Handle TextOptions
|
||||
let alwaysRevealFirst = true;
|
||||
if (options && types.isFunction((<TextEditorOptions>options).apply)) {
|
||||
const hadOptions = (<TextEditorOptions>options).apply(<IDiffEditor>diffEditor, ScrollType.Immediate);
|
||||
if (hadOptions) {
|
||||
alwaysRevealFirst = false; // Do not reveal if we are instructed to open specific line/col
|
||||
// Assert Model Instance
|
||||
if (!(resolvedModel instanceof TextDiffEditorModel) && this.openAsBinary(input, options)) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Listen on diff updated changes to reveal the first change
|
||||
this.diffNavigator = new DiffNavigator(diffEditor, {
|
||||
alwaysRevealFirst
|
||||
// Assert that the current input is still the one we expect. This prevents a race condition when loading a diff takes long and another input was set meanwhile
|
||||
if (!this.input || this.input !== input) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Editor
|
||||
const diffEditor = <IDiffEditor>this.getControl();
|
||||
diffEditor.setModel((<TextDiffEditorModel>resolvedModel).textDiffEditorModel);
|
||||
|
||||
// Handle TextOptions
|
||||
let alwaysRevealFirst = true;
|
||||
if (options && types.isFunction((<TextEditorOptions>options).apply)) {
|
||||
const hadOptions = (<TextEditorOptions>options).apply(<IDiffEditor>diffEditor, ScrollType.Immediate);
|
||||
if (hadOptions) {
|
||||
alwaysRevealFirst = false; // Do not reveal if we are instructed to open specific line/col
|
||||
}
|
||||
}
|
||||
|
||||
// Listen on diff updated changes to reveal the first change
|
||||
this.diffNavigator = new DiffNavigator(diffEditor, {
|
||||
alwaysRevealFirst
|
||||
});
|
||||
this.diffNavigator.addListener(DiffNavigator.Events.UPDATED, () => {
|
||||
this.nextDiffAction.updateEnablement();
|
||||
this.previousDiffAction.updateEnablement();
|
||||
});
|
||||
}, error => {
|
||||
|
||||
// In case we tried to open a file and the response indicates that this is not a text file, fallback to binary diff.
|
||||
if (this.isFileBinaryError(error) && this.openAsBinary(input, options)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Otherwise make sure the error bubbles up
|
||||
return TPromise.wrapError(error);
|
||||
});
|
||||
this.diffNavigator.addListener(DiffNavigator.Events.UPDATED, () => {
|
||||
this.nextDiffAction.updateEnablement();
|
||||
this.previousDiffAction.updateEnablement();
|
||||
});
|
||||
}, error => {
|
||||
|
||||
// In case we tried to open a file and the response indicates that this is not a text file, fallback to binary diff.
|
||||
if (this.isFileBinaryError(error) && this.openAsBinary(input, options)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Otherwise make sure the error bubbles up
|
||||
return TPromise.wrapError(error);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ import types = require('vs/base/common/types');
|
||||
import errors = require('vs/base/common/errors');
|
||||
import DOM = require('vs/base/browser/dom');
|
||||
import { CodeEditor } from 'vs/editor/browser/codeEditor';
|
||||
import { EditorInput, EditorOptions, toResource } from 'vs/workbench/common/editor';
|
||||
import { EditorInput, EditorOptions } from 'vs/workbench/common/editor';
|
||||
import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor';
|
||||
import { IEditorViewState, IEditor, isCommonCodeEditor, isCommonDiffEditor } from 'vs/editor/common/editorCommon';
|
||||
import { Position } from 'vs/platform/editor/common/editor';
|
||||
@@ -66,7 +66,7 @@ export abstract class BaseTextEditor extends BaseEditor {
|
||||
) {
|
||||
super(id, telemetryService, themeService);
|
||||
|
||||
this.toUnbind.push(this.configurationService.onDidUpdateConfiguration(e => this.handleConfigurationChangeEvent(this.configurationService.getConfiguration<IEditorConfiguration>(this.getResource()))));
|
||||
this.toUnbind.push(this.configurationService.onDidChangeConfiguration(e => this.handleConfigurationChangeEvent(this.configurationService.getConfiguration<IEditorConfiguration>(this.getResource()))));
|
||||
}
|
||||
|
||||
protected get instantiationService(): IInstantiationService {
|
||||
@@ -196,6 +196,7 @@ export abstract class BaseTextEditor extends BaseEditor {
|
||||
// Update editor options after having set the input. We do this because there can be
|
||||
// editor input specific options (e.g. an ARIA label depending on the input showing)
|
||||
this.updateEditorConfiguration();
|
||||
this._editorContainer.getHTMLElement().setAttribute('aria-label', this.computeAriaLabel());
|
||||
});
|
||||
}
|
||||
|
||||
@@ -240,7 +241,7 @@ export abstract class BaseTextEditor extends BaseEditor {
|
||||
*/
|
||||
protected saveTextEditorViewState(key: string): void {
|
||||
const memento = this.getMemento(this.storageService, Scope.WORKSPACE);
|
||||
let textEditorViewStateMemento = memento[TEXT_EDITOR_VIEW_STATE_PREFERENCE_KEY];
|
||||
let textEditorViewStateMemento: { [key: string]: { [position: number]: IEditorViewState } } = memento[TEXT_EDITOR_VIEW_STATE_PREFERENCE_KEY];
|
||||
if (!textEditorViewStateMemento) {
|
||||
textEditorViewStateMemento = Object.create(null);
|
||||
memento[TEXT_EDITOR_VIEW_STATE_PREFERENCE_KEY] = textEditorViewStateMemento;
|
||||
@@ -248,7 +249,7 @@ export abstract class BaseTextEditor extends BaseEditor {
|
||||
|
||||
const editorViewState = this.getControl().saveViewState();
|
||||
|
||||
let lastKnownViewState: ITextEditorViewState = textEditorViewStateMemento[key];
|
||||
let lastKnownViewState = textEditorViewStateMemento[key];
|
||||
if (!lastKnownViewState) {
|
||||
lastKnownViewState = Object.create(null);
|
||||
textEditorViewStateMemento[key] = lastKnownViewState;
|
||||
@@ -264,7 +265,7 @@ export abstract class BaseTextEditor extends BaseEditor {
|
||||
*/
|
||||
protected clearTextEditorViewState(keys: string[]): void {
|
||||
const memento = this.getMemento(this.storageService, Scope.WORKSPACE);
|
||||
const textEditorViewStateMemento = memento[TEXT_EDITOR_VIEW_STATE_PREFERENCE_KEY];
|
||||
const textEditorViewStateMemento: { [key: string]: { [position: number]: IEditorViewState } } = memento[TEXT_EDITOR_VIEW_STATE_PREFERENCE_KEY];
|
||||
if (textEditorViewStateMemento) {
|
||||
keys.forEach(key => delete textEditorViewStateMemento[key]);
|
||||
}
|
||||
@@ -275,9 +276,9 @@ export abstract class BaseTextEditor extends BaseEditor {
|
||||
*/
|
||||
protected loadTextEditorViewState(key: string): IEditorViewState {
|
||||
const memento = this.getMemento(this.storageService, Scope.WORKSPACE);
|
||||
const textEditorViewStateMemento = memento[TEXT_EDITOR_VIEW_STATE_PREFERENCE_KEY];
|
||||
const textEditorViewStateMemento: { [key: string]: { [position: number]: IEditorViewState } } = memento[TEXT_EDITOR_VIEW_STATE_PREFERENCE_KEY];
|
||||
if (textEditorViewStateMemento) {
|
||||
const viewState: ITextEditorViewState = textEditorViewStateMemento[key];
|
||||
const viewState = textEditorViewStateMemento[key];
|
||||
if (viewState) {
|
||||
return viewState[this.position];
|
||||
}
|
||||
@@ -317,7 +318,7 @@ export abstract class BaseTextEditor extends BaseEditor {
|
||||
}
|
||||
|
||||
if (this.input) {
|
||||
return toResource(this.input);
|
||||
return this.input.getResource();
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
@@ -55,16 +55,12 @@ export class TextResourceEditor extends BaseTextEditor {
|
||||
}
|
||||
|
||||
public setInput(input: EditorInput, options?: EditorOptions): TPromise<void> {
|
||||
const oldInput = this.input;
|
||||
super.setInput(input, options);
|
||||
|
||||
// Detect options
|
||||
// Return early for same input unless we force to open
|
||||
const forceOpen = options && options.forceOpen;
|
||||
if (!forceOpen && input.matches(this.input)) {
|
||||
|
||||
// Same Input
|
||||
if (!forceOpen && input.matches(oldInput)) {
|
||||
|
||||
// TextOptions (avoiding instanceof here for a reason, do not change!)
|
||||
// Still apply options if any (avoiding instanceof here for a reason, do not change!)
|
||||
const textOptions = <TextEditorOptions>options;
|
||||
if (textOptions && types.isFunction(textOptions.apply)) {
|
||||
textOptions.apply(this.getControl(), ScrollType.Smooth);
|
||||
@@ -74,39 +70,41 @@ export class TextResourceEditor extends BaseTextEditor {
|
||||
}
|
||||
|
||||
// Remember view settings if input changes
|
||||
this.saveTextEditorViewStateForInput(oldInput);
|
||||
this.saveTextEditorViewStateForInput(this.input);
|
||||
|
||||
// Different Input (Reload)
|
||||
return input.resolve(true).then((resolvedModel: EditorModel) => {
|
||||
// Set input and resolve
|
||||
return super.setInput(input, options).then(() => {
|
||||
return input.resolve(true).then((resolvedModel: EditorModel) => {
|
||||
|
||||
// Assert Model instance
|
||||
if (!(resolvedModel instanceof BaseTextEditorModel)) {
|
||||
return TPromise.wrapError<void>(new Error('Unable to open file as text'));
|
||||
}
|
||||
// Assert Model instance
|
||||
if (!(resolvedModel instanceof BaseTextEditorModel)) {
|
||||
return TPromise.wrapError<void>(new Error('Unable to open file as text'));
|
||||
}
|
||||
|
||||
// Assert that the current input is still the one we expect. This prevents a race condition when loading takes long and another input was set meanwhile
|
||||
if (!this.input || this.input !== input) {
|
||||
return null;
|
||||
}
|
||||
// Assert that the current input is still the one we expect. This prevents a race condition when loading takes long and another input was set meanwhile
|
||||
if (!this.input || this.input !== input) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Set Editor Model
|
||||
const textEditor = this.getControl();
|
||||
const textEditorModel = resolvedModel.textEditorModel;
|
||||
textEditor.setModel(textEditorModel);
|
||||
// Set Editor Model
|
||||
const textEditor = this.getControl();
|
||||
const textEditorModel = resolvedModel.textEditorModel;
|
||||
textEditor.setModel(textEditorModel);
|
||||
|
||||
// Apply Options from TextOptions
|
||||
let optionsGotApplied = false;
|
||||
const textOptions = <TextEditorOptions>options;
|
||||
if (textOptions && types.isFunction(textOptions.apply)) {
|
||||
optionsGotApplied = textOptions.apply(textEditor, ScrollType.Immediate);
|
||||
}
|
||||
// Apply Options from TextOptions
|
||||
let optionsGotApplied = false;
|
||||
const textOptions = <TextEditorOptions>options;
|
||||
if (textOptions && types.isFunction(textOptions.apply)) {
|
||||
optionsGotApplied = textOptions.apply(textEditor, ScrollType.Immediate);
|
||||
}
|
||||
|
||||
// Otherwise restore View State
|
||||
if (!optionsGotApplied) {
|
||||
this.restoreViewState(input);
|
||||
}
|
||||
// Otherwise restore View State
|
||||
if (!optionsGotApplied) {
|
||||
this.restoreViewState(input);
|
||||
}
|
||||
|
||||
return void 0;
|
||||
return void 0;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -60,6 +60,7 @@ export interface ITitleAreaControl {
|
||||
getContainer(): HTMLElement;
|
||||
refresh(instant?: boolean): void;
|
||||
update(instant?: boolean): void;
|
||||
updateEditorActionsToolbar(): void;
|
||||
layout(): void;
|
||||
dispose(): void;
|
||||
}
|
||||
@@ -259,6 +260,12 @@ export abstract class TitleControl extends Themable implements ITitleAreaControl
|
||||
|
||||
// Log in telemetry
|
||||
if (this.telemetryService) {
|
||||
/* __GDPR__
|
||||
"workbenchActionExecuted" : {
|
||||
"id" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
|
||||
"from": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
|
||||
}
|
||||
*/
|
||||
this.telemetryService.publicLog('workbenchActionExecuted', { id: e.action.id, from: 'editorPart' });
|
||||
}
|
||||
}));
|
||||
@@ -334,7 +341,7 @@ export abstract class TitleControl extends Themable implements ITitleAreaControl
|
||||
return { primary, secondary };
|
||||
}
|
||||
|
||||
protected updateEditorActionsToolbar(): void {
|
||||
public updateEditorActionsToolbar(): void {
|
||||
const group = this.context;
|
||||
if (!group) {
|
||||
return;
|
||||
@@ -543,7 +550,7 @@ export function handleWorkspaceExternalDrop(
|
||||
|
||||
// Multiple folders: Create new workspace with folders and open
|
||||
else if (folders.length > 1) {
|
||||
workspacesToOpen = workspacesService.createWorkspace([...folders].map(folder => folder.fsPath)).then(workspace => [workspace.configPath]);
|
||||
workspacesToOpen = workspacesService.createWorkspace(folders.map(folder => ({ uri: folder }))).then(workspace => [workspace.configPath]);
|
||||
}
|
||||
|
||||
// Open
|
||||
|
||||
@@ -40,13 +40,13 @@ export abstract class BaseWebviewEditor extends BaseEditor {
|
||||
|
||||
protected saveViewState(resource: URI | string, editorViewState: HtmlPreviewEditorViewState): void {
|
||||
const memento = this.getMemento(this.storageService, Scope.WORKSPACE);
|
||||
let editorViewStateMemento = memento[this.viewStateStorageKey];
|
||||
let editorViewStateMemento: { [key: string]: { [position: number]: HtmlPreviewEditorViewState } } = memento[this.viewStateStorageKey];
|
||||
if (!editorViewStateMemento) {
|
||||
editorViewStateMemento = Object.create(null);
|
||||
memento[this.viewStateStorageKey] = editorViewStateMemento;
|
||||
}
|
||||
|
||||
let fileViewState: HtmlPreviewEditorViewStates = editorViewStateMemento[resource.toString()];
|
||||
let fileViewState = editorViewStateMemento[resource.toString()];
|
||||
if (!fileViewState) {
|
||||
fileViewState = Object.create(null);
|
||||
editorViewStateMemento[resource.toString()] = fileViewState;
|
||||
@@ -59,9 +59,9 @@ export abstract class BaseWebviewEditor extends BaseEditor {
|
||||
|
||||
protected loadViewState(resource: URI | string): HtmlPreviewEditorViewState | null {
|
||||
const memento = this.getMemento(this.storageService, Scope.WORKSPACE);
|
||||
const editorViewStateMemento = memento[this.viewStateStorageKey];
|
||||
const editorViewStateMemento: { [key: string]: { [position: number]: HtmlPreviewEditorViewState } } = memento[this.viewStateStorageKey];
|
||||
if (editorViewStateMemento) {
|
||||
const fileViewState: HtmlPreviewEditorViewStates = editorViewStateMemento[resource.toString()];
|
||||
const fileViewState = editorViewStateMemento[resource.toString()];
|
||||
if (fileViewState) {
|
||||
return fileViewState[this.position];
|
||||
}
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="#262626"><circle cx="3.5" cy="7.5" r="2.5"/><circle cx="8.5" cy="7.5" r="2.5"/><circle cx="13.5" cy="7.5" r="2.5"/></g><g fill="#C5C5C5"><circle cx="3.5" cy="7.5" r="1.5"/><circle cx="8.5" cy="7.5" r="1.5"/><circle cx="13.5" cy="7.5" r="1.5"/></g></svg>
|
||||
|
After Width: | Height: | Size: 325 B |
1
src/vs/workbench/browser/parts/panel/media/ellipsis.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="#F6F6F6"><circle cx="3.5" cy="7.5" r="2.5"/><circle cx="8.5" cy="7.5" r="2.5"/><circle cx="13.5" cy="7.5" r="2.5"/></g><g fill="#424242"><circle cx="3.5" cy="7.5" r="1.5"/><circle cx="8.5" cy="7.5" r="1.5"/><circle cx="13.5" cy="7.5" r="1.5"/></g></svg>
|
||||
|
After Width: | Height: | Size: 325 B |
36
src/vs/workbench/browser/parts/panel/media/left-inverse.svg
Normal file
@@ -0,0 +1,36 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
viewBox="0 0 16 16"
|
||||
version="1.1"
|
||||
id="svg3828"
|
||||
sodipodi:docname="left-inverse.svg"
|
||||
inkscape:version="0.92.2 5c3e80d, 2017-08-06">
|
||||
<style
|
||||
id="style3821">.icon-canvas-transparent{opacity:0;fill:#2d2d30}.icon-vs-out{fill:#2d2d30}.icon-vs-bg{fill:#c5c5c5}</style>
|
||||
<path
|
||||
class="icon-canvas-transparent"
|
||||
d="M16 16H0V0h16v16z"
|
||||
id="canvas" />
|
||||
<path
|
||||
class="icon-vs-out"
|
||||
d="M.379 5.652l2.828-2.828 4.76 4.761 4.826-4.826 2.828 2.828-7.654 7.654L.379 5.652z"
|
||||
id="outline"
|
||||
style="display: none;" />
|
||||
<g
|
||||
id="iconBg"
|
||||
transform="rotate(90,7.8976546,8.1705758)">
|
||||
<path
|
||||
class="icon-vs-bg"
|
||||
d="M 7.967,11.827 1.793,5.652 3.207,4.238 l 4.76,4.761 4.826,-4.826 1.414,1.414 z"
|
||||
id="path3825"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#c5c5c5" />
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.2 KiB |
36
src/vs/workbench/browser/parts/panel/media/left.svg
Normal file
@@ -0,0 +1,36 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
viewBox="0 0 16 16"
|
||||
version="1.1"
|
||||
id="svg3828"
|
||||
sodipodi:docname="left.svg"
|
||||
inkscape:version="0.92.2 5c3e80d, 2017-08-06">
|
||||
<style
|
||||
id="style3821">.icon-canvas-transparent{opacity:0;fill:#f6f6f6}.icon-vs-out{fill:#f6f6f6}.icon-vs-bg{fill:#424242}</style>
|
||||
<path
|
||||
class="icon-canvas-transparent"
|
||||
d="M16 16H0V0h16v16z"
|
||||
id="canvas" />
|
||||
<path
|
||||
class="icon-vs-out"
|
||||
d="M.379 5.652l2.828-2.828 4.76 4.761 4.826-4.826 2.828 2.828-7.654 7.654L.379 5.652z"
|
||||
id="outline"
|
||||
style="display: none;" />
|
||||
<g
|
||||
id="iconBg"
|
||||
transform="rotate(90,7.8976546,8.1705758)">
|
||||
<path
|
||||
class="icon-vs-bg"
|
||||
d="M 7.967,11.827 1.793,5.652 3.207,4.238 l 4.76,4.761 4.826,-4.826 1.414,1.414 z"
|
||||
id="path3825"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#424242" />
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.2 KiB |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.icon-canvas-transparent,.icon-vs-out{fill:#2d2d30;}.icon-canvas-transparent{opacity:0;}.icon-vs-fg{fill:#2b282e;}.icon-vs-bg{fill:#c5c5c5;}</style></defs><title>BottomRowOfTwoRows_16x</title><path class="icon-canvas-transparent" d="M16,0V16H0V0Z"/><path class="icon-vs-out" d="M16,0V15H1V0Z" style="display: none;"/><path class="icon-vs-fg" d="M14,2V8H3V2Z" style="display: none;"/><path class="icon-vs-bg" d="M2,1V14H15V1ZM14,8H3V2H14Z"/></svg>
|
||||
|
After Width: | Height: | Size: 519 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.icon-canvas-transparent,.icon-vs-out{fill:#f6f6f6;}.icon-canvas-transparent{opacity:0;}.icon-vs-fg{fill:#f0eff1;}.icon-vs-bg{fill:#424242;}</style></defs><title>BottomRowOfTwoRows_16x</title><path class="icon-canvas-transparent" d="M16,0V16H0V0Z"/><path class="icon-vs-out" d="M16,0V15H1V0Z" style="display: none;"/><path class="icon-vs-fg" d="M14,2V8H3V2Z" style="display: none;"/><path class="icon-vs-bg" d="M2,1V14H15V1ZM14,8H3V2H14Z"/></svg>
|
||||
|
After Width: | Height: | Size: 519 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.icon-canvas-transparent,.icon-vs-out{fill:#2d2d30;}.icon-canvas-transparent{opacity:0;}.icon-vs-fg{fill:#2b282e;}.icon-vs-bg{fill:#c5c5c5;}</style></defs><title>RightColumnOfTwoColumns_16x</title><path class="icon-canvas-transparent" d="M16,0V16H0V0Z"/><path class="icon-vs-out" d="M16,0V15H1V0Z" style="display: none;"/><path class="icon-vs-fg" d="M9,2V13H3V2Z" style="display: none;"/><path class="icon-vs-bg" d="M2,1V14H15V1ZM9,13H3V2H9Z"/></svg>
|
||||
|
After Width: | Height: | Size: 523 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.icon-canvas-transparent,.icon-vs-out{fill:#f6f6f6;}.icon-canvas-transparent{opacity:0;}.icon-vs-fg{fill:#f0eff1;}.icon-vs-bg{fill:#424242;}</style></defs><title>RightColumnOfTwoColumns_16x</title><path class="icon-canvas-transparent" d="M16,0V16H0V0Z"/><path class="icon-vs-out" d="M16,0V15H1V0Z" style="display: none;"/><path class="icon-vs-fg" d="M9,2V13H3V2Z" style="display: none;"/><path class="icon-vs-bg" d="M2,1V14H15V1ZM9,13H3V2H9Z"/></svg>
|
||||
|
After Width: | Height: | Size: 523 B |
@@ -13,10 +13,25 @@
|
||||
}
|
||||
|
||||
.monaco-workbench > .part.panel .title {
|
||||
border-top-width: 1px;
|
||||
border-top-style: solid;
|
||||
padding-right: 0px;
|
||||
height: 35px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.monaco-workbench > .part.panel.bottom .title {
|
||||
border-top-width: 1px;
|
||||
border-top-style: solid;
|
||||
}
|
||||
|
||||
.monaco-workbench > .part.panel.right {
|
||||
border-left-width: 1px;
|
||||
border-left-style: solid;
|
||||
}
|
||||
|
||||
.monaco-workbench > .part.panel > .composite.title > .title-actions {
|
||||
flex: 0;
|
||||
}
|
||||
|
||||
.monaco-workbench > .part.panel > .title > .title-actions .monaco-action-bar .action-item .action-label {
|
||||
@@ -25,8 +40,17 @@
|
||||
|
||||
/** Panel Switcher */
|
||||
|
||||
.monaco-workbench > .part.panel > .title > .panel-switcher-container.composite-bar > .monaco-action-bar .action-label.toggle-more {
|
||||
background: url('ellipsis.svg') center center no-repeat;
|
||||
display: block;
|
||||
height: 31px;
|
||||
min-width: 28px;
|
||||
margin-left: 0px;
|
||||
margin-right: 0px;
|
||||
}
|
||||
|
||||
.monaco-workbench > .part.panel > .title > .panel-switcher-container > .monaco-action-bar {
|
||||
line-height: 35px;
|
||||
line-height: 32px;
|
||||
}
|
||||
|
||||
.monaco-workbench > .part.panel > .title > .panel-switcher-container > .monaco-action-bar .action-item:first-child .action-label {
|
||||
@@ -41,10 +65,22 @@
|
||||
padding-bottom: 4px; /* puts the bottom border down */
|
||||
}
|
||||
|
||||
.monaco-workbench > .part.panel > .title > .panel-switcher-container > .monaco-action-bar .action-item .action-label.checked {
|
||||
.monaco-workbench > .part.panel > .title > .panel-switcher-container > .monaco-action-bar .action-item.checked .action-label {
|
||||
border-bottom: 1px solid;
|
||||
}
|
||||
|
||||
.monaco-workbench > .part.panel > .title > .panel-switcher-container > .monaco-action-bar .badge .badge-content {
|
||||
top: 8px;
|
||||
right: 0px;
|
||||
position: absolute;
|
||||
font-size: 11px;
|
||||
min-width: 6px;
|
||||
line-height: 18px;
|
||||
padding: 0 5px;
|
||||
border-radius: 20px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/** Actions */
|
||||
|
||||
.monaco-workbench .panel .monaco-action-bar .action-item.select-container {
|
||||
@@ -53,6 +89,7 @@
|
||||
|
||||
.monaco-workbench .panel .monaco-action-bar .action-item .select-box {
|
||||
cursor: pointer;
|
||||
min-width: 110px;
|
||||
}
|
||||
|
||||
.monaco-workbench .hide-panel-action {
|
||||
@@ -60,24 +97,65 @@
|
||||
}
|
||||
|
||||
.monaco-workbench .maximize-panel-action {
|
||||
background: url('up.svg') center center no-repeat;
|
||||
background-image: url('up.svg');
|
||||
}
|
||||
|
||||
.monaco-workbench .panel.right .maximize-panel-action {
|
||||
background-image: url('left.svg');
|
||||
}
|
||||
|
||||
.vs-dark .monaco-workbench .maximize-panel-action,
|
||||
.hc-black .monaco-workbench .maximize-panel-action {
|
||||
background: url('up-inverse.svg') center center no-repeat;
|
||||
background-image: url('up-inverse.svg');
|
||||
}
|
||||
|
||||
.vs-dark .monaco-workbench .panel.right .maximize-panel-action,
|
||||
.hc-black .monaco-workbench .panel.right .maximize-panel-action {
|
||||
background-image: url('left-inverse.svg');
|
||||
}
|
||||
|
||||
.monaco-workbench .minimize-panel-action {
|
||||
background: url('down.svg') center center no-repeat;
|
||||
background-image: url('down.svg');
|
||||
}
|
||||
|
||||
.monaco-workbench .panel.right .minimize-panel-action {
|
||||
background-image: url('right.svg');
|
||||
}
|
||||
|
||||
.vs-dark .monaco-workbench .minimize-panel-action,
|
||||
.hc-black .monaco-workbench .minimize-panel-action {
|
||||
background: url('down-inverse.svg') center center no-repeat;
|
||||
background-image: url('down-inverse.svg');
|
||||
}
|
||||
|
||||
.vs-dark .monaco-workbench .panel.right .minimize-panel-action,
|
||||
.hc-black .monaco-workbench .panel.right .minimize-panel-action {
|
||||
background-image: url('right-inverse.svg');
|
||||
}
|
||||
|
||||
.monaco-workbench .move-panel-to-right {
|
||||
background: url('panel-right.svg') center center no-repeat;
|
||||
}
|
||||
|
||||
.vs-dark .monaco-workbench .move-panel-to-right,
|
||||
.hc-black .monaco-workbench .move-panel-to-right {
|
||||
background: url('panel-right-inverse.svg') center center no-repeat;
|
||||
}
|
||||
|
||||
.monaco-workbench .move-panel-to-bottom {
|
||||
background: url('panel-bottom.svg') center center no-repeat;
|
||||
}
|
||||
|
||||
.vs-dark .monaco-workbench .move-panel-to-bottom,
|
||||
.hc-black .monaco-workbench .move-panel-to-bottom {
|
||||
background: url('panel-bottom-inverse.svg') center center no-repeat;
|
||||
}
|
||||
|
||||
.vs-dark .monaco-workbench .hide-panel-action,
|
||||
.hc-black .monaco-workbench .hide-panel-action {
|
||||
background: url('close-inverse.svg') center center no-repeat;
|
||||
}
|
||||
}
|
||||
|
||||
.vs-dark .monaco-workbench > .part.panel > .title > .panel-switcher-container.composite-bar > .monaco-action-bar .action-label.toggle-more,
|
||||
.hc-black .monaco-workbench > .part.panel > .title > .panel-switcher-container.composite-bar > .monaco-action-bar .action-label.toggle-more {
|
||||
background: url('ellipsis-inverse.svg') center center no-repeat;
|
||||
}
|
||||
|
||||
36
src/vs/workbench/browser/parts/panel/media/right-inverse.svg
Normal file
@@ -0,0 +1,36 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
viewBox="0 0 16 16"
|
||||
version="1.1"
|
||||
id="svg3828"
|
||||
sodipodi:docname="right-inverse.svg"
|
||||
inkscape:version="0.92.2 5c3e80d, 2017-08-06">
|
||||
<style
|
||||
id="style3821">.icon-canvas-transparent{opacity:0;fill:#2d2d30}.icon-vs-out{fill:#2d2d30}.icon-vs-bg{fill:#c5c5c5}</style>
|
||||
<path
|
||||
class="icon-canvas-transparent"
|
||||
d="M16 16H0V0h16v16z"
|
||||
id="canvas" />
|
||||
<path
|
||||
class="icon-vs-out"
|
||||
d="M.379 5.652l2.828-2.828 4.76 4.761 4.826-4.826 2.828 2.828-7.654 7.654L.379 5.652z"
|
||||
id="outline"
|
||||
style="display: none;" />
|
||||
<g
|
||||
id="iconBg"
|
||||
transform="rotate(-90,8.1705757,8.1023454)">
|
||||
<path
|
||||
class="icon-vs-bg"
|
||||
d="M 7.967,11.827 1.793,5.652 3.207,4.238 l 4.76,4.761 4.826,-4.826 1.414,1.414 z"
|
||||
id="path3825"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#c5c5c5" />
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.2 KiB |
36
src/vs/workbench/browser/parts/panel/media/right.svg
Normal file
@@ -0,0 +1,36 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
viewBox="0 0 16 16"
|
||||
version="1.1"
|
||||
id="svg3828"
|
||||
sodipodi:docname="right.svg"
|
||||
inkscape:version="0.92.2 5c3e80d, 2017-08-06">
|
||||
<style
|
||||
id="style3821">.icon-canvas-transparent{opacity:0;fill:#f6f6f6}.icon-vs-out{fill:#f6f6f6}.icon-vs-bg{fill:#424242}</style>
|
||||
<path
|
||||
class="icon-canvas-transparent"
|
||||
d="M16 16H0V0h16v16z"
|
||||
id="canvas" />
|
||||
<path
|
||||
class="icon-vs-out"
|
||||
d="M.379 5.652l2.828-2.828 4.76 4.761 4.826-4.826 2.828 2.828-7.654 7.654L.379 5.652z"
|
||||
id="outline"
|
||||
style="display: none;" />
|
||||
<g
|
||||
id="iconBg"
|
||||
transform="rotate(-90,8.1705757,8.1023454)">
|
||||
<path
|
||||
class="icon-vs-bg"
|
||||
d="M 7.967,11.827 1.793,5.652 3.207,4.238 l 4.76,4.761 4.826,-4.826 1.414,1.414 z"
|
||||
id="path3825"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#424242" />
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.2 KiB |
@@ -11,45 +11,12 @@ import { KeyMod, KeyCode } from 'vs/base/common/keyCodes';
|
||||
import { Action } from 'vs/base/common/actions';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { SyncActionDescriptor } from 'vs/platform/actions/common/actions';
|
||||
import { IWorkbenchActionRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/actionRegistry';
|
||||
import { IPanelService, IPanelIdentifier } from 'vs/workbench/services/panel/common/panelService';
|
||||
import { IPartService, Parts } from 'vs/workbench/services/part/common/partService';
|
||||
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
||||
|
||||
export class PanelAction extends Action {
|
||||
|
||||
constructor(
|
||||
private panel: IPanelIdentifier,
|
||||
@IKeybindingService private keybindingService: IKeybindingService,
|
||||
@IPanelService private panelService: IPanelService
|
||||
) {
|
||||
super(panel.id, panel.name);
|
||||
|
||||
this.tooltip = nls.localize('panelActionTooltip', "{0} ({1})", panel.name, this.getKeybindingLabel(panel.commandId));
|
||||
}
|
||||
|
||||
public run(event): TPromise<any> {
|
||||
return this.panelService.openPanel(this.panel.id, true).then(() => this.activate());
|
||||
}
|
||||
|
||||
public activate(): void {
|
||||
if (!this.checked) {
|
||||
this._setChecked(true);
|
||||
}
|
||||
}
|
||||
|
||||
public deactivate(): void {
|
||||
if (this.checked) {
|
||||
this._setChecked(false);
|
||||
}
|
||||
}
|
||||
|
||||
private getKeybindingLabel(id: string): string {
|
||||
const keys = this.keybindingService.lookupKeybinding(id);
|
||||
|
||||
return keys ? keys.getLabel() : '';
|
||||
}
|
||||
}
|
||||
import { IWorkbenchActionRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/actions';
|
||||
import { IPanelService } from 'vs/workbench/services/panel/common/panelService';
|
||||
import { IPartService, Parts, Position } from 'vs/workbench/services/part/common/partService';
|
||||
import { ActivityAction } from 'vs/workbench/browser/parts/compositebar/compositeBarActions';
|
||||
import { IActivity } from 'vs/workbench/common/activity';
|
||||
import { IConfigurationService, ConfigurationTarget } from 'vs/platform/configuration/common/configuration';
|
||||
|
||||
export class ClosePanelAction extends Action {
|
||||
static ID = 'workbench.action.closePanel';
|
||||
@@ -115,6 +82,46 @@ class FocusPanelAction extends Action {
|
||||
}
|
||||
}
|
||||
|
||||
export class TogglePanelPositionAction extends Action {
|
||||
|
||||
public static ID = 'workbench.action.togglePanelPosition';
|
||||
public static LABEL = nls.localize('toggledPanelPosition', "Toggle Panel Position");
|
||||
private static MOVE_TO_RIGHT_LABEL = nls.localize('moveToRight', "Move to Right");
|
||||
private static MOVE_TO_BOTTOM_LABEL = nls.localize('moveToBottom', "Move to Bottom");
|
||||
private static panelPositionConfigurationKey = 'workbench.panel.location';
|
||||
private toDispose: IDisposable[];
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
label: string,
|
||||
@IPartService private partService: IPartService,
|
||||
@IConfigurationService private configurationService: IConfigurationService
|
||||
|
||||
) {
|
||||
super(id, label, partService.getPanelPosition() === Position.RIGHT ? 'move-panel-to-bottom' : 'move-panel-to-right');
|
||||
this.toDispose = [];
|
||||
const setClassAndLabel = () => {
|
||||
const positionRight = this.partService.getPanelPosition() === Position.RIGHT;
|
||||
this.class = positionRight ? 'move-panel-to-bottom' : 'move-panel-to-right';
|
||||
this.label = positionRight ? TogglePanelPositionAction.MOVE_TO_BOTTOM_LABEL : TogglePanelPositionAction.MOVE_TO_RIGHT_LABEL;
|
||||
};
|
||||
this.toDispose.push(partService.onEditorLayout(() => setClassAndLabel()));
|
||||
setClassAndLabel();
|
||||
}
|
||||
|
||||
public run(): TPromise<any> {
|
||||
const position = this.partService.getPanelPosition();
|
||||
const newPositionValue = (position === Position.BOTTOM) ? 'right' : 'bottom';
|
||||
|
||||
return this.configurationService.updateValue(TogglePanelPositionAction.panelPositionConfigurationKey, newPositionValue, ConfigurationTarget.USER);
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
super.dispose();
|
||||
this.toDispose = dispose(this.toDispose);
|
||||
}
|
||||
}
|
||||
|
||||
export class ToggleMaximizedPanelAction extends Action {
|
||||
|
||||
public static ID = 'workbench.action.toggleMaximizedPanel';
|
||||
@@ -139,7 +146,7 @@ export class ToggleMaximizedPanelAction extends Action {
|
||||
|
||||
public run(): TPromise<any> {
|
||||
// Show panel
|
||||
return this.partService.setPanelHidden(false)
|
||||
return (!this.partService.isVisible(Parts.PANEL_PART) ? this.partService.setPanelHidden(false) : TPromise.as(null))
|
||||
.then(() => this.partService.toggleMaximizedPanel());
|
||||
}
|
||||
|
||||
@@ -149,8 +156,24 @@ export class ToggleMaximizedPanelAction extends Action {
|
||||
}
|
||||
}
|
||||
|
||||
export class PanelActivityAction extends ActivityAction {
|
||||
|
||||
constructor(
|
||||
activity: IActivity,
|
||||
@IPanelService private panelService: IPanelService
|
||||
) {
|
||||
super(activity);
|
||||
}
|
||||
|
||||
public run(event: any): TPromise<any> {
|
||||
return this.panelService.openPanel(this.activity.id, true).then(() => this.activate());
|
||||
}
|
||||
}
|
||||
|
||||
const actionRegistry = Registry.as<IWorkbenchActionRegistry>(WorkbenchExtensions.WorkbenchActions);
|
||||
actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(TogglePanelAction, TogglePanelAction.ID, TogglePanelAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.KEY_J }), 'View: Toggle Panel', nls.localize('view', "View"));
|
||||
actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(FocusPanelAction, FocusPanelAction.ID, FocusPanelAction.LABEL), 'View: Focus into Panel', nls.localize('view', "View"));
|
||||
actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(ToggleMaximizedPanelAction, ToggleMaximizedPanelAction.ID, ToggleMaximizedPanelAction.LABEL), 'View: Toggle Maximized Panel', nls.localize('view', "View"));
|
||||
actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(ClosePanelAction, ClosePanelAction.ID, ClosePanelAction.LABEL), 'View: Close Panel', nls.localize('view', "View"));
|
||||
actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(ClosePanelAction, ClosePanelAction.ID, ClosePanelAction.LABEL), 'View: Close Panel', nls.localize('view', "View"));
|
||||
actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(TogglePanelPositionAction, TogglePanelPositionAction.ID, TogglePanelPositionAction.LABEL), 'View: Toggle Panel Position', nls.localize('view', "View"));
|
||||
actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(ToggleMaximizedPanelAction, ToggleMaximizedPanelAction.ID, undefined), 'View: Toggle Panel Position', nls.localize('view', "View"));
|
||||
|
||||
@@ -4,40 +4,45 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import 'vs/css!./media/panelpart';
|
||||
import nls = require('vs/nls');
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { IAction } from 'vs/base/common/actions';
|
||||
import { IAction, Action } from 'vs/base/common/actions';
|
||||
import Event from 'vs/base/common/event';
|
||||
import { Builder, $ } from 'vs/base/browser/builder';
|
||||
import { Builder, Dimension } from 'vs/base/browser/builder';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { ActionsOrientation } from 'vs/base/browser/ui/actionbar/actionbar';
|
||||
import { Scope } from 'vs/workbench/browser/actions';
|
||||
import { IPanel } from 'vs/workbench/common/panel';
|
||||
import { CompositePart, ICompositeTitleLabel } from 'vs/workbench/browser/parts/compositePart';
|
||||
import { Panel, PanelRegistry, Extensions as PanelExtensions } from 'vs/workbench/browser/panel';
|
||||
import { IPanelService, IPanelIdentifier } from 'vs/workbench/services/panel/common/panelService';
|
||||
import { IPartService, Parts } from 'vs/workbench/services/part/common/partService';
|
||||
import { IPartService, Parts, Position } from 'vs/workbench/services/part/common/partService';
|
||||
import { IStorageService } from 'vs/platform/storage/common/storage';
|
||||
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
|
||||
import { IMessageService } from 'vs/platform/message/common/message';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { ActionsOrientation, ActionBar } from 'vs/base/browser/ui/actionbar/actionbar';
|
||||
import { ClosePanelAction, PanelAction, ToggleMaximizedPanelAction } from 'vs/workbench/browser/parts/panel/panelActions';
|
||||
import { ClosePanelAction, TogglePanelPositionAction, PanelActivityAction, ToggleMaximizedPanelAction } from 'vs/workbench/browser/parts/panel/panelActions';
|
||||
import { IThemeService, registerThemingParticipant, ITheme, ICssStyleCollector } from 'vs/platform/theme/common/themeService';
|
||||
import { PANEL_BACKGROUND, PANEL_BORDER, PANEL_ACTIVE_TITLE_FOREGROUND, PANEL_INACTIVE_TITLE_FOREGROUND, PANEL_ACTIVE_TITLE_BORDER } from 'vs/workbench/common/theme';
|
||||
import { activeContrastBorder, focusBorder, contrastBorder, editorBackground } from 'vs/platform/theme/common/colorRegistry';
|
||||
import { PANEL_BACKGROUND, PANEL_BORDER, PANEL_ACTIVE_TITLE_FOREGROUND, PANEL_INACTIVE_TITLE_FOREGROUND, PANEL_ACTIVE_TITLE_BORDER, PANEL_DRAG_AND_DROP_BACKGROUND } from 'vs/workbench/common/theme';
|
||||
import { activeContrastBorder, focusBorder, contrastBorder, editorBackground, badgeBackground, badgeForeground } from 'vs/platform/theme/common/colorRegistry';
|
||||
import { CompositeBar } from 'vs/workbench/browser/parts/compositebar/compositeBar';
|
||||
import { ToggleCompositePinnedAction } from 'vs/workbench/browser/parts/compositebar/compositeBarActions';
|
||||
import { StandardMouseEvent } from 'vs/base/browser/mouseEvent';
|
||||
import { dispose, IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { IBadge } from 'vs/workbench/services/activity/common/activity';
|
||||
|
||||
export class PanelPart extends CompositePart<Panel> implements IPanelService {
|
||||
|
||||
public static activePanelSettingsKey = 'workbench.panelpart.activepanelid';
|
||||
private static readonly PINNED_PANELS = 'workbench.panel.pinnedPanels';
|
||||
private static readonly MIN_COMPOSITE_BAR_WIDTH = 50;
|
||||
|
||||
public _serviceBrand: any;
|
||||
|
||||
private blockOpeningPanel: boolean;
|
||||
private panelSwitcherBar: ActionBar;
|
||||
|
||||
private panelIdToActions: { [panelId: string]: PanelAction; };
|
||||
private compositeBar: CompositeBar;
|
||||
private dimension: Dimension;
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
@@ -48,7 +53,7 @@ export class PanelPart extends CompositePart<Panel> implements IPanelService {
|
||||
@IPartService partService: IPartService,
|
||||
@IKeybindingService keybindingService: IKeybindingService,
|
||||
@IInstantiationService instantiationService: IInstantiationService,
|
||||
@IThemeService themeService: IThemeService
|
||||
@IThemeService themeService: IThemeService,
|
||||
) {
|
||||
super(
|
||||
messageService,
|
||||
@@ -70,7 +75,26 @@ export class PanelPart extends CompositePart<Panel> implements IPanelService {
|
||||
{ hasTitle: true }
|
||||
);
|
||||
|
||||
this.panelIdToActions = Object.create(null);
|
||||
this.compositeBar = this.instantiationService.createInstance(CompositeBar, {
|
||||
icon: false,
|
||||
storageId: PanelPart.PINNED_PANELS,
|
||||
orientation: ActionsOrientation.HORIZONTAL,
|
||||
composites: this.getPanels(),
|
||||
openComposite: (compositeId: string) => this.openPanel(compositeId, true),
|
||||
getActivityAction: (compositeId: string) => this.instantiationService.createInstance(PanelActivityAction, this.getPanel(compositeId)),
|
||||
getCompositePinnedAction: (compositeId: string) => new ToggleCompositePinnedAction(this.getPanel(compositeId), this.compositeBar),
|
||||
getOnCompositeClickAction: (compositeId: string) => this.instantiationService.createInstance(PanelActivityAction, this.getPanel(compositeId)),
|
||||
getDefaultCompositeId: () => Registry.as<PanelRegistry>(PanelExtensions.Panels).getDefaultPanelId(),
|
||||
hidePart: () => this.partService.setPanelHidden(true),
|
||||
overflowActionSize: 28,
|
||||
colors: {
|
||||
backgroundColor: PANEL_BACKGROUND,
|
||||
badgeBackground,
|
||||
badgeForeground,
|
||||
dragAndDropBackground: PANEL_DRAG_AND_DROP_BACKGROUND
|
||||
}
|
||||
});
|
||||
this.toUnbind.push(this.compositeBar);
|
||||
|
||||
this.registerListeners();
|
||||
}
|
||||
@@ -78,16 +102,15 @@ export class PanelPart extends CompositePart<Panel> implements IPanelService {
|
||||
private registerListeners(): void {
|
||||
|
||||
// Activate panel action on opening of a panel
|
||||
this.toUnbind.push(this.onDidPanelOpen(panel => this.updatePanelActions(panel.getId(), true)));
|
||||
this.toUnbind.push(this.onDidPanelOpen(panel => {
|
||||
this.compositeBar.activateComposite(panel.getId());
|
||||
// Need to relayout composite bar since different panels have different action bar width
|
||||
this.layoutCompositeBar();
|
||||
}));
|
||||
|
||||
// Deactivate panel action on close
|
||||
this.toUnbind.push(this.onDidPanelClose(panel => this.updatePanelActions(panel.getId(), false)));
|
||||
}
|
||||
|
||||
private updatePanelActions(id: string, didOpen: boolean): void {
|
||||
if (this.panelIdToActions[id]) {
|
||||
didOpen ? this.panelIdToActions[id].activate() : this.panelIdToActions[id].deactivate();
|
||||
}
|
||||
this.toUnbind.push(this.onDidPanelClose(panel => this.compositeBar.deactivateComposite(panel.getId())));
|
||||
this.toUnbind.push(this.compositeBar.onDidContextMenu(e => this.showContextMenu(e)));
|
||||
}
|
||||
|
||||
public get onDidPanelOpen(): Event<IPanel> {
|
||||
@@ -98,11 +121,12 @@ export class PanelPart extends CompositePart<Panel> implements IPanelService {
|
||||
return this._onDidCompositeClose.event;
|
||||
}
|
||||
|
||||
protected updateStyles(): void {
|
||||
public updateStyles(): void {
|
||||
super.updateStyles();
|
||||
|
||||
const container = this.getContainer();
|
||||
container.style('background-color', this.getColor(PANEL_BACKGROUND));
|
||||
container.style('border-left-color', this.getColor(PANEL_BORDER) || this.getColor(contrastBorder));
|
||||
|
||||
const title = this.getTitleArea();
|
||||
title.style('border-top-color', this.getColor(PANEL_BORDER) || this.getColor(contrastBorder));
|
||||
@@ -127,6 +151,25 @@ export class PanelPart extends CompositePart<Panel> implements IPanelService {
|
||||
return promise.then(() => this.openComposite(id, focus));
|
||||
}
|
||||
|
||||
public showActivity(panelId: string, badge: IBadge, clazz?: string): IDisposable {
|
||||
return this.compositeBar.showActivity(panelId, badge, clazz);
|
||||
}
|
||||
|
||||
private getPanel(panelId: string): IPanelIdentifier {
|
||||
return Registry.as<PanelRegistry>(PanelExtensions.Panels).getPanels().filter(p => p.id === panelId).pop();
|
||||
}
|
||||
|
||||
private showContextMenu(e: MouseEvent): void {
|
||||
const event = new StandardMouseEvent(e);
|
||||
const actions: Action[] = this.getPanels().map(panel => this.instantiationService.createInstance(ToggleCompositePinnedAction, panel, this.compositeBar));
|
||||
|
||||
this.contextMenuService.showContextMenu({
|
||||
getAnchor: () => { return { x: event.posx, y: event.posy }; },
|
||||
getActions: () => TPromise.as(actions),
|
||||
onHide: () => dispose(actions)
|
||||
});
|
||||
}
|
||||
|
||||
public getPanels(): IPanelIdentifier[] {
|
||||
return Registry.as<PanelRegistry>(PanelExtensions.Panels).getPanels()
|
||||
.sort((v1, v2) => v1.order - v2.order);
|
||||
@@ -135,6 +178,7 @@ export class PanelPart extends CompositePart<Panel> implements IPanelService {
|
||||
protected getActions(): IAction[] {
|
||||
return [
|
||||
this.instantiationService.createInstance(ToggleMaximizedPanelAction, ToggleMaximizedPanelAction.ID, ToggleMaximizedPanelAction.LABEL),
|
||||
this.instantiationService.createInstance(TogglePanelPositionAction, TogglePanelPositionAction.ID, TogglePanelPositionAction.LABEL),
|
||||
this.instantiationService.createInstance(ClosePanelAction, ClosePanelAction.ID, ClosePanelAction.LABEL)
|
||||
];
|
||||
}
|
||||
@@ -152,23 +196,12 @@ export class PanelPart extends CompositePart<Panel> implements IPanelService {
|
||||
}
|
||||
|
||||
protected createTitleLabel(parent: Builder): ICompositeTitleLabel {
|
||||
let titleArea = $(parent).div({
|
||||
'class': ['panel-switcher-container']
|
||||
});
|
||||
|
||||
// Show a panel switcher
|
||||
this.panelSwitcherBar = new ActionBar(titleArea, {
|
||||
orientation: ActionsOrientation.HORIZONTAL,
|
||||
ariaLabel: nls.localize('panelSwitcherBarAriaLabel', "Active Panel Switcher"),
|
||||
animated: false
|
||||
});
|
||||
this.toUnbind.push(this.panelSwitcherBar);
|
||||
|
||||
this.fillPanelSwitcher();
|
||||
const titleArea = this.compositeBar.create(parent.getHTMLElement());
|
||||
titleArea.classList.add('panel-switcher-container');
|
||||
|
||||
return {
|
||||
updateTitle: (id, title, keybinding) => {
|
||||
const action = this.panelIdToActions[id];
|
||||
const action = this.compositeBar.getAction(id);
|
||||
if (action) {
|
||||
action.label = title;
|
||||
}
|
||||
@@ -179,17 +212,37 @@ export class PanelPart extends CompositePart<Panel> implements IPanelService {
|
||||
};
|
||||
}
|
||||
|
||||
private fillPanelSwitcher(): void {
|
||||
const panels = this.getPanels();
|
||||
public layout(dimension: Dimension): Dimension[] {
|
||||
|
||||
this.panelSwitcherBar.push(panels.map(panel => {
|
||||
const action = this.instantiationService.createInstance(PanelAction, panel);
|
||||
if (this.partService.getPanelPosition() === Position.RIGHT) {
|
||||
// Take into account the 1px border when layouting
|
||||
this.dimension = new Dimension(dimension.width - 1, dimension.height);
|
||||
} else {
|
||||
this.dimension = dimension;
|
||||
}
|
||||
const sizes = super.layout(this.dimension);
|
||||
this.layoutCompositeBar();
|
||||
|
||||
this.panelIdToActions[panel.id] = action;
|
||||
this.toUnbind.push(action);
|
||||
return sizes;
|
||||
}
|
||||
|
||||
return action;
|
||||
}));
|
||||
private layoutCompositeBar(): void {
|
||||
if (this.dimension) {
|
||||
let availableWidth = this.dimension.width - 8; // take padding into account
|
||||
if (this.toolBar) {
|
||||
// adjust height for global actions showing
|
||||
availableWidth = Math.max(PanelPart.MIN_COMPOSITE_BAR_WIDTH, availableWidth - this.toolBar.getContainer().getHTMLElement().offsetWidth);
|
||||
}
|
||||
this.compositeBar.layout(new Dimension(availableWidth, this.dimension.height));
|
||||
}
|
||||
}
|
||||
|
||||
public shutdown(): void {
|
||||
// Persist Hidden State
|
||||
this.compositeBar.store();
|
||||
|
||||
// Pass to super
|
||||
super.shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -215,7 +268,7 @@ registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => {
|
||||
if (titleActive || titleActiveBorder) {
|
||||
collector.addRule(`
|
||||
.monaco-workbench > .part.panel > .title > .panel-switcher-container > .monaco-action-bar .action-item:hover .action-label,
|
||||
.monaco-workbench > .part.panel > .title > .panel-switcher-container > .monaco-action-bar .action-item .action-label.checked {
|
||||
.monaco-workbench > .part.panel > .title > .panel-switcher-container > .monaco-action-bar .action-item.checked .action-label {
|
||||
color: ${titleActive};
|
||||
border-bottom-color: ${titleActiveBorder};
|
||||
}
|
||||
@@ -236,13 +289,17 @@ registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => {
|
||||
const focusBorderColor = theme.getColor(focusBorder);
|
||||
if (focusBorderColor) {
|
||||
collector.addRule(`
|
||||
.monaco-workbench > .part.panel > .title > .panel-switcher-container > .monaco-action-bar .action-item .action-label:focus {
|
||||
.monaco-workbench > .part.panel > .title > .panel-switcher-container > .monaco-action-bar .action-item:focus .action-label {
|
||||
color: ${titleActive};
|
||||
border-bottom-color: ${focusBorderColor} !important;
|
||||
border-bottom: 1px solid;
|
||||
}
|
||||
`);
|
||||
collector.addRule(`
|
||||
.monaco-workbench > .part.panel > .title > .panel-switcher-container > .monaco-action-bar .action-item:focus {
|
||||
outline: none;
|
||||
}
|
||||
`);
|
||||
`);
|
||||
}
|
||||
|
||||
// Styling with Outline color (e.g. high contrast theme)
|
||||
@@ -251,7 +308,7 @@ registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => {
|
||||
const outline = theme.getColor(activeContrastBorder);
|
||||
|
||||
collector.addRule(`
|
||||
.monaco-workbench > .part.panel > .title > .panel-switcher-container > .monaco-action-bar .action-item .action-label.checked,
|
||||
.monaco-workbench > .part.panel > .title > .panel-switcher-container > .monaco-action-bar .action-item.checked .action-label,
|
||||
.monaco-workbench > .part.panel > .title > .panel-switcher-container > .monaco-action-bar .action-item .action-label:hover {
|
||||
outline-color: ${outline};
|
||||
outline-width: 1px;
|
||||
@@ -261,7 +318,7 @@ registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => {
|
||||
outline-offset: 3px;
|
||||
}
|
||||
|
||||
.monaco-workbench > .part.panel > .title > .panel-switcher-container > .monaco-action-bar .action-item .action-label:hover:not(.checked) {
|
||||
.monaco-workbench > .part.panel > .title > .panel-switcher-container > .monaco-action-bar .action-item:not(.checked) .action-label:hover {
|
||||
outline-style: dashed;
|
||||
}
|
||||
`);
|
||||
|
||||
@@ -14,13 +14,14 @@ import strings = require('vs/base/common/strings');
|
||||
import filters = require('vs/base/common/filters');
|
||||
import DOM = require('vs/base/browser/dom');
|
||||
import URI from 'vs/base/common/uri';
|
||||
import * as resources from 'vs/base/common/resources';
|
||||
import { defaultGenerator } from 'vs/base/common/idGenerator';
|
||||
import types = require('vs/base/common/types');
|
||||
import { Action, IAction } from 'vs/base/common/actions';
|
||||
import { IIconLabelOptions } from 'vs/base/browser/ui/iconLabel/iconLabel';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { Mode, IEntryRunContext, IAutoFocus, IQuickNavigateConfiguration, IModel } from 'vs/base/parts/quickopen/common/quickOpen';
|
||||
import { QuickOpenEntry, QuickOpenModel, QuickOpenEntryGroup } from 'vs/base/parts/quickopen/browser/quickOpenModel';
|
||||
import { QuickOpenEntry, QuickOpenModel, QuickOpenEntryGroup, compareEntries, QuickOpenItemAccessorClass } from 'vs/base/parts/quickopen/browser/quickOpenModel';
|
||||
import { QuickOpenWidget, HideReason } from 'vs/base/parts/quickopen/browser/quickOpenWidget';
|
||||
import { ContributableActionProvider } from 'vs/workbench/browser/actions';
|
||||
import labels = require('vs/base/common/labels');
|
||||
@@ -31,12 +32,12 @@ import { IResourceInput, IEditorInput } from 'vs/platform/editor/common/editor';
|
||||
import { IModeService } from 'vs/editor/common/services/modeService';
|
||||
import { getIconClasses } from 'vs/workbench/browser/labels';
|
||||
import { IModelService } from 'vs/editor/common/services/modelService';
|
||||
import { EditorInput, toResource, IWorkbenchEditorConfiguration } from 'vs/workbench/common/editor';
|
||||
import { EditorInput, IWorkbenchEditorConfiguration } from 'vs/workbench/common/editor';
|
||||
import { Component } from 'vs/workbench/common/component';
|
||||
import Event, { Emitter } from 'vs/base/common/event';
|
||||
import { IPartService } from 'vs/workbench/services/part/common/partService';
|
||||
import { KeyMod } from 'vs/base/common/keyCodes';
|
||||
import { QuickOpenHandler, QuickOpenHandlerDescriptor, IQuickOpenRegistry, Extensions, EditorQuickOpenEntry, IWorkbenchQuickOpenConfiguration } from 'vs/workbench/browser/quickopen';
|
||||
import { QuickOpenHandler, QuickOpenHandlerDescriptor, IQuickOpenRegistry, Extensions, EditorQuickOpenEntry, CLOSE_ON_FOCUS_LOST_CONFIG } from 'vs/workbench/browser/quickopen';
|
||||
import errors = require('vs/base/common/errors');
|
||||
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { IPickOpenEntry, IFilePickOpenEntry, IInputOptions, IQuickOpenService, IPickOptions, IShowOptions, IPickOpenItem } from 'vs/platform/quickOpen/common/quickOpen';
|
||||
@@ -54,7 +55,8 @@ import { attachQuickOpenStyler } from 'vs/platform/theme/common/styler';
|
||||
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
import { ITree, IActionProvider } from 'vs/base/parts/tree/browser/tree';
|
||||
import { BaseActionItem } from 'vs/base/browser/ui/actionbar/actionbar';
|
||||
import { FileKind } from 'vs/platform/files/common/files';
|
||||
import { FileKind, IFileService } from 'vs/platform/files/common/files';
|
||||
import { scoreItem, ScorerCache, compareItemsByScore, prepareQuery } from 'vs/base/parts/quickopen/common/quickOpenScorer';
|
||||
|
||||
const HELP_PREFIX = '?';
|
||||
|
||||
@@ -99,6 +101,7 @@ export class QuickOpenController extends Component implements IQuickOpenService
|
||||
private previousValue = '';
|
||||
private visibilityChangeTimeoutHandle: number;
|
||||
private closeOnFocusLost: boolean;
|
||||
private editorHistoryHandler: EditorHistoryHandler;
|
||||
|
||||
constructor(
|
||||
@IWorkbenchEditorService private editorService: IWorkbenchEditorService,
|
||||
@@ -111,6 +114,7 @@ export class QuickOpenController extends Component implements IQuickOpenService
|
||||
@IInstantiationService private instantiationService: IInstantiationService,
|
||||
@IPartService private partService: IPartService,
|
||||
@IListService private listService: IListService,
|
||||
@IEnvironmentService private environmentService: IEnvironmentService,
|
||||
@IThemeService themeService: IThemeService
|
||||
) {
|
||||
super(QuickOpenController.ID, themeService);
|
||||
@@ -121,24 +125,30 @@ export class QuickOpenController extends Component implements IQuickOpenService
|
||||
|
||||
this.promisesToCompleteOnHide = [];
|
||||
|
||||
this.editorHistoryHandler = this.instantiationService.createInstance(EditorHistoryHandler);
|
||||
|
||||
this.inQuickOpenMode = new RawContextKey<boolean>('inQuickOpen', false).bindTo(contextKeyService);
|
||||
|
||||
this._onShow = new Emitter<void>();
|
||||
this._onHide = new Emitter<void>();
|
||||
|
||||
this.updateConfiguration(<IWorkbenchQuickOpenConfiguration>this.configurationService.getConfiguration());
|
||||
this.updateConfiguration();
|
||||
|
||||
this.registerListeners();
|
||||
}
|
||||
|
||||
private registerListeners(): void {
|
||||
this.toUnbind.push(this.configurationService.onDidUpdateConfiguration(e => this.updateConfiguration(this.configurationService.getConfiguration<IWorkbenchQuickOpenConfiguration>())));
|
||||
this.toUnbind.push(this.configurationService.onDidChangeConfiguration(e => this.updateConfiguration()));
|
||||
this.toUnbind.push(this.partService.onTitleBarVisibilityChange(() => this.positionQuickOpenWidget()));
|
||||
this.toUnbind.push(browser.onDidChangeZoomLevel(() => this.positionQuickOpenWidget()));
|
||||
}
|
||||
|
||||
private updateConfiguration(settings: IWorkbenchQuickOpenConfiguration): void {
|
||||
this.closeOnFocusLost = settings.workbench && settings.workbench.quickOpen && settings.workbench.quickOpen.closeOnFocusLost;
|
||||
private updateConfiguration(): void {
|
||||
if (this.environmentService.args['sticky-quickopen']) {
|
||||
this.closeOnFocusLost = false;
|
||||
} else {
|
||||
this.closeOnFocusLost = this.configurationService.getValue(CLOSE_ON_FOCUS_LOST_CONFIG);
|
||||
}
|
||||
}
|
||||
|
||||
public get onShow(): Event<void> {
|
||||
@@ -169,7 +179,7 @@ export class QuickOpenController extends Component implements IQuickOpenService
|
||||
: nls.localize('inputModeEntry', "Press 'Enter' to confirm your input or 'Escape' to cancel");
|
||||
|
||||
let currentPick = defaultMessage;
|
||||
let currentValidation = TPromise.as(true);
|
||||
let currentValidation: TPromise<boolean>;
|
||||
let currentDecoration: Severity;
|
||||
let lastValue: string;
|
||||
|
||||
@@ -186,35 +196,49 @@ export class QuickOpenController extends Component implements IQuickOpenService
|
||||
valueSelection: options.valueSelection,
|
||||
inputDecoration: currentDecoration,
|
||||
onDidType: (value) => {
|
||||
lastValue = value;
|
||||
if (lastValue !== value) {
|
||||
|
||||
if (options.validateInput) {
|
||||
if (currentValidation) {
|
||||
currentValidation.cancel();
|
||||
}
|
||||
lastValue = value;
|
||||
|
||||
currentValidation = TPromise.timeout(100).then(() => {
|
||||
return options.validateInput(value).then(message => {
|
||||
currentDecoration = !!message ? Severity.Error : void 0;
|
||||
const newPick = message || defaultMessage;
|
||||
if (newPick !== currentPick) {
|
||||
options.valueSelection = [lastValue.length, lastValue.length];
|
||||
currentPick = newPick;
|
||||
resolve(new TPromise<any>(init));
|
||||
}
|
||||
if (options.validateInput) {
|
||||
if (currentValidation) {
|
||||
currentValidation.cancel();
|
||||
}
|
||||
|
||||
return !message;
|
||||
currentValidation = TPromise.timeout(100).then(() => {
|
||||
return options.validateInput(value).then(message => {
|
||||
currentDecoration = !!message ? Severity.Error : void 0;
|
||||
const newPick = message || defaultMessage;
|
||||
if (newPick !== currentPick) {
|
||||
options.valueSelection = [lastValue.length, lastValue.length];
|
||||
currentPick = newPick;
|
||||
resolve(new TPromise<any>(init));
|
||||
}
|
||||
|
||||
return !message;
|
||||
});
|
||||
}, err => {
|
||||
// ignore
|
||||
return null;
|
||||
});
|
||||
}, err => {
|
||||
// ignore
|
||||
return null;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}, token).then(resolve, reject);
|
||||
};
|
||||
|
||||
return new TPromise(init).then(item => {
|
||||
|
||||
if (!currentValidation) {
|
||||
if (options.validateInput) {
|
||||
currentValidation = options
|
||||
.validateInput(lastValue === void 0 ? options.value : lastValue)
|
||||
.then(message => !message);
|
||||
} else {
|
||||
currentValidation = TPromise.as(true);
|
||||
}
|
||||
}
|
||||
|
||||
return currentValidation.then(valid => {
|
||||
if (valid && item) {
|
||||
return lastValue === void 0 ? (options.value || '') : lastValue;
|
||||
@@ -439,7 +463,7 @@ export class QuickOpenController extends Component implements IQuickOpenService
|
||||
return pickA.index - pickB.index; // restore natural order
|
||||
}
|
||||
|
||||
return QuickOpenEntry.compare(pickA, pickB, normalizedSearchValue);
|
||||
return compareEntries(pickA, pickB, normalizedSearchValue);
|
||||
});
|
||||
|
||||
this.pickOpenWidget.refresh(model, value ? { autoFocusFirstEntry: true } : autoFocus);
|
||||
@@ -525,6 +549,7 @@ export class QuickOpenController extends Component implements IQuickOpenService
|
||||
public show(prefix?: string, options?: IShowOptions): TPromise<void> {
|
||||
let quickNavigateConfiguration = options ? options.quickNavigateConfiguration : void 0;
|
||||
let inputSelection = options ? options.inputSelection : void 0;
|
||||
let autoFocus = options ? options.autoFocus : void 0;
|
||||
|
||||
this.previousValue = prefix;
|
||||
|
||||
@@ -536,11 +561,16 @@ export class QuickOpenController extends Component implements IQuickOpenService
|
||||
const registry = Registry.as<IQuickOpenRegistry>(Extensions.Quickopen);
|
||||
const handlerDescriptor = registry.getQuickOpenHandler(prefix) || registry.getDefaultQuickOpenHandler();
|
||||
|
||||
/* __GDPR__
|
||||
"quickOpenWidgetShown" : {
|
||||
"mode" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
|
||||
"quickNavigate": { "${inline}": [ "${IQuickNavigateConfiguration}" ] }
|
||||
}
|
||||
*/
|
||||
this.telemetryService.publicLog('quickOpenWidgetShown', { mode: handlerDescriptor.getId(), quickNavigate: quickNavigateConfiguration });
|
||||
|
||||
// Trigger onOpen
|
||||
this.resolveHandler(handlerDescriptor)
|
||||
.done(null, errors.onUnexpectedError);
|
||||
this.resolveHandler(handlerDescriptor).done(null, errors.onUnexpectedError);
|
||||
|
||||
// Create upon first open
|
||||
if (!this.quickOpenWidget) {
|
||||
@@ -575,19 +605,21 @@ export class QuickOpenController extends Component implements IQuickOpenService
|
||||
// Show quick open with prefix or editor history
|
||||
if (!this.quickOpenWidget.isVisible() || quickNavigateConfiguration) {
|
||||
if (prefix) {
|
||||
this.quickOpenWidget.show(prefix, { quickNavigateConfiguration, inputSelection });
|
||||
this.quickOpenWidget.show(prefix, { quickNavigateConfiguration, inputSelection, autoFocus });
|
||||
} else {
|
||||
const editorHistory = this.getEditorHistoryWithGroupLabel();
|
||||
if (editorHistory.getEntries().length < 2) {
|
||||
quickNavigateConfiguration = null; // If no entries can be shown, default to normal quick open mode
|
||||
}
|
||||
|
||||
let autoFocus: IAutoFocus;
|
||||
if (!quickNavigateConfiguration) {
|
||||
autoFocus = { autoFocusFirstEntry: true };
|
||||
} else {
|
||||
const visibleEditorCount = this.editorService.getVisibleEditors().length;
|
||||
autoFocus = { autoFocusFirstEntry: visibleEditorCount === 0, autoFocusSecondEntry: visibleEditorCount !== 0 };
|
||||
// Compute auto focus
|
||||
if (!autoFocus) {
|
||||
if (!quickNavigateConfiguration) {
|
||||
autoFocus = { autoFocusFirstEntry: true };
|
||||
} else {
|
||||
const visibleEditorCount = this.editorService.getVisibleEditors().length;
|
||||
autoFocus = { autoFocusFirstEntry: visibleEditorCount === 0, autoFocusSecondEntry: visibleEditorCount !== 0 };
|
||||
}
|
||||
}
|
||||
|
||||
// Update context
|
||||
@@ -695,7 +727,7 @@ export class QuickOpenController extends Component implements IQuickOpenService
|
||||
}
|
||||
|
||||
private getEditorHistoryWithGroupLabel(): QuickOpenModel {
|
||||
const entries: QuickOpenEntry[] = this.getEditorHistoryEntries();
|
||||
const entries: QuickOpenEntry[] = this.editorHistoryHandler.getResults();
|
||||
|
||||
// Apply label to first entry
|
||||
if (entries.length > 0) {
|
||||
@@ -793,7 +825,7 @@ export class QuickOpenController extends Component implements IQuickOpenService
|
||||
private handleDefaultHandler(handler: QuickOpenHandlerDescriptor, value: string, currentResultToken: string): TPromise<void> {
|
||||
|
||||
// Fill in history results if matching
|
||||
const matchingHistoryEntries = this.getEditorHistoryEntries(value);
|
||||
const matchingHistoryEntries = this.editorHistoryHandler.getResults(value);
|
||||
if (matchingHistoryEntries.length > 0) {
|
||||
matchingHistoryEntries[0] = new EditorHistoryEntryGroup(matchingHistoryEntries[0], nls.localize('historyMatches', "recently opened"), false);
|
||||
}
|
||||
@@ -835,59 +867,6 @@ export class QuickOpenController extends Component implements IQuickOpenService
|
||||
});
|
||||
}
|
||||
|
||||
private getEditorHistoryEntries(searchValue?: string): QuickOpenEntry[] {
|
||||
if (searchValue) {
|
||||
searchValue = searchValue.replace(/ /g, ''); // get rid of all whitespace
|
||||
}
|
||||
|
||||
// Just return all if we are not searching
|
||||
const history = this.historyService.getHistory();
|
||||
if (!searchValue) {
|
||||
return history.map(input => this.instantiationService.createInstance(EditorHistoryEntry, input));
|
||||
}
|
||||
|
||||
const searchInPath = searchValue.indexOf(paths.nativeSep) >= 0;
|
||||
|
||||
const results: QuickOpenEntry[] = [];
|
||||
history.forEach(input => {
|
||||
let resource: URI;
|
||||
if (input instanceof EditorInput) {
|
||||
resource = toResource(input, { filter: ['file', 'untitled'] });
|
||||
} else {
|
||||
resource = (input as IResourceInput).resource;
|
||||
}
|
||||
|
||||
if (!resource) {
|
||||
return; //For now, only support to match on inputs that provide resource information
|
||||
}
|
||||
|
||||
let searchTargetToMatch: string;
|
||||
if (searchInPath) {
|
||||
searchTargetToMatch = labels.getPathLabel(resource, this.contextService);
|
||||
} else if (input instanceof EditorInput) {
|
||||
searchTargetToMatch = input.getName();
|
||||
} else {
|
||||
searchTargetToMatch = paths.basename((input as IResourceInput).resource.fsPath);
|
||||
}
|
||||
|
||||
// Check if this entry is a match for the search value
|
||||
if (!filters.matchesFuzzy(searchValue, searchTargetToMatch)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const entry = this.instantiationService.createInstance(EditorHistoryEntry, input);
|
||||
|
||||
const { labelHighlights, descriptionHighlights } = QuickOpenEntry.highlight(entry, searchValue);
|
||||
entry.setHighlights(labelHighlights, descriptionHighlights);
|
||||
|
||||
results.push(entry);
|
||||
});
|
||||
|
||||
// Sort
|
||||
const normalizedSearchValue = strings.stripWildcards(searchValue.toLowerCase());
|
||||
return results.sort((elementA: EditorHistoryEntry, elementB: EditorHistoryEntry) => QuickOpenEntry.compare(elementA, elementB, normalizedSearchValue));
|
||||
}
|
||||
|
||||
private mergeResults(quickOpenModel: QuickOpenModel, handlerResults: QuickOpenEntry[], groupLabel: string): void {
|
||||
|
||||
// Remove results already showing by checking for a "resource" property
|
||||
@@ -1007,7 +986,7 @@ export class QuickOpenController extends Component implements IQuickOpenService
|
||||
return result.then<QuickOpenHandler>(null, (error) => {
|
||||
delete this.mapResolvedHandlersToPrefix[id];
|
||||
|
||||
return TPromise.wrapError(new Error('Unable to instantiate quick open handler ' + handler.moduleName + ' - ' + handler.ctorName + ': ' + JSON.stringify(error)));
|
||||
return TPromise.wrapError(new Error(`Unable to instantiate quick open handler ${handler.getId()}: ${JSON.stringify(error)}`));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1020,7 +999,7 @@ export class QuickOpenController extends Component implements IQuickOpenService
|
||||
}
|
||||
|
||||
// Otherwise load and create
|
||||
return this.mapResolvedHandlersToPrefix[id] = this.instantiationService.createInstance<QuickOpenHandler>(handler);
|
||||
return this.mapResolvedHandlersToPrefix[id] = TPromise.as(handler.instantiate(this.instantiationService));
|
||||
}
|
||||
|
||||
public layout(dimension: Dimension): void {
|
||||
@@ -1191,6 +1170,81 @@ class PickOpenActionProvider implements IActionProvider {
|
||||
}
|
||||
}
|
||||
|
||||
class EditorHistoryHandler {
|
||||
private scorerCache: ScorerCache;
|
||||
|
||||
constructor(
|
||||
@IHistoryService private historyService: IHistoryService,
|
||||
@IInstantiationService private instantiationService: IInstantiationService,
|
||||
@IWorkspaceContextService private contextService: IWorkspaceContextService,
|
||||
@IFileService private fileService: IFileService
|
||||
) {
|
||||
this.scorerCache = Object.create(null);
|
||||
}
|
||||
|
||||
public getResults(searchValue?: string): QuickOpenEntry[] {
|
||||
|
||||
// Massage search for scoring
|
||||
const query = prepareQuery(searchValue);
|
||||
|
||||
// Just return all if we are not searching
|
||||
const history = this.historyService.getHistory();
|
||||
if (!query.value) {
|
||||
return history.map(input => this.instantiationService.createInstance(EditorHistoryEntry, input));
|
||||
}
|
||||
|
||||
// Otherwise filter by search value and sort by score. Include matches on description
|
||||
// in case the user is explicitly including path separators.
|
||||
const accessor = query.containsPathSeparator ? MatchOnDescription : DoNotMatchOnDescription;
|
||||
return history
|
||||
|
||||
// For now, only support to match on inputs that provide resource information
|
||||
.filter(input => {
|
||||
let resource: URI;
|
||||
if (input instanceof EditorInput) {
|
||||
resource = resourceForEditorHistory(input, this.fileService);
|
||||
} else {
|
||||
resource = (input as IResourceInput).resource;
|
||||
}
|
||||
|
||||
return !!resource;
|
||||
})
|
||||
|
||||
// Conver to quick open entries
|
||||
.map(input => this.instantiationService.createInstance(EditorHistoryEntry, input))
|
||||
|
||||
// Make sure the search value is matching
|
||||
.filter(e => {
|
||||
const itemScore = scoreItem(e, query, false, accessor, this.scorerCache);
|
||||
if (!itemScore.score) {
|
||||
return false;
|
||||
}
|
||||
|
||||
e.setHighlights(itemScore.labelMatch, itemScore.descriptionMatch);
|
||||
|
||||
return true;
|
||||
})
|
||||
|
||||
// Sort by score and provide a fallback sorter that keeps the
|
||||
// recency of items in case the score for items is the same
|
||||
.sort((e1, e2) => compareItemsByScore(e1, e2, query, false, accessor, this.scorerCache, (e1, e2, query, accessor) => -1));
|
||||
}
|
||||
}
|
||||
|
||||
class EditorHistoryItemAccessorClass extends QuickOpenItemAccessorClass {
|
||||
|
||||
constructor(private allowMatchOnDescription: boolean) {
|
||||
super();
|
||||
}
|
||||
|
||||
public getItemDescription(entry: QuickOpenEntry): string {
|
||||
return this.allowMatchOnDescription ? entry.getDescription() : void 0;
|
||||
}
|
||||
}
|
||||
|
||||
const MatchOnDescription = new EditorHistoryItemAccessorClass(true);
|
||||
const DoNotMatchOnDescription = new EditorHistoryItemAccessorClass(false);
|
||||
|
||||
export class EditorHistoryEntryGroup extends QuickOpenEntryGroup {
|
||||
// Marker class
|
||||
}
|
||||
@@ -1210,14 +1264,15 @@ export class EditorHistoryEntry extends EditorQuickOpenEntry {
|
||||
@ITextFileService private textFileService: ITextFileService,
|
||||
@IWorkspaceContextService contextService: IWorkspaceContextService,
|
||||
@IConfigurationService private configurationService: IConfigurationService,
|
||||
@IEnvironmentService environmentService: IEnvironmentService
|
||||
@IEnvironmentService environmentService: IEnvironmentService,
|
||||
@IFileService private fileService: IFileService
|
||||
) {
|
||||
super(editorService);
|
||||
|
||||
this.input = input;
|
||||
|
||||
if (input instanceof EditorInput) {
|
||||
this.resource = toResource(input, { filter: ['file', 'untitled'] });
|
||||
this.resource = resourceForEditorHistory(input, fileService);
|
||||
this.label = input.getName();
|
||||
this.description = input.getDescription();
|
||||
this.dirty = input.isDirty();
|
||||
@@ -1225,7 +1280,7 @@ export class EditorHistoryEntry extends EditorQuickOpenEntry {
|
||||
const resourceInput = input as IResourceInput;
|
||||
this.resource = resourceInput.resource;
|
||||
this.label = paths.basename(resourceInput.resource.fsPath);
|
||||
this.description = labels.getPathLabel(paths.dirname(this.resource.fsPath), contextService, environmentService);
|
||||
this.description = labels.getPathLabel(resources.dirname(this.resource), contextService, environmentService);
|
||||
this.dirty = this.resource && this.textFileService.isDirty(this.resource);
|
||||
|
||||
if (this.dirty && this.textFileService.getAutoSaveMode() === AutoSaveMode.AFTER_SHORT_DELAY) {
|
||||
@@ -1282,6 +1337,18 @@ export class EditorHistoryEntry extends EditorQuickOpenEntry {
|
||||
}
|
||||
}
|
||||
|
||||
function resourceForEditorHistory(input: EditorInput, fileService: IFileService): URI {
|
||||
const resource = input ? input.getResource() : void 0;
|
||||
|
||||
// For the editor history we only prefer resources that are either untitled or
|
||||
// can be handled by the file service which indicates they are editable resources.
|
||||
if (resource && (fileService.canHandleResource(resource) || resource.scheme === 'untitled')) {
|
||||
return resource;
|
||||
}
|
||||
|
||||
return void 0;
|
||||
}
|
||||
|
||||
export class RemoveFromEditorHistoryAction extends Action {
|
||||
|
||||
public static ID = 'workbench.action.removeFromEditorHistory';
|
||||
|
||||
@@ -8,7 +8,7 @@ import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { IQuickOpenService } from 'vs/platform/quickOpen/common/quickOpen';
|
||||
import { SyncActionDescriptor, MenuRegistry, MenuId } from 'vs/platform/actions/common/actions';
|
||||
import { KeyMod, KeyCode } from 'vs/base/common/keyCodes';
|
||||
import { IWorkbenchActionRegistry, Extensions as ActionExtensions } from 'vs/workbench/common/actionRegistry';
|
||||
import { IWorkbenchActionRegistry, Extensions as ActionExtensions } from 'vs/workbench/common/actions';
|
||||
import { KeybindingsRegistry } from 'vs/platform/keybinding/common/keybindingsRegistry';
|
||||
import { RemoveFromEditorHistoryAction } from 'vs/workbench/browser/parts/quickopen/quickOpenController';
|
||||
import { QuickOpenSelectNextAction, QuickOpenSelectPreviousAction, inQuickOpenContext, getQuickNavigateHandler, QuickOpenNavigateNextAction, QuickOpenNavigatePreviousAction, defaultQuickOpenContext, QUICKOPEN_ACTION_ID, QUICKOPEN_ACION_LABEL } from 'vs/workbench/browser/parts/quickopen/quickopen';
|
||||
|
||||
@@ -29,6 +29,15 @@ CommandsRegistry.registerCommand(QUICKOPEN_ACTION_ID, function (accessor: Servic
|
||||
});
|
||||
});
|
||||
|
||||
export const QUICKOPEN_FOCUS_SECONDARY_ACTION_ID = 'workbench.action.quickOpenPreviousEditor';
|
||||
CommandsRegistry.registerCommand(QUICKOPEN_FOCUS_SECONDARY_ACTION_ID, function (accessor: ServicesAccessor, prefix: string = null) {
|
||||
const quickOpenService = accessor.get(IQuickOpenService);
|
||||
|
||||
return quickOpenService.show(null, { autoFocus: { autoFocusSecondEntry: true } }).then(() => {
|
||||
return void 0;
|
||||
});
|
||||
});
|
||||
|
||||
export class BaseQuickOpenNavigateAction extends Action {
|
||||
|
||||
constructor(
|
||||
|
||||
@@ -10,7 +10,7 @@ import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { Action, IAction } from 'vs/base/common/actions';
|
||||
import { CompositePart } from 'vs/workbench/browser/parts/compositePart';
|
||||
import { Viewlet, ViewletRegistry, Extensions as ViewletExtensions } from 'vs/workbench/browser/viewlet';
|
||||
import { IWorkbenchActionRegistry, Extensions as ActionExtensions } from 'vs/workbench/common/actionRegistry';
|
||||
import { IWorkbenchActionRegistry, Extensions as ActionExtensions } from 'vs/workbench/common/actions';
|
||||
import { SyncActionDescriptor } from 'vs/platform/actions/common/actions';
|
||||
import { Separator } from 'vs/base/browser/ui/actionbar/actionbar';
|
||||
import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet';
|
||||
|
||||
@@ -17,7 +17,6 @@ import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { ICommandService } from 'vs/platform/commands/common/commands';
|
||||
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { Part } from 'vs/workbench/browser/part';
|
||||
import { IWorkbenchActionRegistry, Extensions as ActionExtensions } from 'vs/workbench/common/actionRegistry';
|
||||
import { StatusbarAlignment, IStatusbarRegistry, Extensions, IStatusbarItem } from 'vs/workbench/browser/parts/statusbar/statusbar';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
@@ -28,7 +27,7 @@ import { IContextMenuService } from 'vs/platform/contextview/browser/contextView
|
||||
import { Action } from 'vs/base/common/actions';
|
||||
import { IThemeService, registerThemingParticipant, ITheme, ICssStyleCollector } from 'vs/platform/theme/common/themeService';
|
||||
import { STATUS_BAR_BACKGROUND, STATUS_BAR_FOREGROUND, STATUS_BAR_NO_FOLDER_BACKGROUND, STATUS_BAR_ITEM_HOVER_BACKGROUND, STATUS_BAR_ITEM_ACTIVE_BACKGROUND, STATUS_BAR_PROMINENT_ITEM_BACKGROUND, STATUS_BAR_PROMINENT_ITEM_HOVER_BACKGROUND, STATUS_BAR_BORDER, STATUS_BAR_NO_FOLDER_FOREGROUND, STATUS_BAR_NO_FOLDER_BORDER } from 'vs/workbench/common/theme';
|
||||
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
|
||||
import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace';
|
||||
import { contrastBorder } from 'vs/platform/theme/common/colorRegistry';
|
||||
import { isThemeColor } from 'vs/editor/common/editorCommon';
|
||||
import { Color } from 'vs/base/common/color';
|
||||
@@ -55,7 +54,7 @@ export class StatusbarPart extends Part implements IStatusbarService {
|
||||
}
|
||||
|
||||
private registerListeners(): void {
|
||||
this.toUnbind.push(this.contextService.onDidChangeWorkspaceRoots(() => this.updateStyles()));
|
||||
this.toUnbind.push(this.contextService.onDidChangeWorkbenchState(() => this.updateStyles()));
|
||||
}
|
||||
|
||||
public addEntry(entry: IStatusbarEntry, alignment: StatusbarAlignment, priority: number = 0): IDisposable {
|
||||
@@ -141,10 +140,10 @@ export class StatusbarPart extends Part implements IStatusbarService {
|
||||
|
||||
const container = this.getContainer();
|
||||
|
||||
container.style('color', this.getColor(this.contextService.hasWorkspace() ? STATUS_BAR_FOREGROUND : STATUS_BAR_NO_FOLDER_FOREGROUND));
|
||||
container.style('background-color', this.getColor(this.contextService.hasWorkspace() ? STATUS_BAR_BACKGROUND : STATUS_BAR_NO_FOLDER_BACKGROUND));
|
||||
container.style('color', this.getColor(this.contextService.getWorkbenchState() !== WorkbenchState.EMPTY ? STATUS_BAR_FOREGROUND : STATUS_BAR_NO_FOLDER_FOREGROUND));
|
||||
container.style('background-color', this.getColor(this.contextService.getWorkbenchState() !== WorkbenchState.EMPTY ? STATUS_BAR_BACKGROUND : STATUS_BAR_NO_FOLDER_BACKGROUND));
|
||||
|
||||
const borderColor = this.getColor(this.contextService.hasWorkspace() ? STATUS_BAR_BORDER : STATUS_BAR_NO_FOLDER_BORDER) || this.getColor(contrastBorder);
|
||||
const borderColor = this.getColor(this.contextService.getWorkbenchState() !== WorkbenchState.EMPTY ? STATUS_BAR_BORDER : STATUS_BAR_NO_FOLDER_BORDER) || this.getColor(contrastBorder);
|
||||
container.style('border-top-width', borderColor ? '1px' : null);
|
||||
container.style('border-top-style', borderColor ? 'solid' : null);
|
||||
container.style('border-top-color', borderColor);
|
||||
@@ -287,23 +286,6 @@ class StatusBarEntryItem implements IStatusbarItem {
|
||||
private executeCommand(id: string, args?: any[]) {
|
||||
args = args || [];
|
||||
|
||||
// Lookup built in commands
|
||||
const builtInActionDescriptor = Registry.as<IWorkbenchActionRegistry>(ActionExtensions.WorkbenchActions).getWorkbenchAction(id);
|
||||
if (builtInActionDescriptor) {
|
||||
const action = this.instantiationService.createInstance(builtInActionDescriptor.syncDescriptor);
|
||||
|
||||
if (action.enabled) {
|
||||
this.telemetryService.publicLog('workbenchActionExecuted', { id: action.id, from: 'status bar' });
|
||||
(action.run() || TPromise.as(null)).done(() => {
|
||||
action.dispose();
|
||||
}, (err) => this.messageService.show(Severity.Error, toErrorMessage(err)));
|
||||
} else {
|
||||
this.messageService.show(Severity.Warning, nls.localize('canNotRun', "Command '{0}' is currently not enabled and can not be run.", action.label || id));
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Maintain old behaviour of always focusing the editor here
|
||||
const activeEditor = this.editorService.getActiveEditor();
|
||||
const codeEditor = getCodeEditor(activeEditor);
|
||||
@@ -311,7 +293,13 @@ class StatusBarEntryItem implements IStatusbarItem {
|
||||
codeEditor.focus();
|
||||
}
|
||||
|
||||
// Fallback to the command service for any other case
|
||||
/* __GDPR__
|
||||
"workbenchActionExecuted" : {
|
||||
"id" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
|
||||
"from": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
|
||||
}
|
||||
*/
|
||||
this.telemetryService.publicLog('workbenchActionExecuted', { id, from: 'status bar' });
|
||||
this.commandService.executeCommand(id, ...args).done(undefined, err => this.messageService.show(Severity.Error, toErrorMessage(err)));
|
||||
}
|
||||
}
|
||||
@@ -349,4 +337,4 @@ registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => {
|
||||
if (statusBarProminentItemHoverBackground) {
|
||||
collector.addRule(`.monaco-workbench > .part.statusbar > .statusbar-item a.status-bar-info:hover:not([disabled]):not(.disabled) { background-color: ${statusBarProminentItemHoverBackground}; }`);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -18,7 +18,7 @@ import * as errors from 'vs/base/common/errors';
|
||||
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
|
||||
import { StandardMouseEvent } from 'vs/base/browser/mouseEvent';
|
||||
import { IAction, Action } from 'vs/base/common/actions';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { IConfigurationService, IConfigurationChangeEvent } from 'vs/platform/configuration/common/configuration';
|
||||
import { IIntegrityService } from 'vs/platform/integrity/common/integrity';
|
||||
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { IEditorGroupService } from 'vs/workbench/services/group/common/groupService';
|
||||
@@ -27,12 +27,13 @@ import nls = require('vs/nls');
|
||||
import * as labels from 'vs/base/common/labels';
|
||||
import { EditorInput, toResource } from 'vs/workbench/common/editor';
|
||||
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
|
||||
import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace';
|
||||
import { Verbosity } from 'vs/platform/editor/common/editor';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import { TITLE_BAR_ACTIVE_BACKGROUND, TITLE_BAR_ACTIVE_FOREGROUND, TITLE_BAR_INACTIVE_FOREGROUND, TITLE_BAR_INACTIVE_BACKGROUND, TITLE_BAR_BORDER } from 'vs/workbench/common/theme';
|
||||
import URI from 'vs/base/common/uri';
|
||||
import { IPartService } from 'vs/workbench/services/part/common/partService';
|
||||
import { isMacintosh } from 'vs/base/common/platform';
|
||||
import URI from 'vs/base/common/uri';
|
||||
|
||||
export class TitlebarPart extends Part implements ITitleService {
|
||||
|
||||
@@ -41,7 +42,7 @@ export class TitlebarPart extends Part implements ITitleService {
|
||||
private static NLS_UNSUPPORTED = nls.localize('patchedWindowTitle', "[Unsupported]");
|
||||
private static NLS_EXTENSION_HOST = nls.localize('devExtensionWindowTitlePrefix', "[Extension Development Host]");
|
||||
private static TITLE_DIRTY = '\u25cf ';
|
||||
private static TITLE_SEPARATOR = ' — ';
|
||||
private static TITLE_SEPARATOR = isMacintosh ? ' — ' : ' - '; // macOS uses special - separator
|
||||
|
||||
private titleContainer: Builder;
|
||||
private title: Builder;
|
||||
@@ -51,7 +52,6 @@ export class TitlebarPart extends Part implements ITitleService {
|
||||
|
||||
private isInactive: boolean;
|
||||
|
||||
private titleTemplate: string;
|
||||
private isPure: boolean;
|
||||
private activeEditorListeners: IDisposable[];
|
||||
|
||||
@@ -81,9 +81,6 @@ export class TitlebarPart extends Part implements ITitleService {
|
||||
|
||||
private init(): void {
|
||||
|
||||
// Read initial config
|
||||
this.onConfigurationChanged();
|
||||
|
||||
// Initial window title when loading is done
|
||||
this.partService.joinCreation().done(() => this.setTitle(this.getWindowTitle()));
|
||||
|
||||
@@ -99,10 +96,11 @@ export class TitlebarPart extends Part implements ITitleService {
|
||||
private registerListeners(): void {
|
||||
this.toUnbind.push(DOM.addDisposableListener(window, DOM.EventType.BLUR, () => this.onBlur()));
|
||||
this.toUnbind.push(DOM.addDisposableListener(window, DOM.EventType.FOCUS, () => this.onFocus()));
|
||||
this.toUnbind.push(this.configurationService.onDidUpdateConfiguration(() => this.onConfigurationChanged(true)));
|
||||
this.toUnbind.push(this.configurationService.onDidChangeConfiguration(e => this.onConfigurationChanged(e)));
|
||||
this.toUnbind.push(this.editorGroupService.onEditorsChanged(() => this.onEditorsChanged()));
|
||||
this.toUnbind.push(this.contextService.onDidChangeWorkspaceRoots(() => this.onDidChangeWorkspaceRoots()));
|
||||
this.toUnbind.push(this.contextService.onDidChangeWorkspaceName(() => this.onDidChangeWorkspaceName()));
|
||||
this.toUnbind.push(this.contextService.onDidChangeWorkspaceFolders(() => this.setTitle(this.getWindowTitle())));
|
||||
this.toUnbind.push(this.contextService.onDidChangeWorkbenchState(() => this.setTitle(this.getWindowTitle())));
|
||||
this.toUnbind.push(this.contextService.onDidChangeWorkspaceName(() => this.setTitle(this.getWindowTitle())));
|
||||
}
|
||||
|
||||
private onBlur(): void {
|
||||
@@ -115,19 +113,8 @@ export class TitlebarPart extends Part implements ITitleService {
|
||||
this.updateStyles();
|
||||
}
|
||||
|
||||
private onDidChangeWorkspaceRoots(): void {
|
||||
this.setTitle(this.getWindowTitle());
|
||||
}
|
||||
|
||||
private onDidChangeWorkspaceName(): void {
|
||||
this.setTitle(this.getWindowTitle());
|
||||
}
|
||||
|
||||
private onConfigurationChanged(update?: boolean): void {
|
||||
const currentTitleTemplate = this.titleTemplate;
|
||||
this.titleTemplate = this.configurationService.lookup<string>('window.title').value;
|
||||
|
||||
if (update && currentTitleTemplate !== this.titleTemplate) {
|
||||
private onConfigurationChanged(event: IConfigurationChangeEvent): void {
|
||||
if (event.affectsConfiguration('window.title')) {
|
||||
this.setTitle(this.getWindowTitle());
|
||||
}
|
||||
}
|
||||
@@ -192,41 +179,32 @@ export class TitlebarPart extends Part implements ITitleService {
|
||||
const input = this.editorService.getActiveEditorInput();
|
||||
const workspace = this.contextService.getWorkspace();
|
||||
|
||||
// Compute root resource
|
||||
// Single Root Workspace: always the single root workspace in this case
|
||||
// Multi Root Workspace: workspace configuration file
|
||||
let root: URI;
|
||||
if (this.contextService.hasMultiFolderWorkspace()) {
|
||||
if (workspace.configuration) {
|
||||
root = workspace.configuration;
|
||||
} else if (this.contextService.hasFolderWorkspace()) {
|
||||
root = workspace.roots[0];
|
||||
} else if (workspace.folders.length) {
|
||||
root = workspace.folders[0].uri;
|
||||
}
|
||||
|
||||
// Compute folder resource
|
||||
// Single Root Workspace: always the root single workspace in this case
|
||||
// Multi Root Workspace: root folder of the currently active file if any
|
||||
let folder: URI;
|
||||
if (workspace) {
|
||||
if (workspace.roots.length === 1) {
|
||||
folder = workspace.roots[0];
|
||||
} else {
|
||||
folder = this.contextService.getRoot(toResource(input, { supportSideBySide: true, filter: 'file' }));
|
||||
}
|
||||
}
|
||||
// Otherwise: root folder of the currently active file if any
|
||||
let folder = this.contextService.getWorkbenchState() === WorkbenchState.FOLDER ? workspace.folders[0] : this.contextService.getWorkspaceFolder(toResource(input, { supportSideBySide: true }));
|
||||
|
||||
// Variables
|
||||
const activeEditorShort = input ? input.getTitle(Verbosity.SHORT) : '';
|
||||
const activeEditorMedium = input ? input.getTitle(Verbosity.MEDIUM) : activeEditorShort;
|
||||
const activeEditorLong = input ? input.getTitle(Verbosity.LONG) : activeEditorMedium;
|
||||
const rootName = workspace ? workspace.name : '';
|
||||
const rootPath = workspace ? labels.getPathLabel(root, void 0, this.environmentService) : '';
|
||||
const folderName = folder ? (paths.basename(folder.fsPath) || folder.fsPath) : '';
|
||||
const folderPath = folder ? labels.getPathLabel(folder, void 0, this.environmentService) : '';
|
||||
const rootName = workspace.name;
|
||||
const rootPath = root ? labels.getPathLabel(root, void 0, this.environmentService) : '';
|
||||
const folderName = folder ? folder.name : '';
|
||||
const folderPath = folder ? labels.getPathLabel(folder.uri, void 0, this.environmentService) : '';
|
||||
const dirty = input && input.isDirty() ? TitlebarPart.TITLE_DIRTY : '';
|
||||
const appName = this.environmentService.appNameLong;
|
||||
const separator = TitlebarPart.TITLE_SEPARATOR;
|
||||
const titleTemplate = this.configurationService.getValue<string>('window.title');
|
||||
|
||||
return labels.template(this.titleTemplate, {
|
||||
return labels.template(titleTemplate, {
|
||||
activeEditorShort,
|
||||
activeEditorLong,
|
||||
activeEditorMedium,
|
||||
@@ -265,6 +243,17 @@ export class TitlebarPart extends Part implements ITitleService {
|
||||
}
|
||||
});
|
||||
|
||||
// Since the title area is used to drag the window, we do not want to steal focus from the
|
||||
// currently active element. So we restore focus after a timeout back to where it was.
|
||||
this.titleContainer.on([DOM.EventType.MOUSE_DOWN], () => {
|
||||
const active = document.activeElement;
|
||||
setTimeout(() => {
|
||||
if (active instanceof HTMLElement) {
|
||||
active.focus();
|
||||
}
|
||||
}, 0 /* need a timeout because we are in capture phase */);
|
||||
}, void 0, true /* use capture to know the currently active element properly */);
|
||||
|
||||
return this.titleContainer;
|
||||
}
|
||||
|
||||
|
||||
10
src/vs/workbench/browser/parts/views/media/panelviewlet.css
Normal file
@@ -0,0 +1,10 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
.monaco-panel-view .panel > .panel-header > .title {
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
}
|
||||
26
src/vs/workbench/browser/parts/views/media/views.css
Normal file
@@ -0,0 +1,26 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
.custom-view-tree-node-item {
|
||||
display: flex;
|
||||
height: 22px;
|
||||
line-height: 22px;
|
||||
}
|
||||
|
||||
.custom-view-tree-node-item > .custom-view-tree-node-item-icon {
|
||||
background-size: 16px;
|
||||
background-position: left center;
|
||||
background-repeat: no-repeat;
|
||||
padding-right: 6px;
|
||||
width: 16px;
|
||||
height: 22px;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
}
|
||||
|
||||
.custom-view-tree-node-item > .custom-view-tree-node-item-label {
|
||||
flex: 1;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
}
|
||||
290
src/vs/workbench/browser/parts/views/panelViewlet.ts
Normal file
@@ -0,0 +1,290 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import 'vs/css!./media/panelviewlet';
|
||||
import * as nls from 'vs/nls';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import Event, { Emitter } from 'vs/base/common/event';
|
||||
import { ColorIdentifier, contrastBorder } from 'vs/platform/theme/common/colorRegistry';
|
||||
import { attachStyler, IColorMapping, IThemable } from 'vs/platform/theme/common/styler';
|
||||
import { SIDE_BAR_DRAG_AND_DROP_BACKGROUND, SIDE_BAR_SECTION_HEADER_FOREGROUND, SIDE_BAR_SECTION_HEADER_BACKGROUND } from 'vs/workbench/common/theme';
|
||||
import { Dimension, Builder } from 'vs/base/browser/builder';
|
||||
import { append, $, trackFocus } from 'vs/base/browser/dom';
|
||||
import { IDisposable, combinedDisposable } from 'vs/base/common/lifecycle';
|
||||
import { firstIndex } from 'vs/base/common/arrays';
|
||||
import { IAction, IActionRunner } from 'vs/base/common/actions';
|
||||
import { IActionItem, ActionsOrientation } from 'vs/base/browser/ui/actionbar/actionbar';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { prepareActions } from 'vs/workbench/browser/actions';
|
||||
import { Viewlet, ViewletRegistry, Extensions } from 'vs/workbench/browser/viewlet';
|
||||
import { ToolBar } from 'vs/base/browser/ui/toolbar/toolbar';
|
||||
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
||||
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import { PanelView, IPanelViewOptions, IPanelOptions, Panel } from 'vs/base/browser/ui/splitview/panelview';
|
||||
|
||||
export interface IPanelColors extends IColorMapping {
|
||||
dropBackground?: ColorIdentifier;
|
||||
headerForeground?: ColorIdentifier;
|
||||
headerBackground?: ColorIdentifier;
|
||||
headerHighContrastBorder?: ColorIdentifier;
|
||||
}
|
||||
|
||||
export function attachPanelStyler(widget: IThemable, themeService: IThemeService) {
|
||||
return attachStyler<IPanelColors>(themeService, {
|
||||
headerForeground: SIDE_BAR_SECTION_HEADER_FOREGROUND,
|
||||
headerBackground: SIDE_BAR_SECTION_HEADER_BACKGROUND,
|
||||
headerHighContrastBorder: contrastBorder,
|
||||
dropBackground: SIDE_BAR_DRAG_AND_DROP_BACKGROUND
|
||||
}, widget);
|
||||
}
|
||||
|
||||
export interface IViewletPanelOptions extends IPanelOptions {
|
||||
actionRunner?: IActionRunner;
|
||||
}
|
||||
|
||||
export abstract class ViewletPanel extends Panel {
|
||||
|
||||
private _onDidFocus = new Emitter<void>();
|
||||
readonly onDidFocus: Event<void> = this._onDidFocus.event;
|
||||
|
||||
protected actionRunner: IActionRunner;
|
||||
protected toolbar: ToolBar;
|
||||
|
||||
constructor(
|
||||
readonly title: string,
|
||||
options: IViewletPanelOptions,
|
||||
@IKeybindingService protected keybindingService: IKeybindingService,
|
||||
@IContextMenuService protected contextMenuService: IContextMenuService
|
||||
) {
|
||||
super(options);
|
||||
|
||||
this.actionRunner = options.actionRunner;
|
||||
}
|
||||
|
||||
render(container: HTMLElement): void {
|
||||
super.render(container);
|
||||
|
||||
const focusTracker = trackFocus(container);
|
||||
this.disposables.push(focusTracker);
|
||||
this.disposables.push(focusTracker.addFocusListener(() => this._onDidFocus.fire()));
|
||||
}
|
||||
|
||||
protected renderHeader(container: HTMLElement): void {
|
||||
this.renderHeaderTitle(container);
|
||||
|
||||
const actions = append(container, $('.actions'));
|
||||
this.toolbar = new ToolBar(actions, this.contextMenuService, {
|
||||
orientation: ActionsOrientation.HORIZONTAL,
|
||||
actionItemProvider: action => this.getActionItem(action),
|
||||
ariaLabel: nls.localize('viewToolbarAriaLabel', "{0} actions", this.title),
|
||||
getKeyBinding: action => this.keybindingService.lookupKeybinding(action.id),
|
||||
actionRunner: this.actionRunner
|
||||
});
|
||||
|
||||
this.disposables.push(this.toolbar);
|
||||
this.updateActions();
|
||||
}
|
||||
|
||||
protected renderHeaderTitle(container: HTMLElement): void {
|
||||
append(container, $('.title', null, this.title));
|
||||
}
|
||||
|
||||
focus(): void {
|
||||
this._onDidFocus.fire();
|
||||
}
|
||||
|
||||
protected updateActions(): void {
|
||||
this.toolbar.setActions(prepareActions(this.getActions()), prepareActions(this.getSecondaryActions()))();
|
||||
this.toolbar.context = this.getActionsContext();
|
||||
}
|
||||
|
||||
getActions(): IAction[] {
|
||||
return [];
|
||||
}
|
||||
|
||||
getSecondaryActions(): IAction[] {
|
||||
return [];
|
||||
}
|
||||
|
||||
getActionItem(action: IAction): IActionItem {
|
||||
return null;
|
||||
}
|
||||
|
||||
getActionsContext(): any {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
getOptimalWidth(): number {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
export interface IViewsViewletOptions extends IPanelViewOptions {
|
||||
showHeaderInTitleWhenSingleView: boolean;
|
||||
}
|
||||
|
||||
interface IViewletPanelItem {
|
||||
panel: ViewletPanel;
|
||||
disposable: IDisposable;
|
||||
}
|
||||
|
||||
export class PanelViewlet extends Viewlet {
|
||||
|
||||
protected lastFocusedPanel: ViewletPanel | undefined;
|
||||
private panelItems: IViewletPanelItem[] = [];
|
||||
private panelview: PanelView;
|
||||
|
||||
get onDidSashChange(): Event<void> {
|
||||
return this.panelview.onDidSashChange;
|
||||
}
|
||||
|
||||
protected get length(): number {
|
||||
return this.panelItems.length;
|
||||
}
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
private options: IViewsViewletOptions,
|
||||
@ITelemetryService telemetryService: ITelemetryService,
|
||||
@IThemeService themeService: IThemeService
|
||||
) {
|
||||
super(id, telemetryService, themeService);
|
||||
}
|
||||
|
||||
async create(parent: Builder): TPromise<void> {
|
||||
super.create(parent);
|
||||
|
||||
const container = parent.getHTMLElement();
|
||||
this.panelview = this._register(new PanelView(container, this.options));
|
||||
this.panelview.onDidDrop(({ from, to }) => this.movePanel(from as ViewletPanel, to as ViewletPanel));
|
||||
}
|
||||
|
||||
getTitle(): string {
|
||||
let title = Registry.as<ViewletRegistry>(Extensions.Viewlets).getViewlet(this.getId()).name;
|
||||
|
||||
if (this.isSingleView()) {
|
||||
title += ': ' + this.panelItems[0].panel.title;
|
||||
}
|
||||
|
||||
return title;
|
||||
}
|
||||
|
||||
getActions(): IAction[] {
|
||||
if (this.isSingleView()) {
|
||||
return this.panelItems[0].panel.getActions();
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
getSecondaryActions(): IAction[] {
|
||||
if (this.isSingleView()) {
|
||||
return this.panelItems[0].panel.getSecondaryActions();
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
focus(): void {
|
||||
super.focus();
|
||||
|
||||
if (this.lastFocusedPanel) {
|
||||
this.lastFocusedPanel.focus();
|
||||
} else if (this.panelItems.length > 0) {
|
||||
this.panelItems[0].panel.focus();
|
||||
}
|
||||
}
|
||||
|
||||
layout(dimension: Dimension): void {
|
||||
this.panelview.layout(dimension.height);
|
||||
}
|
||||
|
||||
getOptimalWidth(): number {
|
||||
const sizes = this.panelItems
|
||||
.map(panelItem => panelItem.panel.getOptimalWidth() || 0);
|
||||
|
||||
return Math.max(...sizes);
|
||||
}
|
||||
|
||||
addPanel(panel: ViewletPanel, size: number, index = this.panelItems.length - 1): void {
|
||||
const disposables: IDisposable[] = [];
|
||||
const onDidFocus = panel.onDidFocus(() => this.lastFocusedPanel = panel, null, disposables);
|
||||
const styler = attachPanelStyler(panel, this.themeService);
|
||||
const disposable = combinedDisposable([onDidFocus, styler]);
|
||||
const panelItem: IViewletPanelItem = { panel, disposable };
|
||||
|
||||
this.panelItems.splice(index, 0, panelItem);
|
||||
this.panelview.addPanel(panel, size, index);
|
||||
|
||||
this.updateViewHeaders();
|
||||
this.updateTitleArea();
|
||||
}
|
||||
|
||||
removePanel(panel: ViewletPanel): void {
|
||||
const index = firstIndex(this.panelItems, i => i.panel === panel);
|
||||
|
||||
if (index === -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.lastFocusedPanel === panel) {
|
||||
this.lastFocusedPanel = undefined;
|
||||
}
|
||||
|
||||
this.panelview.removePanel(panel);
|
||||
const [panelItem] = this.panelItems.splice(index, 1);
|
||||
panelItem.disposable.dispose();
|
||||
|
||||
this.updateViewHeaders();
|
||||
this.updateTitleArea();
|
||||
}
|
||||
|
||||
movePanel(from: ViewletPanel, to: ViewletPanel): void {
|
||||
const fromIndex = firstIndex(this.panelItems, item => item.panel === from);
|
||||
const toIndex = firstIndex(this.panelItems, item => item.panel === to);
|
||||
|
||||
if (fromIndex < 0 || fromIndex >= this.panelItems.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (toIndex < 0 || toIndex >= this.panelItems.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
const [panelItem] = this.panelItems.splice(fromIndex, 1);
|
||||
this.panelItems.splice(toIndex, 0, panelItem);
|
||||
|
||||
this.panelview.movePanel(from, to);
|
||||
}
|
||||
|
||||
resizePanel(panel: ViewletPanel, size: number): void {
|
||||
this.panelview.resizePanel(panel, size);
|
||||
}
|
||||
|
||||
getPanelSize(panel: ViewletPanel): number {
|
||||
return this.panelview.getPanelSize(panel);
|
||||
}
|
||||
|
||||
protected updateViewHeaders(): void {
|
||||
if (this.isSingleView()) {
|
||||
this.panelItems[0].panel.setExpanded(true);
|
||||
this.panelItems[0].panel.headerVisible = false;
|
||||
} else {
|
||||
this.panelItems.forEach(i => i.panel.headerVisible = true);
|
||||
}
|
||||
}
|
||||
|
||||
protected isSingleView(): boolean {
|
||||
return this.options.showHeaderInTitleWhenSingleView && this.panelItems.length === 1;
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
super.dispose();
|
||||
this.panelItems.forEach(i => i.disposable.dispose());
|
||||
this.panelview.dispose();
|
||||
}
|
||||
}
|
||||
451
src/vs/workbench/browser/parts/views/treeView.ts
Normal file
@@ -0,0 +1,451 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import 'vs/css!./media/views';
|
||||
import Event, { Emitter } from 'vs/base/common/event';
|
||||
import { IDisposable, dispose, empty as EmptyDisposable, toDisposable } from 'vs/base/common/lifecycle';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import * as DOM from 'vs/base/browser/dom';
|
||||
import { Builder, $ } from 'vs/base/browser/builder';
|
||||
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 { IListService } from 'vs/platform/list/browser/listService';
|
||||
import { Tree } from 'vs/base/parts/tree/browser/treeImpl';
|
||||
import { ClickBehavior, DefaultController } from 'vs/base/parts/tree/browser/treeDefaults';
|
||||
import { IMenuService, MenuId, MenuItemAction } from 'vs/platform/actions/common/actions';
|
||||
import { attachListStyler } from 'vs/platform/theme/common/styler';
|
||||
import { IThemeService, LIGHT } from 'vs/platform/theme/common/themeService';
|
||||
import { createActionItem, fillInActions } from 'vs/platform/actions/browser/menuItemActionItem';
|
||||
import { IProgressService } from 'vs/platform/progress/common/progress';
|
||||
import { ITree, IDataSource, IRenderer, ContextMenuEvent } from 'vs/base/parts/tree/browser/tree';
|
||||
import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { ActionItem } from 'vs/base/browser/ui/actionbar/actionbar';
|
||||
import { ViewsRegistry } from 'vs/workbench/browser/parts/views/viewsRegistry';
|
||||
import { IExtensionService } from 'vs/platform/extensions/common/extensions';
|
||||
import { ViewsViewletPanel, IViewletViewOptions, IViewOptions } from 'vs/workbench/browser/parts/views/viewsViewlet';
|
||||
import { ICommandService } from 'vs/platform/commands/common/commands';
|
||||
import { TreeItemCollapsibleState, ITreeItem, ITreeViewDataProvider, TreeViewItemHandleArg } from 'vs/workbench/common/views';
|
||||
|
||||
export class TreeView extends ViewsViewletPanel {
|
||||
|
||||
private menus: Menus;
|
||||
private viewFocusContext: IContextKey<boolean>;
|
||||
private activated: boolean = false;
|
||||
private treeInputPromise: TPromise<void>;
|
||||
|
||||
private dataProviderElementChangeListener: IDisposable;
|
||||
|
||||
constructor(
|
||||
private options: IViewletViewOptions,
|
||||
@IMessageService private messageService: IMessageService,
|
||||
@IKeybindingService keybindingService: IKeybindingService,
|
||||
@IContextMenuService contextMenuService: IContextMenuService,
|
||||
@IInstantiationService private instantiationService: IInstantiationService,
|
||||
@IListService private listService: IListService,
|
||||
@IThemeService private themeService: IThemeService,
|
||||
@IContextKeyService private contextKeyService: IContextKeyService,
|
||||
@IExtensionService private extensionService: IExtensionService,
|
||||
@ICommandService private commandService: ICommandService
|
||||
) {
|
||||
super({ ...(options as IViewOptions), ariaHeaderLabel: options.name }, keybindingService, contextMenuService);
|
||||
this.menus = this.instantiationService.createInstance(Menus, this.id);
|
||||
this.viewFocusContext = this.contextKeyService.createKey<boolean>(this.id, void 0);
|
||||
this.menus.onDidChangeTitle(() => this.updateActions(), this, this.disposables);
|
||||
this.themeService.onThemeChange(() => this.tree.refresh() /* soft refresh */, this, this.disposables);
|
||||
if (options.expanded) {
|
||||
this.activate();
|
||||
}
|
||||
}
|
||||
|
||||
public renderBody(container: HTMLElement): void {
|
||||
this.treeContainer = super.renderViewTree(container);
|
||||
DOM.addClass(this.treeContainer, 'tree-explorer-viewlet-tree-view');
|
||||
|
||||
this.tree = this.createViewer($(this.treeContainer));
|
||||
this.setInput();
|
||||
}
|
||||
|
||||
setExpanded(expanded: boolean): void {
|
||||
super.setExpanded(expanded);
|
||||
|
||||
if (expanded) {
|
||||
this.activate();
|
||||
}
|
||||
}
|
||||
|
||||
private activate() {
|
||||
if (!this.activated && this.extensionService) {
|
||||
this.extensionService.activateByEvent(`onView:${this.id}`);
|
||||
this.activated = true;
|
||||
this.setInput();
|
||||
}
|
||||
}
|
||||
|
||||
public createViewer(container: Builder): ITree {
|
||||
const dataSource = this.instantiationService.createInstance(TreeDataSource, this.id);
|
||||
const renderer = this.instantiationService.createInstance(TreeRenderer);
|
||||
const controller = this.instantiationService.createInstance(TreeController, this.id, this.menus);
|
||||
const tree = new Tree(container.getHTMLElement(), {
|
||||
dataSource,
|
||||
renderer,
|
||||
controller
|
||||
}, {
|
||||
keyboardSupport: false
|
||||
});
|
||||
|
||||
this.disposables.push(attachListStyler(tree, this.themeService));
|
||||
this.disposables.push(this.listService.register(tree, [this.viewFocusContext]));
|
||||
tree.addListener('selection', (event: any) => this.onSelection());
|
||||
return tree;
|
||||
}
|
||||
|
||||
getActions(): IAction[] {
|
||||
return [...this.menus.getTitleActions()];
|
||||
}
|
||||
|
||||
getSecondaryActions(): IAction[] {
|
||||
return this.menus.getTitleSecondaryActions();
|
||||
}
|
||||
|
||||
getActionItem(action: IAction): IActionItem {
|
||||
return createActionItem(action, this.keybindingService, this.messageService);
|
||||
}
|
||||
|
||||
public setVisible(visible: boolean): TPromise<void> {
|
||||
return super.setVisible(visible);
|
||||
}
|
||||
|
||||
private setInput(): TPromise<void> {
|
||||
if (this.tree) {
|
||||
if (!this.treeInputPromise) {
|
||||
if (this.listenToDataProvider()) {
|
||||
this.treeInputPromise = this.tree.setInput(new Root());
|
||||
} else {
|
||||
this.treeInputPromise = new TPromise<void>((c, e) => {
|
||||
this.disposables.push(ViewsRegistry.onTreeViewDataProviderRegistered(id => {
|
||||
if (this.id === id) {
|
||||
if (this.listenToDataProvider()) {
|
||||
this.tree.setInput(new Root()).then(() => c(null));
|
||||
}
|
||||
}
|
||||
}));
|
||||
});
|
||||
}
|
||||
}
|
||||
return this.treeInputPromise;
|
||||
}
|
||||
return TPromise.as(null);
|
||||
}
|
||||
|
||||
private listenToDataProvider(): boolean {
|
||||
let dataProvider = ViewsRegistry.getTreeViewDataProvider(this.id);
|
||||
if (dataProvider) {
|
||||
if (this.dataProviderElementChangeListener) {
|
||||
this.dataProviderElementChangeListener.dispose();
|
||||
}
|
||||
this.dataProviderElementChangeListener = dataProvider.onDidChange(element => this.refresh(element));
|
||||
const disposable = dataProvider.onDispose(() => {
|
||||
this.dataProviderElementChangeListener.dispose();
|
||||
this.tree.setInput(new Root());
|
||||
disposable.dispose();
|
||||
});
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public getOptimalWidth(): number {
|
||||
const parentNode = this.tree.getHTMLElement();
|
||||
const childNodes = [].slice.call(parentNode.querySelectorAll('.outline-item-label > a'));
|
||||
|
||||
return DOM.getLargestChildWidth(parentNode, childNodes);
|
||||
}
|
||||
|
||||
private onSelection(): void {
|
||||
const selection: ITreeItem = this.tree.getSelection()[0];
|
||||
if (selection) {
|
||||
if (selection.command) {
|
||||
this.commandService.executeCommand(selection.command.id, ...(selection.command.arguments || []));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private refresh(elements: ITreeItem[]): void {
|
||||
elements = elements ? elements : [this.tree.getInput()];
|
||||
for (const element of elements) {
|
||||
element.children = null;
|
||||
this.tree.refresh(element);
|
||||
}
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
dispose(this.disposables);
|
||||
if (this.dataProviderElementChangeListener) {
|
||||
this.dataProviderElementChangeListener.dispose();
|
||||
}
|
||||
dispose(this.disposables);
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
class Root implements ITreeItem {
|
||||
label = 'root';
|
||||
handle = -1;
|
||||
collapsibleState = TreeItemCollapsibleState.Expanded;
|
||||
}
|
||||
|
||||
class TreeDataSource implements IDataSource {
|
||||
|
||||
constructor(
|
||||
private id: string,
|
||||
@IProgressService private progressService: IProgressService
|
||||
) {
|
||||
}
|
||||
|
||||
public getId(tree: ITree, node: ITreeItem): string {
|
||||
return '' + node.handle;
|
||||
}
|
||||
|
||||
public hasChildren(tree: ITree, node: ITreeItem): boolean {
|
||||
if (!this.getDataProvider()) {
|
||||
return false;
|
||||
}
|
||||
return node.collapsibleState === TreeItemCollapsibleState.Collapsed || node.collapsibleState === TreeItemCollapsibleState.Expanded;
|
||||
}
|
||||
|
||||
public getChildren(tree: ITree, node: ITreeItem): TPromise<any[]> {
|
||||
if (node.children) {
|
||||
return TPromise.as(node.children);
|
||||
}
|
||||
|
||||
const dataProvider = this.getDataProvider();
|
||||
if (dataProvider) {
|
||||
const promise = node instanceof Root ? dataProvider.getElements() : dataProvider.getChildren(node);
|
||||
this.progressService.showWhile(promise, 100);
|
||||
return promise.then(children => {
|
||||
node.children = children;
|
||||
return children;
|
||||
});
|
||||
}
|
||||
return TPromise.as(null);
|
||||
}
|
||||
|
||||
public shouldAutoexpand(tree: ITree, node: ITreeItem): boolean {
|
||||
return node.collapsibleState === TreeItemCollapsibleState.Expanded;
|
||||
}
|
||||
|
||||
public getParent(tree: ITree, node: any): TPromise<any> {
|
||||
return TPromise.as(null);
|
||||
}
|
||||
|
||||
private getDataProvider(): ITreeViewDataProvider {
|
||||
return ViewsRegistry.getTreeViewDataProvider(this.id);
|
||||
}
|
||||
}
|
||||
|
||||
interface ITreeExplorerTemplateData {
|
||||
icon: Builder;
|
||||
label: Builder;
|
||||
}
|
||||
|
||||
class TreeRenderer implements IRenderer {
|
||||
|
||||
private static ITEM_HEIGHT = 22;
|
||||
private static TREE_TEMPLATE_ID = 'treeExplorer';
|
||||
|
||||
constructor( @IThemeService private themeService: IThemeService) {
|
||||
}
|
||||
|
||||
public getHeight(tree: ITree, element: any): number {
|
||||
return TreeRenderer.ITEM_HEIGHT;
|
||||
}
|
||||
|
||||
public getTemplateId(tree: ITree, element: any): string {
|
||||
return TreeRenderer.TREE_TEMPLATE_ID;
|
||||
}
|
||||
|
||||
public renderTemplate(tree: ITree, templateId: string, container: HTMLElement): ITreeExplorerTemplateData {
|
||||
const el = $(container);
|
||||
const item = $('.custom-view-tree-node-item');
|
||||
item.appendTo(el);
|
||||
|
||||
const icon = $('.custom-view-tree-node-item-icon').appendTo(item);
|
||||
const label = $('.custom-view-tree-node-item-label').appendTo(item);
|
||||
const link = $('a.label').appendTo(label);
|
||||
|
||||
return { label: link, icon };
|
||||
}
|
||||
|
||||
public renderElement(tree: ITree, node: ITreeItem, templateId: string, templateData: ITreeExplorerTemplateData): void {
|
||||
templateData.label.text(node.label).title(node.label);
|
||||
|
||||
const theme = this.themeService.getTheme();
|
||||
const icon = theme.type === LIGHT ? node.icon : node.iconDark;
|
||||
|
||||
if (icon) {
|
||||
templateData.icon.getHTMLElement().style.backgroundImage = `url('${icon}')`;
|
||||
DOM.addClass(templateData.icon.getHTMLElement(), 'custom-view-tree-node-item-icon');
|
||||
} else {
|
||||
templateData.icon.getHTMLElement().style.backgroundImage = '';
|
||||
DOM.removeClass(templateData.icon.getHTMLElement(), 'custom-view-tree-node-item-icon');
|
||||
}
|
||||
}
|
||||
|
||||
public disposeTemplate(tree: ITree, templateId: string, templateData: ITreeExplorerTemplateData): void {
|
||||
}
|
||||
}
|
||||
|
||||
class TreeController extends DefaultController {
|
||||
|
||||
constructor(
|
||||
private treeViewId: string,
|
||||
private menus: Menus,
|
||||
@IContextMenuService private contextMenuService: IContextMenuService,
|
||||
@IKeybindingService private _keybindingService: IKeybindingService
|
||||
) {
|
||||
super({ clickBehavior: ClickBehavior.ON_MOUSE_UP /* do not change to not break DND */, keyboardSupport: false });
|
||||
}
|
||||
|
||||
public onContextMenu(tree: ITree, node: ITreeItem, event: ContextMenuEvent): boolean {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
tree.setFocus(node);
|
||||
const actions = this.menus.getResourceContextActions(node);
|
||||
if (!actions.length) {
|
||||
return true;
|
||||
}
|
||||
const anchor = { x: event.posx, y: event.posy };
|
||||
this.contextMenuService.showContextMenu({
|
||||
getAnchor: () => anchor,
|
||||
|
||||
getActions: () => {
|
||||
return TPromise.as(actions);
|
||||
},
|
||||
|
||||
getActionItem: (action) => {
|
||||
const keybinding = this._keybindingService.lookupKeybinding(action.id);
|
||||
if (keybinding) {
|
||||
return new ActionItem(action, action, { label: true, keybinding: keybinding.getLabel() });
|
||||
}
|
||||
return null;
|
||||
},
|
||||
|
||||
onHide: (wasCancelled?: boolean) => {
|
||||
if (wasCancelled) {
|
||||
tree.DOMFocus();
|
||||
}
|
||||
},
|
||||
|
||||
getActionsContext: () => (<TreeViewItemHandleArg>{ $treeViewId: this.treeViewId, $treeItemHandle: node.handle }),
|
||||
|
||||
actionRunner: new MultipleSelectionActionRunner(() => tree.getSelection())
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
class MultipleSelectionActionRunner extends ActionRunner {
|
||||
|
||||
constructor(private getSelectedResources: () => any[]) {
|
||||
super();
|
||||
}
|
||||
|
||||
runAction(action: IAction, context: any): TPromise<any> {
|
||||
if (action instanceof MenuItemAction) {
|
||||
const selection = this.getSelectedResources();
|
||||
const filteredSelection = selection.filter(s => s !== context);
|
||||
|
||||
if (selection.length === filteredSelection.length || selection.length === 1) {
|
||||
return action.run(context);
|
||||
}
|
||||
|
||||
return action.run(context, ...filteredSelection);
|
||||
}
|
||||
|
||||
return super.runAction(action, context);
|
||||
}
|
||||
}
|
||||
|
||||
class Menus implements IDisposable {
|
||||
|
||||
private disposables: IDisposable[] = [];
|
||||
private titleDisposable: IDisposable = EmptyDisposable;
|
||||
private titleActions: IAction[] = [];
|
||||
private titleSecondaryActions: IAction[] = [];
|
||||
|
||||
private _onDidChangeTitle = new Emitter<void>();
|
||||
get onDidChangeTitle(): Event<void> { return this._onDidChangeTitle.event; }
|
||||
|
||||
constructor(
|
||||
private id: string,
|
||||
@IContextKeyService private contextKeyService: IContextKeyService,
|
||||
@IMenuService private menuService: IMenuService
|
||||
) {
|
||||
if (this.titleDisposable) {
|
||||
this.titleDisposable.dispose();
|
||||
this.titleDisposable = EmptyDisposable;
|
||||
}
|
||||
|
||||
const _contextKeyService = this.contextKeyService.createScoped();
|
||||
_contextKeyService.createKey('view', id);
|
||||
|
||||
const titleMenu = this.menuService.createMenu(MenuId.ViewTitle, _contextKeyService);
|
||||
const updateActions = () => {
|
||||
this.titleActions = [];
|
||||
this.titleSecondaryActions = [];
|
||||
fillInActions(titleMenu, null, { primary: this.titleActions, secondary: this.titleSecondaryActions });
|
||||
this._onDidChangeTitle.fire();
|
||||
};
|
||||
|
||||
const listener = titleMenu.onDidChange(updateActions);
|
||||
updateActions();
|
||||
|
||||
this.titleDisposable = toDisposable(() => {
|
||||
listener.dispose();
|
||||
titleMenu.dispose();
|
||||
_contextKeyService.dispose();
|
||||
this.titleActions = [];
|
||||
this.titleSecondaryActions = [];
|
||||
});
|
||||
}
|
||||
|
||||
getTitleActions(): IAction[] {
|
||||
return this.titleActions;
|
||||
}
|
||||
|
||||
getTitleSecondaryActions(): IAction[] {
|
||||
return this.titleSecondaryActions;
|
||||
}
|
||||
|
||||
getResourceContextActions(element: ITreeItem): IAction[] {
|
||||
return this.getActions(MenuId.ViewItemContext, { key: 'viewItem', value: element.contextValue }).secondary;
|
||||
}
|
||||
|
||||
private getActions(menuId: MenuId, context: { key: string, value: string }): { primary: IAction[]; secondary: IAction[]; } {
|
||||
const contextKeyService = this.contextKeyService.createScoped();
|
||||
contextKeyService.createKey('view', this.id);
|
||||
contextKeyService.createKey(context.key, context.value);
|
||||
|
||||
const menu = this.menuService.createMenu(menuId, contextKeyService);
|
||||
const primary: IAction[] = [];
|
||||
const secondary: IAction[] = [];
|
||||
const result = { primary, secondary };
|
||||
fillInActions(menu, { shouldForwardArgs: true }, result);
|
||||
|
||||
menu.dispose();
|
||||
contextKeyService.dispose();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this.disposables = dispose(this.disposables);
|
||||
}
|
||||
}
|
||||
150
src/vs/workbench/browser/parts/views/viewsRegistry.ts
Normal file
@@ -0,0 +1,150 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import Event, { Emitter } from 'vs/base/common/event';
|
||||
import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { ITreeViewDataProvider } from 'vs/workbench/common/views';
|
||||
import { localize } from 'vs/nls';
|
||||
|
||||
export class ViewLocation {
|
||||
|
||||
static readonly Explorer = new ViewLocation('explorer');
|
||||
static readonly Debug = new ViewLocation('debug');
|
||||
static readonly Extensions = new ViewLocation('extensions');
|
||||
static readonly SCM = new ViewLocation('scm');
|
||||
|
||||
constructor(private _id: string) {
|
||||
}
|
||||
|
||||
get id(): string {
|
||||
return this._id;
|
||||
}
|
||||
|
||||
static getContributedViewLocation(value: string): ViewLocation {
|
||||
switch (value) {
|
||||
case ViewLocation.Explorer.id: return ViewLocation.Explorer;
|
||||
case ViewLocation.Debug.id: return ViewLocation.Debug;
|
||||
}
|
||||
return void 0;
|
||||
}
|
||||
}
|
||||
|
||||
export interface IViewDescriptor {
|
||||
|
||||
readonly id: string;
|
||||
|
||||
readonly name: string;
|
||||
|
||||
readonly location: ViewLocation;
|
||||
|
||||
// TODO do we really need this?!
|
||||
readonly ctor: any;
|
||||
|
||||
readonly when?: ContextKeyExpr;
|
||||
|
||||
readonly order?: number;
|
||||
|
||||
readonly size?: number;
|
||||
|
||||
readonly collapsed?: boolean;
|
||||
|
||||
readonly canToggleVisibility?: boolean;
|
||||
}
|
||||
|
||||
export interface IViewsRegistry {
|
||||
|
||||
readonly onViewsRegistered: Event<IViewDescriptor[]>;
|
||||
|
||||
readonly onViewsDeregistered: Event<IViewDescriptor[]>;
|
||||
|
||||
readonly onTreeViewDataProviderRegistered: Event<string>;
|
||||
|
||||
registerViews(views: IViewDescriptor[]): void;
|
||||
|
||||
deregisterViews(ids: string[], location: ViewLocation): void;
|
||||
|
||||
registerTreeViewDataProvider(id: string, factory: ITreeViewDataProvider): void;
|
||||
|
||||
deregisterTreeViewDataProviders(): void;
|
||||
|
||||
getViews(loc: ViewLocation): IViewDescriptor[];
|
||||
|
||||
getTreeViewDataProvider(id: string): ITreeViewDataProvider;
|
||||
|
||||
}
|
||||
|
||||
export const ViewsRegistry: IViewsRegistry = new class {
|
||||
|
||||
private _onViewsRegistered: Emitter<IViewDescriptor[]> = new Emitter<IViewDescriptor[]>();
|
||||
readonly onViewsRegistered: Event<IViewDescriptor[]> = this._onViewsRegistered.event;
|
||||
|
||||
private _onViewsDeregistered: Emitter<IViewDescriptor[]> = new Emitter<IViewDescriptor[]>();
|
||||
readonly onViewsDeregistered: Event<IViewDescriptor[]> = this._onViewsDeregistered.event;
|
||||
|
||||
private _onTreeViewDataProviderRegistered: Emitter<string> = new Emitter<string>();
|
||||
readonly onTreeViewDataProviderRegistered: Event<string> = this._onTreeViewDataProviderRegistered.event;
|
||||
|
||||
private _views: Map<ViewLocation, IViewDescriptor[]> = new Map<ViewLocation, IViewDescriptor[]>();
|
||||
private _treeViewDataPoviders: Map<string, ITreeViewDataProvider> = new Map<string, ITreeViewDataProvider>();
|
||||
|
||||
registerViews(viewDescriptors: IViewDescriptor[]): void {
|
||||
if (viewDescriptors.length) {
|
||||
for (const viewDescriptor of viewDescriptors) {
|
||||
let views = this._views.get(viewDescriptor.location);
|
||||
if (!views) {
|
||||
views = [];
|
||||
this._views.set(viewDescriptor.location, views);
|
||||
}
|
||||
if (views.some(v => v.id === viewDescriptor.id)) {
|
||||
throw new Error(localize('duplicateId', "A view with id `{0}` is already registered in the location `{1}`", viewDescriptor.id, viewDescriptor.location.id));
|
||||
}
|
||||
views.push(viewDescriptor);
|
||||
}
|
||||
this._onViewsRegistered.fire(viewDescriptors);
|
||||
}
|
||||
}
|
||||
|
||||
deregisterViews(ids: string[], location: ViewLocation): void {
|
||||
const views = this._views.get(location);
|
||||
|
||||
if (!views) {
|
||||
return;
|
||||
}
|
||||
|
||||
const viewsToDeregister = views.filter(view => ids.indexOf(view.id) !== -1);
|
||||
|
||||
if (viewsToDeregister.length) {
|
||||
this._views.set(location, views.filter(view => ids.indexOf(view.id) === -1));
|
||||
}
|
||||
|
||||
this._onViewsDeregistered.fire(viewsToDeregister);
|
||||
}
|
||||
|
||||
registerTreeViewDataProvider<T>(id: string, factory: ITreeViewDataProvider) {
|
||||
if (!this.isDataProviderRegistered(id)) {
|
||||
// TODO: throw error
|
||||
}
|
||||
this._treeViewDataPoviders.set(id, factory);
|
||||
this._onTreeViewDataProviderRegistered.fire(id);
|
||||
}
|
||||
|
||||
deregisterTreeViewDataProviders(): void {
|
||||
this._treeViewDataPoviders.clear();
|
||||
}
|
||||
|
||||
getViews(loc: ViewLocation): IViewDescriptor[] {
|
||||
return this._views.get(loc) || [];
|
||||
}
|
||||
|
||||
getTreeViewDataProvider(id: string): ITreeViewDataProvider {
|
||||
return this._treeViewDataPoviders.get(id);
|
||||
}
|
||||
|
||||
private isDataProviderRegistered(id: string): boolean {
|
||||
let registered = false;
|
||||
this._views.forEach(views => registered = registered || views.some(view => view.id === id));
|
||||
return registered;
|
||||
}
|
||||
};
|
||||
653
src/vs/workbench/browser/parts/views/viewsViewlet.ts
Normal file
@@ -0,0 +1,653 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as nls from 'vs/nls';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import * as errors from 'vs/base/common/errors';
|
||||
import * as DOM from 'vs/base/browser/dom';
|
||||
import { $, Dimension, Builder } from 'vs/base/browser/builder';
|
||||
import { Scope } from 'vs/workbench/common/memento';
|
||||
import { dispose, IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { IAction, IActionRunner } from 'vs/base/common/actions';
|
||||
import { IActionItem } from 'vs/base/browser/ui/actionbar/actionbar';
|
||||
import { ITree } from 'vs/base/parts/tree/browser/tree';
|
||||
import { firstIndex } from 'vs/base/common/arrays';
|
||||
import { DelayedDragHandler } from 'vs/base/browser/dnd';
|
||||
import { IExtensionService } from 'vs/platform/extensions/common/extensions';
|
||||
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
||||
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
|
||||
import { ViewsRegistry, ViewLocation, IViewDescriptor } from 'vs/workbench/browser/parts/views/viewsRegistry';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
|
||||
import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace';
|
||||
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { StandardMouseEvent } from 'vs/base/browser/mouseEvent';
|
||||
import { PanelViewlet, ViewletPanel } from 'vs/workbench/browser/parts/views/panelViewlet';
|
||||
import { IPanelOptions } from 'vs/base/browser/ui/splitview/panelview';
|
||||
|
||||
export interface IViewOptions extends IPanelOptions {
|
||||
id: string;
|
||||
name: string;
|
||||
actionRunner: IActionRunner;
|
||||
}
|
||||
|
||||
export interface IViewConstructorSignature<T extends ViewsViewletPanel> {
|
||||
new(options: IViewOptions, ...services: { _serviceBrand: any; }[]): T;
|
||||
}
|
||||
|
||||
export abstract class ViewsViewletPanel extends ViewletPanel {
|
||||
|
||||
readonly id: string;
|
||||
readonly name: string;
|
||||
protected treeContainer: HTMLElement;
|
||||
|
||||
// TODO@sandeep why is tree here? isn't this coming only from TreeView
|
||||
protected tree: ITree;
|
||||
protected isDisposed: boolean;
|
||||
private _isVisible: boolean;
|
||||
private dragHandler: DelayedDragHandler;
|
||||
|
||||
constructor(
|
||||
options: IViewOptions,
|
||||
protected keybindingService: IKeybindingService,
|
||||
protected contextMenuService: IContextMenuService
|
||||
) {
|
||||
super(options.name, options, keybindingService, contextMenuService);
|
||||
|
||||
this.id = options.id;
|
||||
this.name = options.name;
|
||||
this._expanded = options.expanded;
|
||||
}
|
||||
|
||||
setExpanded(expanded: boolean): void {
|
||||
this.updateTreeVisibility(this.tree, expanded);
|
||||
super.setExpanded(expanded);
|
||||
}
|
||||
|
||||
protected renderHeader(container: HTMLElement): void {
|
||||
super.renderHeader(container);
|
||||
|
||||
// Expand on drag over
|
||||
this.dragHandler = new DelayedDragHandler(container, () => this.setExpanded(true));
|
||||
}
|
||||
|
||||
protected renderViewTree(container: HTMLElement): HTMLElement {
|
||||
const treeContainer = document.createElement('div');
|
||||
container.appendChild(treeContainer);
|
||||
return treeContainer;
|
||||
}
|
||||
|
||||
getViewer(): ITree {
|
||||
return this.tree;
|
||||
}
|
||||
|
||||
isVisible(): boolean {
|
||||
return this._isVisible;
|
||||
}
|
||||
|
||||
setVisible(visible: boolean): TPromise<void> {
|
||||
if (this._isVisible !== visible) {
|
||||
this._isVisible = visible;
|
||||
this.updateTreeVisibility(this.tree, visible && this.isExpanded());
|
||||
}
|
||||
|
||||
return TPromise.as(null);
|
||||
}
|
||||
|
||||
focus(): void {
|
||||
super.focus();
|
||||
this.focusTree();
|
||||
}
|
||||
|
||||
protected reveal(element: any, relativeTop?: number): TPromise<void> {
|
||||
if (!this.tree) {
|
||||
return TPromise.as(null); // return early if viewlet has not yet been created
|
||||
}
|
||||
|
||||
return this.tree.reveal(element, relativeTop);
|
||||
}
|
||||
|
||||
layoutBody(size: number): void {
|
||||
if (this.tree) {
|
||||
this.treeContainer.style.height = size + 'px';
|
||||
this.tree.layout(size);
|
||||
}
|
||||
}
|
||||
|
||||
getActions(): IAction[] {
|
||||
return [];
|
||||
}
|
||||
|
||||
getSecondaryActions(): IAction[] {
|
||||
return [];
|
||||
}
|
||||
|
||||
getActionItem(action: IAction): IActionItem {
|
||||
return null;
|
||||
}
|
||||
|
||||
getActionsContext(): any {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
getOptimalWidth(): number {
|
||||
return 0;
|
||||
}
|
||||
|
||||
create(): TPromise<void> {
|
||||
return TPromise.as(null);
|
||||
}
|
||||
|
||||
shutdown(): void {
|
||||
// Subclass to implement
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this.isDisposed = true;
|
||||
this.treeContainer = null;
|
||||
|
||||
if (this.tree) {
|
||||
this.tree.dispose();
|
||||
}
|
||||
|
||||
if (this.dragHandler) {
|
||||
this.dragHandler.dispose();
|
||||
}
|
||||
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
private updateTreeVisibility(tree: ITree, isVisible: boolean): void {
|
||||
if (!tree) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isVisible) {
|
||||
$(tree.getHTMLElement()).show();
|
||||
} else {
|
||||
$(tree.getHTMLElement()).hide(); // make sure the tree goes out of the tabindex world by hiding it
|
||||
}
|
||||
|
||||
if (isVisible) {
|
||||
tree.onVisible();
|
||||
} else {
|
||||
tree.onHidden();
|
||||
}
|
||||
}
|
||||
|
||||
private focusTree(): void {
|
||||
if (!this.tree) {
|
||||
return; // return early if viewlet has not yet been created
|
||||
}
|
||||
|
||||
// Make sure the current selected element is revealed
|
||||
const selection = this.tree.getSelection();
|
||||
if (selection.length > 0) {
|
||||
this.reveal(selection[0], 0.5).done(null, errors.onUnexpectedError);
|
||||
}
|
||||
|
||||
// Pass Focus to Viewer
|
||||
this.tree.DOMFocus();
|
||||
}
|
||||
}
|
||||
|
||||
export interface IViewletViewOptions extends IViewOptions {
|
||||
viewletSettings: object;
|
||||
}
|
||||
|
||||
export interface IViewState {
|
||||
collapsed: boolean;
|
||||
size: number | undefined;
|
||||
isHidden: boolean;
|
||||
order: number;
|
||||
}
|
||||
|
||||
export class ViewsViewlet extends PanelViewlet {
|
||||
|
||||
private viewHeaderContextMenuListeners: IDisposable[] = [];
|
||||
private viewletSettings: object;
|
||||
private readonly viewsContextKeys: Set<string> = new Set<string>();
|
||||
private viewsViewletPanels: ViewsViewletPanel[] = [];
|
||||
private didLayout = false;
|
||||
protected viewsStates: Map<string, IViewState> = new Map<string, IViewState>();
|
||||
private areExtensionsReady: boolean = false;
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
private location: ViewLocation,
|
||||
private showHeaderInTitleWhenSingleView: boolean,
|
||||
@ITelemetryService telemetryService: ITelemetryService,
|
||||
@IStorageService protected storageService: IStorageService,
|
||||
@IInstantiationService protected instantiationService: IInstantiationService,
|
||||
@IThemeService themeService: IThemeService,
|
||||
@IContextKeyService protected contextKeyService: IContextKeyService,
|
||||
@IContextMenuService protected contextMenuService: IContextMenuService,
|
||||
@IExtensionService protected extensionService: IExtensionService
|
||||
) {
|
||||
super(id, { showHeaderInTitleWhenSingleView, dnd: true }, telemetryService, themeService);
|
||||
|
||||
this.viewletSettings = this.getMemento(storageService, Scope.WORKSPACE);
|
||||
}
|
||||
|
||||
async create(parent: Builder): TPromise<void> {
|
||||
await super.create(parent);
|
||||
|
||||
this._register(this.onDidSashChange(() => this.updateAllViewsSizes()));
|
||||
this._register(ViewsRegistry.onViewsRegistered(this.onViewsRegistered, this));
|
||||
this._register(ViewsRegistry.onViewsDeregistered(this.onViewsDeregistered, this));
|
||||
this._register(this.contextKeyService.onDidChangeContext(keys => this.onContextChanged(keys)));
|
||||
|
||||
// Update headers after and title contributed views after available, since we read from cache in the beginning to know if the viewlet has single view or not. Ref #29609
|
||||
this.extensionService.onReady().then(() => {
|
||||
this.areExtensionsReady = true;
|
||||
this.updateHeaders();
|
||||
});
|
||||
|
||||
this.onViewsRegistered(ViewsRegistry.getViews(this.location));
|
||||
this.focus();
|
||||
}
|
||||
|
||||
getContextMenuActions(): IAction[] {
|
||||
return this.getViewDescriptorsFromRegistry(true)
|
||||
.filter(viewDescriptor => viewDescriptor.canToggleVisibility && this.contextKeyService.contextMatchesRules(viewDescriptor.when))
|
||||
.map(viewDescriptor => (<IAction>{
|
||||
id: `${viewDescriptor.id}.toggleVisibility`,
|
||||
label: viewDescriptor.name,
|
||||
checked: this.isCurrentlyVisible(viewDescriptor),
|
||||
enabled: true,
|
||||
run: () => this.toggleViewVisibility(viewDescriptor.id)
|
||||
}));
|
||||
}
|
||||
|
||||
setVisible(visible: boolean): TPromise<void> {
|
||||
return super.setVisible(visible)
|
||||
.then(() => TPromise.join(this.viewsViewletPanels.filter(view => view.isVisible() !== visible)
|
||||
.map((view) => view.setVisible(visible))))
|
||||
.then(() => void 0);
|
||||
}
|
||||
|
||||
layout(dimension: Dimension): void {
|
||||
super.layout(dimension);
|
||||
|
||||
if (!this.didLayout) {
|
||||
this.didLayout = true;
|
||||
this._resizePanels();
|
||||
}
|
||||
|
||||
this.updateAllViewsSizes();
|
||||
}
|
||||
|
||||
getOptimalWidth(): number {
|
||||
const additionalMargin = 16;
|
||||
const optimalWidth = Math.max(...this.viewsViewletPanels.map(view => view.getOptimalWidth() || 0));
|
||||
return optimalWidth + additionalMargin;
|
||||
}
|
||||
|
||||
shutdown(): void {
|
||||
this.viewsViewletPanels.forEach((view) => view.shutdown());
|
||||
super.shutdown();
|
||||
}
|
||||
|
||||
toggleViewVisibility(id: string, visible?: boolean): void {
|
||||
const view = this.getView(id);
|
||||
let viewState = this.viewsStates.get(id);
|
||||
|
||||
if ((visible === true && view) || (visible === false && !view)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (view) {
|
||||
viewState = viewState || this.createViewState(view);
|
||||
viewState.isHidden = true;
|
||||
} else {
|
||||
viewState = viewState || { collapsed: true, size: void 0, isHidden: false, order: void 0 };
|
||||
viewState.isHidden = false;
|
||||
}
|
||||
this.viewsStates.set(id, viewState);
|
||||
this.updateViews();
|
||||
}
|
||||
|
||||
private onViewsRegistered(views: IViewDescriptor[]): TPromise<ViewsViewletPanel[]> {
|
||||
this.viewsContextKeys.clear();
|
||||
for (const viewDescriptor of this.getViewDescriptorsFromRegistry()) {
|
||||
if (viewDescriptor.when) {
|
||||
for (const key of viewDescriptor.when.keys()) {
|
||||
this.viewsContextKeys.add(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return this.updateViews();
|
||||
}
|
||||
|
||||
private onViewsDeregistered(views: IViewDescriptor[]): TPromise<ViewsViewletPanel[]> {
|
||||
return this.updateViews(views);
|
||||
}
|
||||
|
||||
private onContextChanged(keys: string[]): void {
|
||||
if (!keys) {
|
||||
return;
|
||||
}
|
||||
|
||||
let hasToUpdate: boolean = false;
|
||||
for (const key of keys) {
|
||||
if (this.viewsContextKeys.has(key)) {
|
||||
hasToUpdate = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (hasToUpdate) {
|
||||
this.updateViews();
|
||||
}
|
||||
}
|
||||
|
||||
protected updateViews(unregisteredViews: IViewDescriptor[] = []): TPromise<ViewsViewletPanel[]> {
|
||||
const registeredViews = this.getViewDescriptorsFromRegistry();
|
||||
const [visible, toAdd, toRemove] = registeredViews.reduce<[IViewDescriptor[], IViewDescriptor[], IViewDescriptor[]]>((result, viewDescriptor) => {
|
||||
const isCurrentlyVisible = this.isCurrentlyVisible(viewDescriptor);
|
||||
const canBeVisible = this.canBeVisible(viewDescriptor);
|
||||
|
||||
if (canBeVisible) {
|
||||
result[0].push(viewDescriptor);
|
||||
}
|
||||
|
||||
if (!isCurrentlyVisible && canBeVisible) {
|
||||
result[1].push(viewDescriptor);
|
||||
}
|
||||
|
||||
if (isCurrentlyVisible && !canBeVisible) {
|
||||
result[2].push(viewDescriptor);
|
||||
}
|
||||
|
||||
return result;
|
||||
|
||||
}, [[], [], []]);
|
||||
|
||||
toRemove.push(...unregisteredViews.filter(viewDescriptor => this.isCurrentlyVisible(viewDescriptor)));
|
||||
|
||||
const toCreate: ViewsViewletPanel[] = [];
|
||||
|
||||
if (toAdd.length || toRemove.length) {
|
||||
const panels = [...this.viewsViewletPanels];
|
||||
|
||||
for (const view of panels) {
|
||||
let viewState = this.viewsStates.get(view.id);
|
||||
if (!viewState || typeof viewState.size === 'undefined' || !view.isExpanded() !== viewState.collapsed) {
|
||||
viewState = this.updateViewStateSize(view);
|
||||
this.viewsStates.set(view.id, viewState);
|
||||
}
|
||||
}
|
||||
|
||||
if (toRemove.length) {
|
||||
for (const viewDescriptor of toRemove) {
|
||||
let view = this.getView(viewDescriptor.id);
|
||||
const viewState = this.updateViewStateSize(view);
|
||||
this.viewsStates.set(view.id, viewState);
|
||||
this.removePanel(view);
|
||||
this.viewsViewletPanels.splice(this.viewsViewletPanels.indexOf(view), 1);
|
||||
}
|
||||
}
|
||||
|
||||
for (const viewDescriptor of toAdd) {
|
||||
let viewState = this.viewsStates.get(viewDescriptor.id);
|
||||
let index = visible.indexOf(viewDescriptor);
|
||||
const view = this.createView(viewDescriptor,
|
||||
{
|
||||
id: viewDescriptor.id,
|
||||
name: viewDescriptor.name,
|
||||
actionRunner: this.getActionRunner(),
|
||||
expanded: !(viewState ? viewState.collapsed : viewDescriptor.collapsed),
|
||||
viewletSettings: this.viewletSettings
|
||||
});
|
||||
toCreate.push(view);
|
||||
|
||||
const size = (viewState && viewState.size) || viewDescriptor.size || 200;
|
||||
this.addPanel(view, size, index);
|
||||
this.viewsViewletPanels.splice(index, 0, view);
|
||||
|
||||
this.viewsStates.set(view.id, this.updateViewStateSize(view));
|
||||
}
|
||||
|
||||
return TPromise.join(toCreate.map(view => view.create()))
|
||||
.then(() => this.onViewsUpdated())
|
||||
.then(() => this._resizePanels())
|
||||
.then(() => toCreate);
|
||||
}
|
||||
|
||||
return TPromise.as([]);
|
||||
}
|
||||
|
||||
private updateAllViewsSizes(): void {
|
||||
for (const view of this.viewsViewletPanels) {
|
||||
let viewState = this.updateViewStateSize(view);
|
||||
this.viewsStates.set(view.id, viewState);
|
||||
}
|
||||
}
|
||||
|
||||
private _resizePanels(): void {
|
||||
if (!this.didLayout) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const panel of this.viewsViewletPanels) {
|
||||
const viewState = this.viewsStates.get(panel.id);
|
||||
const size = (viewState && viewState.size) || 200;
|
||||
this.resizePanel(panel, size);
|
||||
}
|
||||
}
|
||||
|
||||
movePanel(from: ViewletPanel, to: ViewletPanel): void {
|
||||
const fromIndex = firstIndex(this.viewsViewletPanels, panel => panel === from);
|
||||
const toIndex = firstIndex(this.viewsViewletPanels, panel => panel === to);
|
||||
|
||||
if (fromIndex < 0 || fromIndex >= this.viewsViewletPanels.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (toIndex < 0 || toIndex >= this.viewsViewletPanels.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
super.movePanel(from, to);
|
||||
|
||||
const [panel] = this.viewsViewletPanels.splice(fromIndex, 1);
|
||||
this.viewsViewletPanels.splice(toIndex, 0, panel);
|
||||
|
||||
for (let order = 0; order < this.viewsViewletPanels.length; order++) {
|
||||
const view = this.viewsStates.get(this.viewsViewletPanels[order].id);
|
||||
|
||||
if (!view) {
|
||||
continue;
|
||||
}
|
||||
|
||||
view.order = order;
|
||||
}
|
||||
}
|
||||
|
||||
protected getDefaultViewSize(): number | undefined {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
private isCurrentlyVisible(viewDescriptor: IViewDescriptor): boolean {
|
||||
return !!this.getView(viewDescriptor.id);
|
||||
}
|
||||
|
||||
private canBeVisible(viewDescriptor: IViewDescriptor): boolean {
|
||||
const viewstate = this.viewsStates.get(viewDescriptor.id);
|
||||
if (viewDescriptor.canToggleVisibility && viewstate && viewstate.isHidden) {
|
||||
return false;
|
||||
}
|
||||
return this.contextKeyService.contextMatchesRules(viewDescriptor.when);
|
||||
}
|
||||
|
||||
private onViewsUpdated(): TPromise<void> {
|
||||
this.viewHeaderContextMenuListeners = dispose(this.viewHeaderContextMenuListeners);
|
||||
|
||||
for (const viewDescriptor of this.getViewDescriptorsFromRegistry()) {
|
||||
const view = this.getView(viewDescriptor.id);
|
||||
|
||||
if (view) {
|
||||
this.viewHeaderContextMenuListeners.push(DOM.addDisposableListener(view.draggableElement, DOM.EventType.CONTEXT_MENU, e => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
if (viewDescriptor.canToggleVisibility) {
|
||||
this.onContextMenu(new StandardMouseEvent(e), view);
|
||||
}
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
return this.setVisible(this.isVisible());
|
||||
}
|
||||
|
||||
private updateHeaders(): void {
|
||||
if (this.viewsViewletPanels.length) {
|
||||
this.updateTitleArea();
|
||||
this.updateViewHeaders();
|
||||
}
|
||||
}
|
||||
|
||||
private onContextMenu(event: StandardMouseEvent, view: ViewsViewletPanel): void {
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
|
||||
let anchor: { x: number, y: number } = { x: event.posx, y: event.posy };
|
||||
this.contextMenuService.showContextMenu({
|
||||
getAnchor: () => anchor,
|
||||
getActions: () => TPromise.as([<IAction>{
|
||||
id: `${view.id}.removeView`,
|
||||
label: nls.localize('hideView', "Hide from Side Bar"),
|
||||
enabled: true,
|
||||
run: () => this.toggleViewVisibility(view.id)
|
||||
}]),
|
||||
});
|
||||
}
|
||||
|
||||
protected isSingleView(): boolean {
|
||||
if (!this.showHeaderInTitleWhenSingleView) {
|
||||
return false;
|
||||
}
|
||||
if (this.getViewDescriptorsFromRegistry().length === 0) {
|
||||
return false;
|
||||
}
|
||||
if (this.length > 1) {
|
||||
return false;
|
||||
}
|
||||
// Check in cache so that view do not jump. See #29609
|
||||
if (ViewLocation.getContributedViewLocation(this.location.id) && !this.areExtensionsReady) {
|
||||
let visibleViewsCount = 0;
|
||||
this.viewsStates.forEach((viewState, id) => {
|
||||
if (!viewState.isHidden) {
|
||||
visibleViewsCount++;
|
||||
}
|
||||
});
|
||||
return visibleViewsCount === 1;
|
||||
}
|
||||
return super.isSingleView();
|
||||
}
|
||||
|
||||
protected getViewDescriptorsFromRegistry(defaultOrder: boolean = false): IViewDescriptor[] {
|
||||
return ViewsRegistry.getViews(this.location)
|
||||
.sort((a, b) => {
|
||||
const viewStateA = this.viewsStates.get(a.id);
|
||||
const viewStateB = this.viewsStates.get(b.id);
|
||||
const orderA = !defaultOrder && viewStateA ? viewStateA.order : a.order;
|
||||
const orderB = !defaultOrder && viewStateB ? viewStateB.order : b.order;
|
||||
|
||||
if (orderB === void 0 || orderB === null) {
|
||||
return -1;
|
||||
}
|
||||
if (orderA === void 0 || orderA === null) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
return orderA - orderB;
|
||||
});
|
||||
}
|
||||
|
||||
protected createView(viewDescriptor: IViewDescriptor, options: IViewletViewOptions): ViewsViewletPanel {
|
||||
return this.instantiationService.createInstance(viewDescriptor.ctor, options);
|
||||
}
|
||||
|
||||
protected get views(): ViewsViewletPanel[] {
|
||||
return this.viewsViewletPanels;
|
||||
}
|
||||
|
||||
protected getView(id: string): ViewsViewletPanel {
|
||||
return this.viewsViewletPanels.filter(view => view.id === id)[0];
|
||||
}
|
||||
|
||||
private updateViewStateSize(view: ViewsViewletPanel): IViewState {
|
||||
const currentState = this.viewsStates.get(view.id);
|
||||
const newViewState = this.createViewState(view);
|
||||
return currentState ? { ...currentState, collapsed: newViewState.collapsed, size: newViewState.size } : newViewState;
|
||||
}
|
||||
|
||||
protected createViewState(view: ViewsViewletPanel): IViewState {
|
||||
return {
|
||||
collapsed: !view.isExpanded(),
|
||||
size: this.getPanelSize(view),
|
||||
isHidden: false,
|
||||
order: this.viewsViewletPanels.indexOf(view)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export class PersistentViewsViewlet extends ViewsViewlet {
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
location: ViewLocation,
|
||||
private viewletStateStorageId: string,
|
||||
showHeaderInTitleWhenSingleView: boolean,
|
||||
@ITelemetryService telemetryService: ITelemetryService,
|
||||
@IStorageService storageService: IStorageService,
|
||||
@IInstantiationService instantiationService: IInstantiationService,
|
||||
@IThemeService themeService: IThemeService,
|
||||
@IWorkspaceContextService protected contextService: IWorkspaceContextService,
|
||||
@IContextKeyService contextKeyService: IContextKeyService,
|
||||
@IContextMenuService contextMenuService: IContextMenuService,
|
||||
@IExtensionService extensionService: IExtensionService
|
||||
) {
|
||||
super(id, location, showHeaderInTitleWhenSingleView, telemetryService, storageService, instantiationService, themeService, contextKeyService, contextMenuService, extensionService);
|
||||
}
|
||||
|
||||
create(parent: Builder): TPromise<void> {
|
||||
this.loadViewsStates();
|
||||
return super.create(parent);
|
||||
}
|
||||
|
||||
shutdown(): void {
|
||||
this.saveViewsStates();
|
||||
super.shutdown();
|
||||
}
|
||||
|
||||
protected saveViewsStates(): void {
|
||||
const viewsStates = {};
|
||||
const registeredViewDescriptors = this.getViewDescriptorsFromRegistry();
|
||||
this.viewsStates.forEach((viewState, id) => {
|
||||
const view = this.getView(id);
|
||||
|
||||
if (view) {
|
||||
viewsStates[id] = this.createViewState(view);
|
||||
} else {
|
||||
const viewDescriptor = registeredViewDescriptors.filter(v => v.id === id)[0];
|
||||
if (viewDescriptor) {
|
||||
viewsStates[id] = viewState;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
this.storageService.store(this.viewletStateStorageId, JSON.stringify(viewsStates), this.contextService.getWorkbenchState() !== WorkbenchState.EMPTY ? StorageScope.WORKSPACE : StorageScope.GLOBAL);
|
||||
}
|
||||
|
||||
protected loadViewsStates(): void {
|
||||
const viewsStates = JSON.parse(this.storageService.get(this.viewletStateStorageId, this.contextService.getWorkbenchState() !== WorkbenchState.EMPTY ? StorageScope.WORKSPACE : StorageScope.GLOBAL, '{}'));
|
||||
Object.keys(viewsStates).forEach(id => this.viewsStates.set(id, <IViewState>viewsStates[id]));
|
||||
}
|
||||
}
|
||||