Merge from master
@@ -3,27 +3,30 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import 'vs/css!./media/activityaction';
|
||||
import * as nls from 'vs/nls';
|
||||
import * as DOM from 'vs/base/browser/dom';
|
||||
import { EventType as TouchEventType, GestureEvent } from 'vs/base/browser/touch';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { Action } from 'vs/base/common/actions';
|
||||
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
|
||||
import { ViewletDescriptor } from 'vs/workbench/browser/viewlet';
|
||||
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 { 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';
|
||||
import { ActivityAction, ActivityActionItem, ICompositeBarColors, ToggleCompositePinnedAction, ICompositeBar } from 'vs/workbench/browser/parts/compositebar/compositeBarActions';
|
||||
import { StandardMouseEvent } from 'vs/base/browser/mouseEvent';
|
||||
import { EventType as TouchEventType, GestureEvent } from 'vs/base/browser/touch';
|
||||
import { Action } from 'vs/base/common/actions';
|
||||
import { KeyCode } from 'vs/base/common/keyCodes';
|
||||
import { dispose } from 'vs/base/common/lifecycle';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { SyncActionDescriptor } from 'vs/platform/actions/common/actions';
|
||||
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import URI from 'vs/base/common/uri';
|
||||
import { activeContrastBorder, focusBorder } from 'vs/platform/theme/common/colorRegistry';
|
||||
import { ICssStyleCollector, ITheme, IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService';
|
||||
import { ActivityAction, ActivityActionItem, ICompositeBar, ICompositeBarColors, ToggleCompositePinnedAction } from 'vs/workbench/browser/parts/compositeBarActions';
|
||||
import { ViewletDescriptor } from 'vs/workbench/browser/viewlet';
|
||||
import { Extensions as ActionExtensions, IWorkbenchActionRegistry } from 'vs/workbench/common/actions';
|
||||
import { IActivity, IGlobalActivity } from 'vs/workbench/common/activity';
|
||||
import { ACTIVITY_BAR_FOREGROUND } from 'vs/workbench/common/theme';
|
||||
import { IActivityService } from 'vs/workbench/services/activity/common/activity';
|
||||
import { IPartService, Parts } from 'vs/workbench/services/part/common/partService';
|
||||
import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet';
|
||||
|
||||
export class ViewletActivityAction extends ActivityAction {
|
||||
|
||||
@@ -40,15 +43,15 @@ export class ViewletActivityAction extends ActivityAction {
|
||||
super(activity);
|
||||
}
|
||||
|
||||
run(event: any): TPromise<any> {
|
||||
run(event: any): Thenable<any> {
|
||||
if (event instanceof MouseEvent && event.button === 2) {
|
||||
return TPromise.as(false); // do not run on right click
|
||||
return Promise.resolve(false); // do not run on right click
|
||||
}
|
||||
|
||||
// prevent accident trigger on a doubleclick (to help nervous people)
|
||||
const now = Date.now();
|
||||
if (now > this.lastRun /* https://github.com/Microsoft/vscode/issues/25830 */ && now - this.lastRun < ViewletActivityAction.preventDoubleClickDelay) {
|
||||
return TPromise.as(true);
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
this.lastRun = now;
|
||||
|
||||
@@ -58,7 +61,8 @@ export class ViewletActivityAction extends ActivityAction {
|
||||
// Hide sidebar if selected viewlet already visible
|
||||
if (sideBarVisible && activeViewlet && activeViewlet.getId() === this.activity.id) {
|
||||
this.logAction('hide');
|
||||
return this.partService.setSideBarHidden(true);
|
||||
this.partService.setSideBarHidden(true);
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
|
||||
this.logAction('show');
|
||||
@@ -86,13 +90,14 @@ export class ToggleViewletAction extends Action {
|
||||
super(_viewlet.id, _viewlet.name);
|
||||
}
|
||||
|
||||
run(): TPromise<any> {
|
||||
run(): Thenable<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) {
|
||||
return this.partService.setSideBarHidden(true);
|
||||
this.partService.setSideBarHidden(true);
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
|
||||
return this.viewletService.openViewlet(this._viewlet.id, true);
|
||||
@@ -110,7 +115,7 @@ export class GlobalActivityActionItem extends ActivityActionItem {
|
||||
|
||||
constructor(
|
||||
action: GlobalActivityAction,
|
||||
colors: ICompositeBarColors,
|
||||
colors: (theme: ITheme) => ICompositeBarColors,
|
||||
@IThemeService themeService: IThemeService,
|
||||
@IContextMenuService protected contextMenuService: IContextMenuService
|
||||
) {
|
||||
@@ -122,28 +127,29 @@ export class GlobalActivityActionItem extends ActivityActionItem {
|
||||
|
||||
// Context menus are triggered on mouse down so that an item can be picked
|
||||
// and executed with releasing the mouse over it
|
||||
this.$container.on(DOM.EventType.MOUSE_DOWN, (e: MouseEvent) => {
|
||||
|
||||
this._register(DOM.addDisposableListener(this.container, DOM.EventType.MOUSE_DOWN, (e: MouseEvent) => {
|
||||
DOM.EventHelper.stop(e, true);
|
||||
|
||||
const event = new StandardMouseEvent(e);
|
||||
this.showContextMenu({ x: event.posx, y: event.posy });
|
||||
});
|
||||
}));
|
||||
|
||||
this.$container.on(DOM.EventType.KEY_UP, (e: KeyboardEvent) => {
|
||||
this._register(DOM.addDisposableListener(this.container, DOM.EventType.KEY_UP, (e: KeyboardEvent) => {
|
||||
let event = new StandardKeyboardEvent(e);
|
||||
if (event.equals(KeyCode.Enter) || event.equals(KeyCode.Space)) {
|
||||
DOM.EventHelper.stop(e, true);
|
||||
|
||||
this.showContextMenu(this.$container.getHTMLElement());
|
||||
this.showContextMenu(this.container);
|
||||
}
|
||||
});
|
||||
}));
|
||||
|
||||
this.$container.on(TouchEventType.Tap, (e: GestureEvent) => {
|
||||
this._register(DOM.addDisposableListener(this.container, 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 {
|
||||
@@ -153,7 +159,7 @@ export class GlobalActivityActionItem extends ActivityActionItem {
|
||||
|
||||
this.contextMenuService.showContextMenu({
|
||||
getAnchor: () => location,
|
||||
getActions: () => TPromise.as(actions),
|
||||
getActions: () => actions,
|
||||
onHide: () => dispose(actions)
|
||||
});
|
||||
}
|
||||
@@ -171,12 +177,10 @@ export class PlaceHolderViewletActivityAction extends ViewletActivityAction {
|
||||
|
||||
const iconClass = `.monaco-workbench > .activitybar .monaco-action-bar .action-label.${this.class}`; // Generate Placeholder CSS to show the icon in the activity bar
|
||||
DOM.createCSSRule(iconClass, `-webkit-mask: url('${iconUrl || ''}') no-repeat 50% 50%`);
|
||||
this.enabled = false;
|
||||
}
|
||||
|
||||
setActivity(activity: IActivity): void {
|
||||
this.activity = activity;
|
||||
this.enabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -184,18 +188,97 @@ export class PlaceHolderToggleCompositePinnedAction extends ToggleCompositePinne
|
||||
|
||||
constructor(id: string, compositeBar: ICompositeBar) {
|
||||
super({ id, name: id, cssClass: void 0 }, compositeBar);
|
||||
|
||||
this.enabled = false;
|
||||
}
|
||||
|
||||
setActivity(activity: IActivity): void {
|
||||
this.label = activity.name;
|
||||
this.enabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
class SwitchSideBarViewAction extends Action {
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
name: string,
|
||||
@IViewletService private viewletService: IViewletService,
|
||||
@IActivityService private activityService: IActivityService
|
||||
) {
|
||||
super(id, name);
|
||||
}
|
||||
|
||||
run(offset: number): Thenable<any> {
|
||||
const pinnedViewletIds = this.activityService.getPinnedViewletIds();
|
||||
|
||||
const activeViewlet = this.viewletService.getActiveViewlet();
|
||||
if (!activeViewlet) {
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
let targetViewletId: string;
|
||||
for (let i = 0; i < pinnedViewletIds.length; i++) {
|
||||
if (pinnedViewletIds[i] === activeViewlet.getId()) {
|
||||
targetViewletId = pinnedViewletIds[(i + pinnedViewletIds.length + offset) % pinnedViewletIds.length];
|
||||
break;
|
||||
}
|
||||
}
|
||||
return this.viewletService.openViewlet(targetViewletId, true);
|
||||
}
|
||||
}
|
||||
|
||||
export class PreviousSideBarViewAction extends SwitchSideBarViewAction {
|
||||
|
||||
static readonly ID = 'workbench.action.previousSideBarView';
|
||||
static LABEL = nls.localize('previousSideBarView', 'Previous Side Bar View');
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
name: string,
|
||||
@IViewletService viewletService: IViewletService,
|
||||
@IActivityService activityService: IActivityService
|
||||
) {
|
||||
super(id, name, viewletService, activityService);
|
||||
}
|
||||
|
||||
run(): Thenable<any> {
|
||||
return super.run(-1);
|
||||
}
|
||||
}
|
||||
|
||||
export class NextSideBarViewAction extends SwitchSideBarViewAction {
|
||||
|
||||
static readonly ID = 'workbench.action.nextSideBarView';
|
||||
static LABEL = nls.localize('nextSideBarView', 'Next Side Bar View');
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
name: string,
|
||||
@IViewletService viewletService: IViewletService,
|
||||
@IActivityService activityService: IActivityService
|
||||
) {
|
||||
super(id, name, viewletService, activityService);
|
||||
}
|
||||
|
||||
run(): Thenable<any> {
|
||||
return super.run(1);
|
||||
}
|
||||
}
|
||||
|
||||
const registry = Registry.as<IWorkbenchActionRegistry>(ActionExtensions.WorkbenchActions);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(PreviousSideBarViewAction, PreviousSideBarViewAction.ID, PreviousSideBarViewAction.LABEL), 'View: Open Previous Side Bar View', nls.localize('view', "View"));
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(NextSideBarViewAction, NextSideBarViewAction.ID, NextSideBarViewAction.LABEL), 'View: Open Next Side Bar View', nls.localize('view', "View"));
|
||||
|
||||
registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => {
|
||||
|
||||
const activeForegroundColor = theme.getColor(ACTIVITY_BAR_FOREGROUND);
|
||||
if (activeForegroundColor) {
|
||||
collector.addRule(`
|
||||
.monaco-workbench > .activitybar > .content .monaco-action-bar .action-item.active .action-label,
|
||||
.monaco-workbench > .activitybar > .content .monaco-action-bar .action-item:focus .action-label,
|
||||
.monaco-workbench > .activitybar > .content .monaco-action-bar .action-item:hover .action-label {
|
||||
background-color: ${activeForegroundColor} !important;
|
||||
}
|
||||
`);
|
||||
}
|
||||
|
||||
// Styling with Outline color (e.g. high contrast theme)
|
||||
const outline = theme.getColor(activeContrastBorder);
|
||||
if (outline) {
|
||||
@@ -207,7 +290,6 @@ registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => {
|
||||
left: 9px;
|
||||
height: 32px;
|
||||
width: 32px;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.monaco-workbench > .activitybar > .content .monaco-action-bar .action-item.active:before,
|
||||
@@ -221,12 +303,6 @@ registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => {
|
||||
outline: 1px dashed;
|
||||
}
|
||||
|
||||
.monaco-workbench > .activitybar > .content .monaco-action-bar .action-item.active:before,
|
||||
.monaco-workbench > .activitybar > .content .monaco-action-bar .action-item.checked:before,
|
||||
.monaco-workbench > .activitybar > .content .monaco-action-bar .action-item:hover:before {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.monaco-workbench > .activitybar > .content .monaco-action-bar .action-item:focus:before {
|
||||
border-left-color: ${outline};
|
||||
}
|
||||
@@ -246,21 +322,10 @@ registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => {
|
||||
const focusBorderColor = theme.getColor(focusBorder);
|
||||
if (focusBorderColor) {
|
||||
collector.addRule(`
|
||||
.monaco-workbench > .activitybar > .content .monaco-action-bar .action-item.active .action-label,
|
||||
.monaco-workbench > .activitybar > .content .monaco-action-bar .action-item.checked .action-label,
|
||||
.monaco-workbench > .activitybar > .content .monaco-action-bar .action-item:focus .action-label,
|
||||
.monaco-workbench > .activitybar > .content .monaco-action-bar .action-item:hover .action-label {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.monaco-workbench > .activitybar > .content .monaco-action-bar .action-item .action-label {
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.monaco-workbench > .activitybar > .content .monaco-action-bar .action-item:focus:before {
|
||||
border-left-color: ${focusBorderColor};
|
||||
}
|
||||
`);
|
||||
.monaco-workbench > .activitybar > .content .monaco-action-bar .action-item:focus:before {
|
||||
border-left-color: ${focusBorderColor};
|
||||
}
|
||||
`);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -3,12 +3,9 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import 'vs/css!./media/activitybarpart';
|
||||
import * as nls from 'vs/nls';
|
||||
import { illegalArgument } from 'vs/base/common/errors';
|
||||
import { $ } from 'vs/base/browser/builder';
|
||||
import { ActionsOrientation, ActionBar } from 'vs/base/browser/ui/actionbar/actionbar';
|
||||
import { GlobalActivityExtensions, IGlobalActivityRegistry } from 'vs/workbench/common/activity';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
@@ -20,43 +17,43 @@ import { IPartService, Parts, Position as SideBarPosition } from 'vs/workbench/s
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IDisposable, toDisposable } from 'vs/base/common/lifecycle';
|
||||
import { ToggleActivityBarVisibilityAction } from 'vs/workbench/browser/actions/toggleActivityBarVisibility';
|
||||
import { IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService';
|
||||
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 { IThemeService, registerThemingParticipant, ITheme } from 'vs/platform/theme/common/themeService';
|
||||
import { ACTIVITY_BAR_BACKGROUND, ACTIVITY_BAR_BORDER, ACTIVITY_BAR_FOREGROUND, ACTIVITY_BAR_BADGE_BACKGROUND, ACTIVITY_BAR_BADGE_FOREGROUND, ACTIVITY_BAR_DRAG_AND_DROP_BACKGROUND, ACTIVITY_BAR_INACTIVE_FOREGROUND } from 'vs/workbench/common/theme';
|
||||
import { contrastBorder } from 'vs/platform/theme/common/colorRegistry';
|
||||
import { CompositeBar } from 'vs/workbench/browser/parts/compositebar/compositeBar';
|
||||
import { CompositeBar } from 'vs/workbench/browser/parts/compositeBar';
|
||||
import { isMacintosh } from 'vs/base/common/platform';
|
||||
import { ILifecycleService, LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle';
|
||||
import { scheduleAtNextAnimationFrame, Dimension } from 'vs/base/browser/dom';
|
||||
import { scheduleAtNextAnimationFrame, Dimension, addClass } from 'vs/base/browser/dom';
|
||||
import { Color } from 'vs/base/common/color';
|
||||
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
|
||||
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
|
||||
import URI from 'vs/base/common/uri';
|
||||
import { ToggleCompositePinnedAction } from 'vs/workbench/browser/parts/compositebar/compositeBarActions';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { ToggleCompositePinnedAction, ICompositeBarColors } from 'vs/workbench/browser/parts/compositeBarActions';
|
||||
import { ViewletDescriptor } from 'vs/workbench/browser/viewlet';
|
||||
import { IViewsService, IViewContainersRegistry, Extensions as ViewContainerExtensions, ViewContainer, TEST_VIEW_CONTAINER_ID, IViewDescriptorCollection } from 'vs/workbench/common/views';
|
||||
import { IContextKeyService, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { IViewlet } from 'vs/workbench/common/viewlet';
|
||||
|
||||
interface IPlaceholderComposite {
|
||||
const SCM_VIEWLET_ID = 'workbench.view.scm';
|
||||
|
||||
interface ICachedViewlet {
|
||||
id: string;
|
||||
iconUrl: URI;
|
||||
views?: { when: string }[];
|
||||
}
|
||||
|
||||
export class ActivitybarPart extends Part {
|
||||
|
||||
private static readonly ACTION_HEIGHT = 50;
|
||||
private static readonly PINNED_VIEWLETS = 'workbench.activity.pinnedViewlets';
|
||||
private static readonly PLACEHOLDER_VIEWLETS = 'workbench.activity.placeholderViewlets';
|
||||
private static readonly COLORS = {
|
||||
backgroundColor: ACTIVITY_BAR_FOREGROUND,
|
||||
badgeBackground: ACTIVITY_BAR_BADGE_BACKGROUND,
|
||||
badgeForeground: ACTIVITY_BAR_BADGE_FOREGROUND,
|
||||
dragAndDropBackground: ACTIVITY_BAR_DRAG_AND_DROP_BACKGROUND
|
||||
};
|
||||
private static readonly CACHED_VIEWLETS = 'workbench.activity.placeholderViewlets';
|
||||
|
||||
private dimension: Dimension;
|
||||
|
||||
private globalActionBar: ActionBar;
|
||||
private globalActivityIdToActions: { [globalActivityId: string]: GlobalActivityAction; } = Object.create(null);
|
||||
|
||||
private placeholderComposites: IPlaceholderComposite[] = [];
|
||||
private cachedViewlets: ICachedViewlet[] = [];
|
||||
private compositeBar: CompositeBar;
|
||||
private compositeActions: { [compositeId: string]: { activityAction: ViewletActivityAction, pinnedAction: ToggleCompositePinnedAction } } = Object.create(null);
|
||||
|
||||
@@ -68,9 +65,11 @@ export class ActivitybarPart extends Part {
|
||||
@IThemeService themeService: IThemeService,
|
||||
@ILifecycleService private lifecycleService: ILifecycleService,
|
||||
@IStorageService private storageService: IStorageService,
|
||||
@IExtensionService private extensionService: IExtensionService
|
||||
@IExtensionService private extensionService: IExtensionService,
|
||||
@IViewsService private viewsService: IViewsService,
|
||||
@IContextKeyService private contextKeyService: IContextKeyService
|
||||
) {
|
||||
super(id, { hasTitle: false }, themeService);
|
||||
super(id, { hasTitle: false }, themeService, storageService);
|
||||
|
||||
this.compositeBar = this._register(this.instantiationService.createInstance(CompositeBar, {
|
||||
icon: true,
|
||||
@@ -84,28 +83,20 @@ export class ActivitybarPart extends Part {
|
||||
getDefaultCompositeId: () => this.viewletService.getDefaultViewletId(),
|
||||
hidePart: () => this.partService.setSideBarHidden(true),
|
||||
compositeSize: 50,
|
||||
colors: ActivitybarPart.COLORS,
|
||||
colors: theme => this.getActivitybarItemColors(theme),
|
||||
overflowActionSize: ActivitybarPart.ACTION_HEIGHT
|
||||
}));
|
||||
|
||||
const previousState = this.storageService.get(ActivitybarPart.PLACEHOLDER_VIEWLETS, StorageScope.GLOBAL, void 0);
|
||||
if (previousState) {
|
||||
let parsedPreviousState = <IPlaceholderComposite[]>JSON.parse(previousState);
|
||||
parsedPreviousState.forEach((s) => {
|
||||
if (typeof s.iconUrl === 'object') {
|
||||
s.iconUrl = URI.revive(s.iconUrl);
|
||||
} else {
|
||||
s.iconUrl = void 0;
|
||||
}
|
||||
});
|
||||
this.placeholderComposites = parsedPreviousState;
|
||||
} else {
|
||||
this.placeholderComposites = this.compositeBar.getCompositesFromStorage().map(id => (<IPlaceholderComposite>{ id, iconUrl: void 0 }));
|
||||
const previousState = this.storageService.get(ActivitybarPart.CACHED_VIEWLETS, StorageScope.GLOBAL, '[]');
|
||||
this.cachedViewlets = (<ICachedViewlet[]>JSON.parse(previousState)).map(({ id, iconUrl, views }) => ({ id, views, iconUrl: typeof iconUrl === 'object' ? URI.revive(iconUrl) : void 0 }));
|
||||
for (const cachedViewlet of this.cachedViewlets) {
|
||||
if (this.shouldBeHidden(cachedViewlet.id, cachedViewlet)) {
|
||||
this.compositeBar.hideComposite(cachedViewlet.id);
|
||||
}
|
||||
}
|
||||
|
||||
this.registerListeners();
|
||||
this.updateCompositebar();
|
||||
this.updatePlaceholderComposites();
|
||||
this.onDidRegisterViewlets(viewletService.getAllViewlets());
|
||||
}
|
||||
|
||||
// {{SQL CARBON EDIT}}
|
||||
@@ -121,10 +112,10 @@ export class ActivitybarPart extends Part {
|
||||
}
|
||||
|
||||
private registerListeners(): void {
|
||||
this._register(this.viewletService.onDidViewletRegister(() => this.updateCompositebar()));
|
||||
this._register(this.viewletService.onDidViewletRegister(viewlet => this.onDidRegisterViewlets([viewlet])));
|
||||
|
||||
// Activate viewlet action on opening of a viewlet
|
||||
this._register(this.viewletService.onDidViewletOpen(viewlet => this.compositeBar.activateComposite(viewlet.getId())));
|
||||
this._register(this.viewletService.onDidViewletOpen(viewlet => this.onDidViewletOpen(viewlet)));
|
||||
|
||||
// Deactivate viewlet action on close
|
||||
this._register(this.viewletService.onDidViewletClose(viewlet => this.compositeBar.deactivateComposite(viewlet.getId())));
|
||||
@@ -132,7 +123,7 @@ export class ActivitybarPart extends Part {
|
||||
if (enabled) {
|
||||
this.compositeBar.addComposite(this.viewletService.getViewlet(id));
|
||||
} else {
|
||||
this.removeComposite(id);
|
||||
this.removeComposite(id, true);
|
||||
}
|
||||
}));
|
||||
|
||||
@@ -140,8 +131,36 @@ export class ActivitybarPart extends Part {
|
||||
}
|
||||
|
||||
private onDidRegisterExtensions(): void {
|
||||
this.removeNotExistingPlaceholderComposites();
|
||||
this.updateCompositebar();
|
||||
this.removeNotExistingComposites();
|
||||
for (const viewlet of this.viewletService.getAllViewlets()) {
|
||||
this.enableCompositeActions(viewlet);
|
||||
const viewContainer = this.getViewContainer(viewlet.id);
|
||||
if (viewContainer) {
|
||||
const viewDescriptors = this.viewsService.getViewDescriptors(viewContainer);
|
||||
this.onDidChangeActiveViews(viewlet, viewDescriptors);
|
||||
viewDescriptors.onDidChangeActiveViews(() => this.onDidChangeActiveViews(viewlet, viewDescriptors));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private onDidChangeActiveViews(viewlet: ViewletDescriptor, viewDescriptors: IViewDescriptorCollection): void {
|
||||
if (viewDescriptors.activeViewDescriptors.length) {
|
||||
this.compositeBar.addComposite(viewlet);
|
||||
} else {
|
||||
this.removeComposite(viewlet.id, true);
|
||||
}
|
||||
}
|
||||
|
||||
private onDidViewletOpen(viewlet: IViewlet): void {
|
||||
// Update the composite bar by adding
|
||||
this.compositeBar.addComposite(this.viewletService.getViewlet(viewlet.getId()));
|
||||
this.compositeBar.activateComposite(viewlet.getId());
|
||||
const viewletDescriptor = this.viewletService.getViewlet(viewlet.getId());
|
||||
const viewContainer = this.getViewContainer(viewletDescriptor.id);
|
||||
if (viewContainer && this.viewsService.getViewDescriptors(viewContainer).activeViewDescriptors.length === 0) {
|
||||
// Update the composite bar by hiding
|
||||
this.removeComposite(viewletDescriptor.id, true);
|
||||
}
|
||||
}
|
||||
|
||||
showActivity(viewletOrActionId: string, badge: IBadge, clazz?: string, priority?: number): IDisposable {
|
||||
@@ -168,14 +187,19 @@ export class ActivitybarPart extends Part {
|
||||
}
|
||||
|
||||
createContentArea(parent: HTMLElement): HTMLElement {
|
||||
const $el = $(parent);
|
||||
const $result = $('.content').appendTo($el);
|
||||
const content = document.createElement('div');
|
||||
addClass(content, 'content');
|
||||
parent.appendChild(content);
|
||||
|
||||
// Top Actionbar with action items for each viewlet action
|
||||
this.compositeBar.create($result.getHTMLElement());
|
||||
this.compositeBar.create(content);
|
||||
|
||||
// Top Actionbar with action items for each viewlet action
|
||||
this.createGlobalActivityActionBar($('.global-activity').appendTo($result).getHTMLElement());
|
||||
const globalActivities = document.createElement('div');
|
||||
addClass(globalActivities, 'global-activity');
|
||||
content.appendChild(globalActivities);
|
||||
|
||||
this.createGlobalActivityActionBar(globalActivities);
|
||||
|
||||
// TODO@Ben: workaround for https://github.com/Microsoft/vscode/issues/45700
|
||||
// It looks like there are rendering glitches on macOS with Chrome 61 when
|
||||
@@ -183,7 +207,7 @@ export class ActivitybarPart extends Part {
|
||||
// The workaround is to promote the element onto its own drawing layer. We do
|
||||
// this only after the workbench has loaded because otherwise there is ugly flicker.
|
||||
if (isMacintosh) {
|
||||
this.lifecycleService.when(LifecyclePhase.Running).then(() => {
|
||||
this.lifecycleService.when(LifecyclePhase.Restored).then(() => {
|
||||
scheduleAtNextAnimationFrame(() => { // another delay...
|
||||
scheduleAtNextAnimationFrame(() => { // ...to prevent more flickering on startup
|
||||
registerThemingParticipant((theme, collector) => {
|
||||
@@ -198,26 +222,37 @@ export class ActivitybarPart extends Part {
|
||||
});
|
||||
}
|
||||
|
||||
return $result.getHTMLElement();
|
||||
return content;
|
||||
}
|
||||
|
||||
updateStyles(): void {
|
||||
super.updateStyles();
|
||||
|
||||
// Part container
|
||||
const container = $(this.getContainer());
|
||||
const container = this.getContainer();
|
||||
const background = this.getColor(ACTIVITY_BAR_BACKGROUND);
|
||||
container.style('background-color', background);
|
||||
container.style.backgroundColor = background;
|
||||
|
||||
const borderColor = this.getColor(ACTIVITY_BAR_BORDER) || this.getColor(contrastBorder);
|
||||
const isPositionLeft = this.partService.getSideBarPosition() === SideBarPosition.LEFT;
|
||||
container.style('box-sizing', borderColor && isPositionLeft ? 'border-box' : null);
|
||||
container.style('border-right-width', borderColor && isPositionLeft ? '1px' : null);
|
||||
container.style('border-right-style', borderColor && isPositionLeft ? 'solid' : null);
|
||||
container.style('border-right-color', isPositionLeft ? borderColor : null);
|
||||
container.style('border-left-width', borderColor && !isPositionLeft ? '1px' : null);
|
||||
container.style('border-left-style', borderColor && !isPositionLeft ? 'solid' : null);
|
||||
container.style('border-left-color', !isPositionLeft ? borderColor : null);
|
||||
container.style.boxSizing = borderColor && isPositionLeft ? 'border-box' : null;
|
||||
container.style.borderRightWidth = borderColor && isPositionLeft ? '1px' : null;
|
||||
container.style.borderRightStyle = borderColor && isPositionLeft ? 'solid' : null;
|
||||
container.style.borderRightColor = isPositionLeft ? borderColor : null;
|
||||
container.style.borderLeftWidth = borderColor && !isPositionLeft ? '1px' : null;
|
||||
container.style.borderLeftStyle = borderColor && !isPositionLeft ? 'solid' : null;
|
||||
container.style.borderLeftColor = !isPositionLeft ? borderColor : null;
|
||||
}
|
||||
|
||||
private getActivitybarItemColors(theme: ITheme): ICompositeBarColors {
|
||||
return <ICompositeBarColors>{
|
||||
activeForegroundColor: theme.getColor(ACTIVITY_BAR_FOREGROUND),
|
||||
inactiveForegroundColor: theme.getColor(ACTIVITY_BAR_INACTIVE_FOREGROUND),
|
||||
badgeBackground: theme.getColor(ACTIVITY_BAR_BADGE_BACKGROUND),
|
||||
badgeForeground: theme.getColor(ACTIVITY_BAR_BADGE_FOREGROUND),
|
||||
dragAndDropBackground: theme.getColor(ACTIVITY_BAR_DRAG_AND_DROP_BACKGROUND),
|
||||
activeBackgroundColor: null, inactiveBackgroundColor: null, activeBorderBottomColor: null,
|
||||
};
|
||||
}
|
||||
|
||||
private createGlobalActivityActionBar(container: HTMLElement): void {
|
||||
@@ -228,7 +263,7 @@ export class ActivitybarPart extends Part {
|
||||
.map(a => new GlobalActivityAction(a));
|
||||
|
||||
this.globalActionBar = this._register(new ActionBar(container, {
|
||||
actionItemProvider: a => this.instantiationService.createInstance(GlobalActivityActionItem, a, ActivitybarPart.COLORS),
|
||||
actionItemProvider: a => this.instantiationService.createInstance(GlobalActivityActionItem, a, theme => this.getActivitybarItemColors(theme)),
|
||||
orientation: ActionsOrientation.VERTICAL,
|
||||
ariaLabel: nls.localize('globalActions', "Global Actions"),
|
||||
animated: false
|
||||
@@ -250,9 +285,9 @@ export class ActivitybarPart extends Part {
|
||||
pinnedAction: new ToggleCompositePinnedAction(viewlet, this.compositeBar)
|
||||
};
|
||||
} else {
|
||||
const placeHolderComposite = this.placeholderComposites.filter(c => c.id === compositeId)[0];
|
||||
const cachedComposite = this.cachedViewlets.filter(c => c.id === compositeId)[0];
|
||||
compositeActions = {
|
||||
activityAction: this.instantiationService.createInstance(PlaceHolderViewletActivityAction, compositeId, placeHolderComposite.iconUrl),
|
||||
activityAction: this.instantiationService.createInstance(PlaceHolderViewletActivityAction, compositeId, cachedComposite && cachedComposite.iconUrl),
|
||||
pinnedAction: new PlaceHolderToggleCompositePinnedAction(compositeId, this.compositeBar)
|
||||
};
|
||||
}
|
||||
@@ -263,45 +298,48 @@ export class ActivitybarPart extends Part {
|
||||
return compositeActions;
|
||||
}
|
||||
|
||||
private updateCompositebar(): void {
|
||||
const viewlets = this.viewletService.getViewlets();
|
||||
private onDidRegisterViewlets(viewlets: ViewletDescriptor[]): void {
|
||||
for (const viewlet of viewlets) {
|
||||
this.compositeBar.addComposite(viewlet);
|
||||
|
||||
// Pin it by default if it is new => it does not has a placeholder
|
||||
if (this.placeholderComposites.every(c => c.id !== viewlet.id)) {
|
||||
this.compositeBar.pin(viewlet.id);
|
||||
}
|
||||
|
||||
this.enableCompositeActions(viewlet);
|
||||
const cachedViewlet = this.cachedViewlets.filter(({ id }) => id === viewlet.id)[0];
|
||||
const activeViewlet = this.viewletService.getActiveViewlet();
|
||||
if (activeViewlet && activeViewlet.getId() === viewlet.id) {
|
||||
this.compositeBar.pin(viewlet.id);
|
||||
this.compositeBar.activateComposite(viewlet.id);
|
||||
const isActive = activeViewlet && activeViewlet.getId() === viewlet.id;
|
||||
|
||||
if (isActive || !this.shouldBeHidden(viewlet.id, cachedViewlet)) {
|
||||
this.compositeBar.addComposite(viewlet);
|
||||
|
||||
// Pin it by default if it is new
|
||||
if (!cachedViewlet) {
|
||||
this.compositeBar.pin(viewlet.id);
|
||||
}
|
||||
|
||||
if (isActive) {
|
||||
this.compositeBar.activateComposite(viewlet.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private updatePlaceholderComposites(): void {
|
||||
const viewlets = this.viewletService.getViewlets();
|
||||
for (const { id } of this.placeholderComposites) {
|
||||
private shouldBeHidden(viewletId: string, cachedViewlet: ICachedViewlet): boolean {
|
||||
return cachedViewlet && cachedViewlet.views && cachedViewlet.views.length
|
||||
? cachedViewlet.views.every(({ when }) => when && !this.contextKeyService.contextMatchesRules(ContextKeyExpr.deserialize(when)))
|
||||
: viewletId === TEST_VIEW_CONTAINER_ID /* Hide Test viewlet for the first time or it had no views registered before */;
|
||||
}
|
||||
|
||||
private removeNotExistingComposites(): void {
|
||||
const viewlets = this.viewletService.getAllViewlets();
|
||||
for (const { id } of this.compositeBar.getComposites()) {
|
||||
if (viewlets.every(viewlet => viewlet.id !== id)) {
|
||||
this.compositeBar.addComposite({ id, name: id, order: void 0 });
|
||||
this.removeComposite(id, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private removeNotExistingPlaceholderComposites(): void {
|
||||
const viewlets = this.viewletService.getViewlets();
|
||||
for (const { id } of this.placeholderComposites) {
|
||||
if (viewlets.every(viewlet => viewlet.id !== id)) {
|
||||
this.removeComposite(id);
|
||||
}
|
||||
private removeComposite(compositeId: string, hide: boolean): void {
|
||||
if (hide) {
|
||||
this.compositeBar.hideComposite(compositeId);
|
||||
} else {
|
||||
this.compositeBar.removeComposite(compositeId);
|
||||
}
|
||||
}
|
||||
|
||||
private removeComposite(compositeId: string): void {
|
||||
this.compositeBar.removeComposite(compositeId);
|
||||
const compositeActions = this.compositeActions[compositeId];
|
||||
if (compositeActions) {
|
||||
compositeActions.activityAction.dispose();
|
||||
@@ -320,8 +358,12 @@ export class ActivitybarPart extends Part {
|
||||
}
|
||||
}
|
||||
|
||||
getPinned(): string[] {
|
||||
return this.viewletService.getViewlets().map(v => v.id).filter(id => this.compositeBar.isPinned(id));
|
||||
getPinnedViewletIds(): string[] {
|
||||
const pinnedCompositeIds = this.compositeBar.getPinnedComposites().map(v => v.id);
|
||||
return this.viewletService.getViewlets()
|
||||
.filter(v => this.compositeBar.isPinned(v.id))
|
||||
.sort((v1, v2) => pinnedCompositeIds.indexOf(v1.id) - pinnedCompositeIds.indexOf(v2.id))
|
||||
.map(v => v.id);
|
||||
}
|
||||
|
||||
layout(dimension: Dimension): Dimension[] {
|
||||
@@ -344,10 +386,29 @@ export class ActivitybarPart extends Part {
|
||||
return sizes;
|
||||
}
|
||||
|
||||
shutdown(): void {
|
||||
const state = this.viewletService.getViewlets().map(viewlet => ({ id: viewlet.id, iconUrl: viewlet.iconUrl }));
|
||||
this.storageService.store(ActivitybarPart.PLACEHOLDER_VIEWLETS, JSON.stringify(state), StorageScope.GLOBAL);
|
||||
protected saveState(): void {
|
||||
const state: ICachedViewlet[] = [];
|
||||
for (const { id, iconUrl } of this.viewletService.getAllViewlets()) {
|
||||
const viewContainer = this.getViewContainer(id);
|
||||
const views: { when: string }[] = [];
|
||||
if (viewContainer) {
|
||||
for (const { when } of this.viewsService.getViewDescriptors(viewContainer).allViewDescriptors) {
|
||||
views.push({ when: when ? when.serialize() : void 0 });
|
||||
}
|
||||
}
|
||||
state.push({ id, iconUrl, views });
|
||||
}
|
||||
this.storageService.store(ActivitybarPart.CACHED_VIEWLETS, JSON.stringify(state), StorageScope.GLOBAL);
|
||||
|
||||
super.shutdown();
|
||||
super.saveState();
|
||||
}
|
||||
|
||||
private getViewContainer(viewletId: string): ViewContainer | undefined {
|
||||
// TODO: @Joao Remove this after moving SCM Viewlet to ViewContainerViewlet - https://github.com/Microsoft/vscode/issues/49054
|
||||
if (viewletId === SCM_VIEWLET_ID) {
|
||||
return null;
|
||||
}
|
||||
const viewContainerRegistry = Registry.as<IViewContainersRegistry>(ViewContainerExtensions.ViewContainersRegistry);
|
||||
return viewContainerRegistry.get(viewletId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" width="32" height="32"><defs><style>.icon-canvas-transparent{fill:#f6f6f6;opacity:0;}.cls-1{fill:#fff;}</style></defs><title>Ellipsis_32x</title><g id="canvas"><path class="icon-canvas-transparent" d="M32,32H0V0H32Z" transform="translate(0 0)"/></g><g id="iconBg"><path class="cls-1" d="M9,16a3,3,0,1,1-3-3A3,3,0,0,1,9,16Zm7-3a3,3,0,1,0,3,3A3,3,0,0,0,16,13Zm10,0a3,3,0,1,0,3,3A3,3,0,0,0,26,13Z" transform="translate(0 0)"/></g></svg>
|
||||
<svg fill="none" height="28" viewBox="0 0 28 28" width="28" xmlns="http://www.w3.org/2000/svg"><path d="m7.53846 13.7692c0 .5477-.16241 1.0831-.4667 1.5385-.30428.4554-.73678.8104-1.24279 1.02s-1.06281.2644-1.59999.1576c-.53718-.1069-1.03061-.3706-1.41789-.7579s-.65103-.8807-.75788-1.4179-.05201-1.094.15759-1.6c.20959-.506.56453-.9385 1.01993-1.2428s.9908-.4667 1.5385-.4667c.73445 0 1.43881.2918 1.95814.8111.51934.5193.81109 1.2237.81109 1.9581zm6.46154-2.7692c-.5477 0-1.0831.1624-1.5385.4667s-.8103.7368-1.0199 1.2428-.2645 1.0628-.1576 1.6c.1068.5372.3706 1.0306.7579 1.4179.3872.3873.8807.651 1.4179.7579.5371.1068 1.0939.052 1.5999-.1576s.9385-.5646 1.2428-1.02.4667-.9908.4667-1.5385c0-.7344-.2917-1.4388-.8111-1.9581-.5193-.5193-1.2237-.8111-1.9581-.8111zm9.2308 0c-.5477 0-1.0831.1624-1.5385.4667s-.8104.7368-1.02 1.2428-.2644 1.0628-.1576 1.6c.1069.5372.3706 1.0306.7579 1.4179s.8807.651 1.4179.7579c.5372.1068 1.094.052 1.6-.1576s.9385-.5646 1.2428-1.02.4667-.9908.4667-1.5385c0-.7344-.2918-1.4388-.8111-1.9581s-1.2237-.8111-1.9581-.8111z" fill="#fff"/></svg>
|
||||
|
Before Width: | Height: | Size: 493 B After Width: | Height: | Size: 1.0 KiB |
@@ -3,8 +3,6 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import * as nls from 'vs/nls';
|
||||
import { Action, IAction } from 'vs/base/common/actions';
|
||||
import { illegalArgument } from 'vs/base/common/errors';
|
||||
@@ -14,27 +12,29 @@ 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, ActionsOrientation, Separator } from 'vs/base/browser/ui/actionbar/actionbar';
|
||||
import { CompositeActionItem, CompositeOverflowActivityAction, ICompositeActivity, CompositeOverflowActivityActionItem, ActivityAction, ICompositeBar, ICompositeBarColors } from 'vs/workbench/browser/parts/compositebar/compositeBarActions';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { CompositeActionItem, CompositeOverflowActivityAction, ICompositeActivity, CompositeOverflowActivityActionItem, ActivityAction, ICompositeBar, ICompositeBarColors, DraggedCompositeIdentifier } from 'vs/workbench/browser/parts/compositeBarActions';
|
||||
import { Dimension, $, addDisposableListener, EventType, EventHelper } from 'vs/base/browser/dom';
|
||||
import { StandardMouseEvent } from 'vs/base/browser/mouseEvent';
|
||||
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
|
||||
import { Widget } from 'vs/base/browser/ui/widget';
|
||||
import { isUndefinedOrNull } from 'vs/base/common/types';
|
||||
import { LocalSelectionTransfer } from 'vs/workbench/browser/dnd';
|
||||
import { ITheme } from 'vs/platform/theme/common/themeService';
|
||||
|
||||
export interface ICompositeBarOptions {
|
||||
icon: boolean;
|
||||
storageId: string;
|
||||
orientation: ActionsOrientation;
|
||||
colors: ICompositeBarColors;
|
||||
colors: (theme: ITheme) => ICompositeBarColors;
|
||||
compositeSize: number;
|
||||
overflowActionSize: number;
|
||||
getActivityAction: (compositeId: string) => ActivityAction;
|
||||
getCompositePinnedAction: (compositeId: string) => Action;
|
||||
getOnCompositeClickAction: (compositeId: string) => Action;
|
||||
getContextMenuActions: () => Action[];
|
||||
openComposite: (compositeId: string) => TPromise<any>;
|
||||
openComposite: (compositeId: string) => Thenable<any>;
|
||||
getDefaultCompositeId: () => string;
|
||||
hidePart: () => TPromise<any>;
|
||||
hidePart: () => void;
|
||||
}
|
||||
|
||||
export class CompositeBar extends Widget implements ICompositeBar {
|
||||
@@ -46,26 +46,31 @@ export class CompositeBar extends Widget implements ICompositeBar {
|
||||
private compositeOverflowActionItem: CompositeOverflowActivityActionItem;
|
||||
|
||||
private model: CompositeBarModel;
|
||||
private storedState: ISerializedCompositeBarItem[];
|
||||
private visibleComposites: string[];
|
||||
private compositeSizeInBar: Map<string, number>;
|
||||
|
||||
private compositeTransfer: LocalSelectionTransfer<DraggedCompositeIdentifier>;
|
||||
|
||||
constructor(
|
||||
private options: ICompositeBarOptions,
|
||||
@IInstantiationService private instantiationService: IInstantiationService,
|
||||
@IStorageService private storageService: IStorageService,
|
||||
@IStorageService storageService: IStorageService,
|
||||
@IContextMenuService private contextMenuService: IContextMenuService
|
||||
) {
|
||||
super();
|
||||
|
||||
this.model = new CompositeBarModel(options);
|
||||
this.storedState = this.loadCompositeItemsFromStorage();
|
||||
this.model = new CompositeBarModel(options, storageService);
|
||||
this.visibleComposites = [];
|
||||
this.compositeSizeInBar = new Map<string, number>();
|
||||
this.compositeTransfer = LocalSelectionTransfer.getInstance<DraggedCompositeIdentifier>();
|
||||
}
|
||||
|
||||
getCompositesFromStorage(): string[] {
|
||||
return this.storedState.map(s => s.id);
|
||||
getComposites(): ICompositeBarItem[] {
|
||||
return [...this.model.items];
|
||||
}
|
||||
|
||||
getPinnedComposites(): ICompositeBarItem[] {
|
||||
return this.model.pinnedItems;
|
||||
}
|
||||
|
||||
create(parent: HTMLElement): HTMLElement {
|
||||
@@ -76,7 +81,7 @@ export class CompositeBar extends Widget implements ICompositeBar {
|
||||
return this.compositeOverflowActionItem;
|
||||
}
|
||||
const item = this.model.findItem(action.id);
|
||||
return item && this.instantiationService.createInstance(CompositeActionItem, action, item.pinnedAction, this.options.colors, this.options.icon, this);
|
||||
return item && this.instantiationService.createInstance(CompositeActionItem, action, item.pinnedAction, () => this.getContextMenuActions(), this.options.colors, this.options.icon, this);
|
||||
},
|
||||
orientation: this.options.orientation,
|
||||
ariaLabel: nls.localize('activityBarAriaLabel', "Active View Switcher"),
|
||||
@@ -88,12 +93,13 @@ export class CompositeBar extends Widget implements ICompositeBar {
|
||||
|
||||
// Allow to drop at the end to move composites to the end
|
||||
this._register(addDisposableListener(parent, EventType.DROP, (e: DragEvent) => {
|
||||
const draggedCompositeId = CompositeActionItem.getDraggedCompositeId();
|
||||
if (draggedCompositeId) {
|
||||
if (this.compositeTransfer.hasData(DraggedCompositeIdentifier.prototype)) {
|
||||
EventHelper.stop(e, true);
|
||||
CompositeActionItem.clearDraggedComposite();
|
||||
|
||||
const targetItem = this.model.items[this.model.items.length - 1];
|
||||
const draggedCompositeId = this.compositeTransfer.getData(DraggedCompositeIdentifier.prototype)[0].id;
|
||||
this.compositeTransfer.clearData(DraggedCompositeIdentifier.prototype);
|
||||
|
||||
const targetItem = this.model.visibleItems[this.model.visibleItems.length - 1];
|
||||
if (targetItem && targetItem.id !== draggedCompositeId) {
|
||||
this.move(draggedCompositeId, targetItem.id);
|
||||
}
|
||||
@@ -113,38 +119,17 @@ export class CompositeBar extends Widget implements ICompositeBar {
|
||||
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.computeSizes(this.model.items);
|
||||
this.computeSizes(this.model.visibleItems);
|
||||
}
|
||||
|
||||
this.updateCompositeSwitcher();
|
||||
}
|
||||
|
||||
addComposite({ id, name, order }: { id: string; name: string, order: number }): void {
|
||||
const state = this.storedState.filter(s => s.id === id)[0];
|
||||
const pinned = state ? state.pinned : true;
|
||||
let index = order >= 0 ? order : this.model.items.length;
|
||||
|
||||
if (state) {
|
||||
// Find the index by looking its previous item
|
||||
index = 0;
|
||||
for (let i = this.storedState.indexOf(state) - 1; i >= 0; i--) {
|
||||
const previousItemId = this.storedState[i].id;
|
||||
const previousItemIndex = this.model.findIndex(previousItemId);
|
||||
if (previousItemIndex !== -1) {
|
||||
index = previousItemIndex + 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add to the model
|
||||
if (this.model.add(id, name, order, index)) {
|
||||
if (this.model.add(id, name, order)) {
|
||||
this.computeSizes([this.model.findItem(id)]);
|
||||
if (pinned) {
|
||||
this.pin(id);
|
||||
} else {
|
||||
this.updateCompositeSwitcher();
|
||||
}
|
||||
this.updateCompositeSwitcher();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -161,6 +146,13 @@ export class CompositeBar extends Widget implements ICompositeBar {
|
||||
}
|
||||
}
|
||||
|
||||
hideComposite(id: string): void {
|
||||
if (this.model.hide(id)) {
|
||||
this.resetActiveComposite(id);
|
||||
this.updateCompositeSwitcher();
|
||||
}
|
||||
}
|
||||
|
||||
activateComposite(id: string): void {
|
||||
const previousActiveItem = this.model.activeItem;
|
||||
if (this.model.activate(id)) {
|
||||
@@ -200,8 +192,7 @@ export class CompositeBar extends Widget implements ICompositeBar {
|
||||
this.updateCompositeSwitcher();
|
||||
|
||||
if (open) {
|
||||
this.options.openComposite(compositeId)
|
||||
.done(() => this.activateComposite(compositeId)); // Activate after opening
|
||||
this.options.openComposite(compositeId).then(() => this.activateComposite(compositeId)); // Activate after opening
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -211,34 +202,38 @@ export class CompositeBar extends Widget implements ICompositeBar {
|
||||
|
||||
this.updateCompositeSwitcher();
|
||||
|
||||
const defaultCompositeId = this.options.getDefaultCompositeId();
|
||||
this.resetActiveComposite(compositeId);
|
||||
}
|
||||
}
|
||||
|
||||
// Case: composite is not the active one or the active one is a different one
|
||||
// Solv: we do nothing
|
||||
if (!this.model.activeItem || this.model.activeItem.id !== compositeId) {
|
||||
return;
|
||||
}
|
||||
private resetActiveComposite(compositeId: string) {
|
||||
const defaultCompositeId = this.options.getDefaultCompositeId();
|
||||
|
||||
// Deactivate itself
|
||||
this.deactivateComposite(compositeId);
|
||||
// Case: composite is not the active one or the active one is a different one
|
||||
// Solv: we do nothing
|
||||
if (!this.model.activeItem || this.model.activeItem.id !== compositeId) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Case: composite is not the default composite and default composite is still showing
|
||||
// Solv: we open the default composite
|
||||
if (defaultCompositeId !== compositeId && this.isPinned(defaultCompositeId)) {
|
||||
this.options.openComposite(defaultCompositeId);
|
||||
}
|
||||
// Deactivate itself
|
||||
this.deactivateComposite(compositeId);
|
||||
|
||||
// Case: we closed the last visible composite
|
||||
// Solv: we hide the part
|
||||
else if (this.visibleComposites.length === 1) {
|
||||
this.options.hidePart();
|
||||
}
|
||||
// Case: composite is not the default composite and default composite is still showing
|
||||
// Solv: we open the default composite
|
||||
if (defaultCompositeId !== compositeId && this.isPinned(defaultCompositeId)) {
|
||||
this.options.openComposite(defaultCompositeId);
|
||||
}
|
||||
|
||||
// Case: we closed the default composite
|
||||
// Solv: we open the next visible composite from top
|
||||
else {
|
||||
this.options.openComposite(this.visibleComposites.filter(cid => cid !== compositeId)[0]);
|
||||
}
|
||||
// Case: we closed the last visible composite
|
||||
// Solv: we hide the part
|
||||
else if (this.visibleComposites.length === 1) {
|
||||
this.options.hidePart();
|
||||
}
|
||||
|
||||
// Case: we closed the default composite
|
||||
// Solv: we open the next visible composite from top
|
||||
else {
|
||||
this.options.openComposite(this.visibleComposites.filter(cid => cid !== compositeId)[0]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -282,7 +277,7 @@ export class CompositeBar extends Widget implements ICompositeBar {
|
||||
return; // We have not been rendered yet so there is nothing to update.
|
||||
}
|
||||
|
||||
let compositesToShow = this.model.items.filter(item =>
|
||||
let compositesToShow = this.model.visibleItems.filter(item =>
|
||||
item.pinned
|
||||
|| (this.model.activeItem && this.model.activeItem.id === item.id) /* Show the active composite even if it is not pinned */
|
||||
).map(item => item.id);
|
||||
@@ -385,11 +380,11 @@ export class CompositeBar extends Widget implements ICompositeBar {
|
||||
}
|
||||
|
||||
// Persist
|
||||
this.saveCompositeItems();
|
||||
this.model.saveState();
|
||||
}
|
||||
|
||||
private getOverflowingComposites(): { id: string, name: string }[] {
|
||||
let overflowingIds = this.model.items.filter(item => item.pinned).map(item => item.id);
|
||||
let overflowingIds = this.model.visibleItems.filter(item => item.pinned).map(item => item.id);
|
||||
|
||||
// Show the active composite even if it is not pinned
|
||||
if (this.model.activeItem && !this.model.activeItem.pinned) {
|
||||
@@ -397,16 +392,23 @@ export class CompositeBar extends Widget implements ICompositeBar {
|
||||
}
|
||||
|
||||
overflowingIds = overflowingIds.filter(compositeId => this.visibleComposites.indexOf(compositeId) === -1);
|
||||
return this.model.items.filter(c => overflowingIds.indexOf(c.id) !== -1);
|
||||
return this.model.visibleItems.filter(c => overflowingIds.indexOf(c.id) !== -1);
|
||||
}
|
||||
|
||||
private showContextMenu(e: MouseEvent): void {
|
||||
EventHelper.stop(e, true);
|
||||
const event = new StandardMouseEvent(e);
|
||||
const actions: IAction[] = this.model.items
|
||||
this.contextMenuService.showContextMenu({
|
||||
getAnchor: () => { return { x: event.posx, y: event.posy }; },
|
||||
getActions: () => this.getContextMenuActions()
|
||||
});
|
||||
}
|
||||
|
||||
private getContextMenuActions(): IAction[] {
|
||||
const actions: IAction[] = this.model.visibleItems
|
||||
.map(({ id, name, activityAction }) => (<IAction>{
|
||||
id,
|
||||
label: name,
|
||||
label: name || id,
|
||||
checked: this.isPinned(id),
|
||||
enabled: activityAction.enabled,
|
||||
run: () => {
|
||||
@@ -422,22 +424,7 @@ export class CompositeBar extends Widget implements ICompositeBar {
|
||||
actions.push(new Separator());
|
||||
actions.push(...otherActions);
|
||||
}
|
||||
this.contextMenuService.showContextMenu({
|
||||
getAnchor: () => { return { x: event.posx, y: event.posy }; },
|
||||
getActions: () => TPromise.as(actions),
|
||||
});
|
||||
}
|
||||
|
||||
private loadCompositeItemsFromStorage(): ISerializedCompositeBarItem[] {
|
||||
const storedStates = <Array<string | ISerializedCompositeBarItem>>JSON.parse(this.storageService.get(this.options.storageId, StorageScope.GLOBAL, '[]'));
|
||||
const compositeStates = <ISerializedCompositeBarItem[]>storedStates.map(c =>
|
||||
typeof c === 'string' /* migration from pinned states to composites states */ ? { id: c, pinned: true } : c);
|
||||
return compositeStates;
|
||||
}
|
||||
|
||||
private saveCompositeItems(): void {
|
||||
this.storedState = this.model.toJSON();
|
||||
this.storageService.store(this.options.storageId, JSON.stringify(this.storedState), StorageScope.GLOBAL);
|
||||
return actions;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -445,6 +432,7 @@ interface ISerializedCompositeBarItem {
|
||||
id: string;
|
||||
pinned: boolean;
|
||||
order: number;
|
||||
visible: boolean;
|
||||
}
|
||||
|
||||
interface ICompositeBarItem extends ISerializedCompositeBarItem {
|
||||
@@ -456,15 +444,32 @@ interface ICompositeBarItem extends ISerializedCompositeBarItem {
|
||||
|
||||
class CompositeBarModel {
|
||||
|
||||
readonly items: ICompositeBarItem[] = [];
|
||||
private readonly options: ICompositeBarOptions;
|
||||
readonly items: ICompositeBarItem[];
|
||||
|
||||
activeItem: ICompositeBarItem;
|
||||
|
||||
constructor(private options: ICompositeBarOptions) { }
|
||||
constructor(
|
||||
options: ICompositeBarOptions,
|
||||
private storageService: IStorageService,
|
||||
) {
|
||||
this.options = options;
|
||||
this.items = this.loadItemStates();
|
||||
}
|
||||
|
||||
private createCompositeBarItem(id: string, name: string, order: number, pinned: boolean): ICompositeBarItem {
|
||||
get visibleItems(): ICompositeBarItem[] {
|
||||
return this.items.filter(item => item.visible);
|
||||
}
|
||||
|
||||
get pinnedItems(): ICompositeBarItem[] {
|
||||
return this.items.filter(item => item.visible && item.pinned);
|
||||
}
|
||||
|
||||
private createCompositeBarItem(id: string, name: string, order: number, pinned: boolean, visible: boolean): ICompositeBarItem {
|
||||
const options = this.options;
|
||||
return {
|
||||
id, name, pinned, order, activity: [],
|
||||
id, name, pinned, order, visible,
|
||||
activity: [],
|
||||
get activityAction() {
|
||||
return options.getActivityAction(id);
|
||||
},
|
||||
@@ -474,20 +479,31 @@ class CompositeBarModel {
|
||||
};
|
||||
}
|
||||
|
||||
add(id: string, name: string, order: number, index: number): boolean {
|
||||
add(id: string, name: string, order: number): boolean {
|
||||
const item = this.findItem(id);
|
||||
if (item) {
|
||||
item.order = order;
|
||||
let changed = false;
|
||||
item.name = name;
|
||||
return false;
|
||||
if (!isUndefinedOrNull(order)) {
|
||||
changed = item.order !== order;
|
||||
item.order = order;
|
||||
}
|
||||
if (!item.visible) {
|
||||
item.visible = true;
|
||||
changed = true;
|
||||
}
|
||||
return changed;
|
||||
} else {
|
||||
if (index === void 0) {
|
||||
index = 0;
|
||||
const item = this.createCompositeBarItem(id, name, order, true, true);
|
||||
if (isUndefinedOrNull(order)) {
|
||||
this.items.push(item);
|
||||
} else {
|
||||
let index = 0;
|
||||
while (index < this.items.length && this.items[index].order < order) {
|
||||
index++;
|
||||
}
|
||||
this.items.splice(index, 0, item);
|
||||
}
|
||||
this.items.splice(index, 0, this.createCompositeBarItem(id, name, order, false));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -502,6 +518,19 @@ class CompositeBarModel {
|
||||
return false;
|
||||
}
|
||||
|
||||
hide(id: string): boolean {
|
||||
for (const item of this.items) {
|
||||
if (item.id === id) {
|
||||
if (item.visible) {
|
||||
item.visible = false;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
move(compositeId: string, toCompositeId: string): boolean {
|
||||
|
||||
const fromIndex = this.findIndex(compositeId);
|
||||
@@ -610,7 +639,7 @@ class CompositeBarModel {
|
||||
return this.items.filter(item => item.id === id)[0];
|
||||
}
|
||||
|
||||
findIndex(id: string): number {
|
||||
private findIndex(id: string): number {
|
||||
for (let index = 0; index < this.items.length; index++) {
|
||||
if (this.items[index].id === id) {
|
||||
return index;
|
||||
@@ -619,7 +648,16 @@ class CompositeBarModel {
|
||||
return -1;
|
||||
}
|
||||
|
||||
toJSON(): ISerializedCompositeBarItem[] {
|
||||
return this.items.map(({ id, pinned, order }) => ({ id, pinned, order }));
|
||||
private loadItemStates(): ICompositeBarItem[] {
|
||||
const storedStates = <Array<string | ISerializedCompositeBarItem>>JSON.parse(this.storageService.get(this.options.storageId, StorageScope.GLOBAL, '[]'));
|
||||
return <ICompositeBarItem[]>storedStates.map(c => {
|
||||
const serialized: ISerializedCompositeBarItem = typeof c === 'string' /* migration from pinned states to composites states */ ? { id: c, pinned: true, order: void 0, visible: true } : c;
|
||||
return this.createCompositeBarItem(serialized.id, void 0, serialized.order, serialized.pinned, isUndefinedOrNull(serialized.visible) ? true : serialized.visible);
|
||||
});
|
||||
}
|
||||
|
||||
saveState(): void {
|
||||
const serialized = this.items.map(({ id, pinned, order, visible }) => ({ id, pinned, order, visible }));
|
||||
this.storageService.store(this.options.storageId, JSON.stringify(serialized), StorageScope.GLOBAL);
|
||||
}
|
||||
}
|
||||
@@ -3,13 +3,9 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import * as nls from '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, IDisposable, Disposable, toDisposable } from 'vs/base/common/lifecycle';
|
||||
@@ -22,6 +18,8 @@ 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';
|
||||
import { DragAndDropObserver, LocalSelectionTransfer } from 'vs/workbench/browser/dnd';
|
||||
import { Color } from 'vs/base/common/color';
|
||||
|
||||
export interface ICompositeActivity {
|
||||
badge: IBadge;
|
||||
@@ -112,26 +110,30 @@ export class ActivityAction extends Action {
|
||||
}
|
||||
|
||||
export interface ICompositeBarColors {
|
||||
backgroundColor: string;
|
||||
badgeBackground: string;
|
||||
badgeForeground: string;
|
||||
dragAndDropBackground: string;
|
||||
activeBackgroundColor: Color;
|
||||
inactiveBackgroundColor: Color;
|
||||
activeBorderBottomColor: Color;
|
||||
activeForegroundColor: Color;
|
||||
inactiveForegroundColor: Color;
|
||||
badgeBackground: Color;
|
||||
badgeForeground: Color;
|
||||
dragAndDropBackground: Color;
|
||||
}
|
||||
|
||||
export interface IActivityActionItemOptions extends IBaseActionItemOptions {
|
||||
icon?: boolean;
|
||||
colors: ICompositeBarColors;
|
||||
colors: (theme: ITheme) => ICompositeBarColors;
|
||||
}
|
||||
|
||||
export class ActivityActionItem extends BaseActionItem {
|
||||
protected $container: Builder;
|
||||
protected $label: Builder;
|
||||
protected $badge: Builder;
|
||||
protected container: HTMLElement;
|
||||
protected label: HTMLElement;
|
||||
protected badge: HTMLElement;
|
||||
protected options: IActivityActionItemOptions;
|
||||
|
||||
private $badgeContent: Builder;
|
||||
private badgeContent: HTMLElement;
|
||||
private badgeDisposable: IDisposable = Disposable.None;
|
||||
private mouseUpTimeout: number;
|
||||
private mouseUpTimeout: any;
|
||||
|
||||
constructor(
|
||||
action: ActivityAction,
|
||||
@@ -140,9 +142,9 @@ export class ActivityActionItem extends BaseActionItem {
|
||||
) {
|
||||
super(null, action, options);
|
||||
|
||||
this.themeService.onThemeChange(this.onThemeChange, this, this._callOnDispose);
|
||||
action.onDidChangeActivity(this.updateActivity, this, this._callOnDispose);
|
||||
action.onDidChangeBadge(this.updateBadge, this, this._callOnDispose);
|
||||
this._register(this.themeService.onThemeChange(this.onThemeChange, this));
|
||||
this._register(action.onDidChangeActivity(this.updateActivity, this));
|
||||
this._register(action.onDidChangeBadge(this.updateBadge, this));
|
||||
}
|
||||
|
||||
protected get activity(): IActivity {
|
||||
@@ -151,60 +153,67 @@ export class ActivityActionItem extends BaseActionItem {
|
||||
|
||||
protected updateStyles(): void {
|
||||
const theme = this.themeService.getTheme();
|
||||
const colors = this.options.colors(theme);
|
||||
|
||||
// Label
|
||||
if (this.$label && this.options.icon) {
|
||||
const background = theme.getColor(this.options.colors.backgroundColor);
|
||||
|
||||
this.$label.style('background-color', background ? background.toString() : null);
|
||||
if (this.label) {
|
||||
if (this.options.icon) {
|
||||
const foreground = this._action.checked ? colors.activeBackgroundColor || colors.activeForegroundColor : colors.inactiveBackgroundColor || colors.inactiveForegroundColor;
|
||||
this.label.style.backgroundColor = foreground ? foreground.toString() : null;
|
||||
} else {
|
||||
const foreground = this._action.checked ? colors.activeForegroundColor : colors.inactiveForegroundColor;
|
||||
const borderBottomColor = this._action.checked ? colors.activeBorderBottomColor : null;
|
||||
this.label.style.color = foreground ? foreground.toString() : null;
|
||||
this.label.style.borderBottomColor = borderBottomColor ? borderBottomColor.toString() : null;
|
||||
}
|
||||
}
|
||||
|
||||
// Badge
|
||||
if (this.$badgeContent) {
|
||||
const badgeForeground = theme.getColor(this.options.colors.badgeForeground);
|
||||
const badgeBackground = theme.getColor(this.options.colors.badgeBackground);
|
||||
if (this.badgeContent) {
|
||||
const badgeForeground = colors.badgeForeground;
|
||||
const badgeBackground = 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.color = badgeForeground ? badgeForeground.toString() : null;
|
||||
this.badgeContent.style.backgroundColor = 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);
|
||||
this.badgeContent.style.borderStyle = contrastBorderColor ? 'solid' : null;
|
||||
this.badgeContent.style.borderWidth = contrastBorderColor ? '1px' : null;
|
||||
this.badgeContent.style.borderColor = contrastBorderColor ? contrastBorderColor.toString() : null;
|
||||
}
|
||||
}
|
||||
|
||||
render(container: HTMLElement): void {
|
||||
super.render(container);
|
||||
|
||||
this.container = container;
|
||||
|
||||
// Make the container tab-able for keyboard navigation
|
||||
this.$container = $(container).attr({
|
||||
tabIndex: '0',
|
||||
role: 'button'
|
||||
});
|
||||
this.container.tabIndex = 0;
|
||||
this.container.setAttribute('role', this.options.icon ? 'button' : 'tab');
|
||||
|
||||
// Try hard to prevent keyboard only focus feedback when using mouse
|
||||
this.$container.on(dom.EventType.MOUSE_DOWN, () => {
|
||||
this.$container.addClass('clicked');
|
||||
});
|
||||
this._register(dom.addDisposableListener(this.container, dom.EventType.MOUSE_DOWN, () => {
|
||||
dom.addClass(this.container, 'clicked');
|
||||
}));
|
||||
|
||||
this.$container.on(dom.EventType.MOUSE_UP, () => {
|
||||
this._register(dom.addDisposableListener(this.container, dom.EventType.MOUSE_UP, () => {
|
||||
if (this.mouseUpTimeout) {
|
||||
clearTimeout(this.mouseUpTimeout);
|
||||
}
|
||||
|
||||
this.mouseUpTimeout = setTimeout(() => {
|
||||
this.$container.removeClass('clicked');
|
||||
dom.removeClass(this.container, 'clicked');
|
||||
}, 800); // delayed to prevent focus feedback from showing on mouse up
|
||||
});
|
||||
}));
|
||||
|
||||
// Label
|
||||
this.$label = $('a.action-label').appendTo(this.builder);
|
||||
this.label = dom.append(this.element, dom.$('a.action-label'));
|
||||
|
||||
this.$badge = this.builder.clone().div({ 'class': 'badge' }, badge => {
|
||||
this.$badgeContent = badge.div({ 'class': 'badge-content' });
|
||||
});
|
||||
this.$badge.hide();
|
||||
// Badge
|
||||
this.badge = dom.append(this.element, dom.$('.badge'));
|
||||
this.badgeContent = dom.append(this.badge, dom.$('.badge-content'));
|
||||
|
||||
dom.hide(this.badge);
|
||||
|
||||
this.updateActivity();
|
||||
this.updateStyles();
|
||||
@@ -222,7 +231,7 @@ export class ActivityActionItem extends BaseActionItem {
|
||||
|
||||
protected updateBadge(): void {
|
||||
const action = this.getAction();
|
||||
if (!this.$badge || !this.$badgeContent || !(action instanceof ActivityAction)) {
|
||||
if (!this.badge || !this.badgeContent || !(action instanceof ActivityAction)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -232,8 +241,8 @@ export class ActivityActionItem extends BaseActionItem {
|
||||
this.badgeDisposable.dispose();
|
||||
this.badgeDisposable = Disposable.None;
|
||||
|
||||
this.$badgeContent.empty();
|
||||
this.$badge.hide();
|
||||
dom.clearNode(this.badgeContent);
|
||||
dom.hide(this.badge);
|
||||
|
||||
if (badge) {
|
||||
|
||||
@@ -241,35 +250,39 @@ export class ActivityActionItem extends BaseActionItem {
|
||||
if (badge instanceof NumberBadge) {
|
||||
if (badge.number) {
|
||||
let number = badge.number.toString();
|
||||
if (badge.number > 9999) {
|
||||
number = nls.localize('largeNumberBadge', '10k+');
|
||||
} else if (badge.number > 999) {
|
||||
number = number.charAt(0) + 'k';
|
||||
if (badge.number > 999) {
|
||||
const noOfThousands = badge.number / 1000;
|
||||
const floor = Math.floor(noOfThousands);
|
||||
if (noOfThousands > floor) {
|
||||
number = nls.localize('largeNumberBadge1', '{0}k+', floor);
|
||||
} else {
|
||||
number = nls.localize('largeNumberBadge2', '{0}k', noOfThousands);
|
||||
}
|
||||
}
|
||||
this.$badgeContent.text(number);
|
||||
this.$badge.show();
|
||||
this.badgeContent.textContent = number;
|
||||
dom.show(this.badge);
|
||||
}
|
||||
}
|
||||
|
||||
// Text
|
||||
else if (badge instanceof TextBadge) {
|
||||
this.$badgeContent.text(badge.text);
|
||||
this.$badge.show();
|
||||
this.badgeContent.textContent = badge.text;
|
||||
dom.show(this.badge);
|
||||
}
|
||||
|
||||
// Text
|
||||
else if (badge instanceof IconBadge) {
|
||||
this.$badge.show();
|
||||
dom.show(this.badge);
|
||||
}
|
||||
|
||||
// Progress
|
||||
else if (badge instanceof ProgressBadge) {
|
||||
this.$badge.show();
|
||||
dom.show(this.badge);
|
||||
}
|
||||
|
||||
if (clazz) {
|
||||
this.$badge.addClass(clazz);
|
||||
this.badgeDisposable = toDisposable(() => this.$badge.removeClass(clazz));
|
||||
dom.addClasses(this.badge, clazz);
|
||||
this.badgeDisposable = toDisposable(() => dom.removeClasses(this.badge, clazz));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -287,20 +300,20 @@ export class ActivityActionItem extends BaseActionItem {
|
||||
this.updateTitle(title);
|
||||
}
|
||||
|
||||
private updateLabel(): void {
|
||||
protected updateLabel(): void {
|
||||
if (this.activity.cssClass) {
|
||||
this.$label.addClass(this.activity.cssClass);
|
||||
dom.addClasses(this.label, this.activity.cssClass);
|
||||
}
|
||||
if (!this.options.icon) {
|
||||
this.$label.text(this.getAction().label);
|
||||
this.label.textContent = this.getAction().label;
|
||||
}
|
||||
}
|
||||
|
||||
private updateTitle(title: string): void {
|
||||
[this.$label, this.$badge, this.$container].forEach(b => {
|
||||
if (b) {
|
||||
b.attr('aria-label', title);
|
||||
b.title(title);
|
||||
[this.label, this.badge, this.container].forEach(element => {
|
||||
if (element) {
|
||||
element.setAttribute('aria-label', title);
|
||||
element.title = title;
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -312,7 +325,7 @@ export class ActivityActionItem extends BaseActionItem {
|
||||
clearTimeout(this.mouseUpTimeout);
|
||||
}
|
||||
|
||||
this.$badge.destroy();
|
||||
this.badge.remove();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -328,10 +341,10 @@ export class CompositeOverflowActivityAction extends ActivityAction {
|
||||
});
|
||||
}
|
||||
|
||||
run(event: any): TPromise<any> {
|
||||
run(event: any): Promise<any> {
|
||||
this.showMenu();
|
||||
|
||||
return TPromise.as(true);
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -344,7 +357,7 @@ export class CompositeOverflowActivityActionItem extends ActivityActionItem {
|
||||
private getActiveCompositeId: () => string,
|
||||
private getBadge: (compositeId: string) => IBadge,
|
||||
private getCompositeOpenAction: (compositeId: string) => Action,
|
||||
colors: ICompositeBarColors,
|
||||
colors: (theme: ITheme) => ICompositeBarColors,
|
||||
@IContextMenuService private contextMenuService: IContextMenuService,
|
||||
@IThemeService themeService: IThemeService
|
||||
) {
|
||||
@@ -359,8 +372,8 @@ export class CompositeOverflowActivityActionItem extends ActivityActionItem {
|
||||
this.actions = this.getActions();
|
||||
|
||||
this.contextMenuService.showContextMenu({
|
||||
getAnchor: () => this.builder.getHTMLElement(),
|
||||
getActions: () => TPromise.as(this.actions),
|
||||
getAnchor: () => this.element,
|
||||
getActions: () => this.actions,
|
||||
onHide: () => dispose(this.actions)
|
||||
});
|
||||
}
|
||||
@@ -403,23 +416,32 @@ class ManageExtensionAction extends Action {
|
||||
super('activitybar.manage.extension', nls.localize('manageExtension', "Manage Extension"));
|
||||
}
|
||||
|
||||
run(id: string): TPromise<any> {
|
||||
run(id: string): Promise<any> {
|
||||
return this.commandService.executeCommand('_extensions.manage', id);
|
||||
}
|
||||
}
|
||||
|
||||
export class DraggedCompositeIdentifier {
|
||||
constructor(private _compositeId: string) { }
|
||||
|
||||
get id(): string {
|
||||
return this._compositeId;
|
||||
}
|
||||
}
|
||||
|
||||
export class CompositeActionItem extends ActivityActionItem {
|
||||
|
||||
private static manageExtensionAction: ManageExtensionAction;
|
||||
private static draggedCompositeId: string;
|
||||
|
||||
private compositeActivity: IActivity;
|
||||
private cssClass: string;
|
||||
private compositeTransfer: LocalSelectionTransfer<DraggedCompositeIdentifier>;
|
||||
|
||||
constructor(
|
||||
private compositeActivityAction: ActivityAction,
|
||||
private toggleCompositePinnedAction: Action,
|
||||
colors: ICompositeBarColors,
|
||||
private contextMenuActionsProvider: () => Action[],
|
||||
colors: (theme: ITheme) => ICompositeBarColors,
|
||||
icon: boolean,
|
||||
private compositeBar: ICompositeBar,
|
||||
@IContextMenuService private contextMenuService: IContextMenuService,
|
||||
@@ -430,12 +452,13 @@ export class CompositeActionItem extends ActivityActionItem {
|
||||
super(compositeActivityAction, { draggable: true, colors, icon }, themeService);
|
||||
|
||||
this.cssClass = compositeActivityAction.class;
|
||||
this.compositeTransfer = LocalSelectionTransfer.getInstance<DraggedCompositeIdentifier>();
|
||||
|
||||
if (!CompositeActionItem.manageExtensionAction) {
|
||||
CompositeActionItem.manageExtensionAction = instantiationService.createInstance(ManageExtensionAction);
|
||||
}
|
||||
|
||||
compositeActivityAction.onDidChangeActivity(() => { this.compositeActivity = null; this.updateActivity(); }, this, this._callOnDispose);
|
||||
this._register(compositeActivityAction.onDidChangeActivity(() => { this.compositeActivity = null; this.updateActivity(); }, this));
|
||||
}
|
||||
|
||||
protected get activity(): IActivity {
|
||||
@@ -470,74 +493,67 @@ export class CompositeActionItem extends ActivityActionItem {
|
||||
render(container: HTMLElement): void {
|
||||
super.render(container);
|
||||
|
||||
this._updateChecked();
|
||||
this._updateEnabled();
|
||||
this.updateChecked();
|
||||
this.updateEnabled();
|
||||
|
||||
this.$container.on('contextmenu', e => {
|
||||
this._register(dom.addDisposableListener(this.container, dom.EventType.CONTEXT_MENU, e => {
|
||||
dom.EventHelper.stop(e, true);
|
||||
|
||||
this.showContextMenu(container);
|
||||
});
|
||||
}));
|
||||
|
||||
// Allow to drag
|
||||
this.$container.on(dom.EventType.DRAG_START, (e: DragEvent) => {
|
||||
this._register(dom.addDisposableListener(this.container, dom.EventType.DRAG_START, (e: DragEvent) => {
|
||||
e.dataTransfer.effectAllowed = 'move';
|
||||
this.setDraggedComposite(this.activity.id);
|
||||
|
||||
// Registe as dragged to local transfer
|
||||
this.compositeTransfer.setData([new DraggedCompositeIdentifier(this.activity.id)], DraggedCompositeIdentifier.prototype);
|
||||
|
||||
// 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);
|
||||
}
|
||||
});
|
||||
this._register(new DragAndDropObserver(this.container, {
|
||||
onDragEnter: e => {
|
||||
if (this.compositeTransfer.hasData(DraggedCompositeIdentifier.prototype) && this.compositeTransfer.getData(DraggedCompositeIdentifier.prototype)[0].id !== this.activity.id) {
|
||||
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) {
|
||||
onDragLeave: e => {
|
||||
if (this.compositeTransfer.hasData(DraggedCompositeIdentifier.prototype)) {
|
||||
this.updateFromDragging(container, false);
|
||||
}
|
||||
},
|
||||
|
||||
onDragEnd: e => {
|
||||
if (this.compositeTransfer.hasData(DraggedCompositeIdentifier.prototype)) {
|
||||
this.updateFromDragging(container, false);
|
||||
|
||||
this.compositeTransfer.clearData(DraggedCompositeIdentifier.prototype);
|
||||
}
|
||||
},
|
||||
|
||||
onDrop: e => {
|
||||
dom.EventHelper.stop(e, true);
|
||||
|
||||
if (this.compositeTransfer.hasData(DraggedCompositeIdentifier.prototype)) {
|
||||
const draggedCompositeId = this.compositeTransfer.getData(DraggedCompositeIdentifier.prototype)[0].id;
|
||||
if (draggedCompositeId !== this.activity.id) {
|
||||
this.updateFromDragging(container, false);
|
||||
this.compositeTransfer.clearData(DraggedCompositeIdentifier.prototype);
|
||||
|
||||
this.compositeBar.move(draggedCompositeId, this.activity.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// 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.badge, this.label].forEach(b => new DelayedDragHandler(b, () => {
|
||||
if (!this.compositeTransfer.hasData(DraggedCompositeIdentifier.prototype) && !this.getAction().checked) {
|
||||
this.getAction().run();
|
||||
}
|
||||
}));
|
||||
@@ -547,23 +563,11 @@ export class CompositeActionItem extends ActivityActionItem {
|
||||
|
||||
private updateFromDragging(element: HTMLElement, isDragging: boolean): void {
|
||||
const theme = this.themeService.getTheme();
|
||||
const dragBackground = theme.getColor(this.options.colors.dragAndDropBackground);
|
||||
const dragBackground = this.options.colors(theme).dragAndDropBackground;
|
||||
|
||||
element.style.backgroundColor = isDragging && dragBackground ? dragBackground.toString() : null;
|
||||
}
|
||||
|
||||
static getDraggedCompositeId(): string {
|
||||
return CompositeActionItem.draggedCompositeId;
|
||||
}
|
||||
|
||||
private setDraggedComposite(compositeId: string): void {
|
||||
CompositeActionItem.draggedCompositeId = compositeId;
|
||||
}
|
||||
|
||||
static clearDraggedComposite(): void {
|
||||
CompositeActionItem.draggedCompositeId = void 0;
|
||||
}
|
||||
|
||||
private showContextMenu(container: HTMLElement): void {
|
||||
const actions: Action[] = [this.toggleCompositePinnedAction];
|
||||
if ((<any>this.compositeActivityAction.activity).extensionId) {
|
||||
@@ -579,52 +583,65 @@ export class CompositeActionItem extends ActivityActionItem {
|
||||
this.toggleCompositePinnedAction.label = nls.localize('keep', "Keep");
|
||||
}
|
||||
|
||||
const otherActions = this.contextMenuActionsProvider();
|
||||
if (otherActions.length) {
|
||||
actions.push(new Separator());
|
||||
actions.push(...otherActions);
|
||||
}
|
||||
|
||||
const elementPosition = dom.getDomNodePagePosition(container);
|
||||
const anchor = {
|
||||
x: Math.floor(elementPosition.left + (elementPosition.width / 2)),
|
||||
y: elementPosition.top + elementPosition.height
|
||||
};
|
||||
|
||||
this.contextMenuService.showContextMenu({
|
||||
getAnchor: () => container,
|
||||
getAnchor: () => anchor,
|
||||
getActionsContext: () => this.activity.id,
|
||||
getActions: () => TPromise.as(actions)
|
||||
getActions: () => actions
|
||||
});
|
||||
}
|
||||
|
||||
focus(): void {
|
||||
this.$container.domFocus();
|
||||
this.container.focus();
|
||||
}
|
||||
|
||||
protected _updateClass(): void {
|
||||
protected updateClass(): void {
|
||||
if (this.cssClass) {
|
||||
this.$label.removeClass(this.cssClass);
|
||||
dom.removeClasses(this.label, this.cssClass);
|
||||
}
|
||||
|
||||
this.cssClass = this.getAction().class;
|
||||
if (this.cssClass) {
|
||||
this.$label.addClass(this.cssClass);
|
||||
dom.addClasses(this.label, this.cssClass);
|
||||
}
|
||||
}
|
||||
|
||||
protected _updateChecked(): void {
|
||||
protected updateChecked(): void {
|
||||
if (this.getAction().checked) {
|
||||
this.$container.addClass('checked');
|
||||
this.$container.attr('aria-label', nls.localize('compositeActive', "{0} active", this.$container.getHTMLElement().title));
|
||||
dom.addClass(this.container, 'checked');
|
||||
this.container.setAttribute('aria-label', nls.localize('compositeActive', "{0} active", this.container.title));
|
||||
} else {
|
||||
this.$container.removeClass('checked');
|
||||
this.$container.attr('aria-label', this.$container.getHTMLElement().title);
|
||||
dom.removeClass(this.container, 'checked');
|
||||
this.container.setAttribute('aria-label', this.container.title);
|
||||
}
|
||||
this.updateStyles();
|
||||
}
|
||||
|
||||
protected _updateEnabled(): void {
|
||||
protected updateEnabled(): void {
|
||||
if (this.getAction().enabled) {
|
||||
this.builder.removeClass('disabled');
|
||||
dom.removeClass(this.element, 'disabled');
|
||||
} else {
|
||||
this.builder.addClass('disabled');
|
||||
dom.addClass(this.element, 'disabled');
|
||||
}
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
super.dispose();
|
||||
|
||||
CompositeActionItem.clearDraggedComposite();
|
||||
this.compositeTransfer.clearData(DraggedCompositeIdentifier.prototype);
|
||||
|
||||
this.$label.destroy();
|
||||
this.label.remove();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -639,7 +656,7 @@ export class ToggleCompositePinnedAction extends Action {
|
||||
this.checked = this.activity && this.compositeBar.isPinned(this.activity.id);
|
||||
}
|
||||
|
||||
run(context: string): TPromise<any> {
|
||||
run(context: string): Promise<any> {
|
||||
const id = this.activity ? this.activity.id : context;
|
||||
|
||||
if (this.compositeBar.isPinned(id)) {
|
||||
@@ -648,6 +665,6 @@ export class ToggleCompositePinnedAction extends Action {
|
||||
this.compositeBar.pin(id);
|
||||
}
|
||||
|
||||
return TPromise.as(true);
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
}
|
||||
@@ -3,17 +3,12 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import 'vs/css!./media/compositepart';
|
||||
import * as nls from 'vs/nls';
|
||||
import { defaultGenerator } from 'vs/base/common/idGenerator';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
|
||||
import { Builder, $ } from 'vs/base/browser/builder';
|
||||
import * as strings from 'vs/base/common/strings';
|
||||
import { Emitter } from 'vs/base/common/event';
|
||||
import * as types from 'vs/base/common/types';
|
||||
import * as errors from 'vs/base/common/errors';
|
||||
import { ToolBar } from 'vs/base/browser/ui/toolbar/toolbar';
|
||||
import { IActionItem, ActionsOrientation } from 'vs/base/browser/ui/actionbar/actionbar';
|
||||
@@ -35,7 +30,8 @@ import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import { attachProgressBarStyler } from 'vs/platform/theme/common/styler';
|
||||
import { INotificationService } from 'vs/platform/notification/common/notification';
|
||||
import { Dimension } from 'vs/base/browser/dom';
|
||||
import { Dimension, append, $, addClass, hide, show, addClasses } from 'vs/base/browser/dom';
|
||||
import { AnchorAlignment } from 'vs/base/browser/ui/contextview/contextview';
|
||||
|
||||
export interface ICompositeTitleLabel {
|
||||
|
||||
@@ -52,13 +48,13 @@ export interface ICompositeTitleLabel {
|
||||
|
||||
export abstract class CompositePart<T extends Composite> extends Part {
|
||||
|
||||
protected _onDidCompositeOpen = this._register(new Emitter<IComposite>());
|
||||
protected _onDidCompositeOpen = this._register(new Emitter<{ composite: IComposite, focus: boolean }>());
|
||||
protected _onDidCompositeClose = this._register(new Emitter<IComposite>());
|
||||
|
||||
protected toolBar: ToolBar;
|
||||
|
||||
private instantiatedCompositeListeners: IDisposable[];
|
||||
private mapCompositeToCompositeContainer: { [compositeId: string]: Builder; };
|
||||
private mapCompositeToCompositeContainer: { [compositeId: string]: HTMLElement; };
|
||||
private mapActionsBindingToComposite: { [compositeId: string]: () => void; };
|
||||
private mapProgressServiceToComposite: { [compositeId: string]: IProgressService; };
|
||||
private activeComposite: Composite;
|
||||
@@ -88,7 +84,7 @@ export abstract class CompositePart<T extends Composite> extends Part {
|
||||
id: string,
|
||||
options: IPartOptions
|
||||
) {
|
||||
super(id, options, themeService);
|
||||
super(id, options, themeService, storageService);
|
||||
|
||||
this.instantiatedCompositeListeners = [];
|
||||
this.mapCompositeToCompositeContainer = {};
|
||||
@@ -99,7 +95,7 @@ export abstract class CompositePart<T extends Composite> extends Part {
|
||||
this.lastActiveCompositeId = storageService.get(activeCompositeSettingsKey, StorageScope.WORKSPACE, this.defaultCompositeId);
|
||||
}
|
||||
|
||||
protected openComposite(id: string, focus?: boolean): TPromise<Composite> {
|
||||
protected openComposite(id: string, focus?: boolean): Composite {
|
||||
// Check if composite already visible and just focus in that case
|
||||
if (this.activeComposite && this.activeComposite.getId() === id) {
|
||||
if (focus) {
|
||||
@@ -107,66 +103,57 @@ export abstract class CompositePart<T extends Composite> extends Part {
|
||||
}
|
||||
|
||||
// Fullfill promise with composite that is being opened
|
||||
return TPromise.as(this.activeComposite);
|
||||
return this.activeComposite;
|
||||
}
|
||||
|
||||
// Open
|
||||
return this.doOpenComposite(id, focus);
|
||||
}
|
||||
|
||||
private doOpenComposite(id: string, focus?: boolean): TPromise<Composite> {
|
||||
private doOpenComposite(id: string, focus?: boolean): Composite {
|
||||
|
||||
// Use a generated token to avoid race conditions from long running promises
|
||||
const currentCompositeOpenToken = defaultGenerator.nextId();
|
||||
this.currentCompositeOpenToken = currentCompositeOpenToken;
|
||||
|
||||
// Hide current
|
||||
let hidePromise: TPromise<Composite>;
|
||||
if (this.activeComposite) {
|
||||
hidePromise = this.hideActiveComposite();
|
||||
} else {
|
||||
hidePromise = TPromise.as(null);
|
||||
this.hideActiveComposite();
|
||||
}
|
||||
|
||||
return hidePromise.then(() => {
|
||||
// Update Title
|
||||
this.updateTitle(id);
|
||||
|
||||
// Update Title
|
||||
this.updateTitle(id);
|
||||
// Create composite
|
||||
const composite = this.createComposite(id, true);
|
||||
|
||||
// Create 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 undefined;
|
||||
}
|
||||
|
||||
// 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();
|
||||
}
|
||||
|
||||
// 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();
|
||||
}
|
||||
|
||||
// Fullfill promise with composite that is being opened
|
||||
return composite;
|
||||
});
|
||||
}).then(composite => {
|
||||
if (composite) {
|
||||
this._onDidCompositeOpen.fire(composite);
|
||||
// Check if composite already visible and just focus in that case
|
||||
if (this.activeComposite && this.activeComposite.getId() === composite.getId()) {
|
||||
if (focus) {
|
||||
composite.focus();
|
||||
}
|
||||
|
||||
this._onDidCompositeOpen.fire({ composite, focus });
|
||||
return composite;
|
||||
});
|
||||
}
|
||||
|
||||
// Show Composite and Focus
|
||||
this.showComposite(composite);
|
||||
if (focus) {
|
||||
composite.focus();
|
||||
}
|
||||
|
||||
// Return with the composite that is being opened
|
||||
if (composite) {
|
||||
this._onDidCompositeOpen.fire({ composite, focus });
|
||||
}
|
||||
|
||||
return composite;
|
||||
}
|
||||
|
||||
protected createComposite(id: string, isActive?: boolean): Composite {
|
||||
@@ -199,7 +186,7 @@ export abstract class CompositePart<T extends Composite> extends Part {
|
||||
throw new Error(strings.format('Unable to find composite with id {0}', id));
|
||||
}
|
||||
|
||||
protected showComposite(composite: Composite): TPromise<void> {
|
||||
protected showComposite(composite: Composite): void {
|
||||
|
||||
// Remember Composite
|
||||
this.activeComposite = composite;
|
||||
@@ -215,105 +202,92 @@ export abstract class CompositePart<T extends Composite> extends Part {
|
||||
// Remember
|
||||
this.lastActiveCompositeId = this.activeComposite.getId();
|
||||
|
||||
let createCompositePromise: TPromise<void>;
|
||||
|
||||
// Composites created for the first time
|
||||
let compositeContainer = this.mapCompositeToCompositeContainer[composite.getId()];
|
||||
if (!compositeContainer) {
|
||||
|
||||
// Build Container off-DOM
|
||||
compositeContainer = $().div({
|
||||
'class': ['composite', this.compositeCSSClass],
|
||||
id: composite.getId()
|
||||
}, div => {
|
||||
createCompositePromise = composite.create(div.getHTMLElement()).then(() => {
|
||||
composite.updateStyles();
|
||||
});
|
||||
});
|
||||
compositeContainer = $('.composite');
|
||||
addClasses(compositeContainer, this.compositeCSSClass);
|
||||
compositeContainer.id = composite.getId();
|
||||
|
||||
composite.create(compositeContainer);
|
||||
composite.updateStyles();
|
||||
|
||||
// Remember composite container
|
||||
this.mapCompositeToCompositeContainer[composite.getId()] = compositeContainer;
|
||||
}
|
||||
|
||||
// Composite already exists but is hidden
|
||||
else {
|
||||
createCompositePromise = TPromise.as(null);
|
||||
}
|
||||
|
||||
// Report progress for slow loading composites (but only if we did not create the composites before already)
|
||||
const progressService = this.mapProgressServiceToComposite[composite.getId()];
|
||||
if (progressService && !compositeContainer) {
|
||||
this.mapProgressServiceToComposite[composite.getId()].showWhile(createCompositePromise, this.partService.isCreated() ? 800 : 3200 /* less ugly initial startup */);
|
||||
this.mapProgressServiceToComposite[composite.getId()].showWhile(Promise.resolve(), this.partService.isRestored() ? 800 : 3200 /* less ugly initial startup */);
|
||||
}
|
||||
|
||||
// Fill Content and Actions
|
||||
return createCompositePromise.then(() => {
|
||||
// Make sure that the user meanwhile did not open another composite or closed the part containing the composite
|
||||
if (!this.activeComposite || composite.getId() !== this.activeComposite.getId()) {
|
||||
return void 0;
|
||||
}
|
||||
|
||||
// Make sure that the user meanwhile did not open another composite or closed the part containing the composite
|
||||
if (!this.activeComposite || composite.getId() !== this.activeComposite.getId()) {
|
||||
return void 0;
|
||||
// Take Composite on-DOM and show
|
||||
this.getContentArea().appendChild(compositeContainer);
|
||||
show(compositeContainer);
|
||||
|
||||
// Setup action runner
|
||||
this.toolBar.actionRunner = composite.getActionRunner();
|
||||
|
||||
// Update title with composite title if it differs from descriptor
|
||||
const descriptor = this.registry.getComposite(composite.getId());
|
||||
if (descriptor && descriptor.name !== composite.getTitle()) {
|
||||
this.updateTitle(composite.getId(), composite.getTitle());
|
||||
}
|
||||
|
||||
// Handle Composite Actions
|
||||
let actionsBinding = this.mapActionsBindingToComposite[composite.getId()];
|
||||
if (!actionsBinding) {
|
||||
actionsBinding = this.collectCompositeActions(composite);
|
||||
this.mapActionsBindingToComposite[composite.getId()] = actionsBinding;
|
||||
}
|
||||
actionsBinding();
|
||||
|
||||
if (this.telemetryActionsListener) {
|
||||
this.telemetryActionsListener.dispose();
|
||||
this.telemetryActionsListener = null;
|
||||
}
|
||||
|
||||
// Action Run Handling
|
||||
this.telemetryActionsListener = this.toolBar.actionRunner.onDidRun(e => {
|
||||
|
||||
// Check for Error
|
||||
if (e.error && !errors.isPromiseCanceledError(e.error)) {
|
||||
this.notificationService.error(e.error);
|
||||
}
|
||||
|
||||
// Take Composite on-DOM and show
|
||||
compositeContainer.build(this.getContentArea());
|
||||
compositeContainer.show();
|
||||
|
||||
// Setup action runner
|
||||
this.toolBar.actionRunner = composite.getActionRunner();
|
||||
|
||||
// Update title with composite title if it differs from descriptor
|
||||
const descriptor = this.registry.getComposite(composite.getId());
|
||||
if (descriptor && descriptor.name !== composite.getTitle()) {
|
||||
this.updateTitle(composite.getId(), composite.getTitle());
|
||||
// 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 });
|
||||
}
|
||||
});
|
||||
|
||||
// Handle Composite Actions
|
||||
let actionsBinding = this.mapActionsBindingToComposite[composite.getId()];
|
||||
if (!actionsBinding) {
|
||||
actionsBinding = this.collectCompositeActions(composite);
|
||||
this.mapActionsBindingToComposite[composite.getId()] = actionsBinding;
|
||||
}
|
||||
actionsBinding();
|
||||
// Indicate to composite that it is now visible
|
||||
composite.setVisible(true);
|
||||
|
||||
if (this.telemetryActionsListener) {
|
||||
this.telemetryActionsListener.dispose();
|
||||
this.telemetryActionsListener = null;
|
||||
}
|
||||
// Make sure that the user meanwhile did not open another composite or closed the part containing the composite
|
||||
if (!this.activeComposite || composite.getId() !== this.activeComposite.getId()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Action Run Handling
|
||||
this.telemetryActionsListener = this.toolBar.actionRunner.onDidRun(e => {
|
||||
|
||||
// Check for Error
|
||||
if (e.error && !errors.isPromiseCanceledError(e.error)) {
|
||||
this.notificationService.error(e.error);
|
||||
}
|
||||
|
||||
// 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 });
|
||||
}
|
||||
});
|
||||
|
||||
// Indicate to composite that it is now visible
|
||||
return composite.setVisible(true).then(() => {
|
||||
|
||||
// Make sure that the user meanwhile did not open another composite or closed the part containing the composite
|
||||
if (!this.activeComposite || composite.getId() !== this.activeComposite.getId()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Make sure the composite is layed out
|
||||
if (this.contentAreaSize) {
|
||||
composite.layout(this.contentAreaSize);
|
||||
}
|
||||
});
|
||||
}, error => this.onError(error));
|
||||
// Make sure the composite is layed out
|
||||
if (this.contentAreaSize) {
|
||||
composite.layout(this.contentAreaSize);
|
||||
}
|
||||
}
|
||||
|
||||
protected onTitleAreaUpdate(compositeId: string): void {
|
||||
@@ -375,9 +349,9 @@ export abstract class CompositePart<T extends Composite> extends Part {
|
||||
return this.lastActiveCompositeId;
|
||||
}
|
||||
|
||||
protected hideActiveComposite(): TPromise<Composite> {
|
||||
protected hideActiveComposite(): Composite {
|
||||
if (!this.activeComposite) {
|
||||
return TPromise.as(null); // Nothing to do
|
||||
return undefined; // Nothing to do
|
||||
}
|
||||
|
||||
const composite = this.activeComposite;
|
||||
@@ -386,65 +360,58 @@ export abstract class CompositePart<T extends Composite> extends Part {
|
||||
const compositeContainer = this.mapCompositeToCompositeContainer[composite.getId()];
|
||||
|
||||
// Indicate to Composite
|
||||
return composite.setVisible(false).then(() => {
|
||||
composite.setVisible(false);
|
||||
|
||||
// Take Container Off-DOM and hide
|
||||
compositeContainer.offDOM();
|
||||
compositeContainer.hide();
|
||||
// Take Container Off-DOM and hide
|
||||
compositeContainer.remove();
|
||||
hide(compositeContainer);
|
||||
|
||||
// Clear any running Progress
|
||||
this.progressBar.stop().hide();
|
||||
// Clear any running Progress
|
||||
this.progressBar.stop().hide();
|
||||
|
||||
// Empty Actions
|
||||
this.toolBar.setActions([])();
|
||||
this._onDidCompositeClose.fire(composite);
|
||||
// Empty Actions
|
||||
this.toolBar.setActions([])();
|
||||
this._onDidCompositeClose.fire(composite);
|
||||
|
||||
return composite;
|
||||
});
|
||||
return composite;
|
||||
}
|
||||
|
||||
createTitleArea(parent: HTMLElement): HTMLElement {
|
||||
|
||||
// Title Area Container
|
||||
const titleArea = $(parent).div({
|
||||
'class': ['composite', 'title']
|
||||
});
|
||||
const titleArea = append(parent, $('.composite'));
|
||||
addClass(titleArea, 'title');
|
||||
|
||||
// Left Title Label
|
||||
this.titleLabel = this.createTitleLabel(titleArea.getHTMLElement());
|
||||
this.titleLabel = this.createTitleLabel(titleArea);
|
||||
|
||||
// Right Actions Container
|
||||
$(titleArea).div({
|
||||
'class': 'title-actions'
|
||||
}, div => {
|
||||
const titleActionsContainer = append(titleArea, $('.title-actions'));
|
||||
|
||||
// Toolbar
|
||||
this.toolBar = this._register(new ToolBar(div.getHTMLElement(), this.contextMenuService, {
|
||||
actionItemProvider: action => this.actionItemProvider(action as Action),
|
||||
orientation: ActionsOrientation.HORIZONTAL,
|
||||
getKeyBinding: action => this.keybindingService.lookupKeybinding(action.id)
|
||||
}));
|
||||
});
|
||||
// Toolbar
|
||||
this.toolBar = this._register(new ToolBar(titleActionsContainer, this.contextMenuService, {
|
||||
actionItemProvider: action => this.actionItemProvider(action as Action),
|
||||
orientation: ActionsOrientation.HORIZONTAL,
|
||||
getKeyBinding: action => this.keybindingService.lookupKeybinding(action.id),
|
||||
anchorAlignmentProvider: () => this.getTitleAreaDropDownAnchorAlignment()
|
||||
}));
|
||||
|
||||
return titleArea.getHTMLElement();
|
||||
return titleArea;
|
||||
}
|
||||
|
||||
protected createTitleLabel(parent: HTMLElement): ICompositeTitleLabel {
|
||||
let titleLabel: Builder;
|
||||
$(parent).div({
|
||||
'class': 'title-label'
|
||||
}, div => {
|
||||
titleLabel = div.element('h2');
|
||||
});
|
||||
const titleContainer = append(parent, $('.title-label'));
|
||||
const titleLabel = append(titleContainer, $('h2'));
|
||||
|
||||
const $this = this;
|
||||
return {
|
||||
updateTitle: (id, title, keybinding) => {
|
||||
titleLabel.safeInnerHtml(title);
|
||||
titleLabel.title(keybinding ? nls.localize('titleTooltip', "{0} ({1})", title, keybinding) : title);
|
||||
titleLabel.innerHTML = strings.escape(title);
|
||||
titleLabel.title = keybinding ? nls.localize('titleTooltip', "{0} ({1})", title, keybinding) : title;
|
||||
},
|
||||
|
||||
updateStyles: () => {
|
||||
titleLabel.style('color', $this.getColor($this.titleForegroundColor));
|
||||
titleLabel.style.color = $this.getColor($this.titleForegroundColor);
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -467,17 +434,13 @@ export abstract class CompositePart<T extends Composite> extends Part {
|
||||
}
|
||||
|
||||
createContentArea(parent: HTMLElement): HTMLElement {
|
||||
return $(parent).div({
|
||||
'class': 'content'
|
||||
}, div => {
|
||||
this.progressBar = this._register(new ProgressBar(div.getHTMLElement()));
|
||||
this._register(attachProgressBarStyler(this.progressBar, this.themeService));
|
||||
this.progressBar.hide();
|
||||
}).getHTMLElement();
|
||||
}
|
||||
const contentContainer = append(parent, $('.content'));
|
||||
|
||||
private onError(error: any): void {
|
||||
this.notificationService.error(types.isString(error) ? new Error(error) : error);
|
||||
this.progressBar = this._register(new ProgressBar(contentContainer));
|
||||
this._register(attachProgressBarStyler(this.progressBar, this.themeService));
|
||||
this.progressBar.hide();
|
||||
|
||||
return contentContainer;
|
||||
}
|
||||
|
||||
getProgressIndicator(id: string): IProgressService {
|
||||
@@ -492,6 +455,10 @@ export abstract class CompositePart<T extends Composite> extends Part {
|
||||
return [];
|
||||
}
|
||||
|
||||
protected getTitleAreaDropDownAnchorAlignment(): AnchorAlignment {
|
||||
return AnchorAlignment.RIGHT;
|
||||
}
|
||||
|
||||
layout(dimension: Dimension): Dimension[] {
|
||||
|
||||
// Pass to super
|
||||
@@ -506,12 +473,6 @@ export abstract class CompositePart<T extends Composite> extends Part {
|
||||
return sizes;
|
||||
}
|
||||
|
||||
shutdown(): void {
|
||||
this.instantiatedComposites.forEach(i => i.shutdown());
|
||||
|
||||
super.shutdown();
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this.mapCompositeToCompositeContainer = null;
|
||||
this.mapProgressServiceToComposite = null;
|
||||
|
||||
@@ -2,18 +2,16 @@
|
||||
* 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 { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { Panel } from 'vs/workbench/browser/panel';
|
||||
import { EditorInput, EditorOptions, IEditor, GroupIdentifier, IEditorMemento } from 'vs/workbench/common/editor';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { IEditorGroup, IEditorGroupsService } from 'vs/workbench/services/group/common/editorGroupsService';
|
||||
import { IStorageService } from 'vs/platform/storage/common/storage';
|
||||
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
|
||||
import { LRUCache } from 'vs/base/common/map';
|
||||
import URI from 'vs/base/common/uri';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { once, Event } from 'vs/base/common/event';
|
||||
import { isEmptyObject } from 'vs/base/common/types';
|
||||
import { DEFAULT_EDITOR_MIN_DIMENSIONS, DEFAULT_EDITOR_MAX_DIMENSIONS } from 'vs/workbench/browser/parts/editor/editor';
|
||||
@@ -43,16 +41,17 @@ export abstract class BaseEditor extends Panel implements IEditor {
|
||||
readonly onDidSizeConstraintsChange: Event<{ width: number; height: number; }> = Event.None;
|
||||
|
||||
protected _input: EditorInput;
|
||||
protected _options: EditorOptions;
|
||||
|
||||
private _options: EditorOptions;
|
||||
private _group: IEditorGroup;
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
telemetryService: ITelemetryService,
|
||||
themeService: IThemeService
|
||||
themeService: IThemeService,
|
||||
storageService: IStorageService
|
||||
) {
|
||||
super(id, telemetryService, themeService);
|
||||
super(id, telemetryService, themeService, storageService);
|
||||
}
|
||||
|
||||
get input(): EditorInput {
|
||||
@@ -82,7 +81,7 @@ export abstract class BaseEditor extends Panel implements IEditor {
|
||||
this._input = input;
|
||||
this._options = options;
|
||||
|
||||
return TPromise.wrap<void>(null);
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -105,15 +104,11 @@ export abstract class BaseEditor extends Panel implements IEditor {
|
||||
this._options = options;
|
||||
}
|
||||
|
||||
create(parent: HTMLElement): void; // create is sync for editors
|
||||
create(parent: HTMLElement): TPromise<void>;
|
||||
create(parent: HTMLElement): TPromise<void> {
|
||||
const res = super.create(parent);
|
||||
create(parent: HTMLElement): void {
|
||||
super.create(parent);
|
||||
|
||||
// Create Editor
|
||||
this.createEditor(parent);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -121,15 +116,10 @@ export abstract class BaseEditor extends Panel implements IEditor {
|
||||
*/
|
||||
protected abstract createEditor(parent: HTMLElement): void;
|
||||
|
||||
setVisible(visible: boolean, group?: IEditorGroup): void; // setVisible is sync for editors
|
||||
setVisible(visible: boolean, group?: IEditorGroup): TPromise<void>;
|
||||
setVisible(visible: boolean, group?: IEditorGroup): TPromise<void> {
|
||||
const promise = super.setVisible(visible);
|
||||
|
||||
setVisible(visible: boolean, group?: IEditorGroup): void {
|
||||
super.setVisible(visible);
|
||||
// Propagate to Editor
|
||||
this.setEditorVisible(visible, group);
|
||||
|
||||
return promise;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -143,28 +133,28 @@ export abstract class BaseEditor extends Panel implements IEditor {
|
||||
this._group = group;
|
||||
}
|
||||
|
||||
protected getEditorMemento<T>(storageService: IStorageService, editorGroupService: IEditorGroupsService, key: string, limit: number = 10): IEditorMemento<T> {
|
||||
protected getEditorMemento<T>(editorGroupService: IEditorGroupsService, key: string, limit: number = 10): IEditorMemento<T> {
|
||||
const mementoKey = `${this.getId()}${key}`;
|
||||
|
||||
let editorMemento = BaseEditor.EDITOR_MEMENTOS.get(mementoKey);
|
||||
if (!editorMemento) {
|
||||
editorMemento = new EditorMemento(this.getId(), key, this.getMemento(storageService), limit, editorGroupService);
|
||||
editorMemento = new EditorMemento(this.getId(), key, this.getMemento(StorageScope.WORKSPACE), limit, editorGroupService);
|
||||
BaseEditor.EDITOR_MEMENTOS.set(mementoKey, editorMemento);
|
||||
}
|
||||
|
||||
return editorMemento;
|
||||
}
|
||||
|
||||
shutdown(): void {
|
||||
protected saveState(): void {
|
||||
|
||||
// Shutdown all editor memento for this editor type
|
||||
// Save all editor memento for this editor type
|
||||
BaseEditor.EDITOR_MEMENTOS.forEach(editorMemento => {
|
||||
if (editorMemento.id === this.getId()) {
|
||||
editorMemento.shutdown();
|
||||
editorMemento.saveState();
|
||||
}
|
||||
});
|
||||
|
||||
super.shutdown();
|
||||
super.saveState();
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
@@ -195,9 +185,9 @@ export class EditorMemento<T> implements IEditorMemento<T> {
|
||||
return this._id;
|
||||
}
|
||||
|
||||
saveState(group: IEditorGroup, resource: URI, state: T): void;
|
||||
saveState(group: IEditorGroup, editor: EditorInput, state: T): void;
|
||||
saveState(group: IEditorGroup, resourceOrEditor: URI | EditorInput, state: T): void {
|
||||
saveEditorState(group: IEditorGroup, resource: URI, state: T): void;
|
||||
saveEditorState(group: IEditorGroup, editor: EditorInput, state: T): void;
|
||||
saveEditorState(group: IEditorGroup, resourceOrEditor: URI | EditorInput, state: T): void {
|
||||
const resource = this.doGetResource(resourceOrEditor);
|
||||
if (!resource || !group) {
|
||||
return; // we are not in a good state to save any state for a resource
|
||||
@@ -216,14 +206,14 @@ export class EditorMemento<T> implements IEditorMemento<T> {
|
||||
// Automatically clear when editor input gets disposed if any
|
||||
if (resourceOrEditor instanceof EditorInput) {
|
||||
once(resourceOrEditor.onDispose)(() => {
|
||||
this.clearState(resource);
|
||||
this.clearEditorState(resource);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
loadState(group: IEditorGroup, resource: URI): T;
|
||||
loadState(group: IEditorGroup, editor: EditorInput): T;
|
||||
loadState(group: IEditorGroup, resourceOrEditor: URI | EditorInput): T {
|
||||
loadEditorState(group: IEditorGroup, resource: URI): T;
|
||||
loadEditorState(group: IEditorGroup, editor: EditorInput): T;
|
||||
loadEditorState(group: IEditorGroup, resourceOrEditor: URI | EditorInput): T {
|
||||
const resource = this.doGetResource(resourceOrEditor);
|
||||
if (!resource || !group) {
|
||||
return void 0; // we are not in a good state to load any state for a resource
|
||||
@@ -239,13 +229,21 @@ export class EditorMemento<T> implements IEditorMemento<T> {
|
||||
return void 0;
|
||||
}
|
||||
|
||||
clearState(resource: URI): void;
|
||||
clearState(editor: EditorInput): void;
|
||||
clearState(resourceOrEditor: URI | EditorInput): void {
|
||||
clearEditorState(resource: URI, group?: IEditorGroup): void;
|
||||
clearEditorState(editor: EditorInput, group?: IEditorGroup): void;
|
||||
clearEditorState(resourceOrEditor: URI | EditorInput, group?: IEditorGroup): void {
|
||||
const resource = this.doGetResource(resourceOrEditor);
|
||||
if (resource) {
|
||||
const cache = this.doLoad();
|
||||
cache.delete(resource.toString());
|
||||
|
||||
if (group) {
|
||||
const resourceViewState = cache.get(resource.toString());
|
||||
if (resourceViewState) {
|
||||
delete resourceViewState[group.id];
|
||||
}
|
||||
} else {
|
||||
cache.delete(resource.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -271,7 +269,7 @@ export class EditorMemento<T> implements IEditorMemento<T> {
|
||||
return this.cache;
|
||||
}
|
||||
|
||||
shutdown(): void {
|
||||
saveState(): void {
|
||||
const cache = this.doLoad();
|
||||
|
||||
// Cleanup once during shutdown
|
||||
@@ -300,4 +298,4 @@ export class EditorMemento<T> implements IEditorMemento<T> {
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,8 +3,6 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import * as nls from 'vs/nls';
|
||||
import { BINARY_DIFF_EDITOR_ID } from 'vs/workbench/common/editor';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
@@ -12,6 +10,7 @@ import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import { SideBySideEditor } from 'vs/workbench/browser/parts/editor/sideBySideEditor';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { BaseBinaryResourceEditor } from 'vs/workbench/browser/parts/editor/binaryEditor';
|
||||
import { IStorageService } from 'vs/platform/storage/common/storage';
|
||||
|
||||
/**
|
||||
* An implementation of editor for diffing binary files like images or videos.
|
||||
@@ -23,9 +22,10 @@ export class BinaryResourceDiffEditor extends SideBySideEditor {
|
||||
constructor(
|
||||
@ITelemetryService telemetryService: ITelemetryService,
|
||||
@IInstantiationService instantiationService: IInstantiationService,
|
||||
@IThemeService themeService: IThemeService
|
||||
@IThemeService themeService: IThemeService,
|
||||
@IStorageService storageService: IStorageService
|
||||
) {
|
||||
super(telemetryService, instantiationService, themeService);
|
||||
super(telemetryService, instantiationService, themeService, storageService);
|
||||
}
|
||||
|
||||
getMetadata(): string {
|
||||
|
||||
@@ -3,12 +3,8 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import * as nls from 'vs/nls';
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { Builder, $ } from 'vs/base/browser/builder';
|
||||
import { EditorInput, EditorOptions } from 'vs/workbench/common/editor';
|
||||
import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor';
|
||||
import { BinaryEditorModel } from 'vs/workbench/common/editor/binaryEditorModel';
|
||||
@@ -17,13 +13,15 @@ import { DomScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableEle
|
||||
import { ScrollbarVisibility } from 'vs/base/common/scrollable';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import { ResourceViewerContext, ResourceViewer } from 'vs/workbench/browser/parts/editor/resourceViewer';
|
||||
import URI from 'vs/base/common/uri';
|
||||
import { Dimension } from 'vs/base/browser/dom';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { Dimension, size, clearNode } from 'vs/base/browser/dom';
|
||||
import { IFileService } from 'vs/platform/files/common/files';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { dispose } from 'vs/base/common/lifecycle';
|
||||
import { IStorageService } from 'vs/platform/storage/common/storage';
|
||||
|
||||
export interface IOpenCallbacks {
|
||||
openInternal: (input: EditorInput, options: EditorOptions) => void;
|
||||
openInternal: (input: EditorInput, options: EditorOptions) => Thenable<void>;
|
||||
openExternal: (uri: URI) => void;
|
||||
}
|
||||
|
||||
@@ -35,9 +33,12 @@ export abstract class BaseBinaryResourceEditor extends BaseEditor {
|
||||
private readonly _onMetadataChanged: Emitter<void> = this._register(new Emitter<void>());
|
||||
get onMetadataChanged(): Event<void> { return this._onMetadataChanged.event; }
|
||||
|
||||
private readonly _onDidOpenInPlace: Emitter<void> = this._register(new Emitter<void>());
|
||||
get onDidOpenInPlace(): Event<void> { return this._onDidOpenInPlace.event; }
|
||||
|
||||
private callbacks: IOpenCallbacks;
|
||||
private metadata: string;
|
||||
private binaryContainer: Builder;
|
||||
private binaryContainer: HTMLElement;
|
||||
private scrollbar: DomScrollableElement;
|
||||
private resourceViewerContext: ResourceViewerContext;
|
||||
|
||||
@@ -47,8 +48,9 @@ export abstract class BaseBinaryResourceEditor extends BaseEditor {
|
||||
telemetryService: ITelemetryService,
|
||||
themeService: IThemeService,
|
||||
@IFileService private readonly _fileService: IFileService,
|
||||
@IStorageService storageService: IStorageService
|
||||
) {
|
||||
super(id, telemetryService, themeService);
|
||||
super(id, telemetryService, themeService, storageService);
|
||||
|
||||
this.callbacks = callbacks;
|
||||
}
|
||||
@@ -60,14 +62,13 @@ export abstract class BaseBinaryResourceEditor extends BaseEditor {
|
||||
protected createEditor(parent: HTMLElement): void {
|
||||
|
||||
// Container for Binary
|
||||
const binaryContainerElement = document.createElement('div');
|
||||
binaryContainerElement.className = 'binary-container';
|
||||
this.binaryContainer = $(binaryContainerElement);
|
||||
this.binaryContainer.style('outline', 'none');
|
||||
this.binaryContainer.tabindex(0); // enable focus support from the editor part (do not remove)
|
||||
this.binaryContainer = document.createElement('div');
|
||||
this.binaryContainer.className = 'binary-container';
|
||||
this.binaryContainer.style.outline = 'none';
|
||||
this.binaryContainer.tabIndex = 0; // enable focus support from the editor part (do not remove)
|
||||
|
||||
// Custom Scrollbars
|
||||
this.scrollbar = this._register(new DomScrollableElement(binaryContainerElement, { horizontal: ScrollbarVisibility.Auto, vertical: ScrollbarVisibility.Auto }));
|
||||
this.scrollbar = this._register(new DomScrollableElement(this.binaryContainer, { horizontal: ScrollbarVisibility.Auto, vertical: ScrollbarVisibility.Auto }));
|
||||
parent.appendChild(this.scrollbar.getDomNode());
|
||||
}
|
||||
|
||||
@@ -82,16 +83,16 @@ export abstract class BaseBinaryResourceEditor extends BaseEditor {
|
||||
|
||||
// Assert Model instance
|
||||
if (!(model instanceof BinaryEditorModel)) {
|
||||
return TPromise.wrapError<void>(new Error('Unable to open file as binary'));
|
||||
return Promise.reject(new Error('Unable to open file as binary'));
|
||||
}
|
||||
|
||||
// Render Input
|
||||
this.resourceViewerContext = ResourceViewer.show(
|
||||
{ name: model.getName(), resource: model.getResource(), size: model.getSize(), etag: model.getETag(), mime: model.getMime() },
|
||||
this._fileService,
|
||||
this.binaryContainer.getHTMLElement(),
|
||||
this.binaryContainer,
|
||||
this.scrollbar,
|
||||
resource => this.callbacks.openInternal(input, options),
|
||||
resource => this.handleOpenInternalCallback(input, options),
|
||||
resource => this.callbacks.openExternal(resource),
|
||||
meta => this.handleMetadataChanged(meta)
|
||||
);
|
||||
@@ -101,6 +102,14 @@ export abstract class BaseBinaryResourceEditor extends BaseEditor {
|
||||
});
|
||||
}
|
||||
|
||||
private handleOpenInternalCallback(input: EditorInput, options: EditorOptions) {
|
||||
this.callbacks.openInternal(input, options).then(() => {
|
||||
|
||||
// Signal to listeners that the binary editor has been opened in-place
|
||||
this._onDidOpenInPlace.fire();
|
||||
});
|
||||
}
|
||||
|
||||
private handleMetadataChanged(meta: string): void {
|
||||
this.metadata = meta;
|
||||
|
||||
@@ -116,8 +125,9 @@ export abstract class BaseBinaryResourceEditor extends BaseEditor {
|
||||
// Clear Meta
|
||||
this.handleMetadataChanged(null);
|
||||
|
||||
// Empty HTML Container
|
||||
$(this.binaryContainer).empty();
|
||||
// Clear Resource Viewer
|
||||
clearNode(this.binaryContainer);
|
||||
this.resourceViewerContext = dispose(this.resourceViewerContext);
|
||||
|
||||
super.clearInput();
|
||||
}
|
||||
@@ -125,21 +135,21 @@ export abstract class BaseBinaryResourceEditor extends BaseEditor {
|
||||
layout(dimension: Dimension): void {
|
||||
|
||||
// Pass on to Binary Container
|
||||
this.binaryContainer.size(dimension.width, dimension.height);
|
||||
size(this.binaryContainer, dimension.width, dimension.height);
|
||||
this.scrollbar.scanDomNode();
|
||||
if (this.resourceViewerContext) {
|
||||
if (this.resourceViewerContext && this.resourceViewerContext.layout) {
|
||||
this.resourceViewerContext.layout(dimension);
|
||||
}
|
||||
}
|
||||
|
||||
focus(): void {
|
||||
this.binaryContainer.domFocus();
|
||||
this.binaryContainer.focus();
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this.binaryContainer.remove();
|
||||
|
||||
// Destroy Container
|
||||
this.binaryContainer.destroy();
|
||||
this.resourceViewerContext = dispose(this.resourceViewerContext);
|
||||
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@@ -3,18 +3,17 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import { BreadcrumbsWidget } from 'vs/base/browser/ui/breadcrumbs/breadcrumbsWidget';
|
||||
import { IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { GroupIdentifier } from 'vs/workbench/common/editor';
|
||||
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { Emitter, Event } from 'vs/base/common/event';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { IConfigurationRegistry, Extensions } from 'vs/platform/configuration/common/configurationRegistry';
|
||||
import * as glob from 'vs/base/common/glob';
|
||||
import { IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { localize } from 'vs/nls';
|
||||
import { IConfigurationOverrides, IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { Extensions, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry';
|
||||
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
|
||||
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { GroupIdentifier } from 'vs/workbench/common/editor';
|
||||
|
||||
export const IBreadcrumbsService = createDecorator<IBreadcrumbsService>('IEditorBreadcrumbsService');
|
||||
|
||||
@@ -57,8 +56,10 @@ registerSingleton(IBreadcrumbsService, BreadcrumbsService);
|
||||
export abstract class BreadcrumbsConfig<T> {
|
||||
|
||||
name: string;
|
||||
value: T;
|
||||
onDidChange: Event<T>;
|
||||
onDidChange: Event<void>;
|
||||
|
||||
abstract getValue(overrides?: IConfigurationOverrides): T;
|
||||
abstract updateValue(value: T, overrides?: IConfigurationOverrides): Thenable<void>;
|
||||
abstract dispose(): void;
|
||||
|
||||
private constructor() {
|
||||
@@ -69,30 +70,31 @@ export abstract class BreadcrumbsConfig<T> {
|
||||
static UseQuickPick = BreadcrumbsConfig._stub<boolean>('breadcrumbs.useQuickPick');
|
||||
static FilePath = BreadcrumbsConfig._stub<'on' | 'off' | 'last'>('breadcrumbs.filePath');
|
||||
static SymbolPath = BreadcrumbsConfig._stub<'on' | 'off' | 'last'>('breadcrumbs.symbolPath');
|
||||
static SymbolSortOrder = BreadcrumbsConfig._stub<'position' | 'name' | 'type'>('breadcrumbs.symbolSortOrder');
|
||||
static FilterOnType = BreadcrumbsConfig._stub<boolean>('breadcrumbs.filterOnType');
|
||||
|
||||
static FileExcludes = BreadcrumbsConfig._stub<glob.IExpression>('files.exclude');
|
||||
|
||||
private static _stub<T>(name: string): { bindTo(service: IConfigurationService): BreadcrumbsConfig<T> } {
|
||||
return {
|
||||
bindTo(service) {
|
||||
let value: T = service.getValue(name);
|
||||
let onDidChange = new Emitter<T>();
|
||||
let onDidChange = new Emitter<void>();
|
||||
|
||||
let listener = service.onDidChangeConfiguration(e => {
|
||||
if (e.affectsConfiguration(name)) {
|
||||
value = service.getValue(name);
|
||||
onDidChange.fire(value);
|
||||
onDidChange.fire(undefined);
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
name,
|
||||
get value() {
|
||||
return value;
|
||||
},
|
||||
set value(newValue: T) {
|
||||
service.updateValue(name, newValue);
|
||||
value = newValue;
|
||||
},
|
||||
onDidChange: onDidChange.event,
|
||||
return new class implements BreadcrumbsConfig<T>{
|
||||
readonly name = name;
|
||||
readonly onDidChange = onDidChange.event;
|
||||
getValue(overrides?: IConfigurationOverrides): T {
|
||||
return service.getValue(name, overrides);
|
||||
}
|
||||
updateValue(newValue: T, overrides?: IConfigurationOverrides): Thenable<void> {
|
||||
return service.updateValue(name, newValue, overrides);
|
||||
}
|
||||
dispose(): void {
|
||||
listener.dispose();
|
||||
onDidChange.dispose();
|
||||
@@ -141,6 +143,22 @@ Registry.as<IConfigurationRegistry>(Extensions.Configuration).registerConfigurat
|
||||
localize('symbolpath.last', "Only show the current symbol in the breadcrumbs view."),
|
||||
]
|
||||
},
|
||||
'breadcrumbs.symbolSortOrder': {
|
||||
description: localize('symbolSortOrder', "Controls how symbols are sorted in the breadcrumbs outline view."),
|
||||
type: 'string',
|
||||
default: 'position',
|
||||
enum: ['position', 'name', 'type'],
|
||||
enumDescriptions: [
|
||||
localize('symbolSortOrder.position', "Show symbol outline in file position order."),
|
||||
localize('symbolSortOrder.name', "Show symbol outline in alphabetical order."),
|
||||
localize('symbolSortOrder.type', "Show symbol outline in symbol type order."),
|
||||
]
|
||||
},
|
||||
// 'breadcrumbs.filterOnType': {
|
||||
// description: localize('filterOnType', "Controls whether the breadcrumb picker filters or highlights when typing."),
|
||||
// type: 'boolean',
|
||||
// default: false
|
||||
// },
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -3,43 +3,48 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import * as dom from 'vs/base/browser/dom';
|
||||
import { StandardMouseEvent } from 'vs/base/browser/mouseEvent';
|
||||
import { BreadcrumbsItem, BreadcrumbsWidget, IBreadcrumbsItemEvent } from 'vs/base/browser/ui/breadcrumbs/breadcrumbsWidget';
|
||||
import { IconLabel } from 'vs/base/browser/ui/iconLabel/iconLabel';
|
||||
import { tail } from 'vs/base/common/arrays';
|
||||
import { timeout } from 'vs/base/common/async';
|
||||
import { KeyCode, KeyMod } from 'vs/base/common/keyCodes';
|
||||
import { combinedDisposable, dispose, IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
import { isEqual } from 'vs/base/common/resources';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import 'vs/css!./media/breadcrumbscontrol';
|
||||
import { ICodeEditor, isCodeEditor } from 'vs/editor/browser/editorBrowser';
|
||||
import { ICodeEditor, isCodeEditor, isDiffEditor } from 'vs/editor/browser/editorBrowser';
|
||||
import { Range } from 'vs/editor/common/core/range';
|
||||
import { ICodeEditorViewState, ScrollType } from 'vs/editor/common/editorCommon';
|
||||
import { symbolKindToCssClass } from 'vs/editor/common/modes';
|
||||
import { OutlineElement, OutlineGroup, OutlineModel, TreeElement } from 'vs/editor/contrib/documentSymbols/outlineModel';
|
||||
import { localize } from 'vs/nls';
|
||||
import { MenuId, MenuRegistry } from 'vs/platform/actions/common/actions';
|
||||
import { CommandsRegistry } from 'vs/platform/commands/common/commands';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { ContextKeyExpr, IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { IContextViewService } from 'vs/platform/contextview/browser/contextView';
|
||||
import { FileKind, IFileService } from 'vs/platform/files/common/files';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { FileKind, IFileService, IFileStat } from 'vs/platform/files/common/files';
|
||||
import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
|
||||
import { IListService, WorkbenchListFocusContextKey } from 'vs/platform/list/browser/listService';
|
||||
import { IQuickOpenService } from 'vs/platform/quickOpen/common/quickOpen';
|
||||
import { ColorIdentifier, ColorFunction } from 'vs/platform/theme/common/colorRegistry';
|
||||
import { attachBreadcrumbsStyler } from 'vs/platform/theme/common/styler';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
|
||||
import { FileLabel } from 'vs/workbench/browser/labels';
|
||||
import { BreadcrumbsConfig, IBreadcrumbsService } from 'vs/workbench/browser/parts/editor/breadcrumbs';
|
||||
import { BreadcrumbElement, EditorBreadcrumbsModel, FileElement } from 'vs/workbench/browser/parts/editor/breadcrumbsModel';
|
||||
import { createBreadcrumbsPicker, BreadcrumbsPicker } from 'vs/workbench/browser/parts/editor/breadcrumbsPicker';
|
||||
import { EditorGroupView } from 'vs/workbench/browser/parts/editor/editorGroupView';
|
||||
import { IEditorService, SIDE_GROUP, SIDE_GROUP_TYPE, ACTIVE_GROUP_TYPE, ACTIVE_GROUP } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { BreadcrumbsPicker, createBreadcrumbsPicker } from 'vs/workbench/browser/parts/editor/breadcrumbsPicker';
|
||||
import { SideBySideEditorInput } from 'vs/workbench/common/editor';
|
||||
import { ACTIVE_GROUP, ACTIVE_GROUP_TYPE, IEditorService, SIDE_GROUP, SIDE_GROUP_TYPE } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { IEditorGroupsService } from 'vs/workbench/services/group/common/editorGroupsService';
|
||||
import { MenuRegistry, MenuId } from 'vs/platform/actions/common/actions';
|
||||
import { localize } from 'vs/nls';
|
||||
import { CommandsRegistry } from 'vs/platform/commands/common/commands';
|
||||
import { tail } from 'vs/base/common/arrays';
|
||||
import { WorkbenchListFocusContextKey } from 'vs/platform/list/browser/listService';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService';
|
||||
import { IEditorGroupView } from 'vs/workbench/browser/parts/editor/editor';
|
||||
|
||||
class Item extends BreadcrumbsItem {
|
||||
|
||||
@@ -116,20 +121,22 @@ export interface IBreadcrumbsControlOptions {
|
||||
showFileIcons: boolean;
|
||||
showSymbolIcons: boolean;
|
||||
showDecorationColors: boolean;
|
||||
extraClasses: string[];
|
||||
breadcrumbsBackground: ColorIdentifier | ColorFunction;
|
||||
}
|
||||
|
||||
export class BreadcrumbsControl {
|
||||
|
||||
static HEIGHT = 25;
|
||||
static HEIGHT = 22;
|
||||
|
||||
static readonly Payload_Reveal = {};
|
||||
static readonly Payload_RevealAside = {};
|
||||
static readonly Payload_Pick = {};
|
||||
|
||||
static CK_BreadcrumbsPossible = new RawContextKey('breadcrumbsPossible', false);
|
||||
static CK_BreadcrumbsVisible = new RawContextKey('breadcrumbsVisible', false);
|
||||
static CK_BreadcrumbsActive = new RawContextKey('breadcrumbsActive', false);
|
||||
|
||||
private readonly _ckBreadcrumbsPossible: IContextKey<boolean>;
|
||||
private readonly _ckBreadcrumbsVisible: IContextKey<boolean>;
|
||||
private readonly _ckBreadcrumbsActive: IContextKey<boolean>;
|
||||
|
||||
@@ -145,29 +152,31 @@ export class BreadcrumbsControl {
|
||||
constructor(
|
||||
container: HTMLElement,
|
||||
private readonly _options: IBreadcrumbsControlOptions,
|
||||
private readonly _editorGroup: EditorGroupView,
|
||||
private readonly _editorGroup: IEditorGroupView,
|
||||
@IContextKeyService private readonly _contextKeyService: IContextKeyService,
|
||||
@IContextViewService private readonly _contextViewService: IContextViewService,
|
||||
@IEditorService private readonly _editorService: IEditorService,
|
||||
@ICodeEditorService private readonly _codeEditorService: ICodeEditorService,
|
||||
@IWorkspaceContextService private readonly _workspaceService: IWorkspaceContextService,
|
||||
@IInstantiationService private readonly _instantiationService: IInstantiationService,
|
||||
@IThemeService private readonly _themeService: IThemeService,
|
||||
@IQuickOpenService private readonly _quickOpenService: IQuickOpenService,
|
||||
@IConfigurationService private readonly _configurationService: IConfigurationService,
|
||||
@IFileService private readonly _fileService: IFileService,
|
||||
@ITelemetryService private readonly _telemetryService: ITelemetryService,
|
||||
@IBreadcrumbsService breadcrumbsService: IBreadcrumbsService,
|
||||
) {
|
||||
this.domNode = document.createElement('div');
|
||||
dom.addClass(this.domNode, 'breadcrumbs-control');
|
||||
dom.addClasses(this.domNode, ..._options.extraClasses);
|
||||
dom.append(container, this.domNode);
|
||||
|
||||
this._widget = new BreadcrumbsWidget(this.domNode);
|
||||
this._widget.onDidSelectItem(this._onSelectEvent, this, this._disposables);
|
||||
this._widget.onDidFocusItem(this._onFocusEvent, this, this._disposables);
|
||||
this._widget.onDidChangeFocus(this._updateCkBreadcrumbsActive, this, this._disposables);
|
||||
this._disposables.push(attachBreadcrumbsStyler(this._widget, this._themeService));
|
||||
this._disposables.push(attachBreadcrumbsStyler(this._widget, this._themeService, { breadcrumbsBackground: _options.breadcrumbsBackground }));
|
||||
|
||||
this._ckBreadcrumbsPossible = BreadcrumbsControl.CK_BreadcrumbsPossible.bindTo(this._contextKeyService);
|
||||
this._ckBreadcrumbsVisible = BreadcrumbsControl.CK_BreadcrumbsVisible.bindTo(this._contextKeyService);
|
||||
this._ckBreadcrumbsActive = BreadcrumbsControl.CK_BreadcrumbsActive.bindTo(this._contextKeyService);
|
||||
|
||||
@@ -179,6 +188,7 @@ export class BreadcrumbsControl {
|
||||
dispose(): void {
|
||||
this._disposables = dispose(this._disposables);
|
||||
this._breadcrumbsDisposables = dispose(this._breadcrumbsDisposables);
|
||||
this._ckBreadcrumbsPossible.reset();
|
||||
this._ckBreadcrumbsVisible.reset();
|
||||
this._ckBreadcrumbsActive.reset();
|
||||
this._cfUseQuickPick.dispose();
|
||||
@@ -201,12 +211,18 @@ export class BreadcrumbsControl {
|
||||
}
|
||||
|
||||
update(): boolean {
|
||||
const input = this._editorGroup.activeEditor;
|
||||
this._breadcrumbsDisposables = dispose(this._breadcrumbsDisposables);
|
||||
|
||||
// honor diff editors and such
|
||||
let input = this._editorGroup.activeEditor;
|
||||
if (input instanceof SideBySideEditorInput) {
|
||||
input = input.master;
|
||||
}
|
||||
|
||||
if (!input || !input.getResource() || (input.getResource().scheme !== Schemas.untitled && !this._fileService.canHandleResource(input.getResource()))) {
|
||||
// cleanup and return when there is no input or when
|
||||
// we cannot handle this input
|
||||
this._ckBreadcrumbsPossible.set(false);
|
||||
if (!this.isHidden()) {
|
||||
this.hide();
|
||||
return true;
|
||||
@@ -217,9 +233,10 @@ export class BreadcrumbsControl {
|
||||
|
||||
dom.toggleClass(this.domNode, 'hidden', false);
|
||||
this._ckBreadcrumbsVisible.set(true);
|
||||
this._ckBreadcrumbsPossible.set(true);
|
||||
|
||||
let control = this._editorGroup.activeControl.getControl() as ICodeEditor;
|
||||
let model = new EditorBreadcrumbsModel(input.getResource(), isCodeEditor(control) ? control : undefined, this._workspaceService, this._configurationService);
|
||||
let editor = this._getActiveCodeEditor();
|
||||
let model = new EditorBreadcrumbsModel(input.getResource(), editor, this._workspaceService, this._configurationService);
|
||||
dom.toggleClass(this.domNode, 'relative-path', model.isRelative());
|
||||
|
||||
let updateBreadcrumbs = () => {
|
||||
@@ -243,6 +260,17 @@ export class BreadcrumbsControl {
|
||||
return true;
|
||||
}
|
||||
|
||||
private _getActiveCodeEditor(): ICodeEditor {
|
||||
let control = this._editorGroup.activeControl.getControl();
|
||||
let editor: ICodeEditor;
|
||||
if (isCodeEditor(control)) {
|
||||
editor = control as ICodeEditor;
|
||||
} else if (isDiffEditor(control)) {
|
||||
editor = control.getModifiedEditor();
|
||||
}
|
||||
return editor;
|
||||
}
|
||||
|
||||
private _onFocusEvent(event: IBreadcrumbsItemEvent): void {
|
||||
if (event.item && this._breadcrumbsPickerShowing) {
|
||||
return this._widget.setSelection(event.item);
|
||||
@@ -254,8 +282,15 @@ export class BreadcrumbsControl {
|
||||
return;
|
||||
}
|
||||
|
||||
this._editorGroup.focus();
|
||||
const { element } = event.item as Item;
|
||||
this._editorGroup.focus();
|
||||
|
||||
/* __GDPR__
|
||||
"breadcrumbs/select" : {
|
||||
"type": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
|
||||
}
|
||||
*/
|
||||
this._telemetryService.publicLog('breadcrumbs/select', { type: element instanceof TreeElement ? 'symbol' : 'file' });
|
||||
|
||||
const group = this._getEditorGroup(event.payload);
|
||||
if (group !== undefined) {
|
||||
@@ -266,7 +301,7 @@ export class BreadcrumbsControl {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._cfUseQuickPick.value) {
|
||||
if (this._cfUseQuickPick.getValue()) {
|
||||
// using quick pick
|
||||
this._widget.setFocused(undefined);
|
||||
this._widget.setSelection(undefined);
|
||||
@@ -276,41 +311,86 @@ export class BreadcrumbsControl {
|
||||
|
||||
// show picker
|
||||
let picker: BreadcrumbsPicker;
|
||||
let editor = this._getActiveCodeEditor();
|
||||
let editorDecorations: string[] = [];
|
||||
let editorViewState: ICodeEditorViewState;
|
||||
|
||||
this._contextViewService.showContextView({
|
||||
render: (parent: HTMLElement) => {
|
||||
picker = createBreadcrumbsPicker(this._instantiationService, parent, element);
|
||||
let listener = picker.onDidPickElement(data => {
|
||||
let selectListener = picker.onDidPickElement(data => {
|
||||
if (data.target) {
|
||||
editorViewState = undefined;
|
||||
}
|
||||
this._contextViewService.hideContextView(this);
|
||||
this._revealInEditor(event, data.target, this._getEditorGroup(data.payload && data.payload.originalEvent));
|
||||
this._revealInEditor(event, data.target, this._getEditorGroup(data.payload && data.payload.originalEvent), (data.payload && data.payload.originalEvent && data.payload.originalEvent.middleButton));
|
||||
/* __GDPR__
|
||||
"breadcrumbs/open" : {
|
||||
"type": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
|
||||
}
|
||||
*/
|
||||
this._telemetryService.publicLog('breadcrumbs/open', { type: !data ? 'nothing' : data.target instanceof TreeElement ? 'symbol' : 'file' });
|
||||
});
|
||||
let focusListener = picker.onDidFocusElement(data => {
|
||||
if (!editor || !(data.target instanceof OutlineElement)) {
|
||||
return;
|
||||
}
|
||||
if (!editorViewState) {
|
||||
editorViewState = editor.saveViewState();
|
||||
}
|
||||
const { symbol } = data.target;
|
||||
editor.revealRangeInCenter(symbol.range, ScrollType.Smooth);
|
||||
editorDecorations = editor.deltaDecorations(editorDecorations, [{
|
||||
range: symbol.range,
|
||||
options: {
|
||||
className: 'rangeHighlight',
|
||||
isWholeLine: true
|
||||
}
|
||||
}]);
|
||||
|
||||
});
|
||||
this._breadcrumbsPickerShowing = true;
|
||||
this._updateCkBreadcrumbsActive();
|
||||
|
||||
return combinedDisposable([listener, picker]);
|
||||
return combinedDisposable([selectListener, focusListener, picker]);
|
||||
},
|
||||
getAnchor() {
|
||||
getAnchor: () => {
|
||||
let maxInnerWidth = window.innerWidth - 8 /*a little less the full widget*/;
|
||||
let maxHeight = Math.min(window.innerHeight * .7, 300);
|
||||
|
||||
let pickerHeight = 330;
|
||||
let pickerWidth = Math.max(220, dom.getTotalWidth(event.node));
|
||||
let pickerWidth = Math.min(maxInnerWidth, Math.max(240, maxInnerWidth / 4.17));
|
||||
let pickerArrowSize = 8;
|
||||
let pickerArrowOffset: number;
|
||||
|
||||
let data = dom.getDomNodePagePosition(event.node.firstChild as HTMLElement);
|
||||
let y = data.top + data.height - pickerArrowSize;
|
||||
let y = data.top + data.height + pickerArrowSize;
|
||||
if (y + maxHeight >= window.innerHeight) {
|
||||
maxHeight = window.innerHeight - y - 30 /* room for shadow and status bar*/;
|
||||
}
|
||||
let x = data.left;
|
||||
if (x + pickerWidth >= window.innerWidth) {
|
||||
x = window.innerWidth - pickerWidth;
|
||||
if (x + pickerWidth >= maxInnerWidth) {
|
||||
x = maxInnerWidth - pickerWidth;
|
||||
}
|
||||
if (event.payload instanceof StandardMouseEvent) {
|
||||
pickerArrowOffset = event.payload.posx - x - pickerArrowSize;
|
||||
let maxPickerArrowOffset = pickerWidth - 2 * pickerArrowSize;
|
||||
pickerArrowOffset = event.payload.posx - x;
|
||||
if (pickerArrowOffset > maxPickerArrowOffset) {
|
||||
x = Math.min(maxInnerWidth - pickerWidth, x + pickerArrowOffset - maxPickerArrowOffset);
|
||||
pickerArrowOffset = maxPickerArrowOffset;
|
||||
}
|
||||
} else {
|
||||
pickerArrowOffset = (data.left + (data.width * .3)) - x;
|
||||
}
|
||||
picker.layout(pickerHeight, pickerWidth, pickerArrowSize, Math.max(0, pickerArrowOffset));
|
||||
picker.setInput(element);
|
||||
picker.setInput(element, maxHeight, pickerWidth, pickerArrowSize, Math.max(0, pickerArrowOffset));
|
||||
return { x, y };
|
||||
},
|
||||
onHide: (data) => {
|
||||
if (editor) {
|
||||
editor.deltaDecorations(editorDecorations, []);
|
||||
if (editorViewState) {
|
||||
editor.restoreViewState(editorViewState);
|
||||
}
|
||||
}
|
||||
this._breadcrumbsPickerShowing = false;
|
||||
this._updateCkBreadcrumbsActive();
|
||||
if (data === this) {
|
||||
@@ -326,11 +406,11 @@ export class BreadcrumbsControl {
|
||||
this._ckBreadcrumbsActive.set(value);
|
||||
}
|
||||
|
||||
private _revealInEditor(event: IBreadcrumbsItemEvent, element: any, group: SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE): void {
|
||||
private _revealInEditor(event: IBreadcrumbsItemEvent, element: any, group: SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE, pinned: boolean = false): void {
|
||||
if (element instanceof FileElement) {
|
||||
if (element.kind === FileKind.FILE) {
|
||||
// open file in editor
|
||||
this._editorService.openEditor({ resource: element.uri }, group);
|
||||
// open file in any editor
|
||||
this._editorService.openEditor({ resource: element.uri, options: { pinned: pinned } }, group);
|
||||
} else {
|
||||
// show next picker
|
||||
let items = this._widget.getItems();
|
||||
@@ -340,12 +420,15 @@ export class BreadcrumbsControl {
|
||||
}
|
||||
|
||||
} else if (element instanceof OutlineElement) {
|
||||
// open symbol in editor
|
||||
// open symbol in code editor
|
||||
let model = OutlineModel.get(element);
|
||||
this._editorService.openEditor({
|
||||
this._codeEditorService.openCodeEditor({
|
||||
resource: model.textModel.uri,
|
||||
options: { selection: Range.collapseToStart(element.symbol.selectionRange) }
|
||||
}, group);
|
||||
options: {
|
||||
selection: Range.collapseToStart(element.symbol.selectionRange),
|
||||
revealInCenterIfOutsideViewport: true
|
||||
}
|
||||
}, this._getActiveCodeEditor(), group === SIDE_GROUP);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -362,16 +445,12 @@ export class BreadcrumbsControl {
|
||||
|
||||
//#region commands
|
||||
|
||||
MenuRegistry.appendMenuItem(MenuId.CommandPalette, {
|
||||
command: {
|
||||
id: 'breadcrumbs.focusAndSelect',
|
||||
title: localize('cmd.focus', "Focus Breadcrumbs")
|
||||
}
|
||||
});
|
||||
// toggle command
|
||||
MenuRegistry.appendMenuItem(MenuId.CommandPalette, {
|
||||
command: {
|
||||
id: 'breadcrumbs.toggle',
|
||||
title: localize('cmd.toggle', "Toggle Breadcrumbs")
|
||||
title: { value: localize('cmd.toggle', "Toggle Breadcrumbs"), original: 'Toggle Breadcrumbs' },
|
||||
category: localize('cmd.category', "View")
|
||||
}
|
||||
});
|
||||
MenuRegistry.appendMenuItem(MenuId.MenubarViewMenu, {
|
||||
@@ -379,42 +458,73 @@ MenuRegistry.appendMenuItem(MenuId.MenubarViewMenu, {
|
||||
order: 99,
|
||||
command: {
|
||||
id: 'breadcrumbs.toggle',
|
||||
title: localize('cmd.toggle', "Toggle Breadcrumbs")
|
||||
title: localize('miToggleBreadcrumbs', "Toggle &&Breadcrumbs"),
|
||||
toggled: ContextKeyExpr.equals('config.breadcrumbs.enabled', true)
|
||||
}
|
||||
});
|
||||
CommandsRegistry.registerCommand('breadcrumbs.toggle', accessor => {
|
||||
let config = accessor.get(IConfigurationService);
|
||||
let value = BreadcrumbsConfig.IsEnabled.bindTo(config).value;
|
||||
BreadcrumbsConfig.IsEnabled.bindTo(config).value = !value;
|
||||
let value = BreadcrumbsConfig.IsEnabled.bindTo(config).getValue();
|
||||
BreadcrumbsConfig.IsEnabled.bindTo(config).updateValue(!value);
|
||||
});
|
||||
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
id: 'breadcrumbs.focus',
|
||||
weight: KeybindingWeight.WorkbenchContrib,
|
||||
primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.US_SEMICOLON,
|
||||
when: BreadcrumbsControl.CK_BreadcrumbsVisible,
|
||||
handler(accessor) {
|
||||
const groups = accessor.get(IEditorGroupsService);
|
||||
const breadcrumbs = accessor.get(IBreadcrumbsService);
|
||||
const widget = breadcrumbs.getWidget(groups.activeGroup.id);
|
||||
// focus/focus-and-select
|
||||
function focusAndSelectHandler(accessor: ServicesAccessor, select: boolean): void {
|
||||
// find widget and focus/select
|
||||
const groups = accessor.get(IEditorGroupsService);
|
||||
const breadcrumbs = accessor.get(IBreadcrumbsService);
|
||||
const widget = breadcrumbs.getWidget(groups.activeGroup.id);
|
||||
if (widget) {
|
||||
const item = tail(widget.getItems());
|
||||
widget.setFocused(item);
|
||||
if (select) {
|
||||
widget.setSelection(item, BreadcrumbsControl.Payload_Pick);
|
||||
}
|
||||
}
|
||||
}
|
||||
MenuRegistry.appendMenuItem(MenuId.CommandPalette, {
|
||||
command: {
|
||||
id: 'breadcrumbs.focusAndSelect',
|
||||
title: { value: localize('cmd.focus', "Focus Breadcrumbs"), original: 'Focus Breadcrumbs' },
|
||||
precondition: BreadcrumbsControl.CK_BreadcrumbsVisible
|
||||
}
|
||||
});
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
id: 'breadcrumbs.focusAndSelect',
|
||||
weight: KeybindingWeight.WorkbenchContrib,
|
||||
primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.US_DOT,
|
||||
when: BreadcrumbsControl.CK_BreadcrumbsVisible,
|
||||
handler(accessor) {
|
||||
const groups = accessor.get(IEditorGroupsService);
|
||||
const breadcrumbs = accessor.get(IBreadcrumbsService);
|
||||
const widget = breadcrumbs.getWidget(groups.activeGroup.id);
|
||||
const item = tail(widget.getItems());
|
||||
widget.setFocused(item);
|
||||
widget.setSelection(item, BreadcrumbsControl.Payload_Pick);
|
||||
when: BreadcrumbsControl.CK_BreadcrumbsPossible,
|
||||
handler: accessor => focusAndSelectHandler(accessor, true)
|
||||
});
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
id: 'breadcrumbs.focus',
|
||||
weight: KeybindingWeight.WorkbenchContrib,
|
||||
primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.US_SEMICOLON,
|
||||
when: BreadcrumbsControl.CK_BreadcrumbsPossible,
|
||||
handler: accessor => focusAndSelectHandler(accessor, false)
|
||||
});
|
||||
|
||||
// this commands is only enabled when breadcrumbs are
|
||||
// disabled which it then enables and focuses
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
id: 'breadcrumbs.toggleToOn',
|
||||
weight: KeybindingWeight.WorkbenchContrib,
|
||||
primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.US_DOT,
|
||||
when: ContextKeyExpr.not('config.breadcrumbs.enabled'),
|
||||
handler: async accessor => {
|
||||
const instant = accessor.get(IInstantiationService);
|
||||
const config = accessor.get(IConfigurationService);
|
||||
// check if enabled and iff not enable
|
||||
const isEnabled = BreadcrumbsConfig.IsEnabled.bindTo(config);
|
||||
if (!isEnabled.getValue()) {
|
||||
await isEnabled.updateValue(true);
|
||||
await timeout(50); // hacky - the widget might not be ready yet...
|
||||
}
|
||||
return instant.invokeFunction(focusAndSelectHandler, true);
|
||||
}
|
||||
});
|
||||
|
||||
// navigation
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
id: 'breadcrumbs.focusNext',
|
||||
weight: KeybindingWeight.WorkbenchContrib,
|
||||
@@ -492,10 +602,26 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
primary: KeyMod.CtrlCmd | KeyCode.Enter,
|
||||
when: ContextKeyExpr.and(BreadcrumbsControl.CK_BreadcrumbsVisible, BreadcrumbsControl.CK_BreadcrumbsActive, WorkbenchListFocusContextKey),
|
||||
handler(accessor) {
|
||||
const groups = accessor.get(IEditorGroupsService);
|
||||
const breadcrumbs = accessor.get(IBreadcrumbsService);
|
||||
const widget = breadcrumbs.getWidget(groups.activeGroup.id);
|
||||
widget.setSelection(widget.getFocused(), BreadcrumbsControl.Payload_RevealAside);
|
||||
const editors = accessor.get(IEditorService);
|
||||
const lists = accessor.get(IListService);
|
||||
const element = <OutlineElement | IFileStat>lists.lastFocusedList.getFocus();
|
||||
if (element instanceof OutlineElement) {
|
||||
// open symbol in editor
|
||||
return editors.openEditor({
|
||||
resource: OutlineModel.get(element).textModel.uri,
|
||||
options: { selection: Range.collapseToStart(element.symbol.selectionRange) }
|
||||
}, SIDE_GROUP);
|
||||
|
||||
} else if (URI.isUri(element.resource)) {
|
||||
// open file in editor
|
||||
return editors.openEditor({
|
||||
resource: element.resource,
|
||||
}, SIDE_GROUP);
|
||||
|
||||
} else {
|
||||
// ignore
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
});
|
||||
//#endregion
|
||||
|
||||
@@ -3,8 +3,6 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import { equals } from 'vs/base/common/arrays';
|
||||
import { TimeoutTimer } from 'vs/base/common/async';
|
||||
import { CancellationTokenSource } from 'vs/base/common/cancellation';
|
||||
@@ -12,9 +10,8 @@ import { size } from 'vs/base/common/collections';
|
||||
import { onUnexpectedError } from 'vs/base/common/errors';
|
||||
import { debounceEvent, Emitter, Event } from 'vs/base/common/event';
|
||||
import { dispose, IDisposable } from 'vs/base/common/lifecycle';
|
||||
import * as paths from 'vs/base/common/paths';
|
||||
import { isEqual } from 'vs/base/common/resources';
|
||||
import URI from 'vs/base/common/uri';
|
||||
import { isEqual, dirname } from 'vs/base/common/resources';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
|
||||
import { IPosition } from 'vs/editor/common/core/position';
|
||||
import { DocumentSymbolProviderRegistry } from 'vs/editor/common/modes';
|
||||
@@ -82,16 +79,16 @@ export class EditorBreadcrumbsModel {
|
||||
let result: BreadcrumbElement[] = [];
|
||||
|
||||
// file path elements
|
||||
if (this._cfgFilePath.value === 'on') {
|
||||
if (this._cfgFilePath.getValue() === 'on') {
|
||||
result = result.concat(this._fileInfo.path);
|
||||
} else if (this._cfgFilePath.value === 'last' && this._fileInfo.path.length > 0) {
|
||||
} else if (this._cfgFilePath.getValue() === 'last' && this._fileInfo.path.length > 0) {
|
||||
result = result.concat(this._fileInfo.path.slice(-1));
|
||||
}
|
||||
|
||||
// symbol path elements
|
||||
if (this._cfgSymbolPath.value === 'on') {
|
||||
if (this._cfgSymbolPath.getValue() === 'on') {
|
||||
result = result.concat(this._outlineElements);
|
||||
} else if (this._cfgSymbolPath.value === 'last' && this._outlineElements.length > 0) {
|
||||
} else if (this._cfgSymbolPath.getValue() === 'last' && this._outlineElements.length > 0) {
|
||||
result = result.concat(this._outlineElements.slice(-1));
|
||||
}
|
||||
|
||||
@@ -117,7 +114,7 @@ export class EditorBreadcrumbsModel {
|
||||
break;
|
||||
}
|
||||
info.path.unshift(new FileElement(uri, info.path.length === 0 ? FileKind.FILE : FileKind.FOLDER));
|
||||
uri = uri.with({ path: paths.dirname(uri.path) });
|
||||
uri = dirname(uri);
|
||||
}
|
||||
|
||||
if (info.folder && workspaceService.getWorkbenchState() === WorkbenchState.WORKSPACE) {
|
||||
@@ -177,7 +174,7 @@ export class EditorBreadcrumbsModel {
|
||||
this._updateOutlineElements(this._getOutlineElements(model, this._editor.getPosition()));
|
||||
this._outlineDisposables.push(this._editor.onDidChangeCursorPosition(_ => {
|
||||
timeout.cancelAndSet(() => {
|
||||
if (!buffer.isDisposed() && versionIdThen === buffer.getVersionId()) {
|
||||
if (!buffer.isDisposed() && versionIdThen === buffer.getVersionId() && this._editor.getModel()) {
|
||||
this._updateOutlineElements(this._getOutlineElements(model, this._editor.getPosition()));
|
||||
}
|
||||
}, 150);
|
||||
|
||||
@@ -3,37 +3,47 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import { onDidChangeZoomLevel } from 'vs/base/browser/browser';
|
||||
import * as dom from 'vs/base/browser/dom';
|
||||
import { compareFileNames } from 'vs/base/common/comparers';
|
||||
import { onUnexpectedError } from 'vs/base/common/errors';
|
||||
import { Emitter, Event } from 'vs/base/common/event';
|
||||
import { createMatches, FuzzyScore, fuzzyScore } from 'vs/base/common/filters';
|
||||
import * as glob from 'vs/base/common/glob';
|
||||
import { dispose, IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { dirname, isEqual } from 'vs/base/common/resources';
|
||||
import URI from 'vs/base/common/uri';
|
||||
import { join } from 'vs/base/common/paths';
|
||||
import { basename, dirname, isEqual } from 'vs/base/common/resources';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { IDataSource, IRenderer, ISelectionEvent, ISorter, ITree } from 'vs/base/parts/tree/browser/tree';
|
||||
import { IDataSource, IFilter, IRenderer, ISorter, ITree } from 'vs/base/parts/tree/browser/tree';
|
||||
import 'vs/css!./media/breadcrumbscontrol';
|
||||
import { OutlineElement, OutlineModel, TreeElement } from 'vs/editor/contrib/documentSymbols/outlineModel';
|
||||
import { OutlineDataSource, OutlineItemComparator, OutlineRenderer } from 'vs/editor/contrib/documentSymbols/outlineTree';
|
||||
import { OutlineDataSource, OutlineItemComparator, OutlineRenderer, OutlineItemCompareType } from 'vs/editor/contrib/documentSymbols/outlineTree';
|
||||
import { localize } from 'vs/nls';
|
||||
import { FileKind, IFileService, IFileStat } from 'vs/platform/files/common/files';
|
||||
import { IInstantiationService, IConstructorSignature1 } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { HighlightingWorkbenchTree, IHighlightingTreeConfiguration, IHighlightingRenderer } from 'vs/platform/list/browser/listService';
|
||||
import { IThemeService, DARK } from 'vs/platform/theme/common/themeService';
|
||||
import { FileLabel } from 'vs/workbench/browser/labels';
|
||||
import { BreadcrumbElement, FileElement } from 'vs/workbench/browser/parts/editor/breadcrumbsModel';
|
||||
import { onUnexpectedError } from 'vs/base/common/errors';
|
||||
import { breadcrumbsPickerBackground } from 'vs/platform/theme/common/colorRegistry';
|
||||
import { FuzzyScore, createMatches, fuzzyScore } from 'vs/base/common/filters';
|
||||
import { IWorkspaceContextService, IWorkspace, IWorkspaceFolder } from 'vs/platform/workspace/common/workspace';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { FileKind, IFileService, IFileStat } from 'vs/platform/files/common/files';
|
||||
import { IConstructorSignature1, IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { HighlightingWorkbenchTree, IHighlighter, IHighlightingTreeConfiguration, IHighlightingTreeOptions } from 'vs/platform/list/browser/listService';
|
||||
import { breadcrumbsPickerBackground, widgetShadow } from 'vs/platform/theme/common/colorRegistry';
|
||||
import { IWorkspace, IWorkspaceContextService, IWorkspaceFolder } from 'vs/platform/workspace/common/workspace';
|
||||
import { FileLabel } from 'vs/workbench/browser/labels';
|
||||
import { BreadcrumbsConfig } from 'vs/workbench/browser/parts/editor/breadcrumbs';
|
||||
import { BreadcrumbElement, FileElement } from 'vs/workbench/browser/parts/editor/breadcrumbsModel';
|
||||
import { IFileIconTheme, IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService';
|
||||
|
||||
export function createBreadcrumbsPicker(instantiationService: IInstantiationService, parent: HTMLElement, element: BreadcrumbElement): BreadcrumbsPicker {
|
||||
let ctor: IConstructorSignature1<HTMLElement, BreadcrumbsPicker> = element instanceof FileElement ? BreadcrumbsFilePicker : BreadcrumbsOutlinePicker;
|
||||
return instantiationService.createInstance(ctor, parent);
|
||||
}
|
||||
|
||||
interface ILayoutInfo {
|
||||
maxHeight: number;
|
||||
width: number;
|
||||
arrowSize: number;
|
||||
arrowOffset: number;
|
||||
inputHeight: number;
|
||||
}
|
||||
|
||||
export abstract class BreadcrumbsPicker {
|
||||
|
||||
protected readonly _disposables = new Array<IDisposable>();
|
||||
@@ -42,14 +52,20 @@ export abstract class BreadcrumbsPicker {
|
||||
protected readonly _treeContainer: HTMLDivElement;
|
||||
protected readonly _tree: HighlightingWorkbenchTree;
|
||||
protected readonly _focus: dom.IFocusTracker;
|
||||
protected readonly _symbolSortOrder: BreadcrumbsConfig<'position' | 'name' | 'type'>;
|
||||
private _layoutInfo: ILayoutInfo;
|
||||
|
||||
private readonly _onDidPickElement = new Emitter<{ target: any, payload: any }>();
|
||||
readonly onDidPickElement: Event<{ target: any, payload: any }> = this._onDidPickElement.event;
|
||||
|
||||
private readonly _onDidFocusElement = new Emitter<{ target: any, payload: any }>();
|
||||
readonly onDidFocusElement: Event<{ target: any, payload: any }> = this._onDidFocusElement.event;
|
||||
|
||||
constructor(
|
||||
parent: HTMLElement,
|
||||
@IInstantiationService protected readonly _instantiationService: IInstantiationService,
|
||||
@IThemeService protected readonly _themeService: IThemeService,
|
||||
@IWorkbenchThemeService protected readonly _themeService: IWorkbenchThemeService,
|
||||
@IConfigurationService private readonly _configurationService: IConfigurationService,
|
||||
) {
|
||||
this._domNode = document.createElement('div');
|
||||
this._domNode.className = 'monaco-breadcrumbs-picker show-file-icons';
|
||||
@@ -57,42 +73,71 @@ export abstract class BreadcrumbsPicker {
|
||||
|
||||
this._focus = dom.trackFocus(this._domNode);
|
||||
this._focus.onDidBlur(_ => this._onDidPickElement.fire({ target: undefined, payload: undefined }), undefined, this._disposables);
|
||||
this._disposables.push(onDidChangeZoomLevel(_ => this._onDidPickElement.fire({ target: undefined, payload: undefined })));
|
||||
|
||||
const theme = this._themeService.getTheme();
|
||||
const color = theme.getColor(breadcrumbsPickerBackground);
|
||||
|
||||
this._arrow = document.createElement('div');
|
||||
this._arrow.style.width = '0';
|
||||
this._arrow.style.borderStyle = 'solid';
|
||||
this._arrow.style.borderWidth = '8px';
|
||||
this._arrow.className = 'arrow';
|
||||
this._arrow.style.borderColor = `transparent transparent ${color.toString()}`;
|
||||
this._domNode.appendChild(this._arrow);
|
||||
|
||||
this._treeContainer = document.createElement('div');
|
||||
this._treeContainer.style.background = color.toString();
|
||||
this._treeContainer.style.paddingTop = '2px';
|
||||
this._treeContainer.style.boxShadow = `0px 5px 8px ${(theme.type === DARK ? color.darken(.6) : color.darken(.2))}`;
|
||||
this._treeContainer.style.boxShadow = `0px 5px 8px ${this._themeService.getTheme().getColor(widgetShadow)}`;
|
||||
this._domNode.appendChild(this._treeContainer);
|
||||
|
||||
const treeConifg = this._completeTreeConfiguration({ dataSource: undefined, renderer: undefined });
|
||||
this._symbolSortOrder = BreadcrumbsConfig.SymbolSortOrder.bindTo(this._configurationService);
|
||||
|
||||
const filterConfig = BreadcrumbsConfig.FilterOnType.bindTo(this._configurationService);
|
||||
this._disposables.push(filterConfig);
|
||||
|
||||
const treeConfig = this._completeTreeConfiguration({ dataSource: undefined, renderer: undefined, highlighter: undefined });
|
||||
this._tree = this._instantiationService.createInstance(
|
||||
HighlightingWorkbenchTree,
|
||||
this._treeContainer,
|
||||
treeConifg,
|
||||
{ useShadows: false },
|
||||
treeConfig,
|
||||
<IHighlightingTreeOptions>{ useShadows: false, filterOnType: filterConfig.getValue(), showTwistie: false, twistiePixels: 12 },
|
||||
{ placeholder: localize('placeholder', "Find") }
|
||||
);
|
||||
this._disposables.push(this._tree.onDidChangeSelection(e => {
|
||||
if (e.payload !== this._tree) {
|
||||
const target = this._getTargetFromSelectionEvent(e);
|
||||
if (!target) {
|
||||
return;
|
||||
const target = this._getTargetFromEvent(e.selection[0], e.payload);
|
||||
if (target) {
|
||||
setTimeout(_ => {// need to debounce here because this disposes the tree and the tree doesn't like to be disposed on click
|
||||
this._onDidPickElement.fire({ target, payload: e.payload });
|
||||
}, 0);
|
||||
}
|
||||
setTimeout(_ => {// need to debounce here because this disposes the tree and the tree doesn't like to be disposed on click
|
||||
this._onDidPickElement.fire({ target, payload: e.payload });
|
||||
}, 0);
|
||||
}
|
||||
}));
|
||||
this._disposables.push(this._tree.onDidChangeFocus(e => {
|
||||
const target = this._getTargetFromEvent(e.focus, e.payload);
|
||||
if (target) {
|
||||
this._onDidFocusElement.fire({ target, payload: e.payload });
|
||||
}
|
||||
}));
|
||||
this._disposables.push(this._tree.onDidStartFiltering(() => {
|
||||
this._layoutInfo.inputHeight = 36;
|
||||
this._layout();
|
||||
}));
|
||||
this._disposables.push(this._tree.onDidExpandItem(() => {
|
||||
this._layout();
|
||||
}));
|
||||
this._disposables.push(this._tree.onDidCollapseItem(() => {
|
||||
this._layout();
|
||||
}));
|
||||
|
||||
// tree icon theme specials
|
||||
dom.addClass(this._treeContainer, 'file-icon-themable-tree');
|
||||
dom.addClass(this._treeContainer, 'show-file-icons');
|
||||
const onFileIconThemeChange = (fileIconTheme: IFileIconTheme) => {
|
||||
dom.toggleClass(this._treeContainer, 'align-icons-and-twisties', fileIconTheme.hasFileIcons && !fileIconTheme.hasFolderIcons);
|
||||
dom.toggleClass(this._treeContainer, 'hide-arrows', fileIconTheme.hidesExplorerArrows === true);
|
||||
};
|
||||
this._disposables.push(_themeService.onDidFileIconThemeChange(onFileIconThemeChange));
|
||||
onFileIconThemeChange(_themeService.getFileIconTheme());
|
||||
|
||||
this._domNode.focus();
|
||||
}
|
||||
@@ -102,14 +147,20 @@ export abstract class BreadcrumbsPicker {
|
||||
this._onDidPickElement.dispose();
|
||||
this._tree.dispose();
|
||||
this._focus.dispose();
|
||||
this._symbolSortOrder.dispose();
|
||||
}
|
||||
|
||||
setInput(input: any): void {
|
||||
setInput(input: any, maxHeight: number, width: number, arrowSize: number, arrowOffset: number): void {
|
||||
let actualInput = this._getInput(input);
|
||||
this._tree.setInput(actualInput).then(() => {
|
||||
|
||||
this._layoutInfo = { maxHeight, width, arrowSize, arrowOffset, inputHeight: 0 };
|
||||
this._layout();
|
||||
|
||||
// use proper selection, reveal
|
||||
let selection = this._getInitialSelection(this._tree, input);
|
||||
if (selection) {
|
||||
this._tree.reveal(selection, .5).then(() => {
|
||||
return this._tree.reveal(selection, .5).then(() => {
|
||||
this._tree.setSelection([selection], this._tree);
|
||||
this._tree.setFocus(selection);
|
||||
this._tree.domFocus();
|
||||
@@ -118,25 +169,37 @@ export abstract class BreadcrumbsPicker {
|
||||
this._tree.focusFirst();
|
||||
this._tree.setSelection([this._tree.getFocus()], this._tree);
|
||||
this._tree.domFocus();
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
}, onUnexpectedError);
|
||||
}
|
||||
|
||||
layout(height: number, width: number, arrowSize: number, arrowOffset: number) {
|
||||
this._domNode.style.height = `${height}px`;
|
||||
this._domNode.style.width = `${width}px`;
|
||||
this._arrow.style.borderWidth = `${arrowSize}px`;
|
||||
this._arrow.style.marginLeft = `${arrowOffset}px`;
|
||||
private _layout(info: ILayoutInfo = this._layoutInfo): void {
|
||||
|
||||
this._treeContainer.style.height = `${height - 2 * arrowSize}px`;
|
||||
this._treeContainer.style.width = `${width}px`;
|
||||
let count = 0;
|
||||
let nav = this._tree.getNavigator(undefined, false);
|
||||
while (nav.next() && count < 13) { count += 1; }
|
||||
|
||||
let headerHeight = 2 * info.arrowSize;
|
||||
let treeHeight = Math.min(info.maxHeight - headerHeight, count * 22);
|
||||
let totalHeight = treeHeight + headerHeight;
|
||||
|
||||
this._domNode.style.height = `${totalHeight}px`;
|
||||
this._domNode.style.width = `${info.width}px`;
|
||||
this._arrow.style.top = `-${2 * info.arrowSize}px`;
|
||||
this._arrow.style.borderWidth = `${info.arrowSize}px`;
|
||||
this._arrow.style.marginLeft = `${info.arrowOffset}px`;
|
||||
this._treeContainer.style.height = `${treeHeight}px`;
|
||||
this._treeContainer.style.width = `${info.width}px`;
|
||||
this._tree.layout();
|
||||
this._layoutInfo = info;
|
||||
|
||||
}
|
||||
|
||||
protected abstract _getInput(input: BreadcrumbElement): any;
|
||||
protected abstract _getInitialSelection(tree: ITree, input: BreadcrumbElement): any;
|
||||
protected abstract _completeTreeConfiguration(config: IHighlightingTreeConfiguration): IHighlightingTreeConfiguration;
|
||||
protected abstract _getTargetFromSelectionEvent(e: ISelectionEvent): any | undefined;
|
||||
protected abstract _getTargetFromEvent(element: any, payload: any): any | undefined;
|
||||
}
|
||||
|
||||
//#region - Files
|
||||
@@ -167,7 +230,7 @@ export class FileDataSource implements IDataSource {
|
||||
|
||||
getChildren(tree: ITree, element: IWorkspace | IWorkspaceFolder | IFileStat | URI): TPromise<IWorkspaceFolder[] | IFileStat[]> {
|
||||
if (IWorkspace.isIWorkspace(element)) {
|
||||
return TPromise.as(element.folders).then(folders => {
|
||||
return Promise.resolve(element.folders).then(folders => {
|
||||
for (let child of folders) {
|
||||
this._parents.set(element, child);
|
||||
}
|
||||
@@ -191,13 +254,80 @@ export class FileDataSource implements IDataSource {
|
||||
}
|
||||
|
||||
getParent(tree: ITree, element: IWorkspace | URI | IWorkspaceFolder | IFileStat): TPromise<IWorkspaceFolder | IFileStat> {
|
||||
return TPromise.as(this._parents.get(element));
|
||||
return Promise.resolve(this._parents.get(element));
|
||||
}
|
||||
}
|
||||
|
||||
export class FileRenderer implements IRenderer, IHighlightingRenderer {
|
||||
export class FileFilter implements IFilter {
|
||||
|
||||
private readonly _scores = new Map<string, FuzzyScore>();
|
||||
private readonly _cachedExpressions = new Map<string, glob.ParsedExpression>();
|
||||
private readonly _disposables: IDisposable[] = [];
|
||||
|
||||
constructor(
|
||||
@IWorkspaceContextService private readonly _workspaceService: IWorkspaceContextService,
|
||||
@IConfigurationService configService: IConfigurationService,
|
||||
) {
|
||||
const config = BreadcrumbsConfig.FileExcludes.bindTo(configService);
|
||||
const update = () => {
|
||||
_workspaceService.getWorkspace().folders.forEach(folder => {
|
||||
const excludesConfig = config.getValue({ resource: folder.uri });
|
||||
if (!excludesConfig) {
|
||||
return;
|
||||
}
|
||||
// adjust patterns to be absolute in case they aren't
|
||||
// free floating (**/)
|
||||
const adjustedConfig: glob.IExpression = {};
|
||||
for (const pattern in excludesConfig) {
|
||||
if (typeof excludesConfig[pattern] !== 'boolean') {
|
||||
continue;
|
||||
}
|
||||
let patternAbs = pattern.indexOf('**/') !== 0
|
||||
? join(folder.uri.path, pattern)
|
||||
: pattern;
|
||||
|
||||
adjustedConfig[patternAbs] = excludesConfig[pattern];
|
||||
}
|
||||
this._cachedExpressions.set(folder.uri.toString(), glob.parse(adjustedConfig));
|
||||
});
|
||||
};
|
||||
update();
|
||||
this._disposables.push(
|
||||
config,
|
||||
config.onDidChange(update),
|
||||
_workspaceService.onDidChangeWorkspaceFolders(update)
|
||||
);
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
dispose(this._disposables);
|
||||
}
|
||||
|
||||
isVisible(tree: ITree, element: IWorkspaceFolder | IFileStat): boolean {
|
||||
if (IWorkspaceFolder.isIWorkspaceFolder(element)) {
|
||||
// not a file
|
||||
return true;
|
||||
}
|
||||
const folder = this._workspaceService.getWorkspaceFolder(element.resource);
|
||||
if (!folder || !this._cachedExpressions.has(folder.uri.toString())) {
|
||||
// no folder or no filer
|
||||
return true;
|
||||
}
|
||||
|
||||
const expression = this._cachedExpressions.get(folder.uri.toString());
|
||||
return !expression(element.resource.path, basename(element.resource));
|
||||
}
|
||||
}
|
||||
|
||||
export class FileHighlighter implements IHighlighter {
|
||||
getHighlightsStorageKey(element: IFileStat | IWorkspaceFolder): string {
|
||||
return IWorkspaceFolder.isIWorkspaceFolder(element) ? element.uri.toString() : element.resource.toString();
|
||||
}
|
||||
getHighlights(tree: ITree, element: IFileStat | IWorkspaceFolder, pattern: string): FuzzyScore {
|
||||
return fuzzyScore(pattern, pattern.toLowerCase(), 0, element.name, element.name.toLowerCase(), 0, true);
|
||||
}
|
||||
}
|
||||
|
||||
export class FileRenderer implements IRenderer {
|
||||
|
||||
constructor(
|
||||
@IInstantiationService private readonly _instantiationService: IInstantiationService,
|
||||
@@ -231,29 +361,14 @@ export class FileRenderer implements IRenderer, IHighlightingRenderer {
|
||||
fileKind,
|
||||
hidePath: true,
|
||||
fileDecorations: fileDecorations,
|
||||
matches: createMatches((this._scores.get(resource.toString()) || [, []])[1])
|
||||
matches: createMatches((tree as HighlightingWorkbenchTree).getHighlighterScore(element)),
|
||||
extraClasses: ['picker-item']
|
||||
});
|
||||
}
|
||||
|
||||
disposeTemplate(tree: ITree, templateId: string, templateData: FileLabel): void {
|
||||
templateData.dispose();
|
||||
}
|
||||
|
||||
updateHighlights(tree: ITree, pattern: string): any {
|
||||
let nav = tree.getNavigator(undefined, false);
|
||||
let topScore: FuzzyScore;
|
||||
let topElement: any;
|
||||
while (nav.next()) {
|
||||
let element = nav.current() as IFileStat | IWorkspaceFolder;
|
||||
let score = fuzzyScore(pattern, element.name, undefined, true);
|
||||
this._scores.set(IWorkspaceFolder.isIWorkspaceFolder(element) ? element.uri.toString() : element.resource.toString(), score);
|
||||
if (!topScore || score && topScore[0] < score[0]) {
|
||||
topScore = score;
|
||||
topElement = element;
|
||||
}
|
||||
}
|
||||
return topElement;
|
||||
}
|
||||
}
|
||||
|
||||
export class FileSorter implements ISorter {
|
||||
@@ -278,10 +393,11 @@ export class BreadcrumbsFilePicker extends BreadcrumbsPicker {
|
||||
constructor(
|
||||
parent: HTMLElement,
|
||||
@IInstantiationService instantiationService: IInstantiationService,
|
||||
@IThemeService themeService: IThemeService,
|
||||
@IWorkbenchThemeService themeService: IWorkbenchThemeService,
|
||||
@IConfigurationService configService: IConfigurationService,
|
||||
@IWorkspaceContextService private readonly _workspaceService: IWorkspaceContextService,
|
||||
) {
|
||||
super(parent, instantiationService, themeService);
|
||||
super(parent, instantiationService, themeService, configService);
|
||||
}
|
||||
|
||||
protected _getInput(input: BreadcrumbElement): any {
|
||||
@@ -308,16 +424,20 @@ export class BreadcrumbsFilePicker extends BreadcrumbsPicker {
|
||||
|
||||
protected _completeTreeConfiguration(config: IHighlightingTreeConfiguration): IHighlightingTreeConfiguration {
|
||||
// todo@joh reuse explorer implementations?
|
||||
const filter = this._instantiationService.createInstance(FileFilter);
|
||||
this._disposables.push(filter);
|
||||
|
||||
config.dataSource = this._instantiationService.createInstance(FileDataSource);
|
||||
config.renderer = this._instantiationService.createInstance(FileRenderer);
|
||||
config.sorter = new FileSorter();
|
||||
config.highlighter = new FileHighlighter();
|
||||
config.filter = filter;
|
||||
return config;
|
||||
}
|
||||
|
||||
protected _getTargetFromSelectionEvent(e: ISelectionEvent): any | undefined {
|
||||
let [first] = e.selection;
|
||||
if (first && !IWorkspaceFolder.isIWorkspaceFolder(first) && !(first as IFileStat).isDirectory) {
|
||||
return new FileElement((first as IFileStat).resource, FileKind.FILE);
|
||||
protected _getTargetFromEvent(element: any, _payload: any): any | undefined {
|
||||
if (element && !IWorkspaceFolder.isIWorkspaceFolder(element) && !(element as IFileStat).isDirectory) {
|
||||
return new FileElement((element as IFileStat).resource, FileKind.FILE);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -325,11 +445,10 @@ export class BreadcrumbsFilePicker extends BreadcrumbsPicker {
|
||||
|
||||
//#region - Symbols
|
||||
|
||||
class HighlightingOutlineRenderer extends OutlineRenderer implements IHighlightingRenderer {
|
||||
|
||||
updateHighlights(tree: ITree, pattern: string): any {
|
||||
let model = OutlineModel.get(tree.getInput());
|
||||
return model.updateMatches(pattern);
|
||||
class OutlineHighlighter implements IHighlighter {
|
||||
getHighlights(tree: ITree, element: OutlineElement, pattern: string): FuzzyScore {
|
||||
OutlineModel.get(element).updateMatches(pattern);
|
||||
return element.score;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -348,18 +467,30 @@ export class BreadcrumbsOutlinePicker extends BreadcrumbsPicker {
|
||||
|
||||
protected _completeTreeConfiguration(config: IHighlightingTreeConfiguration): IHighlightingTreeConfiguration {
|
||||
config.dataSource = this._instantiationService.createInstance(OutlineDataSource);
|
||||
config.renderer = this._instantiationService.createInstance(HighlightingOutlineRenderer);
|
||||
config.sorter = new OutlineItemComparator();
|
||||
config.renderer = this._instantiationService.createInstance(OutlineRenderer);
|
||||
config.sorter = new OutlineItemComparator(this._getOutlineItemComparator());
|
||||
config.highlighter = new OutlineHighlighter();
|
||||
return config;
|
||||
}
|
||||
|
||||
protected _getTargetFromSelectionEvent(e: ISelectionEvent): any | undefined {
|
||||
if (e.payload && e.payload.didClickOnTwistie) {
|
||||
protected _getTargetFromEvent(element: any, payload: any): any | undefined {
|
||||
if (payload && payload.didClickOnTwistie) {
|
||||
return;
|
||||
}
|
||||
let [first] = e.selection;
|
||||
if (first instanceof OutlineElement) {
|
||||
return first;
|
||||
if (element instanceof OutlineElement) {
|
||||
return element;
|
||||
}
|
||||
}
|
||||
|
||||
private _getOutlineItemComparator(): OutlineItemCompareType {
|
||||
switch (this._symbolSortOrder.getValue()) {
|
||||
case 'name':
|
||||
return OutlineItemCompareType.ByName;
|
||||
case 'type':
|
||||
return OutlineItemCompareType.ByKind;
|
||||
case 'position':
|
||||
default:
|
||||
return OutlineItemCompareType.ByPosition;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,16 +2,16 @@
|
||||
* 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 { Registry } from 'vs/platform/registry/common/platform';
|
||||
import * as nls from 'vs/nls';
|
||||
import URI from 'vs/base/common/uri';
|
||||
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 { StatusbarItemDescriptor, IStatusbarRegistry, Extensions as StatusExtensions } from 'vs/workbench/browser/parts/statusbar/statusbar';
|
||||
import { StatusbarAlignment } from 'vs/platform/statusbar/common/statusbar';
|
||||
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 { EditorInput, IEditorInputFactory, SideBySideEditorInput, IEditorInputFactoryRegistry, Extensions as EditorInputExtensions, TextCompareEditorActiveContext } 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';
|
||||
@@ -24,7 +24,7 @@ import { BinaryResourceDiffEditor } from 'vs/workbench/browser/parts/editor/bina
|
||||
import { ChangeEncodingAction, ChangeEOLAction, ChangeModeAction, EditorStatus } from 'vs/workbench/browser/parts/editor/editorStatus';
|
||||
import { IWorkbenchActionRegistry, Extensions as ActionExtensions } from 'vs/workbench/common/actions';
|
||||
import { Scope, IActionBarRegistry, Extensions as ActionBarExtensions, ActionBarContributor } from 'vs/workbench/browser/actions';
|
||||
import { SyncActionDescriptor, MenuRegistry, MenuId } from 'vs/platform/actions/common/actions';
|
||||
import { SyncActionDescriptor, MenuRegistry, MenuId, IMenuItem } from 'vs/platform/actions/common/actions';
|
||||
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
|
||||
import { KeyMod, KeyChord, KeyCode } from 'vs/base/common/keyCodes';
|
||||
import {
|
||||
@@ -38,7 +38,7 @@ import {
|
||||
SplitEditorUpAction, SplitEditorDownAction, MoveEditorToLeftGroupAction, MoveEditorToRightGroupAction, MoveEditorToAboveGroupAction, MoveEditorToBelowGroupAction, CloseAllEditorGroupsAction,
|
||||
JoinAllGroupsAction, FocusLeftGroup, FocusAboveGroup, FocusRightGroup, FocusBelowGroup, EditorLayoutSingleAction, EditorLayoutTwoColumnsAction, EditorLayoutThreeColumnsAction, EditorLayoutTwoByTwoGridAction,
|
||||
EditorLayoutTwoRowsAction, EditorLayoutThreeRowsAction, EditorLayoutTwoColumnsBottomAction, EditorLayoutTwoRowsRightAction, NewEditorGroupLeftAction, NewEditorGroupRightAction,
|
||||
NewEditorGroupAboveAction, NewEditorGroupBelowAction, SplitEditorOrthogonalAction
|
||||
NewEditorGroupAboveAction, NewEditorGroupBelowAction, SplitEditorOrthogonalAction, CloseEditorInAllGroupsAction, NavigateToLastEditLocationAction
|
||||
} from 'vs/workbench/browser/parts/editor/editorActions';
|
||||
import * as editorCommands from 'vs/workbench/browser/parts/editor/editorCommands';
|
||||
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
@@ -48,6 +48,8 @@ import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { isMacintosh } from 'vs/base/common/platform';
|
||||
import { AllEditorsPicker, ActiveEditorGroupPicker } from 'vs/workbench/browser/parts/editor/editorPicker';
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
import { registerEditorContribution } from 'vs/editor/browser/editorExtensions';
|
||||
import { OpenWorkspaceButtonContribution } from 'vs/workbench/browser/parts/editor/editorWidgets';
|
||||
|
||||
// Register String Editor
|
||||
Registry.as<IEditorRegistry>(EditorExtensions.Editors).registerEditor(
|
||||
@@ -217,6 +219,9 @@ class SideBySideEditorInputFactory implements IEditorInputFactory {
|
||||
|
||||
Registry.as<IEditorInputFactoryRegistry>(EditorInputExtensions.EditorInputFactories).registerEditorInputFactory(SideBySideEditorInput.ID, SideBySideEditorInputFactory);
|
||||
|
||||
// Register Editor Contributions
|
||||
registerEditorContribution(OpenWorkspaceButtonContribution);
|
||||
|
||||
// Register Editor Status
|
||||
const statusBar = Registry.as<IStatusbarRegistry>(StatusExtensions.Statusbar);
|
||||
statusBar.registerStatusbarItem(new StatusbarItemDescriptor(EditorStatus, StatusbarAlignment.RIGHT, 100 /* towards the left of the right hand side */));
|
||||
@@ -322,6 +327,7 @@ registry.registerWorkbenchAction(new SyncActionDescriptor(CloseAllEditorsAction,
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(CloseAllEditorGroupsAction, CloseAllEditorGroupsAction.ID, CloseAllEditorGroupsAction.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_W) }), 'View: Close All Editor Groups', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(CloseLeftEditorsInGroupAction, CloseLeftEditorsInGroupAction.ID, CloseLeftEditorsInGroupAction.LABEL), 'View: Close Editors in Group to the Left', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(CloseEditorsInOtherGroupsAction, CloseEditorsInOtherGroupsAction.ID, CloseEditorsInOtherGroupsAction.LABEL), 'View: Close Editors in Other Groups', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(CloseEditorInAllGroupsAction, CloseEditorInAllGroupsAction.ID, CloseEditorInAllGroupsAction.LABEL), 'View: Close Editor in All Groups', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(SplitEditorAction, SplitEditorAction.ID, SplitEditorAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.US_BACKSLASH }), 'View: Split Editor', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(SplitEditorOrthogonalAction, SplitEditorOrthogonalAction.ID, SplitEditorOrthogonalAction.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.US_BACKSLASH) }), 'View: Split Editor Orthogonal', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(SplitEditorLeftAction, SplitEditorLeftAction.ID, SplitEditorLeftAction.LABEL), 'View: Split Editor Left', category);
|
||||
@@ -332,7 +338,7 @@ registry.registerWorkbenchAction(new SyncActionDescriptor(JoinTwoGroupsAction, J
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(JoinAllGroupsAction, JoinAllGroupsAction.ID, JoinAllGroupsAction.LABEL), 'View: Join Editors of All Groups', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(NavigateBetweenGroupsAction, NavigateBetweenGroupsAction.ID, NavigateBetweenGroupsAction.LABEL), 'View: Navigate Between Editor Groups', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(ResetGroupSizesAction, ResetGroupSizesAction.ID, ResetGroupSizesAction.LABEL), 'View: Reset Editor Group Sizes', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(MaximizeGroupAction, MaximizeGroupAction.ID, MaximizeGroupAction.LABEL), 'View: Maximize Editor Group and Hide Sidebar', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(MaximizeGroupAction, MaximizeGroupAction.ID, MaximizeGroupAction.LABEL), 'View: Maximize Editor Group and Hide Side Bar', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(MinimizeOtherGroupsAction, MinimizeOtherGroupsAction.ID, MinimizeOtherGroupsAction.LABEL), 'View: Maximize Editor Group', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(MoveEditorLeftInGroupAction, MoveEditorLeftInGroupAction.ID, MoveEditorLeftInGroupAction.LABEL, { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.PageUp, mac: { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.LeftArrow) } }), 'View: Move Editor Left', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(MoveEditorRightInGroupAction, MoveEditorRightInGroupAction.ID, MoveEditorRightInGroupAction.LABEL, { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.PageDown, mac: { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.RightArrow) } }), 'View: Move Editor Right', category);
|
||||
@@ -361,8 +367,9 @@ registry.registerWorkbenchAction(new SyncActionDescriptor(NewEditorGroupLeftActi
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(NewEditorGroupRightAction, NewEditorGroupRightAction.ID, NewEditorGroupRightAction.LABEL), 'View: New Editor Group to the Right', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(NewEditorGroupAboveAction, NewEditorGroupAboveAction.ID, NewEditorGroupAboveAction.LABEL), 'View: New Editor Group Above', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(NewEditorGroupBelowAction, NewEditorGroupBelowAction.ID, NewEditorGroupBelowAction.LABEL), 'View: New Editor Group Below', 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(NavigateForwardAction, NavigateForwardAction.ID, NavigateForwardAction.LABEL, { primary: 0, 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: 0, 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(NavigateToLastEditLocationAction, NavigateToLastEditLocationAction.ID, NavigateToLastEditLocationAction.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.KEY_Q) }), 'Go to Last Edit Location');
|
||||
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');
|
||||
@@ -438,15 +445,15 @@ MenuRegistry.appendMenuItem(MenuId.EditorTitleContext, { command: { id: editorCo
|
||||
MenuRegistry.appendMenuItem(MenuId.EditorTitleContext, { command: { id: editorCommands.SPLIT_EDITOR_RIGHT, title: nls.localize('splitRight', "Split Right") }, group: '5_split', order: 40 });
|
||||
|
||||
// Editor Title Menu
|
||||
MenuRegistry.appendMenuItem(MenuId.EditorTitle, { command: { id: editorCommands.TOGGLE_DIFF_INLINE_MODE, title: nls.localize('toggleInlineView', "Toggle Inline View") }, group: '1_diff', order: 10, when: ContextKeyExpr.has('isInDiffEditor') });
|
||||
MenuRegistry.appendMenuItem(MenuId.EditorTitle, { command: { id: editorCommands.TOGGLE_DIFF_SIDE_BY_SIDE, title: nls.localize('toggleSideBySideView', "Toggle Side By Side View") }, group: '1_diff', order: 10, when: ContextKeyExpr.has('isInDiffEditor') });
|
||||
MenuRegistry.appendMenuItem(MenuId.EditorTitle, { command: { id: editorCommands.SHOW_EDITORS_IN_GROUP, title: nls.localize('showOpenedEditors', "Show Opened Editors") }, group: '3_open', order: 10, when: ContextKeyExpr.has('config.workbench.editor.showTabs') });
|
||||
MenuRegistry.appendMenuItem(MenuId.EditorTitle, { command: { id: editorCommands.CLOSE_EDITORS_IN_GROUP_COMMAND_ID, title: nls.localize('closeAll', "Close All") }, group: '5_close', order: 10, when: ContextKeyExpr.has('config.workbench.editor.showTabs') });
|
||||
MenuRegistry.appendMenuItem(MenuId.EditorTitle, { command: { id: editorCommands.CLOSE_SAVED_EDITORS_COMMAND_ID, title: nls.localize('closeAllSaved', "Close Saved") }, group: '5_close', order: 20, when: ContextKeyExpr.has('config.workbench.editor.showTabs') });
|
||||
|
||||
interface IEditorToolItem { id: string; title: string; iconDark: string; iconLight: string; }
|
||||
|
||||
function appendEditorToolItem(primary: IEditorToolItem, alternative: IEditorToolItem, when: ContextKeyExpr, order: number): void {
|
||||
MenuRegistry.appendMenuItem(MenuId.EditorTitle, {
|
||||
function appendEditorToolItem(primary: IEditorToolItem, when: ContextKeyExpr, order: number, alternative?: IEditorToolItem): void {
|
||||
const item: IMenuItem = {
|
||||
command: {
|
||||
id: primary.id,
|
||||
title: primary.title,
|
||||
@@ -455,18 +462,23 @@ function appendEditorToolItem(primary: IEditorToolItem, alternative: IEditorTool
|
||||
light: URI.parse(require.toUrl(`vs/workbench/browser/parts/editor/media/${primary.iconLight}`))
|
||||
}
|
||||
},
|
||||
alt: {
|
||||
group: 'navigation',
|
||||
when,
|
||||
order
|
||||
};
|
||||
|
||||
if (alternative) {
|
||||
item.alt = {
|
||||
id: alternative.id,
|
||||
title: alternative.title,
|
||||
iconLocation: {
|
||||
dark: URI.parse(require.toUrl(`vs/workbench/browser/parts/editor/media/${alternative.iconDark}`)),
|
||||
light: URI.parse(require.toUrl(`vs/workbench/browser/parts/editor/media/${alternative.iconLight}`))
|
||||
}
|
||||
},
|
||||
group: 'navigation',
|
||||
when,
|
||||
order
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
MenuRegistry.appendMenuItem(MenuId.EditorTitle, item);
|
||||
}
|
||||
|
||||
// Editor Title Menu: Split Editor
|
||||
@@ -476,14 +488,15 @@ appendEditorToolItem(
|
||||
title: nls.localize('splitEditorRight', "Split Editor Right"),
|
||||
iconDark: 'split-editor-horizontal-inverse.svg',
|
||||
iconLight: 'split-editor-horizontal.svg'
|
||||
}, {
|
||||
},
|
||||
ContextKeyExpr.not('splitEditorsVertically'),
|
||||
100000, // towards the end
|
||||
{
|
||||
id: editorCommands.SPLIT_EDITOR_DOWN,
|
||||
title: nls.localize('splitEditorDown', "Split Editor Down"),
|
||||
iconDark: 'split-editor-vertical-inverse.svg',
|
||||
iconLight: 'split-editor-vertical.svg'
|
||||
},
|
||||
ContextKeyExpr.not('splitEditorsVertically'),
|
||||
100000 /* towards the end */
|
||||
}
|
||||
);
|
||||
|
||||
appendEditorToolItem(
|
||||
@@ -492,14 +505,15 @@ appendEditorToolItem(
|
||||
title: nls.localize('splitEditorDown', "Split Editor Down"),
|
||||
iconDark: 'split-editor-vertical-inverse.svg',
|
||||
iconLight: 'split-editor-vertical.svg'
|
||||
}, {
|
||||
},
|
||||
ContextKeyExpr.has('splitEditorsVertically'),
|
||||
100000, // towards the end
|
||||
{
|
||||
id: editorCommands.SPLIT_EDITOR_RIGHT,
|
||||
title: nls.localize('splitEditorRight', "Split Editor Right"),
|
||||
iconDark: 'split-editor-horizontal-inverse.svg',
|
||||
iconLight: 'split-editor-horizontal.svg'
|
||||
},
|
||||
ContextKeyExpr.has('splitEditorsVertically'),
|
||||
100000 // towards the end
|
||||
}
|
||||
);
|
||||
|
||||
// Editor Title Menu: Close Group (tabs disabled)
|
||||
@@ -509,14 +523,15 @@ appendEditorToolItem(
|
||||
title: nls.localize('close', "Close"),
|
||||
iconDark: 'close-big-inverse-alt.svg',
|
||||
iconLight: 'close-big-alt.svg'
|
||||
}, {
|
||||
},
|
||||
ContextKeyExpr.and(ContextKeyExpr.not('config.workbench.editor.showTabs'), ContextKeyExpr.not('groupActiveEditorDirty')),
|
||||
1000000, // towards the far end
|
||||
{
|
||||
id: editorCommands.CLOSE_EDITORS_IN_GROUP_COMMAND_ID,
|
||||
title: nls.localize('closeAll', "Close All"),
|
||||
iconDark: 'closeall-editors-inverse.svg',
|
||||
iconLight: 'closeall-editors.svg'
|
||||
},
|
||||
ContextKeyExpr.and(ContextKeyExpr.not('config.workbench.editor.showTabs'), ContextKeyExpr.not('groupActiveEditorDirty')),
|
||||
1000000 // towards the end
|
||||
}
|
||||
);
|
||||
|
||||
appendEditorToolItem(
|
||||
@@ -525,22 +540,71 @@ appendEditorToolItem(
|
||||
title: nls.localize('close', "Close"),
|
||||
iconDark: 'close-dirty-inverse-alt.svg',
|
||||
iconLight: 'close-dirty-alt.svg'
|
||||
}, {
|
||||
},
|
||||
ContextKeyExpr.and(ContextKeyExpr.not('config.workbench.editor.showTabs'), ContextKeyExpr.has('groupActiveEditorDirty')),
|
||||
1000000, // towards the far end
|
||||
{
|
||||
id: editorCommands.CLOSE_EDITORS_IN_GROUP_COMMAND_ID,
|
||||
title: nls.localize('closeAll', "Close All"),
|
||||
iconDark: 'closeall-editors-inverse.svg',
|
||||
iconLight: 'closeall-editors.svg'
|
||||
}
|
||||
);
|
||||
|
||||
// Diff Editor Title Menu: Previous Change
|
||||
appendEditorToolItem(
|
||||
{
|
||||
id: editorCommands.GOTO_PREVIOUS_CHANGE,
|
||||
title: nls.localize('navigate.prev.label', "Previous Change"),
|
||||
iconDark: 'previous-diff-inverse.svg',
|
||||
iconLight: 'previous-diff.svg'
|
||||
},
|
||||
ContextKeyExpr.and(ContextKeyExpr.not('config.workbench.editor.showTabs'), ContextKeyExpr.has('groupActiveEditorDirty')),
|
||||
1000000 // towards the end
|
||||
TextCompareEditorActiveContext,
|
||||
10
|
||||
);
|
||||
|
||||
// Diff Editor Title Menu: Next Change
|
||||
appendEditorToolItem(
|
||||
{
|
||||
id: editorCommands.GOTO_NEXT_CHANGE,
|
||||
title: nls.localize('navigate.next.label', "Next Change"),
|
||||
iconDark: 'next-diff-inverse.svg',
|
||||
iconLight: 'next-diff.svg'
|
||||
},
|
||||
TextCompareEditorActiveContext,
|
||||
11
|
||||
);
|
||||
|
||||
// Diff Editor Title Menu: Toggle Ignore Trim Whitespace (Enabled)
|
||||
appendEditorToolItem(
|
||||
{
|
||||
id: editorCommands.TOGGLE_DIFF_IGNORE_TRIM_WHITESPACE,
|
||||
title: nls.localize('ignoreTrimWhitespace.label', "Ignore Trim Whitespace"),
|
||||
iconDark: 'paragraph-inverse.svg',
|
||||
iconLight: 'paragraph.svg'
|
||||
},
|
||||
ContextKeyExpr.and(TextCompareEditorActiveContext, ContextKeyExpr.notEquals('config.diffEditor.ignoreTrimWhitespace', true)),
|
||||
20
|
||||
);
|
||||
|
||||
// Diff Editor Title Menu: Toggle Ignore Trim Whitespace (Disabled)
|
||||
appendEditorToolItem(
|
||||
{
|
||||
id: editorCommands.TOGGLE_DIFF_IGNORE_TRIM_WHITESPACE,
|
||||
title: nls.localize('showTrimWhitespace.label', "Show Trim Whitespace"),
|
||||
iconDark: 'paragraph-disabled-inverse.svg',
|
||||
iconLight: 'paragraph-disabled.svg'
|
||||
},
|
||||
ContextKeyExpr.and(TextCompareEditorActiveContext, ContextKeyExpr.notEquals('config.diffEditor.ignoreTrimWhitespace', false)),
|
||||
20
|
||||
);
|
||||
|
||||
// Editor Commands for Command Palette
|
||||
MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: editorCommands.KEEP_EDITOR_COMMAND_ID, title: nls.localize('keepEditor', "Keep Editor"), category }, when: ContextKeyExpr.has('config.workbench.editor.enablePreview') });
|
||||
MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: editorCommands.CLOSE_EDITORS_IN_GROUP_COMMAND_ID, title: nls.localize('closeEditorsInGroup', "Close All Editors in Group"), category } });
|
||||
MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: editorCommands.CLOSE_SAVED_EDITORS_COMMAND_ID, title: nls.localize('closeSavedEditors', "Close Saved Editors in Group"), category } });
|
||||
MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: editorCommands.CLOSE_OTHER_EDITORS_IN_GROUP_COMMAND_ID, title: nls.localize('closeOtherEditors', "Close Other Editors in Group"), category } });
|
||||
MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: editorCommands.CLOSE_EDITORS_TO_THE_RIGHT_COMMAND_ID, title: nls.localize('closeRightEditors', "Close Editors to the Right in Group"), category } });
|
||||
MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: editorCommands.KEEP_EDITOR_COMMAND_ID, title: { value: nls.localize('keepEditor', "Keep Editor"), original: 'View: Keep Editor' }, category }, when: ContextKeyExpr.has('config.workbench.editor.enablePreview') });
|
||||
MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: editorCommands.CLOSE_EDITORS_IN_GROUP_COMMAND_ID, title: { value: nls.localize('closeEditorsInGroup', "Close All Editors in Group"), original: 'View: Close All Editors in Group' }, category } });
|
||||
MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: editorCommands.CLOSE_SAVED_EDITORS_COMMAND_ID, title: { value: nls.localize('closeSavedEditors', "Close Saved Editors in Group"), original: 'View: Close Saved Editors in Group' }, category } });
|
||||
MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: editorCommands.CLOSE_OTHER_EDITORS_IN_GROUP_COMMAND_ID, title: { value: nls.localize('closeOtherEditors', "Close Other Editors in Group"), original: 'View: Close Other Editors in Group' }, category } });
|
||||
MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: editorCommands.CLOSE_EDITORS_TO_THE_RIGHT_COMMAND_ID, title: { value: nls.localize('closeRightEditors', "Close Editors to the Right in Group"), original: 'View: Close Editors to the Right in Group' }, category } });
|
||||
|
||||
// File menu
|
||||
MenuRegistry.appendMenuItem(MenuId.MenubarRecentMenu, {
|
||||
@@ -676,3 +740,177 @@ MenuRegistry.appendMenuItem(MenuId.MenubarLayoutMenu, {
|
||||
},
|
||||
order: 9
|
||||
});
|
||||
|
||||
// Main Menu Bar Contributions:
|
||||
|
||||
// Forward/Back
|
||||
MenuRegistry.appendMenuItem(MenuId.MenubarGoMenu, {
|
||||
group: '1_fwd_back',
|
||||
command: {
|
||||
id: 'workbench.action.navigateBack',
|
||||
title: nls.localize({ key: 'miBack', comment: ['&& denotes a mnemonic'] }, "&&Back"),
|
||||
precondition: ContextKeyExpr.has('canNavigateBack')
|
||||
},
|
||||
order: 1
|
||||
});
|
||||
|
||||
MenuRegistry.appendMenuItem(MenuId.MenubarGoMenu, {
|
||||
group: '1_fwd_back',
|
||||
command: {
|
||||
id: 'workbench.action.navigateForward',
|
||||
title: nls.localize({ key: 'miForward', comment: ['&& denotes a mnemonic'] }, "&&Forward"),
|
||||
precondition: ContextKeyExpr.has('canNavigateForward')
|
||||
},
|
||||
order: 2
|
||||
});
|
||||
|
||||
// Switch Editor
|
||||
MenuRegistry.appendMenuItem(MenuId.MenubarSwitchEditorMenu, {
|
||||
group: '1_any',
|
||||
command: {
|
||||
id: 'workbench.action.nextEditor',
|
||||
title: nls.localize({ key: 'miNextEditor', comment: ['&& denotes a mnemonic'] }, "&&Next Editor")
|
||||
},
|
||||
order: 1
|
||||
});
|
||||
|
||||
MenuRegistry.appendMenuItem(MenuId.MenubarSwitchEditorMenu, {
|
||||
group: '1_any',
|
||||
command: {
|
||||
id: 'workbench.action.previousEditor',
|
||||
title: nls.localize({ key: 'miPreviousEditor', comment: ['&& denotes a mnemonic'] }, "&&Previous Editor")
|
||||
},
|
||||
order: 2
|
||||
});
|
||||
|
||||
MenuRegistry.appendMenuItem(MenuId.MenubarSwitchEditorMenu, {
|
||||
group: '2_used',
|
||||
command: {
|
||||
id: 'workbench.action.openNextRecentlyUsedEditorInGroup',
|
||||
title: nls.localize({ key: 'miNextEditorInGroup', comment: ['&& denotes a mnemonic'] }, "&&Next Used Editor in Group")
|
||||
},
|
||||
order: 1
|
||||
});
|
||||
|
||||
MenuRegistry.appendMenuItem(MenuId.MenubarSwitchEditorMenu, {
|
||||
group: '2_used',
|
||||
command: {
|
||||
id: 'workbench.action.openPreviousRecentlyUsedEditorInGroup',
|
||||
title: nls.localize({ key: 'miPreviousEditorInGroup', comment: ['&& denotes a mnemonic'] }, "&&Previous Used Editor in Group")
|
||||
},
|
||||
order: 2
|
||||
});
|
||||
|
||||
MenuRegistry.appendMenuItem(MenuId.MenubarGoMenu, {
|
||||
group: '2_switch',
|
||||
title: nls.localize({ key: 'miSwitchEditor', comment: ['&& denotes a mnemonic'] }, "Switch &&Editor"),
|
||||
submenu: MenuId.MenubarSwitchEditorMenu,
|
||||
order: 1
|
||||
});
|
||||
|
||||
// Switch Group
|
||||
MenuRegistry.appendMenuItem(MenuId.MenubarSwitchGroupMenu, {
|
||||
group: '1_focus_index',
|
||||
command: {
|
||||
id: 'workbench.action.focusFirstEditorGroup',
|
||||
title: nls.localize({ key: 'miFocusFirstGroup', comment: ['&& denotes a mnemonic'] }, "Group &&1")
|
||||
},
|
||||
order: 1
|
||||
});
|
||||
|
||||
MenuRegistry.appendMenuItem(MenuId.MenubarSwitchGroupMenu, {
|
||||
group: '1_focus_index',
|
||||
command: {
|
||||
id: 'workbench.action.focusSecondEditorGroup',
|
||||
title: nls.localize({ key: 'miFocusSecondGroup', comment: ['&& denotes a mnemonic'] }, "Group &&2")
|
||||
},
|
||||
order: 2
|
||||
});
|
||||
|
||||
MenuRegistry.appendMenuItem(MenuId.MenubarSwitchGroupMenu, {
|
||||
group: '1_focus_index',
|
||||
command: {
|
||||
id: 'workbench.action.focusThirdEditorGroup',
|
||||
title: nls.localize({ key: 'miFocusThirdGroup', comment: ['&& denotes a mnemonic'] }, "Group &&3")
|
||||
},
|
||||
order: 3
|
||||
});
|
||||
|
||||
MenuRegistry.appendMenuItem(MenuId.MenubarSwitchGroupMenu, {
|
||||
group: '1_focus_index',
|
||||
command: {
|
||||
id: 'workbench.action.focusFourthEditorGroup',
|
||||
title: nls.localize({ key: 'miFocusFourthGroup', comment: ['&& denotes a mnemonic'] }, "Group &&4")
|
||||
},
|
||||
order: 4
|
||||
});
|
||||
|
||||
MenuRegistry.appendMenuItem(MenuId.MenubarSwitchGroupMenu, {
|
||||
group: '1_focus_index',
|
||||
command: {
|
||||
id: 'workbench.action.focusFifthEditorGroup',
|
||||
title: nls.localize({ key: 'miFocusFifthGroup', comment: ['&& denotes a mnemonic'] }, "Group &&5")
|
||||
},
|
||||
order: 5
|
||||
});
|
||||
|
||||
MenuRegistry.appendMenuItem(MenuId.MenubarSwitchGroupMenu, {
|
||||
group: '2_next_prev',
|
||||
command: {
|
||||
id: 'workbench.action.focusNextGroup',
|
||||
title: nls.localize({ key: 'miNextGroup', comment: ['&& denotes a mnemonic'] }, "&&Next Group")
|
||||
},
|
||||
order: 1
|
||||
});
|
||||
|
||||
MenuRegistry.appendMenuItem(MenuId.MenubarSwitchGroupMenu, {
|
||||
group: '2_next_prev',
|
||||
command: {
|
||||
id: 'workbench.action.focusPreviousGroup',
|
||||
title: nls.localize({ key: 'miPreviousGroup', comment: ['&& denotes a mnemonic'] }, "&&Previous Group")
|
||||
},
|
||||
order: 2
|
||||
});
|
||||
|
||||
MenuRegistry.appendMenuItem(MenuId.MenubarSwitchGroupMenu, {
|
||||
group: '3_directional',
|
||||
command: {
|
||||
id: 'workbench.action.focusLeftGroup',
|
||||
title: nls.localize({ key: 'miFocusLeftGroup', comment: ['&& denotes a mnemonic'] }, "Group &&Left")
|
||||
},
|
||||
order: 1
|
||||
});
|
||||
|
||||
MenuRegistry.appendMenuItem(MenuId.MenubarSwitchGroupMenu, {
|
||||
group: '3_directional',
|
||||
command: {
|
||||
id: 'workbench.action.focusRightGroup',
|
||||
title: nls.localize({ key: 'miFocusRightGroup', comment: ['&& denotes a mnemonic'] }, "Group &&Right")
|
||||
},
|
||||
order: 2
|
||||
});
|
||||
|
||||
MenuRegistry.appendMenuItem(MenuId.MenubarSwitchGroupMenu, {
|
||||
group: '3_directional',
|
||||
command: {
|
||||
id: 'workbench.action.focusAboveGroup',
|
||||
title: nls.localize({ key: 'miFocusAboveGroup', comment: ['&& denotes a mnemonic'] }, "Group &&Above")
|
||||
},
|
||||
order: 3
|
||||
});
|
||||
|
||||
MenuRegistry.appendMenuItem(MenuId.MenubarSwitchGroupMenu, {
|
||||
group: '3_directional',
|
||||
command: {
|
||||
id: 'workbench.action.focusBelowGroup',
|
||||
title: nls.localize({ key: 'miFocusBelowGroup', comment: ['&& denotes a mnemonic'] }, "Group &&Below")
|
||||
},
|
||||
order: 4
|
||||
});
|
||||
|
||||
MenuRegistry.appendMenuItem(MenuId.MenubarGoMenu, {
|
||||
group: '2_switch',
|
||||
title: nls.localize({ key: 'miSwitchGroup', comment: ['&& denotes a mnemonic'] }, "Switch &&Group"),
|
||||
submenu: MenuId.MenubarSwitchGroupMenu,
|
||||
order: 2
|
||||
});
|
||||
@@ -3,9 +3,6 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { GroupIdentifier, IWorkbenchEditorConfiguration, IWorkbenchEditorPartConfiguration, EditorOptions, TextEditorOptions, IEditorInput, IEditorIdentifier, IEditorCloseEvent } from 'vs/workbench/common/editor';
|
||||
import { EditorGroup } from 'vs/workbench/common/editor/editorGroup';
|
||||
import { IEditorGroup, GroupDirection, IAddGroupOptions, IMergeGroupOptions, GroupsOrder, IEditorGroupsService } from 'vs/workbench/services/group/common/editorGroupsService';
|
||||
@@ -30,6 +27,7 @@ export interface IEditorPartOptions extends IWorkbenchEditorPartConfiguration {
|
||||
|
||||
export const DEFAULT_EDITOR_PART_OPTIONS: IEditorPartOptions = {
|
||||
showTabs: true,
|
||||
highlightModifiedTabs: false,
|
||||
tabCloseButton: 'right',
|
||||
tabSizing: 'fit',
|
||||
showIcons: true,
|
||||
@@ -104,7 +102,7 @@ export interface IEditorGroupsAccessor {
|
||||
|
||||
export interface IEditorGroupView extends IDisposable, ISerializableView, IEditorGroup {
|
||||
readonly group: EditorGroup;
|
||||
readonly whenRestored: TPromise<void>;
|
||||
readonly whenRestored: Thenable<void>;
|
||||
readonly disposed: boolean;
|
||||
|
||||
readonly onDidFocus: Event<void>;
|
||||
@@ -118,8 +116,6 @@ export interface IEditorGroupView extends IDisposable, ISerializableView, IEdito
|
||||
setActive(isActive: boolean): void;
|
||||
setLabel(label: string): void;
|
||||
relayout(): void;
|
||||
|
||||
shutdown(): void;
|
||||
}
|
||||
|
||||
export function getActiveTextEditorOptions(group: IEditorGroup, expectedActiveEditor?: IEditorInput, presetOptions?: EditorOptions): EditorOptions {
|
||||
@@ -159,5 +155,5 @@ export interface EditorGroupsServiceImpl extends IEditorGroupsService {
|
||||
/**
|
||||
* A promise that resolves when groups have been restored.
|
||||
*/
|
||||
readonly whenRestored: TPromise<void>;
|
||||
readonly whenRestored: Thenable<void>;
|
||||
}
|
||||
|
||||
@@ -2,9 +2,7 @@
|
||||
* 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 { TPromise } from 'vs/base/common/winjs.base';
|
||||
import * as nls from 'vs/nls';
|
||||
import { Action } from 'vs/base/common/actions';
|
||||
import { mixin } from 'vs/base/common/objects';
|
||||
@@ -37,7 +35,7 @@ export class ExecuteCommandAction extends Action {
|
||||
super(id, label);
|
||||
}
|
||||
|
||||
run(): TPromise<any> {
|
||||
run(): Thenable<any> {
|
||||
return this.commandService.executeCommand(this.commandId, this.commandArgs);
|
||||
}
|
||||
}
|
||||
@@ -71,10 +69,10 @@ export class BaseSplitEditorAction extends Action {
|
||||
}));
|
||||
}
|
||||
|
||||
run(context?: IEditorIdentifier): TPromise<any> {
|
||||
run(context?: IEditorIdentifier): Promise<any> {
|
||||
splitEditor(this.editorGroupService, this.direction, context);
|
||||
|
||||
return TPromise.as(true);
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
@@ -189,7 +187,7 @@ export class JoinTwoGroupsAction extends Action {
|
||||
super(id, label);
|
||||
}
|
||||
|
||||
run(context?: IEditorIdentifier): TPromise<any> {
|
||||
run(context?: IEditorIdentifier): Promise<any> {
|
||||
let sourceGroup: IEditorGroup;
|
||||
if (context && typeof context.groupId === 'number') {
|
||||
sourceGroup = this.editorGroupService.getGroup(context.groupId);
|
||||
@@ -203,11 +201,11 @@ export class JoinTwoGroupsAction extends Action {
|
||||
if (targetGroup && sourceGroup !== targetGroup) {
|
||||
this.editorGroupService.mergeGroup(sourceGroup, targetGroup);
|
||||
|
||||
return TPromise.as(true);
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
}
|
||||
|
||||
return TPromise.as(true);
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -224,10 +222,10 @@ export class JoinAllGroupsAction extends Action {
|
||||
super(id, label);
|
||||
}
|
||||
|
||||
run(context?: IEditorIdentifier): TPromise<any> {
|
||||
run(context?: IEditorIdentifier): Promise<any> {
|
||||
mergeAllGroups(this.editorGroupService);
|
||||
|
||||
return TPromise.as(true);
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -244,11 +242,11 @@ export class NavigateBetweenGroupsAction extends Action {
|
||||
super(id, label);
|
||||
}
|
||||
|
||||
run(): TPromise<any> {
|
||||
run(): Thenable<any> {
|
||||
const nextGroup = this.editorGroupService.findGroup({ location: GroupLocation.NEXT }, this.editorGroupService.activeGroup, true);
|
||||
nextGroup.focus();
|
||||
|
||||
return TPromise.as(true);
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -265,10 +263,10 @@ export class FocusActiveGroupAction extends Action {
|
||||
super(id, label);
|
||||
}
|
||||
|
||||
run(): TPromise<any> {
|
||||
run(): Thenable<any> {
|
||||
this.editorGroupService.activeGroup.focus();
|
||||
|
||||
return TPromise.as(true);
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -283,13 +281,13 @@ export abstract class BaseFocusGroupAction extends Action {
|
||||
super(id, label);
|
||||
}
|
||||
|
||||
run(): TPromise<any> {
|
||||
run(): Thenable<any> {
|
||||
const group = this.editorGroupService.findGroup(this.scope, this.editorGroupService.activeGroup, true);
|
||||
if (group) {
|
||||
group.focus();
|
||||
}
|
||||
|
||||
return TPromise.as(true);
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -425,7 +423,7 @@ export class OpenToSideFromQuickOpenAction extends Action {
|
||||
this.class = (preferredDirection === GroupDirection.RIGHT) ? 'quick-open-sidebyside-vertical' : 'quick-open-sidebyside-horizontal';
|
||||
}
|
||||
|
||||
run(context: any): TPromise<any> {
|
||||
run(context: any): Thenable<any> {
|
||||
const entry = toEditorQuickOpenEntry(context);
|
||||
if (entry) {
|
||||
const input = entry.getInput();
|
||||
@@ -439,7 +437,7 @@ export class OpenToSideFromQuickOpenAction extends Action {
|
||||
return this.editorService.openEditor(resourceInput, SIDE_GROUP);
|
||||
}
|
||||
|
||||
return TPromise.as(false);
|
||||
return Promise.resolve(false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -474,7 +472,7 @@ export class CloseEditorAction extends Action {
|
||||
super(id, label, 'close-editor-action');
|
||||
}
|
||||
|
||||
run(context?: IEditorCommandsContext): TPromise<any> {
|
||||
run(context?: IEditorCommandsContext): Promise<any> {
|
||||
return this.commandService.executeCommand(CLOSE_EDITOR_COMMAND_ID, void 0, context);
|
||||
}
|
||||
}
|
||||
@@ -492,7 +490,7 @@ export class CloseOneEditorAction extends Action {
|
||||
super(id, label, 'close-editor-action');
|
||||
}
|
||||
|
||||
run(context?: IEditorCommandsContext): TPromise<any> {
|
||||
run(context?: IEditorCommandsContext): Thenable<any> {
|
||||
let group: IEditorGroup;
|
||||
let editorIndex: number;
|
||||
if (context) {
|
||||
@@ -520,7 +518,7 @@ export class CloseOneEditorAction extends Action {
|
||||
return group.closeEditor(group.activeEditor);
|
||||
}
|
||||
|
||||
return TPromise.as(false);
|
||||
return Promise.resolve(false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -537,7 +535,7 @@ export class RevertAndCloseEditorAction extends Action {
|
||||
super(id, label);
|
||||
}
|
||||
|
||||
run(): TPromise<any> {
|
||||
run(): Thenable<any> {
|
||||
const activeControl = this.editorService.activeControl;
|
||||
if (activeControl) {
|
||||
const editor = activeControl.input;
|
||||
@@ -553,7 +551,7 @@ export class RevertAndCloseEditorAction extends Action {
|
||||
});
|
||||
}
|
||||
|
||||
return TPromise.as(false);
|
||||
return Promise.resolve(false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -571,13 +569,13 @@ export class CloseLeftEditorsInGroupAction extends Action {
|
||||
super(id, label);
|
||||
}
|
||||
|
||||
run(context?: IEditorIdentifier): TPromise<any> {
|
||||
run(context?: IEditorIdentifier): Thenable<any> {
|
||||
const { group, editor } = getTarget(this.editorService, this.editorGroupService, context);
|
||||
if (group && editor) {
|
||||
return group.closeEditors({ direction: CloseDirection.LEFT, except: editor });
|
||||
}
|
||||
|
||||
return TPromise.as(false);
|
||||
return Promise.resolve(false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -616,7 +614,7 @@ export abstract class BaseCloseAllAction extends Action {
|
||||
return groupsToClose;
|
||||
}
|
||||
|
||||
run(): TPromise<any> {
|
||||
run(): Thenable<any> {
|
||||
|
||||
// Just close all if there are no or one dirty editor
|
||||
if (this.textFileService.getDirty().length < 2) {
|
||||
@@ -629,7 +627,7 @@ export abstract class BaseCloseAllAction extends Action {
|
||||
return void 0;
|
||||
}
|
||||
|
||||
let saveOrRevertPromise: TPromise<boolean>;
|
||||
let saveOrRevertPromise: Thenable<boolean>;
|
||||
if (confirm === ConfirmResult.DONT_SAVE) {
|
||||
saveOrRevertPromise = this.textFileService.revertAll(null, { soft: true }).then(() => true);
|
||||
} else {
|
||||
@@ -646,7 +644,7 @@ export abstract class BaseCloseAllAction extends Action {
|
||||
});
|
||||
}
|
||||
|
||||
protected abstract doCloseAll(): TPromise<any>;
|
||||
protected abstract doCloseAll(): Thenable<any>;
|
||||
}
|
||||
|
||||
export class CloseAllEditorsAction extends BaseCloseAllAction {
|
||||
@@ -663,8 +661,8 @@ export class CloseAllEditorsAction extends BaseCloseAllAction {
|
||||
super(id, label, 'action-close-all-files', textFileService, editorGroupService);
|
||||
}
|
||||
|
||||
protected doCloseAll(): TPromise<any> {
|
||||
return TPromise.join(this.groupsToClose.map(g => g.closeAllEditors()));
|
||||
protected doCloseAll(): Promise<any> {
|
||||
return Promise.all(this.groupsToClose.map(g => g.closeAllEditors()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -682,8 +680,8 @@ export class CloseAllEditorGroupsAction extends BaseCloseAllAction {
|
||||
super(id, label, void 0, textFileService, editorGroupService);
|
||||
}
|
||||
|
||||
protected doCloseAll(): TPromise<any> {
|
||||
return TPromise.join(this.groupsToClose.map(g => g.closeAllEditors())).then(() => {
|
||||
protected doCloseAll(): Promise<any> {
|
||||
return Promise.all(this.groupsToClose.map(g => g.closeAllEditors())).then(() => {
|
||||
this.groupsToClose.forEach(group => this.editorGroupService.removeGroup(group));
|
||||
});
|
||||
}
|
||||
@@ -702,11 +700,11 @@ export class CloseEditorsInOtherGroupsAction extends Action {
|
||||
super(id, label);
|
||||
}
|
||||
|
||||
run(context?: IEditorIdentifier): TPromise<any> {
|
||||
run(context?: IEditorIdentifier): Thenable<any> {
|
||||
const groupToSkip = context ? this.editorGroupService.getGroup(context.groupId) : this.editorGroupService.activeGroup;
|
||||
return TPromise.join(this.editorGroupService.getGroups(GroupsOrder.MOST_RECENTLY_ACTIVE).map(g => {
|
||||
return Promise.all(this.editorGroupService.getGroups(GroupsOrder.MOST_RECENTLY_ACTIVE).map(g => {
|
||||
if (g.id === groupToSkip.id) {
|
||||
return TPromise.as(null);
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
|
||||
return g.closeAllEditors();
|
||||
@@ -714,6 +712,30 @@ export class CloseEditorsInOtherGroupsAction extends Action {
|
||||
}
|
||||
}
|
||||
|
||||
export class CloseEditorInAllGroupsAction extends Action {
|
||||
|
||||
static readonly ID = 'workbench.action.closeEditorInAllGroups';
|
||||
static readonly LABEL = nls.localize('closeEditorInAllGroups', "Close Editor in All Groups");
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
label: string,
|
||||
@IEditorGroupsService private editorGroupService: IEditorGroupsService,
|
||||
@IEditorService private editorService: IEditorService
|
||||
) {
|
||||
super(id, label);
|
||||
}
|
||||
|
||||
run(): Thenable<any> {
|
||||
const activeEditor = this.editorService.activeEditor;
|
||||
if (activeEditor) {
|
||||
return Promise.all(this.editorGroupService.getGroups(GroupsOrder.MOST_RECENTLY_ACTIVE).map(g => g.closeEditor(activeEditor)));
|
||||
}
|
||||
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
}
|
||||
|
||||
export class BaseMoveGroupAction extends Action {
|
||||
|
||||
constructor(
|
||||
@@ -725,7 +747,7 @@ export class BaseMoveGroupAction extends Action {
|
||||
super(id, label);
|
||||
}
|
||||
|
||||
run(context?: IEditorIdentifier): TPromise<any> {
|
||||
run(context?: IEditorIdentifier): Promise<any> {
|
||||
let sourceGroup: IEditorGroup;
|
||||
if (context && typeof context.groupId === 'number') {
|
||||
sourceGroup = this.editorGroupService.getGroup(context.groupId);
|
||||
@@ -738,7 +760,7 @@ export class BaseMoveGroupAction extends Action {
|
||||
this.editorGroupService.moveGroup(sourceGroup, targetGroup, this.direction);
|
||||
}
|
||||
|
||||
return TPromise.as(true);
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
|
||||
private findTargetGroup(sourceGroup: IEditorGroup): IEditorGroup {
|
||||
@@ -834,10 +856,10 @@ export class MinimizeOtherGroupsAction extends Action {
|
||||
super(id, label);
|
||||
}
|
||||
|
||||
run(): TPromise<any> {
|
||||
run(): Thenable<any> {
|
||||
this.editorGroupService.arrangeGroups(GroupsArrangement.MINIMIZE_OTHERS);
|
||||
|
||||
return TPromise.as(false);
|
||||
return Promise.resolve(false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -850,10 +872,10 @@ export class ResetGroupSizesAction extends Action {
|
||||
super(id, label);
|
||||
}
|
||||
|
||||
run(): TPromise<any> {
|
||||
run(): Thenable<any> {
|
||||
this.editorGroupService.arrangeGroups(GroupsArrangement.EVEN);
|
||||
|
||||
return TPromise.as(false);
|
||||
return Promise.resolve(false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -872,14 +894,13 @@ export class MaximizeGroupAction extends Action {
|
||||
super(id, label);
|
||||
}
|
||||
|
||||
run(): TPromise<any> {
|
||||
run(): Thenable<any> {
|
||||
if (this.editorService.activeEditor) {
|
||||
this.editorGroupService.arrangeGroups(GroupsArrangement.MINIMIZE_OTHERS);
|
||||
|
||||
return this.partService.setSideBarHidden(true);
|
||||
this.partService.setSideBarHidden(true);
|
||||
}
|
||||
|
||||
return TPromise.as(false);
|
||||
return Promise.resolve(false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -894,15 +915,15 @@ export abstract class BaseNavigateEditorAction extends Action {
|
||||
super(id, label);
|
||||
}
|
||||
|
||||
run(): TPromise<any> {
|
||||
run(): Thenable<any> {
|
||||
const result = this.navigate();
|
||||
if (!result) {
|
||||
return TPromise.as(false);
|
||||
return Promise.resolve(false);
|
||||
}
|
||||
|
||||
const { groupId, editor } = result;
|
||||
if (!editor) {
|
||||
return TPromise.as(false);
|
||||
return Promise.resolve(false);
|
||||
}
|
||||
|
||||
const group = this.editorGroupService.getGroup(groupId);
|
||||
@@ -1081,10 +1102,10 @@ export class NavigateForwardAction extends Action {
|
||||
super(id, label);
|
||||
}
|
||||
|
||||
run(): TPromise<any> {
|
||||
run(): Thenable<any> {
|
||||
this.historyService.forward();
|
||||
|
||||
return TPromise.as(null);
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1097,10 +1118,26 @@ export class NavigateBackwardsAction extends Action {
|
||||
super(id, label);
|
||||
}
|
||||
|
||||
run(): TPromise<any> {
|
||||
run(): Thenable<any> {
|
||||
this.historyService.back();
|
||||
|
||||
return TPromise.as(null);
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
}
|
||||
|
||||
export class NavigateToLastEditLocationAction extends Action {
|
||||
|
||||
static readonly ID = 'workbench.action.navigateToLastEditLocation';
|
||||
static readonly LABEL = nls.localize('navigateToLastEditLocation', "Go to Last Edit Location");
|
||||
|
||||
constructor(id: string, label: string, @IHistoryService private historyService: IHistoryService) {
|
||||
super(id, label);
|
||||
}
|
||||
|
||||
run(): Thenable<any> {
|
||||
this.historyService.openLastEditLocation();
|
||||
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1113,10 +1150,10 @@ export class NavigateLastAction extends Action {
|
||||
super(id, label);
|
||||
}
|
||||
|
||||
run(): TPromise<any> {
|
||||
run(): Thenable<any> {
|
||||
this.historyService.last();
|
||||
|
||||
return TPromise.as(null);
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1133,10 +1170,10 @@ export class ReopenClosedEditorAction extends Action {
|
||||
super(id, label);
|
||||
}
|
||||
|
||||
run(): TPromise<any> {
|
||||
run(): Thenable<any> {
|
||||
this.historyService.reopenLastClosedEditor();
|
||||
|
||||
return TPromise.as(false);
|
||||
return Promise.resolve(false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1148,15 +1185,21 @@ export class ClearRecentFilesAction extends Action {
|
||||
constructor(
|
||||
id: string,
|
||||
label: string,
|
||||
@IWindowsService private windowsService: IWindowsService
|
||||
@IWindowsService private windowsService: IWindowsService,
|
||||
@IHistoryService private historyService: IHistoryService
|
||||
) {
|
||||
super(id, label);
|
||||
}
|
||||
|
||||
run(): TPromise<any> {
|
||||
run(): Thenable<any> {
|
||||
|
||||
// Clear global recently opened
|
||||
this.windowsService.clearRecentlyOpened();
|
||||
|
||||
return TPromise.as(false);
|
||||
// Clear workspace specific recently opened
|
||||
this.historyService.clearRecentlyOpened();
|
||||
|
||||
return Promise.resolve(false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1195,14 +1238,14 @@ export class BaseQuickOpenEditorInGroupAction extends Action {
|
||||
super(id, label);
|
||||
}
|
||||
|
||||
run(): TPromise<any> {
|
||||
run(): Thenable<any> {
|
||||
const keys = this.keybindingService.lookupKeybindings(this.id);
|
||||
|
||||
|
||||
|
||||
this.quickOpenService.show(NAVIGATE_IN_ACTIVE_GROUP_PREFIX, { quickNavigateConfiguration: { keybindings: keys } });
|
||||
|
||||
return TPromise.as(true);
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1250,12 +1293,12 @@ export class OpenPreviousEditorFromHistoryAction extends Action {
|
||||
super(id, label);
|
||||
}
|
||||
|
||||
run(): TPromise<any> {
|
||||
run(): Thenable<any> {
|
||||
const keys = this.keybindingService.lookupKeybindings(this.id);
|
||||
|
||||
this.quickOpenService.show(null, { quickNavigateConfiguration: { keybindings: keys } });
|
||||
|
||||
return TPromise.as(true);
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1268,10 +1311,10 @@ export class OpenNextRecentlyUsedEditorAction extends Action {
|
||||
super(id, label);
|
||||
}
|
||||
|
||||
run(): TPromise<any> {
|
||||
run(): Thenable<any> {
|
||||
this.historyService.forward(true);
|
||||
|
||||
return TPromise.as(null);
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1284,10 +1327,10 @@ export class OpenPreviousRecentlyUsedEditorAction extends Action {
|
||||
super(id, label);
|
||||
}
|
||||
|
||||
run(): TPromise<any> {
|
||||
run(): Thenable<any> {
|
||||
this.historyService.back(true);
|
||||
|
||||
return TPromise.as(null);
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1304,12 +1347,12 @@ export class ClearEditorHistoryAction extends Action {
|
||||
super(id, label);
|
||||
}
|
||||
|
||||
run(): TPromise<any> {
|
||||
run(): Thenable<any> {
|
||||
|
||||
// Editor history
|
||||
this.historyService.clear();
|
||||
|
||||
return TPromise.as(true);
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1576,10 +1619,10 @@ export class BaseCreateEditorGroupAction extends Action {
|
||||
super(id, label);
|
||||
}
|
||||
|
||||
run(): TPromise<any> {
|
||||
run(): Thenable<any> {
|
||||
this.editorGroupService.addGroup(this.editorGroupService.activeGroup, this.direction, { activate: true });
|
||||
|
||||
return TPromise.as(true);
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -12,17 +12,16 @@ import { IEditorService } from 'vs/workbench/services/editor/common/editorServic
|
||||
import { EditorContextKeys } from 'vs/editor/common/editorContextKeys';
|
||||
import { TextDiffEditor } from 'vs/workbench/browser/parts/editor/textDiffEditor';
|
||||
import { KeyMod, KeyCode, KeyChord } from 'vs/base/common/keyCodes';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import URI from 'vs/base/common/uri';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { IQuickOpenService } from 'vs/platform/quickOpen/common/quickOpen';
|
||||
import { IDiffEditorOptions } from 'vs/editor/common/config/editorOptions';
|
||||
import { IListService } from 'vs/platform/list/browser/listService';
|
||||
import { List } from 'vs/base/browser/ui/list/listWidget';
|
||||
import { distinct } from 'vs/base/common/arrays';
|
||||
import { IEditorGroupsService, IEditorGroup, GroupDirection, GroupLocation, GroupsOrder, preferredSideBySideGroupDirection, EditorGroupLayout } from 'vs/workbench/services/group/common/editorGroupsService';
|
||||
import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { CommandsRegistry } from 'vs/platform/commands/common/commands';
|
||||
import { IConfigurationService, ConfigurationTarget } from 'vs/platform/configuration/common/configuration';
|
||||
import { CommandsRegistry, ICommandHandler } from 'vs/platform/commands/common/commands';
|
||||
import { MenuRegistry, MenuId } from 'vs/platform/actions/common/actions';
|
||||
|
||||
export const CLOSE_SAVED_EDITORS_COMMAND_ID = 'workbench.action.closeUnmodifiedEditors';
|
||||
export const CLOSE_EDITORS_IN_GROUP_COMMAND_ID = 'workbench.action.closeEditorsInGroup';
|
||||
@@ -36,7 +35,11 @@ export const MOVE_ACTIVE_EDITOR_COMMAND_ID = 'moveActiveEditor';
|
||||
export const LAYOUT_EDITOR_GROUPS_COMMAND_ID = 'layoutEditorGroups';
|
||||
export const KEEP_EDITOR_COMMAND_ID = 'workbench.action.keepEditor';
|
||||
export const SHOW_EDITORS_IN_GROUP = 'workbench.action.showEditorsInGroup';
|
||||
export const TOGGLE_DIFF_INLINE_MODE = 'toggle.diff.editorMode';
|
||||
|
||||
export const TOGGLE_DIFF_SIDE_BY_SIDE = 'toggle.diff.renderSideBySide';
|
||||
export const GOTO_NEXT_CHANGE = 'workbench.action.compareEditor.nextChange';
|
||||
export const GOTO_PREVIOUS_CHANGE = 'workbench.action.compareEditor.previousChange';
|
||||
export const TOGGLE_DIFF_IGNORE_TRIM_WHITESPACE = 'toggle.diff.ignoreTrimWhitespace';
|
||||
|
||||
export const SPLIT_EDITOR_UP = 'workbench.action.splitEditorUp';
|
||||
export const SPLIT_EDITOR_DOWN = 'workbench.action.splitEditorDown';
|
||||
@@ -46,6 +49,8 @@ export const SPLIT_EDITOR_RIGHT = 'workbench.action.splitEditorRight';
|
||||
export const NAVIGATE_ALL_EDITORS_GROUP_PREFIX = 'edt ';
|
||||
export const NAVIGATE_IN_ACTIVE_GROUP_PREFIX = 'edt active ';
|
||||
|
||||
export const OPEN_EDITOR_AT_INDEX_COMMAND_ID = 'workbench.action.openEditorAtIndex';
|
||||
|
||||
export interface ActiveEditorMoveArguments {
|
||||
to?: 'first' | 'last' | 'left' | 'right' | 'up' | 'down' | 'center' | 'position' | 'previous' | 'next';
|
||||
by?: 'tab' | 'group';
|
||||
@@ -77,7 +82,7 @@ function registerActiveEditorMoveCommand(): void {
|
||||
id: MOVE_ACTIVE_EDITOR_COMMAND_ID,
|
||||
weight: KeybindingWeight.WorkbenchContrib,
|
||||
when: EditorContextKeys.editorTextFocus,
|
||||
primary: null,
|
||||
primary: 0,
|
||||
handler: (accessor, args: any) => moveActiveEditor(args, accessor),
|
||||
description: {
|
||||
description: nls.localize('editorCommand.activeEditorMove.description', "Move the active editor by tabs or groups"),
|
||||
@@ -221,18 +226,18 @@ export function mergeAllGroups(editorGroupService: IEditorGroupsService): void {
|
||||
|
||||
function registerDiffEditorCommands(): void {
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
id: 'workbench.action.compareEditor.nextChange',
|
||||
id: GOTO_NEXT_CHANGE,
|
||||
weight: KeybindingWeight.WorkbenchContrib,
|
||||
when: TextCompareEditorVisibleContext,
|
||||
primary: null,
|
||||
primary: KeyMod.Alt | KeyCode.F5,
|
||||
handler: accessor => navigateInDiffEditor(accessor, true)
|
||||
});
|
||||
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
id: 'workbench.action.compareEditor.previousChange',
|
||||
id: GOTO_PREVIOUS_CHANGE,
|
||||
weight: KeybindingWeight.WorkbenchContrib,
|
||||
when: TextCompareEditorVisibleContext,
|
||||
primary: null,
|
||||
primary: KeyMod.Alt | KeyMod.Shift | KeyCode.F5,
|
||||
handler: accessor => navigateInDiffEditor(accessor, false)
|
||||
});
|
||||
|
||||
@@ -245,27 +250,66 @@ function registerDiffEditorCommands(): void {
|
||||
}
|
||||
}
|
||||
|
||||
function toggleDiffSideBySide(accessor: ServicesAccessor): void {
|
||||
const configurationService = accessor.get(IConfigurationService);
|
||||
|
||||
const newValue = !configurationService.getValue<boolean>('diffEditor.renderSideBySide');
|
||||
configurationService.updateValue('diffEditor.renderSideBySide', newValue, ConfigurationTarget.USER);
|
||||
}
|
||||
|
||||
function toggleDiffIgnoreTrimWhitespace(accessor: ServicesAccessor): void {
|
||||
const configurationService = accessor.get(IConfigurationService);
|
||||
|
||||
const newValue = !configurationService.getValue<boolean>('diffEditor.ignoreTrimWhitespace');
|
||||
configurationService.updateValue('diffEditor.ignoreTrimWhitespace', newValue, ConfigurationTarget.USER);
|
||||
}
|
||||
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
id: TOGGLE_DIFF_INLINE_MODE,
|
||||
id: TOGGLE_DIFF_SIDE_BY_SIDE,
|
||||
weight: KeybindingWeight.WorkbenchContrib,
|
||||
when: void 0,
|
||||
primary: void 0,
|
||||
handler: (accessor, resourceOrContext: URI | IEditorCommandsContext, context?: IEditorCommandsContext) => {
|
||||
const editorGroupService = accessor.get(IEditorGroupsService);
|
||||
handler: accessor => toggleDiffSideBySide(accessor)
|
||||
});
|
||||
|
||||
const { control } = resolveCommandsContext(editorGroupService, getCommandsContext(resourceOrContext, context));
|
||||
if (control instanceof TextDiffEditor) {
|
||||
const widget = control.getControl();
|
||||
const isInlineMode = !widget.renderSideBySide;
|
||||
widget.updateOptions(<IDiffEditorOptions>{
|
||||
renderSideBySide: isInlineMode
|
||||
});
|
||||
}
|
||||
}
|
||||
MenuRegistry.appendMenuItem(MenuId.CommandPalette, {
|
||||
command: {
|
||||
id: TOGGLE_DIFF_SIDE_BY_SIDE,
|
||||
title: {
|
||||
value: nls.localize('toggleInlineView', "Toggle Inline View"),
|
||||
original: 'Compare: Toggle Inline View'
|
||||
},
|
||||
category: nls.localize('compare', "Compare")
|
||||
},
|
||||
when: ContextKeyExpr.has('textCompareEditorActive')
|
||||
});
|
||||
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
id: TOGGLE_DIFF_IGNORE_TRIM_WHITESPACE,
|
||||
weight: KeybindingWeight.WorkbenchContrib,
|
||||
when: void 0,
|
||||
primary: void 0,
|
||||
handler: accessor => toggleDiffIgnoreTrimWhitespace(accessor)
|
||||
});
|
||||
}
|
||||
|
||||
function registerOpenEditorAtIndexCommands(): void {
|
||||
const openEditorAtIndex: ICommandHandler = (accessor: ServicesAccessor, editorIndex: number): void => {
|
||||
const editorService = accessor.get(IEditorService);
|
||||
const activeControl = editorService.activeControl;
|
||||
if (activeControl) {
|
||||
const editor = activeControl.group.getEditor(editorIndex);
|
||||
if (editor) {
|
||||
editorService.openEditor(editor);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// This command takes in the editor index number to open as an argument
|
||||
CommandsRegistry.registerCommand({
|
||||
id: OPEN_EDITOR_AT_INDEX_COMMAND_ID,
|
||||
handler: openEditorAtIndex
|
||||
});
|
||||
|
||||
// Keybindings to focus a specific index in the tab folder if tabs are enabled
|
||||
for (let i = 0; i < 9; i++) {
|
||||
@@ -273,24 +317,12 @@ function registerOpenEditorAtIndexCommands(): void {
|
||||
const visibleIndex = i + 1;
|
||||
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
id: 'workbench.action.openEditorAtIndex' + visibleIndex,
|
||||
id: OPEN_EDITOR_AT_INDEX_COMMAND_ID + visibleIndex,
|
||||
weight: KeybindingWeight.WorkbenchContrib,
|
||||
when: void 0,
|
||||
primary: KeyMod.Alt | toKeyCode(visibleIndex),
|
||||
mac: { primary: KeyMod.WinCtrl | toKeyCode(visibleIndex) },
|
||||
handler: accessor => {
|
||||
const editorService = accessor.get(IEditorService);
|
||||
|
||||
const activeControl = editorService.activeControl;
|
||||
if (activeControl) {
|
||||
const editor = activeControl.group.getEditor(editorIndex);
|
||||
if (editor) {
|
||||
return editorService.openEditor(editor).then(() => void 0);
|
||||
}
|
||||
}
|
||||
|
||||
return void 0;
|
||||
}
|
||||
handler: accessor => openEditorAtIndex(accessor, editorIndex)
|
||||
});
|
||||
}
|
||||
|
||||
@@ -434,7 +466,7 @@ function registerCloseEditorCommands() {
|
||||
contexts.push({ groupId: activeGroup.id }); // active group as fallback
|
||||
}
|
||||
|
||||
return TPromise.join(distinct(contexts.map(c => c.groupId)).map(groupId =>
|
||||
return Promise.all(distinct(contexts.map(c => c.groupId)).map(groupId =>
|
||||
editorGroupService.getGroup(groupId).closeEditors({ savedOnly: true })
|
||||
));
|
||||
}
|
||||
@@ -454,7 +486,7 @@ function registerCloseEditorCommands() {
|
||||
distinctGroupIds.push(editorGroupService.activeGroup.id);
|
||||
}
|
||||
|
||||
return TPromise.join(distinctGroupIds.map(groupId =>
|
||||
return Promise.all(distinctGroupIds.map(groupId =>
|
||||
editorGroupService.getGroup(groupId).closeAllEditors()
|
||||
));
|
||||
}
|
||||
@@ -477,7 +509,7 @@ function registerCloseEditorCommands() {
|
||||
|
||||
const groupIds = distinct(contexts.map(context => context.groupId));
|
||||
|
||||
return TPromise.join(groupIds.map(groupId => {
|
||||
return Promise.all(groupIds.map(groupId => {
|
||||
const group = editorGroupService.getGroup(groupId);
|
||||
const editors = contexts
|
||||
.filter(context => context.groupId === groupId)
|
||||
@@ -526,7 +558,7 @@ function registerCloseEditorCommands() {
|
||||
|
||||
const groupIds = distinct(contexts.map(context => context.groupId));
|
||||
|
||||
return TPromise.join(groupIds.map(groupId => {
|
||||
return Promise.all(groupIds.map(groupId => {
|
||||
const group = editorGroupService.getGroup(groupId);
|
||||
const editors = contexts
|
||||
.filter(context => context.groupId === groupId)
|
||||
@@ -551,7 +583,7 @@ function registerCloseEditorCommands() {
|
||||
return group.closeEditors({ direction: CloseDirection.RIGHT, except: editor });
|
||||
}
|
||||
|
||||
return TPromise.as(false);
|
||||
return Promise.resolve(false);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -568,7 +600,7 @@ function registerCloseEditorCommands() {
|
||||
return group.pinEditor(editor);
|
||||
}
|
||||
|
||||
return TPromise.as(false);
|
||||
return Promise.resolve(false);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -647,7 +679,7 @@ export function getMultiSelectedEditorContexts(editorContext: IEditorCommandsCon
|
||||
|
||||
// First check for a focused list to return the selected items from
|
||||
const list = listService.lastFocusedList;
|
||||
if (list instanceof List && list.isDOMFocused()) {
|
||||
if (list instanceof List && list.getHTMLElement() === document.activeElement) {
|
||||
const elementToContext = (element: IEditorIdentifier | IEditorGroup) => {
|
||||
if (isEditorGroup(element)) {
|
||||
return { groupId: element.id, editorIndex: void 0 };
|
||||
|
||||
@@ -3,8 +3,6 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import { dispose, Disposable, IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { EditorInput, EditorOptions } from 'vs/workbench/common/editor';
|
||||
import { Dimension, show, hide, addClass } from 'vs/base/browser/dom';
|
||||
@@ -15,7 +13,6 @@ import { IPartService } from 'vs/workbench/services/part/common/partService';
|
||||
import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IProgressService, LongRunningOperation } from 'vs/platform/progress/common/progress';
|
||||
import { toWinJsPromise } from 'vs/base/common/async';
|
||||
import { IEditorGroupView, DEFAULT_EDITOR_MIN_DIMENSIONS, DEFAULT_EDITOR_MAX_DIMENSIONS } from 'vs/workbench/browser/parts/editor/editor';
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
|
||||
@@ -171,11 +168,11 @@ export class EditorControl extends Disposable {
|
||||
|
||||
// Show progress while setting input after a certain timeout. If the workbench is opening
|
||||
// be more relaxed about progress showing by increasing the delay a little bit to reduce flicker.
|
||||
const operation = this.editorOperation.start(this.partService.isCreated() ? 800 : 3200);
|
||||
const operation = this.editorOperation.start(this.partService.isRestored() ? 800 : 3200);
|
||||
|
||||
// Call into editor control
|
||||
const editorWillChange = !inputMatches;
|
||||
return toWinJsPromise(control.setInput(editor, options, operation.token)).then(() => {
|
||||
return TPromise.wrap(control.setInput(editor, options, operation.token)).then(() => {
|
||||
|
||||
// Focus (unless prevented or another operation is running)
|
||||
if (operation.isCurrent()) {
|
||||
@@ -233,15 +230,9 @@ export class EditorControl extends Disposable {
|
||||
}
|
||||
}
|
||||
|
||||
shutdown(): void {
|
||||
|
||||
// Forward to all editor controls
|
||||
this.controls.forEach(editor => editor.shutdown());
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this.activeControlDisposeables = dispose(this.activeControlDisposeables);
|
||||
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,8 +3,6 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import 'vs/css!./media/editordroptarget';
|
||||
import { LocalSelectionTransfer, DraggedEditorIdentifier, ResourcesDropHandler, DraggedEditorGroupIdentifier, DragAndDropObserver } from 'vs/workbench/browser/dnd';
|
||||
import { addDisposableListener, EventType, EventHelper, isAncestor, toggleClass, addClass, removeClass } from 'vs/base/browser/dom';
|
||||
@@ -17,6 +15,7 @@ import { isMacintosh } from 'vs/base/common/platform';
|
||||
import { GroupDirection, MergeGroupMode } from 'vs/workbench/services/group/common/editorGroupsService';
|
||||
import { toDisposable } from 'vs/base/common/lifecycle';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { RunOnceScheduler } from 'vs/base/common/async';
|
||||
|
||||
interface IDropOperation {
|
||||
splitDirection?: GroupDirection;
|
||||
@@ -32,6 +31,8 @@ class DropOverlay extends Themable {
|
||||
private currentDropOperation: IDropOperation;
|
||||
private _disposed: boolean;
|
||||
|
||||
private cleanupOverlayScheduler: RunOnceScheduler;
|
||||
|
||||
private readonly editorTransfer = LocalSelectionTransfer.getInstance<DraggedEditorIdentifier>();
|
||||
private readonly groupTransfer = LocalSelectionTransfer.getInstance<DraggedEditorGroupIdentifier>();
|
||||
|
||||
@@ -43,6 +44,8 @@ class DropOverlay extends Themable {
|
||||
) {
|
||||
super(themeService);
|
||||
|
||||
this.cleanupOverlayScheduler = this._register(new RunOnceScheduler(() => this.dispose(), 300));
|
||||
|
||||
this.create();
|
||||
}
|
||||
|
||||
@@ -118,6 +121,11 @@ class DropOverlay extends Themable {
|
||||
|
||||
// Position overlay
|
||||
this.positionOverlay(e.offsetX, e.offsetY, isDraggingGroup);
|
||||
|
||||
// Make sure to stop any running cleanup scheduler to remove the overlay
|
||||
if (this.cleanupOverlayScheduler.isScheduled()) {
|
||||
this.cleanupOverlayScheduler.cancel();
|
||||
}
|
||||
},
|
||||
|
||||
onDragLeave: e => this.dispose(),
|
||||
@@ -144,9 +152,9 @@ class DropOverlay extends Themable {
|
||||
// To protect against this issue we always destroy the overlay as soon as we detect a
|
||||
// mouse event over it. The delay is used to guarantee we are not interfering with the
|
||||
// actual DROP event that can also trigger a mouse over event.
|
||||
setTimeout(() => {
|
||||
this.dispose();
|
||||
}, 300);
|
||||
if (!this.cleanupOverlayScheduler.isScheduled()) {
|
||||
this.cleanupOverlayScheduler.schedule();
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
@@ -3,8 +3,6 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import 'vs/css!./media/editorgroupview';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { EditorGroup, IEditorOpenOptions, EditorCloseEvent, ISerializedEditorGroup, isSerializedEditorGroup } from 'vs/workbench/common/editor/editorGroup';
|
||||
@@ -26,7 +24,7 @@ import { IProgressService } from 'vs/platform/progress/common/progress';
|
||||
import { ProgressService } from 'vs/workbench/services/progress/browser/progressService';
|
||||
import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor';
|
||||
import { localize } from 'vs/nls';
|
||||
import { isPromiseCanceledError, isErrorWithActions, IErrorWithActions } from 'vs/base/common/errors';
|
||||
import { isPromiseCanceledError } from 'vs/base/common/errors';
|
||||
import { dispose, IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { Severity, INotificationService, INotificationActions } from 'vs/platform/notification/common/notification';
|
||||
import { toErrorMessage } from 'vs/base/common/errorMessage';
|
||||
@@ -49,6 +47,9 @@ import { IContextMenuService } from 'vs/platform/contextview/browser/contextView
|
||||
// {{SQL CARBON EDIT}}
|
||||
import { ICommandService } from 'vs/platform/commands/common/commands';
|
||||
import { GlobalNewUntitledFileAction } from 'vs/workbench/parts/files/electron-browser/fileActions';
|
||||
// {{SQL CARBON EDIT}} - End
|
||||
import { isErrorWithActions, IErrorWithActions } from 'vs/base/common/errorsWithActions';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
|
||||
export class EditorGroupView extends Themable implements IEditorGroupView {
|
||||
|
||||
@@ -99,7 +100,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
|
||||
private active: boolean;
|
||||
private dimension: Dimension;
|
||||
|
||||
private _whenRestored: TPromise<void>;
|
||||
private _whenRestored: Thenable<void>;
|
||||
private isRestored: boolean;
|
||||
|
||||
private scopedInstantiationService: IInstantiationService;
|
||||
@@ -250,7 +251,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
|
||||
if (this.isEmpty()) {
|
||||
EventHelper.stop(e);
|
||||
// {{SQL CARBON EDIT}}
|
||||
this.commandService.executeCommand(GlobalNewUntitledFileAction.ID).done(undefined, err => this.notificationService.warn(err));
|
||||
this.commandService.executeCommand(GlobalNewUntitledFileAction.ID).then(undefined, err => this.notificationService.warn(err));
|
||||
}
|
||||
}));
|
||||
|
||||
@@ -313,8 +314,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
|
||||
// Show it
|
||||
this.contextMenuService.showContextMenu({
|
||||
getAnchor: () => anchor,
|
||||
getActions: () => TPromise.as(actions),
|
||||
getKeyBinding: action => this.keybindingService.lookupKeybinding(action.id),
|
||||
getActions: () => actions,
|
||||
onHide: () => this.focus()
|
||||
});
|
||||
}
|
||||
@@ -404,9 +404,9 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
|
||||
}
|
||||
}
|
||||
|
||||
private restoreEditors(from: IEditorGroupView | ISerializedEditorGroup): TPromise<void> {
|
||||
private restoreEditors(from: IEditorGroupView | ISerializedEditorGroup): Thenable<void> {
|
||||
if (this._group.count === 0) {
|
||||
return TPromise.as(void 0); // nothing to show
|
||||
return Promise.resolve(void 0); // nothing to show
|
||||
}
|
||||
|
||||
// Determine editor options
|
||||
@@ -421,11 +421,16 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
|
||||
options.pinned = this._group.isPinned(activeEditor); // preserve pinned state
|
||||
options.preserveFocus = true; // handle focus after editor is opened
|
||||
|
||||
const activeElement = document.activeElement;
|
||||
|
||||
// Show active editor
|
||||
return this.doShowEditor(activeEditor, true, options).then(() => {
|
||||
|
||||
// Set focused now if this is the active group
|
||||
if (this.accessor.activeGroup === this) {
|
||||
// Set focused now if this is the active group and focus has
|
||||
// not changed meanwhile. This prevents focus from being
|
||||
// stolen accidentally on startup when the user already
|
||||
// clicked somewhere.
|
||||
if (this.accessor.activeGroup === this && activeElement === document.activeElement) {
|
||||
this.focus();
|
||||
}
|
||||
});
|
||||
@@ -604,7 +609,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
|
||||
return this._disposed;
|
||||
}
|
||||
|
||||
get whenRestored(): TPromise<void> {
|
||||
get whenRestored(): Thenable<void> {
|
||||
return this._whenRestored;
|
||||
}
|
||||
|
||||
@@ -728,6 +733,11 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
|
||||
|
||||
openEditor(editor: EditorInput, options?: EditorOptions): TPromise<void> {
|
||||
|
||||
// Guard against invalid inputs
|
||||
if (!editor) {
|
||||
return TPromise.as(void 0);
|
||||
}
|
||||
|
||||
// Editor opening event allows for prevention
|
||||
const event = new EditorOpeningEvent(this._group.id, editor, options);
|
||||
this._onWillOpenEditor.fire(event);
|
||||
@@ -764,13 +774,21 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
|
||||
this.accessor.activateGroup(this);
|
||||
}
|
||||
|
||||
// Actually move the editor if a specific index is provided and we figure
|
||||
// out that the editor is already opened at a different index. This
|
||||
// ensures the right set of events are fired to the outside.
|
||||
if (typeof openEditorOptions.index === 'number') {
|
||||
const indexOfEditor = this._group.indexOf(editor);
|
||||
if (indexOfEditor !== -1 && indexOfEditor !== openEditorOptions.index) {
|
||||
this.doMoveEditorInsideGroup(editor, openEditorOptions);
|
||||
}
|
||||
}
|
||||
|
||||
// Update model
|
||||
this._group.openEditor(editor, openEditorOptions);
|
||||
|
||||
// Show editor
|
||||
const showEditorResult = this.doShowEditor(editor, openEditorOptions.active, options);
|
||||
|
||||
return showEditorResult;
|
||||
return this.doShowEditor(editor, openEditorOptions.active, options);
|
||||
}
|
||||
|
||||
private doShowEditor(editor: EditorInput, active: boolean, options?: EditorOptions): TPromise<void> {
|
||||
@@ -846,7 +864,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
|
||||
const startingIndex = this.getIndexOfEditor(editor) + 1;
|
||||
|
||||
// Open the other ones inactive
|
||||
return TPromise.join(editors.map(({ editor, options }, index) => {
|
||||
return Promise.all(editors.map(({ editor, options }, index) => {
|
||||
const adjustedEditorOptions = options || new EditorOptions();
|
||||
adjustedEditorOptions.inactive = true;
|
||||
adjustedEditorOptions.pinned = true;
|
||||
@@ -963,13 +981,13 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
|
||||
this.doCloseInactiveEditor(editor);
|
||||
}
|
||||
|
||||
// Forward to title control & breadcrumbs
|
||||
// Forward to title control
|
||||
this.titleAreaControl.closeEditor(editor);
|
||||
}
|
||||
|
||||
private doCloseActiveEditor(focusNext = this.accessor.activeGroup === this, fromError?: boolean): void {
|
||||
const editorToClose = this.activeEditor;
|
||||
const editorHasFocus = isAncestor(document.activeElement, this.element);
|
||||
const restoreFocus = this.shouldRestoreFocus(this.element);
|
||||
|
||||
// Optimization: if we are about to close the last editor in this group and settings
|
||||
// are configured to close the group since it will be empty, we first set the last
|
||||
@@ -983,7 +1001,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
|
||||
const mostRecentlyActiveGroups = this.accessor.getGroups(GroupsOrder.MOST_RECENTLY_ACTIVE);
|
||||
const nextActiveGroup = mostRecentlyActiveGroups[1]; // [0] will be the current one, so take [1]
|
||||
if (nextActiveGroup) {
|
||||
if (editorHasFocus) {
|
||||
if (restoreFocus) {
|
||||
nextActiveGroup.focus();
|
||||
} else {
|
||||
this.accessor.activateGroup(nextActiveGroup);
|
||||
@@ -1020,7 +1038,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
|
||||
this.editorControl.closeEditor(editorToClose);
|
||||
|
||||
// Restore focus to group container as needed unless group gets closed
|
||||
if (editorHasFocus && !closeEmptyGroup) {
|
||||
if (restoreFocus && !closeEmptyGroup) {
|
||||
this.focus();
|
||||
}
|
||||
|
||||
@@ -1034,6 +1052,17 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
|
||||
}
|
||||
}
|
||||
|
||||
private shouldRestoreFocus(target: Element): boolean {
|
||||
const activeElement = document.activeElement;
|
||||
|
||||
if (activeElement === document.body) {
|
||||
return true; // always restore focus if nothing is focused currently
|
||||
}
|
||||
|
||||
// otherwise check for the active element being an ancestor of the target
|
||||
return isAncestor(activeElement, target);
|
||||
}
|
||||
|
||||
private doCloseInactiveEditor(editor: EditorInput) {
|
||||
|
||||
// Update model
|
||||
@@ -1368,10 +1397,6 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
|
||||
|
||||
//#endregion
|
||||
|
||||
shutdown(): void {
|
||||
this.editorControl.shutdown();
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this._disposed = true;
|
||||
|
||||
@@ -1427,7 +1452,7 @@ registerThemingParticipant((theme, collector, environment) => {
|
||||
const letterpress = `resources/letterpress${theme.type === 'dark' ? '-dark' : theme.type === 'hc' ? '-hc' : ''}.svg`;
|
||||
collector.addRule(`
|
||||
.monaco-workbench > .part.editor > .content .editor-group-container.empty .editor-group-letterpress {
|
||||
background-image: url('${join(environment.appRoot, letterpress)}')
|
||||
background-image: url('${URI.file(join(environment.appRoot, letterpress)).toString()}')
|
||||
}
|
||||
`);
|
||||
|
||||
|
||||
@@ -3,20 +3,18 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import 'vs/workbench/browser/parts/editor/editor.contribution';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import { Part } from 'vs/workbench/browser/part';
|
||||
import { Dimension, isAncestor, toggleClass, addClass, $ } from 'vs/base/browser/dom';
|
||||
import { Event, Emitter, once, Relay, anyEvent } from 'vs/base/common/event';
|
||||
import { contrastBorder, editorBackground, registerColor } from 'vs/platform/theme/common/colorRegistry';
|
||||
import { contrastBorder, editorBackground } from 'vs/platform/theme/common/colorRegistry';
|
||||
import { GroupDirection, IAddGroupOptions, GroupsArrangement, GroupOrientation, IMergeGroupOptions, MergeGroupMode, ICopyEditorOptions, GroupsOrder, GroupChangeKind, GroupLocation, IFindGroupScope, EditorGroupLayout, GroupLayoutArgument } from 'vs/workbench/services/group/common/editorGroupsService';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { Direction, SerializableGrid, Sizing, ISerializedGrid, Orientation, ISerializedNode, GridBranchNode, isGridBranchNode, GridNode, createSerializedGrid, Grid } from 'vs/base/browser/ui/grid/grid';
|
||||
import { Direction, SerializableGrid, Sizing, ISerializedGrid, Orientation, GridBranchNode, isGridBranchNode, GridNode, createSerializedGrid, Grid } from 'vs/base/browser/ui/grid/grid';
|
||||
import { GroupIdentifier, IWorkbenchEditorConfiguration } from 'vs/workbench/common/editor';
|
||||
import { values } from 'vs/base/common/map';
|
||||
import { EDITOR_GROUP_BORDER } from 'vs/workbench/common/theme';
|
||||
import { EDITOR_GROUP_BORDER, EDITOR_PANE_BACKGROUND } from 'vs/workbench/common/theme';
|
||||
import { distinct } from 'vs/base/common/arrays';
|
||||
import { IEditorGroupsAccessor, IEditorGroupView, IEditorPartOptions, getEditorPartOptions, impactsEditorPartOptions, IEditorPartOptionsChangeEvent, EditorGroupsServiceImpl } from 'vs/workbench/browser/parts/editor/editor';
|
||||
import { EditorGroupView } from 'vs/workbench/browser/parts/editor/editorGroupView';
|
||||
@@ -24,15 +22,14 @@ import { IConfigurationService, IConfigurationChangeEvent } from 'vs/platform/co
|
||||
import { IDisposable, dispose, toDisposable } from 'vs/base/common/lifecycle';
|
||||
import { assign } from 'vs/base/common/objects';
|
||||
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
|
||||
import { Scope } from 'vs/workbench/common/memento';
|
||||
import { ISerializedEditorGroup, isSerializedEditorGroup } from 'vs/workbench/common/editor/editorGroup';
|
||||
import { TValueCallback, TPromise } from 'vs/base/common/winjs.base';
|
||||
import { always } from 'vs/base/common/async';
|
||||
import { EditorDropTarget } from 'vs/workbench/browser/parts/editor/editorDropTarget';
|
||||
import { localize } from 'vs/nls';
|
||||
import { Color } from 'vs/base/common/color';
|
||||
import { CenteredViewLayout } from 'vs/base/browser/ui/centered/centeredViewLayout';
|
||||
import { IView, orthogonal } from 'vs/base/browser/ui/grid/gridview';
|
||||
import { onUnexpectedError } from 'vs/base/common/errors';
|
||||
|
||||
// {{SQL CARBON EDIT}}
|
||||
import { convertEditorInput } from 'sql/parts/common/customInputConverter';
|
||||
@@ -86,12 +83,6 @@ class GridWidgetView<T extends IView> implements IView {
|
||||
}
|
||||
}
|
||||
|
||||
export const EDITOR_PANE_BACKGROUND = registerColor('editorPane.background', {
|
||||
dark: editorBackground,
|
||||
light: editorBackground,
|
||||
hc: editorBackground
|
||||
}, localize('editorPaneBackground', "Background color of the editor pane visible on the left and right side of the centered editor layout."));
|
||||
|
||||
export class EditorPart extends Part implements EditorGroupsServiceImpl, IEditorGroupsAccessor {
|
||||
|
||||
_serviceBrand: any;
|
||||
@@ -128,7 +119,7 @@ export class EditorPart extends Part implements EditorGroupsServiceImpl, IEditor
|
||||
private dimension: Dimension;
|
||||
private _preferredSize: Dimension;
|
||||
|
||||
private memento: object;
|
||||
private workspaceMemento: object;
|
||||
private globalMemento: object;
|
||||
|
||||
private _partOptions: IEditorPartOptions;
|
||||
@@ -142,8 +133,8 @@ export class EditorPart extends Part implements EditorGroupsServiceImpl, IEditor
|
||||
private gridWidget: SerializableGrid<IEditorGroupView>;
|
||||
private gridWidgetView: GridWidgetView<IEditorGroupView>;
|
||||
|
||||
private _whenRestored: TPromise<void>;
|
||||
private whenRestoredComplete: TValueCallback<void>;
|
||||
private _whenRestored: Thenable<void>;
|
||||
private whenRestoredResolve: () => void;
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
@@ -151,19 +142,18 @@ export class EditorPart extends Part implements EditorGroupsServiceImpl, IEditor
|
||||
@IInstantiationService private instantiationService: IInstantiationService,
|
||||
@IThemeService themeService: IThemeService,
|
||||
@IConfigurationService private configurationService: IConfigurationService,
|
||||
@IStorageService private storageService: IStorageService
|
||||
@IStorageService storageService: IStorageService
|
||||
) {
|
||||
super(id, { hasTitle: false }, themeService);
|
||||
super(id, { hasTitle: false }, themeService, storageService);
|
||||
|
||||
this.gridWidgetView = new GridWidgetView<IEditorGroupView>();
|
||||
|
||||
this._partOptions = getEditorPartOptions(this.configurationService.getValue<IWorkbenchEditorConfiguration>());
|
||||
this.memento = this.getMemento(this.storageService, Scope.WORKSPACE);
|
||||
this.globalMemento = this.getMemento(this.storageService, Scope.GLOBAL);
|
||||
|
||||
this._whenRestored = new TPromise(resolve => {
|
||||
this.whenRestoredComplete = resolve;
|
||||
});
|
||||
this.workspaceMemento = this.getMemento(StorageScope.WORKSPACE);
|
||||
this.globalMemento = this.getMemento(StorageScope.GLOBAL);
|
||||
|
||||
this._whenRestored = new Promise(resolve => (this.whenRestoredResolve = resolve));
|
||||
|
||||
this.registerListeners();
|
||||
}
|
||||
@@ -236,7 +226,7 @@ export class EditorPart extends Part implements EditorGroupsServiceImpl, IEditor
|
||||
return this.gridWidget.orientation === Orientation.VERTICAL ? GroupOrientation.VERTICAL : GroupOrientation.HORIZONTAL;
|
||||
}
|
||||
|
||||
get whenRestored(): TPromise<void> {
|
||||
get whenRestored(): Thenable<void> {
|
||||
return this._whenRestored;
|
||||
}
|
||||
|
||||
@@ -376,7 +366,7 @@ export class EditorPart extends Part implements EditorGroupsServiceImpl, IEditor
|
||||
}
|
||||
|
||||
applyLayout(layout: EditorGroupLayout): void {
|
||||
const gridHasFocus = isAncestor(document.activeElement, this.container);
|
||||
const restoreFocus = this.shouldRestoreFocus(this.container);
|
||||
|
||||
// Determine how many groups we need overall
|
||||
let layoutGroupsCount = 0;
|
||||
@@ -429,19 +419,33 @@ export class EditorPart extends Part implements EditorGroupsServiceImpl, IEditor
|
||||
// Mark preferred size as changed
|
||||
this.resetPreferredSize();
|
||||
|
||||
// Events for groupd that got added
|
||||
// Events for groups that got added
|
||||
this.getGroups(GroupsOrder.GRID_APPEARANCE).forEach(groupView => {
|
||||
if (currentGroupViews.indexOf(groupView) === -1) {
|
||||
this._onDidAddGroup.fire(groupView);
|
||||
}
|
||||
});
|
||||
|
||||
// Update labels
|
||||
this.updateGroupLabels();
|
||||
|
||||
// Restore focus as needed
|
||||
if (gridHasFocus) {
|
||||
if (restoreFocus) {
|
||||
this._activeGroup.focus();
|
||||
}
|
||||
}
|
||||
|
||||
private shouldRestoreFocus(target: Element): boolean {
|
||||
const activeElement = document.activeElement;
|
||||
|
||||
if (activeElement === document.body) {
|
||||
return true; // always restore focus if nothing is focused currently
|
||||
}
|
||||
|
||||
// otherwise check for the active element being an ancestor of the target
|
||||
return isAncestor(activeElement, target);
|
||||
}
|
||||
|
||||
private isTwoDimensionalGrid(): boolean {
|
||||
const views = this.gridWidget.getViews();
|
||||
if (isGridBranchNode(views)) {
|
||||
@@ -485,6 +489,9 @@ export class EditorPart extends Part implements EditorGroupsServiceImpl, IEditor
|
||||
// Event
|
||||
this._onDidAddGroup.fire(newGroupView);
|
||||
|
||||
// Update labels
|
||||
this.updateGroupLabels();
|
||||
|
||||
return newGroupView;
|
||||
}
|
||||
|
||||
@@ -622,7 +629,7 @@ export class EditorPart extends Part implements EditorGroupsServiceImpl, IEditor
|
||||
}
|
||||
|
||||
private doRemoveEmptyGroup(groupView: IEditorGroupView): void {
|
||||
const gridHasFocus = isAncestor(document.activeElement, this.container);
|
||||
const restoreFocus = this.shouldRestoreFocus(this.container);
|
||||
|
||||
// Activate next group if the removed one was active
|
||||
if (this._activeGroup === groupView) {
|
||||
@@ -637,16 +644,12 @@ export class EditorPart extends Part implements EditorGroupsServiceImpl, IEditor
|
||||
|
||||
// Restore focus if we had it previously (we run this after gridWidget.removeView() is called
|
||||
// because removing a view can mean to reparent it and thus focus would be removed otherwise)
|
||||
if (gridHasFocus) {
|
||||
if (restoreFocus) {
|
||||
this._activeGroup.focus();
|
||||
}
|
||||
|
||||
// Update labels: since our labels are created using the index of the
|
||||
// group, removing a group might produce gaps. So we iterate over all
|
||||
// groups and reassign the label based on the index.
|
||||
this.getGroups(GroupsOrder.CREATION_TIME).forEach((group, index) => {
|
||||
group.setLabel(this.getGroupLabel(index + 1));
|
||||
});
|
||||
// Update labels
|
||||
this.updateGroupLabels();
|
||||
|
||||
// Update container
|
||||
this.updateContainer();
|
||||
@@ -658,10 +661,6 @@ export class EditorPart extends Part implements EditorGroupsServiceImpl, IEditor
|
||||
this._onDidRemoveGroup.fire(groupView);
|
||||
}
|
||||
|
||||
private getGroupLabel(index: number): string {
|
||||
return localize('groupLabel', "Group {0}", index);
|
||||
}
|
||||
|
||||
moveGroup(group: IEditorGroupView | GroupIdentifier, location: IEditorGroupView | GroupIdentifier, direction: GroupDirection): IEditorGroupView {
|
||||
const sourceView = this.assertGroupView(group);
|
||||
const targetView = this.assertGroupView(location);
|
||||
@@ -670,15 +669,14 @@ export class EditorPart extends Part implements EditorGroupsServiceImpl, IEditor
|
||||
throw new Error('Cannot move group into its own');
|
||||
}
|
||||
|
||||
const groupHasFocus = isAncestor(document.activeElement, sourceView.element);
|
||||
const restoreFocus = this.shouldRestoreFocus(sourceView.element);
|
||||
|
||||
// Move is a simple remove and add of the same view
|
||||
this.gridWidget.removeView(sourceView, Sizing.Distribute);
|
||||
this.gridWidget.addView(sourceView, Sizing.Distribute, targetView, this.toGridViewDirection(direction));
|
||||
// Move through grid widget API
|
||||
this.gridWidget.moveView(sourceView, Sizing.Distribute, targetView, this.toGridViewDirection(direction));
|
||||
|
||||
// Restore focus if we had it previously (we run this after gridWidget.removeView() is called
|
||||
// because removing a view can mean to reparent it and thus focus would be removed otherwise)
|
||||
if (groupHasFocus) {
|
||||
if (restoreFocus) {
|
||||
sourceView.focus();
|
||||
}
|
||||
|
||||
@@ -692,13 +690,13 @@ export class EditorPart extends Part implements EditorGroupsServiceImpl, IEditor
|
||||
const groupView = this.assertGroupView(group);
|
||||
const locationView = this.assertGroupView(location);
|
||||
|
||||
const groupHasFocus = isAncestor(document.activeElement, groupView.element);
|
||||
const restoreFocus = this.shouldRestoreFocus(groupView.element);
|
||||
|
||||
// Copy the group view
|
||||
const copiedGroupView = this.doAddGroup(locationView, direction, groupView);
|
||||
|
||||
// Restore focus if we had it
|
||||
if (groupHasFocus) {
|
||||
if (restoreFocus) {
|
||||
copiedGroupView.focus();
|
||||
}
|
||||
|
||||
@@ -712,7 +710,7 @@ export class EditorPart extends Part implements EditorGroupsServiceImpl, IEditor
|
||||
// Move/Copy editors over into target
|
||||
let index = (options && typeof options.index === 'number') ? options.index : targetView.count;
|
||||
sourceView.editors.forEach(editor => {
|
||||
const inactive = !sourceView.isActive(editor);
|
||||
const inactive = !sourceView.isActive(editor) || this._activeGroup !== sourceView;
|
||||
const copyOptions: ICopyEditorOptions = { index, inactive, preserveFocus: inactive };
|
||||
|
||||
if (options && options.mode === MergeGroupMode.COPY_EDITORS) {
|
||||
@@ -802,6 +800,7 @@ export class EditorPart extends Part implements EditorGroupsServiceImpl, IEditor
|
||||
|
||||
centerLayout(active: boolean): void {
|
||||
this.centeredLayoutWidget.activate(active);
|
||||
this._activeGroup.focus();
|
||||
}
|
||||
|
||||
isLayoutCentered(): boolean {
|
||||
@@ -825,27 +824,47 @@ export class EditorPart extends Part implements EditorGroupsServiceImpl, IEditor
|
||||
}
|
||||
|
||||
// Signal restored
|
||||
always(TPromise.join(this.groups.map(group => group.whenRestored)), () => this.whenRestoredComplete(void 0));
|
||||
always(Promise.all(this.groups.map(group => group.whenRestored)), () => this.whenRestoredResolve());
|
||||
|
||||
// Update container
|
||||
this.updateContainer();
|
||||
}
|
||||
|
||||
private doCreateGridControlWithPreviousState(): void {
|
||||
const uiState = this.doGetPreviousState();
|
||||
const uiState = this.workspaceMemento[EditorPart.EDITOR_PART_UI_STATE_STORAGE_KEY] as IEditorPartUIState;
|
||||
if (uiState && uiState.serializedGrid) {
|
||||
try {
|
||||
|
||||
// MRU
|
||||
this.mostRecentActiveGroups = uiState.mostRecentActiveGroups;
|
||||
// MRU
|
||||
this.mostRecentActiveGroups = uiState.mostRecentActiveGroups;
|
||||
|
||||
// Grid Widget
|
||||
this.doCreateGridControlWithState(uiState.serializedGrid, uiState.activeGroup);
|
||||
// Grid Widget
|
||||
this.doCreateGridControlWithState(uiState.serializedGrid, uiState.activeGroup);
|
||||
|
||||
// Ensure last active group has focus
|
||||
this._activeGroup.focus();
|
||||
// Ensure last active group has focus
|
||||
this._activeGroup.focus();
|
||||
} catch (error) {
|
||||
this.handleGridRestoreError(error, uiState);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private handleGridRestoreError(error: Error, state: IEditorPartUIState): void {
|
||||
|
||||
// Log error
|
||||
onUnexpectedError(new Error(`Error restoring editor grid widget: ${error} (with state: ${JSON.stringify(state)})`));
|
||||
|
||||
// Clear any state we have from the failing restore
|
||||
if (this.gridWidget) {
|
||||
this.doSetGridWidget();
|
||||
}
|
||||
|
||||
this.groupViews.forEach(group => group.dispose());
|
||||
this.groupViews.clear();
|
||||
this._activeGroup = void 0;
|
||||
this.mostRecentActiveGroups = [];
|
||||
}
|
||||
|
||||
private doCreateGridControlWithState(serializedGrid: ISerializedGrid, activeGroupId: GroupIdentifier, editorGroupViewsToReuse?: IEditorGroupView[]): void {
|
||||
|
||||
// Determine group views to reuse if any
|
||||
@@ -857,8 +876,9 @@ export class EditorPart extends Part implements EditorGroupsServiceImpl, IEditor
|
||||
}
|
||||
|
||||
// Create new
|
||||
const groupViews: IEditorGroupView[] = [];
|
||||
const gridWidget = SerializableGrid.deserialize(serializedGrid, {
|
||||
fromJSON: (serializedEditorGroup: ISerializedEditorGroup) => {
|
||||
fromJSON: (serializedEditorGroup: ISerializedEditorGroup | null) => {
|
||||
let groupView: IEditorGroupView;
|
||||
if (reuseGroupViews.length > 0) {
|
||||
groupView = reuseGroupViews.shift();
|
||||
@@ -866,6 +886,8 @@ export class EditorPart extends Part implements EditorGroupsServiceImpl, IEditor
|
||||
groupView = this.doCreateGroupView(serializedEditorGroup);
|
||||
}
|
||||
|
||||
groupViews.push(groupView);
|
||||
|
||||
if (groupView.id === activeGroupId) {
|
||||
this.doSetGroupActive(groupView);
|
||||
}
|
||||
@@ -874,6 +896,18 @@ export class EditorPart extends Part implements EditorGroupsServiceImpl, IEditor
|
||||
}
|
||||
}, { styles: { separatorBorder: this.gridSeparatorBorder } });
|
||||
|
||||
// If the active group was not found when restoring the grid
|
||||
// make sure to make at least one group active. We always need
|
||||
// an active group.
|
||||
if (!this._activeGroup) {
|
||||
this.doSetGroupActive(groupViews[0]);
|
||||
}
|
||||
|
||||
// Validate MRU group views matches grid widget state
|
||||
if (this.mostRecentActiveGroups.some(groupId => !this.getGroup(groupId))) {
|
||||
this.mostRecentActiveGroups = groupViews.map(group => group.id);
|
||||
}
|
||||
|
||||
// Set it
|
||||
this.doSetGridWidget(gridWidget);
|
||||
}
|
||||
@@ -893,127 +927,25 @@ export class EditorPart extends Part implements EditorGroupsServiceImpl, IEditor
|
||||
this.onDidSetGridWidget.fire();
|
||||
}
|
||||
|
||||
private doGetPreviousState(): IEditorPartUIState {
|
||||
const legacyState = this.doGetPreviousLegacyState();
|
||||
if (legacyState) {
|
||||
return legacyState; // TODO@ben remove after a while
|
||||
}
|
||||
|
||||
return this.memento[EditorPart.EDITOR_PART_UI_STATE_STORAGE_KEY] as IEditorPartUIState;
|
||||
}
|
||||
|
||||
private doGetPreviousLegacyState(): IEditorPartUIState {
|
||||
const LEGACY_EDITOR_PART_UI_STATE_STORAGE_KEY = 'editorpart.uiState';
|
||||
const LEGACY_STACKS_MODEL_STORAGE_KEY = 'editorStacks.model';
|
||||
|
||||
interface ILegacyEditorPartUIState {
|
||||
ratio: number[];
|
||||
groupOrientation: 'vertical' | 'horizontal';
|
||||
}
|
||||
|
||||
interface ISerializedLegacyEditorStacksModel {
|
||||
groups: ISerializedEditorGroup[];
|
||||
active: number;
|
||||
}
|
||||
|
||||
let legacyUIState: ISerializedLegacyEditorStacksModel;
|
||||
const legacyUIStateRaw = this.storageService.get(LEGACY_STACKS_MODEL_STORAGE_KEY, StorageScope.WORKSPACE);
|
||||
if (legacyUIStateRaw) {
|
||||
try {
|
||||
legacyUIState = JSON.parse(legacyUIStateRaw);
|
||||
} catch (error) { /* ignore */ }
|
||||
}
|
||||
|
||||
if (legacyUIState) {
|
||||
this.storageService.remove(LEGACY_STACKS_MODEL_STORAGE_KEY, StorageScope.WORKSPACE);
|
||||
}
|
||||
|
||||
const legacyPartState = this.memento[LEGACY_EDITOR_PART_UI_STATE_STORAGE_KEY] as ILegacyEditorPartUIState;
|
||||
if (legacyPartState) {
|
||||
delete this.memento[LEGACY_EDITOR_PART_UI_STATE_STORAGE_KEY];
|
||||
}
|
||||
|
||||
if (legacyUIState && Array.isArray(legacyUIState.groups) && legacyUIState.groups.length > 0) {
|
||||
const splitHorizontally = legacyPartState && legacyPartState.groupOrientation === 'horizontal';
|
||||
|
||||
const legacyState: IEditorPartUIState = Object.create(null);
|
||||
|
||||
const positionOneGroup = legacyUIState.groups[0];
|
||||
const positionTwoGroup = legacyUIState.groups[1];
|
||||
const positionThreeGroup = legacyUIState.groups[2];
|
||||
|
||||
legacyState.activeGroup = legacyUIState.active;
|
||||
legacyState.mostRecentActiveGroups = [legacyUIState.active];
|
||||
|
||||
if (positionTwoGroup || positionThreeGroup) {
|
||||
if (!positionThreeGroup) {
|
||||
legacyState.mostRecentActiveGroups.push(legacyState.activeGroup === 0 ? 1 : 0);
|
||||
} else {
|
||||
if (legacyState.activeGroup === 0) {
|
||||
legacyState.mostRecentActiveGroups.push(1, 2);
|
||||
} else if (legacyState.activeGroup === 1) {
|
||||
legacyState.mostRecentActiveGroups.push(0, 2);
|
||||
} else {
|
||||
legacyState.mostRecentActiveGroups.push(0, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const toNode = function (group: ISerializedEditorGroup, size: number): ISerializedNode {
|
||||
return {
|
||||
data: group,
|
||||
size,
|
||||
type: 'leaf'
|
||||
};
|
||||
};
|
||||
|
||||
const baseSize = 1200; // just some number because layout() was not called yet, but we only need the proportions
|
||||
|
||||
// No split editor
|
||||
if (!positionTwoGroup) {
|
||||
legacyState.serializedGrid = {
|
||||
width: baseSize,
|
||||
height: baseSize,
|
||||
orientation: splitHorizontally ? Orientation.VERTICAL : Orientation.HORIZONTAL,
|
||||
root: toNode(positionOneGroup, baseSize)
|
||||
};
|
||||
}
|
||||
|
||||
// Split editor (2 or 3 columns)
|
||||
else {
|
||||
const children: ISerializedNode[] = [];
|
||||
|
||||
const size = positionThreeGroup ? baseSize / 3 : baseSize / 2;
|
||||
|
||||
children.push(toNode(positionOneGroup, size));
|
||||
children.push(toNode(positionTwoGroup, size));
|
||||
|
||||
if (positionThreeGroup) {
|
||||
children.push(toNode(positionThreeGroup, size));
|
||||
}
|
||||
|
||||
legacyState.serializedGrid = {
|
||||
width: baseSize,
|
||||
height: baseSize,
|
||||
orientation: splitHorizontally ? Orientation.VERTICAL : Orientation.HORIZONTAL,
|
||||
root: {
|
||||
data: children,
|
||||
size: baseSize,
|
||||
type: 'branch'
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return legacyState;
|
||||
}
|
||||
|
||||
return void 0;
|
||||
}
|
||||
|
||||
private updateContainer(): void {
|
||||
toggleClass(this.container, 'empty', this.isEmpty());
|
||||
}
|
||||
|
||||
private updateGroupLabels(): void {
|
||||
|
||||
// Since our labels are created using the index of the
|
||||
// group, adding/removing a group might produce gaps.
|
||||
// So we iterate over all groups and reassign the label
|
||||
// based on the index.
|
||||
this.getGroups(GroupsOrder.GRID_APPEARANCE).forEach((group, index) => {
|
||||
group.setLabel(this.getGroupLabel(index + 1));
|
||||
});
|
||||
}
|
||||
|
||||
private getGroupLabel(index: number): string {
|
||||
return localize('groupLabel', "Group {0}", index);
|
||||
}
|
||||
|
||||
private isEmpty(): boolean {
|
||||
return this.groupViews.size === 1 && this._activeGroup.isEmpty();
|
||||
}
|
||||
@@ -1041,7 +973,7 @@ export class EditorPart extends Part implements EditorGroupsServiceImpl, IEditor
|
||||
//this.editorGroupsControl.refreshTitles();
|
||||
}
|
||||
|
||||
shutdown(): void {
|
||||
protected saveState(): void {
|
||||
|
||||
// Persist grid UI state
|
||||
if (this.gridWidget) {
|
||||
@@ -1052,19 +984,21 @@ export class EditorPart extends Part implements EditorGroupsServiceImpl, IEditor
|
||||
};
|
||||
|
||||
if (this.isEmpty()) {
|
||||
delete this.memento[EditorPart.EDITOR_PART_UI_STATE_STORAGE_KEY];
|
||||
delete this.workspaceMemento[EditorPart.EDITOR_PART_UI_STATE_STORAGE_KEY];
|
||||
} else {
|
||||
this.memento[EditorPart.EDITOR_PART_UI_STATE_STORAGE_KEY] = uiState;
|
||||
this.workspaceMemento[EditorPart.EDITOR_PART_UI_STATE_STORAGE_KEY] = uiState;
|
||||
}
|
||||
}
|
||||
|
||||
// Persist centered view state
|
||||
this.globalMemento[EditorPart.EDITOR_PART_CENTERED_VIEW_STORAGE_KEY] = this.centeredLayoutWidget.state;
|
||||
const centeredLayoutState = this.centeredLayoutWidget.state;
|
||||
if (this.centeredLayoutWidget.isDefault(centeredLayoutState)) {
|
||||
delete this.globalMemento[EditorPart.EDITOR_PART_CENTERED_VIEW_STORAGE_KEY];
|
||||
} else {
|
||||
this.globalMemento[EditorPart.EDITOR_PART_CENTERED_VIEW_STORAGE_KEY] = centeredLayoutState;
|
||||
}
|
||||
|
||||
// Forward to all groups
|
||||
this.groupViews.forEach(group => group.shutdown());
|
||||
|
||||
super.shutdown();
|
||||
super.saveState();
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
|
||||
@@ -2,17 +2,15 @@
|
||||
* 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 'vs/css!./media/editorpicker';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import * as nls from 'vs/nls';
|
||||
import URI from 'vs/base/common/uri';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { IIconLabelValueOptions } from 'vs/base/browser/ui/iconLabel/iconLabel';
|
||||
import { IAutoFocus, Mode, IEntryRunContext, IQuickNavigateConfiguration, IModel } from 'vs/base/parts/quickopen/common/quickOpen';
|
||||
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 { getIconClasses } from 'vs/editor/common/services/getIconClasses';
|
||||
import { IModelService } from 'vs/editor/common/services/modelService';
|
||||
import { QuickOpenHandler } from 'vs/workbench/browser/quickopen';
|
||||
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
@@ -20,6 +18,7 @@ import { IEditorGroupsService, IEditorGroup, EditorsOrder, GroupsOrder } from 'v
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { EditorInput, toResource } from 'vs/workbench/common/editor';
|
||||
import { compareItemsByScore, scoreItem, ScorerCache, prepareQuery } from 'vs/base/parts/quickopen/common/quickOpenScorer';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
|
||||
export class EditorPickerEntry extends QuickOpenEntryGroup {
|
||||
|
||||
@@ -91,10 +90,10 @@ export abstract class BaseEditorPicker extends QuickOpenHandler {
|
||||
this.scorerCache = Object.create(null);
|
||||
}
|
||||
|
||||
getResults(searchValue: string): TPromise<QuickOpenModel> {
|
||||
getResults(searchValue: string, token: CancellationToken): Thenable<QuickOpenModel> {
|
||||
const editorEntries = this.getEditorEntries();
|
||||
if (!editorEntries.length) {
|
||||
return TPromise.as(null);
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
|
||||
// Prepare search for scoring
|
||||
@@ -117,7 +116,7 @@ export abstract class BaseEditorPicker extends QuickOpenHandler {
|
||||
|
||||
// Sorting
|
||||
if (query.value) {
|
||||
const groups = this.editorGroupService.getGroups(GroupsOrder.CREATION_TIME);
|
||||
const groups = this.editorGroupService.getGroups(GroupsOrder.GRID_APPEARANCE);
|
||||
entries.sort((e1, e2) => {
|
||||
if (e1.group !== e2.group) {
|
||||
return groups.indexOf(e1.group) - groups.indexOf(e2.group); // older groups first
|
||||
@@ -139,7 +138,7 @@ export abstract class BaseEditorPicker extends QuickOpenHandler {
|
||||
});
|
||||
}
|
||||
|
||||
return TPromise.as(new QuickOpenModel(entries));
|
||||
return Promise.resolve(new QuickOpenModel(entries));
|
||||
}
|
||||
|
||||
onClose(canceled: boolean): void {
|
||||
@@ -206,7 +205,7 @@ export class AllEditorsPicker extends BaseEditorPicker {
|
||||
protected getEditorEntries(): EditorPickerEntry[] {
|
||||
const entries: EditorPickerEntry[] = [];
|
||||
|
||||
this.editorGroupService.getGroups(GroupsOrder.CREATION_TIME).forEach(group => {
|
||||
this.editorGroupService.getGroups(GroupsOrder.GRID_APPEARANCE).forEach(group => {
|
||||
group.editors.forEach(editor => {
|
||||
entries.push(this.instantiationService.createInstance(EditorPickerEntry, editor, group));
|
||||
});
|
||||
@@ -232,4 +231,4 @@ export class AllEditorsPicker extends BaseEditorPicker {
|
||||
|
||||
return super.getAutoFocus(searchValue, context);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,22 +3,17 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import 'vs/css!./media/editorstatus';
|
||||
import * as nls from 'vs/nls';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { $, append, runAtThisOrScheduleAtNextAnimationFrame, addDisposableListener, getDomNodePagePosition } from 'vs/base/browser/dom';
|
||||
import { $, append, runAtThisOrScheduleAtNextAnimationFrame } from 'vs/base/browser/dom';
|
||||
import * as strings from 'vs/base/common/strings';
|
||||
import * as paths from 'vs/base/common/paths';
|
||||
import * as types from 'vs/base/common/types';
|
||||
import uri from 'vs/base/common/uri';
|
||||
import * as errors from 'vs/base/common/errors';
|
||||
import { URI as uri } from 'vs/base/common/uri';
|
||||
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, IEditor as IBaseEditor, IEditorInput } from 'vs/workbench/common/editor';
|
||||
import { IDisposable, combinedDisposable, dispose } from 'vs/base/common/lifecycle';
|
||||
@@ -31,11 +26,11 @@ import { IndentUsingSpaces, IndentUsingTabs, DetectIndentation, IndentationToSpa
|
||||
import { BaseBinaryResourceEditor } from 'vs/workbench/browser/parts/editor/binaryEditor';
|
||||
import { BinaryResourceDiffEditor } from 'vs/workbench/browser/parts/editor/binaryDiffEditor';
|
||||
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { IQuickOpenService, IPickOpenEntry, IFilePickOpenEntry } from 'vs/platform/quickOpen/common/quickOpen';
|
||||
import { IQuickOpenService } from 'vs/platform/quickOpen/common/quickOpen';
|
||||
import { IWorkspaceConfigurationService } from 'vs/workbench/services/configuration/common/configuration';
|
||||
import { SUPPORTED_ENCODINGS, IFileService, 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 { IModeService, ILanguageSelection } from 'vs/editor/common/services/modeService';
|
||||
import { IModelService } from 'vs/editor/common/services/modelService';
|
||||
import { Range } from 'vs/editor/common/core/range';
|
||||
import { Selection } from 'vs/editor/common/core/selection';
|
||||
@@ -46,18 +41,16 @@ import { ITextFileService } from 'vs/workbench/services/textfile/common/textfile
|
||||
import { ICursorPositionChangedEvent } from 'vs/editor/common/controller/cursorEvents';
|
||||
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 { attachButtonStyler } from 'vs/platform/theme/common/styler';
|
||||
import { widgetShadow, editorWidgetBackground, foreground, darken, contrastBorder } from 'vs/platform/theme/common/colorRegistry';
|
||||
import { ConfigurationTarget } from 'vs/platform/configuration/common/configuration';
|
||||
import { deepClone } from 'vs/base/common/objects';
|
||||
import { ICodeEditor, isCodeEditor, isDiffEditor, getCodeEditor } from 'vs/editor/browser/editorBrowser';
|
||||
import { Button } from 'vs/base/browser/ui/button/button';
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
import { IAnchor } from 'vs/base/browser/ui/contextview/contextview';
|
||||
import { Themable } from 'vs/workbench/common/theme';
|
||||
import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences';
|
||||
import { IQuickInputService, IQuickPickItem, QuickPickInput } from 'vs/platform/quickinput/common/quickInput';
|
||||
import { getIconClasses } from 'vs/editor/common/services/getIconClasses';
|
||||
import { timeout } from 'vs/base/common/async';
|
||||
import { INotificationHandle, INotificationService, Severity } from 'vs/platform/notification/common/notification';
|
||||
import { once } from 'vs/base/common/event';
|
||||
|
||||
class SideBySideEditorEncodingSupport implements IEncodingSupport {
|
||||
constructor(private master: IEncodingSupport, private details: IEncodingSupport) { }
|
||||
@@ -294,7 +287,7 @@ export class EditorStatus implements IStatusbarItem {
|
||||
private activeEditorListeners: IDisposable[];
|
||||
private delayedRender: IDisposable;
|
||||
private toRender: StateChange;
|
||||
private screenReaderExplanation: ScreenReaderDetectedExplanation;
|
||||
private screenReaderNotification: INotificationHandle;
|
||||
|
||||
constructor(
|
||||
@IEditorService private editorService: IEditorService,
|
||||
@@ -304,6 +297,7 @@ export class EditorStatus implements IStatusbarItem {
|
||||
@IModeService private modeService: IModeService,
|
||||
@ITextFileService private textFileService: ITextFileService,
|
||||
@IWorkspaceConfigurationService private readonly configurationService: IWorkspaceConfigurationService,
|
||||
@INotificationService private readonly notificationService: INotificationService
|
||||
) {
|
||||
this.toDispose = [];
|
||||
this.activeEditorListeners = [];
|
||||
@@ -494,28 +488,39 @@ export class EditorStatus implements IStatusbarItem {
|
||||
private onModeClick(): void {
|
||||
const action = this.instantiationService.createInstance(ChangeModeAction, ChangeModeAction.ID, ChangeModeAction.LABEL);
|
||||
|
||||
action.run().done(null, errors.onUnexpectedError);
|
||||
action.run();
|
||||
action.dispose();
|
||||
}
|
||||
|
||||
private onIndentationClick(): void {
|
||||
const action = this.instantiationService.createInstance(ChangeIndentationAction, ChangeIndentationAction.ID, ChangeIndentationAction.LABEL);
|
||||
action.run().done(null, errors.onUnexpectedError);
|
||||
action.run();
|
||||
action.dispose();
|
||||
}
|
||||
|
||||
private onScreenReaderModeClick(): void {
|
||||
const showExplanation = !this.screenReaderExplanation || !this.screenReaderExplanation.visible;
|
||||
if (!this.screenReaderNotification) {
|
||||
this.screenReaderNotification = this.notificationService.prompt(
|
||||
Severity.Info,
|
||||
// {{SQL CARBON EDIT}}
|
||||
nls.localize('screenReaderDetectedExplanation.question', "Are you using a screen reader to operate Azure Data Studio?"),
|
||||
[{
|
||||
label: nls.localize('screenReaderDetectedExplanation.answerYes', "Yes"),
|
||||
run: () => {
|
||||
this.configurationService.updateValue('editor.accessibilitySupport', 'on', ConfigurationTarget.USER);
|
||||
}
|
||||
}, {
|
||||
label: nls.localize('screenReaderDetectedExplanation.answerNo', "No"),
|
||||
run: () => {
|
||||
this.configurationService.updateValue('editor.accessibilitySupport', 'off', ConfigurationTarget.USER);
|
||||
}
|
||||
}],
|
||||
{ sticky: true }
|
||||
);
|
||||
|
||||
if (!this.screenReaderExplanation) {
|
||||
this.screenReaderExplanation = this.instantiationService.createInstance(ScreenReaderDetectedExplanation);
|
||||
this.toDispose.push(this.screenReaderExplanation);
|
||||
}
|
||||
|
||||
if (showExplanation) {
|
||||
this.screenReaderExplanation.show(this.screenRedearModeElement);
|
||||
} else {
|
||||
this.screenReaderExplanation.hide();
|
||||
once(this.screenReaderNotification.onDidClose)(() => {
|
||||
this.screenReaderNotification = null;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -526,14 +531,14 @@ export class EditorStatus implements IStatusbarItem {
|
||||
private onEOLClick(): void {
|
||||
const action = this.instantiationService.createInstance(ChangeEOLAction, ChangeEOLAction.ID, ChangeEOLAction.LABEL);
|
||||
|
||||
action.run().done(null, errors.onUnexpectedError);
|
||||
action.run();
|
||||
action.dispose();
|
||||
}
|
||||
|
||||
private onEncodingClick(): void {
|
||||
const action = this.instantiationService.createInstance(ChangeEncodingAction, ChangeEncodingAction.ID, ChangeEncodingAction.LABEL);
|
||||
|
||||
action.run().done(null, errors.onUnexpectedError);
|
||||
action.run();
|
||||
action.dispose();
|
||||
}
|
||||
|
||||
@@ -617,6 +622,10 @@ export class EditorStatus implements IStatusbarItem {
|
||||
this.activeEditorListeners.push(editor.onMetadataChanged(metadata => {
|
||||
this.onMetadataChange(activeControl);
|
||||
}));
|
||||
|
||||
this.activeEditorListeners.push(editor.onDidOpenInPlace(() => {
|
||||
this.updateStatusBar();
|
||||
}));
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -689,8 +698,8 @@ export class EditorStatus implements IStatusbarItem {
|
||||
screenReaderMode = (editorWidget.getConfiguration().accessibilitySupport === AccessibilitySupport.Enabled);
|
||||
}
|
||||
|
||||
if (screenReaderMode === false && this.screenReaderExplanation && this.screenReaderExplanation.visible) {
|
||||
this.screenReaderExplanation.hide();
|
||||
if (screenReaderMode === false && this.screenReaderNotification) {
|
||||
this.screenReaderNotification.close();
|
||||
}
|
||||
|
||||
this.updateState({ screenReaderMode: screenReaderMode });
|
||||
@@ -819,7 +828,7 @@ export class ShowLanguageExtensionsAction extends Action {
|
||||
this.enabled = galleryService.isEnabled();
|
||||
}
|
||||
|
||||
run(): TPromise<void> {
|
||||
run(): Thenable<void> {
|
||||
return this.commandService.executeCommand('workbench.extensions.action.showExtensionsForLanguage', this.fileExtension).then(() => void 0);
|
||||
}
|
||||
}
|
||||
@@ -836,7 +845,7 @@ export class ChangeModeAction extends Action {
|
||||
@IModelService private modelService: IModelService,
|
||||
@IEditorService private editorService: IEditorService,
|
||||
@IWorkspaceConfigurationService private configurationService: IWorkspaceConfigurationService,
|
||||
@IQuickOpenService private quickOpenService: IQuickOpenService,
|
||||
@IQuickInputService private quickInputService: IQuickInputService,
|
||||
@IPreferencesService private preferencesService: IPreferencesService,
|
||||
@IInstantiationService private instantiationService: IInstantiationService,
|
||||
@IUntitledEditorService private untitledEditorService: IUntitledEditorService
|
||||
@@ -844,10 +853,10 @@ export class ChangeModeAction extends Action {
|
||||
super(actionId, actionLabel);
|
||||
}
|
||||
|
||||
run(): TPromise<any> {
|
||||
run(): Thenable<any> {
|
||||
const activeTextEditorWidget = getCodeEditor(this.editorService.activeTextEditorWidget);
|
||||
if (!activeTextEditorWidget) {
|
||||
return this.quickOpenService.pick([{ label: nls.localize('noEditor', "No text editor active at this time") }]);
|
||||
return this.quickInputService.pick([{ label: nls.localize('noEditor', "No text editor active at this time") }]);
|
||||
}
|
||||
|
||||
const textModel = activeTextEditorWidget.getModel();
|
||||
@@ -868,7 +877,7 @@ export class ChangeModeAction extends Action {
|
||||
|
||||
// All languages are valid picks
|
||||
const languages = this.modeService.getRegisteredLanguageNames();
|
||||
const picks: IPickOpenEntry[] = languages.sort().map((lang, index) => {
|
||||
const picks: QuickPickInput[] = languages.sort().map((lang, index) => {
|
||||
let description: string;
|
||||
if (currentModeId === lang) {
|
||||
description = nls.localize('languageDescription', "({0}) - Configured Language", this.modeService.getModeIdForLanguageName(lang.toLowerCase()));
|
||||
@@ -888,20 +897,20 @@ export class ChangeModeAction extends Action {
|
||||
}
|
||||
}
|
||||
|
||||
return <IFilePickOpenEntry>{
|
||||
return <IQuickPickItem>{
|
||||
label: lang,
|
||||
resource: fakeResource,
|
||||
iconClasses: getIconClasses(this.modelService, this.modeService, fakeResource),
|
||||
description
|
||||
};
|
||||
});
|
||||
|
||||
if (hasLanguageSupport) {
|
||||
picks[0].separator = { border: true, label: nls.localize('languagesPicks', "languages (identifier)") };
|
||||
picks.unshift({ type: 'separator', label: nls.localize('languagesPicks', "languages (identifier)") });
|
||||
}
|
||||
|
||||
// Offer action to configure via settings
|
||||
let configureModeAssociations: IPickOpenEntry;
|
||||
let configureModeSettings: IPickOpenEntry;
|
||||
let configureModeAssociations: IQuickPickItem;
|
||||
let configureModeSettings: IQuickPickItem;
|
||||
let galleryAction: Action;
|
||||
if (hasLanguageSupport) {
|
||||
const ext = paths.extname(resource.fsPath) || paths.basename(resource.fsPath);
|
||||
@@ -918,7 +927,7 @@ export class ChangeModeAction extends Action {
|
||||
}
|
||||
|
||||
// Offer to "Auto Detect"
|
||||
const autoDetectMode: IPickOpenEntry = {
|
||||
const autoDetectMode: IQuickPickItem = {
|
||||
label: nls.localize('autoDetect', "Auto Detect")
|
||||
};
|
||||
|
||||
@@ -926,7 +935,7 @@ export class ChangeModeAction extends Action {
|
||||
picks.unshift(autoDetectMode);
|
||||
}
|
||||
|
||||
return this.quickOpenService.pick(picks, { placeHolder: nls.localize('pickLanguage', "Select Language Mode"), matchOnDescription: true }).then(pick => {
|
||||
return this.quickInputService.pick(picks, { placeHolder: nls.localize('pickLanguage', "Select Language Mode"), matchOnDescription: true }).then(pick => {
|
||||
if (!pick) {
|
||||
return;
|
||||
}
|
||||
@@ -970,23 +979,22 @@ export class ChangeModeAction extends Action {
|
||||
}
|
||||
|
||||
// Find mode
|
||||
let mode: TPromise<IMode>;
|
||||
let languageSelection: ILanguageSelection;
|
||||
if (pick === autoDetectMode) {
|
||||
mode = this.modeService.getOrCreateModeByFilenameOrFirstLine(toResource(activeEditor.input, { supportSideBySide: true }).fsPath, textModel.getLineContent(1));
|
||||
// {{SQL CARBON EDIT}} - use activeEditor.input instead of activeEditor
|
||||
languageSelection = this.modeService.createByFilepathOrFirstLine(toResource(activeEditor.input, { supportSideBySide: true }).fsPath, textModel.getLineContent(1));
|
||||
} else {
|
||||
mode = this.modeService.getOrCreateModeByLanguageName(pick.label);
|
||||
languageSelection = this.modeService.createByLanguageName(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);
|
||||
}
|
||||
});
|
||||
QueryEditorService.sqlLanguageModeCheck(textModel, languageSelection, activeEditor).then((newTextModel) => {
|
||||
if (newTextModel) {
|
||||
self.modelService.setMode(newTextModel, languageSelection);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -995,21 +1003,21 @@ export class ChangeModeAction extends Action {
|
||||
private configureFileAssociation(resource: uri): void {
|
||||
const extension = paths.extname(resource.fsPath);
|
||||
const basename = paths.basename(resource.fsPath);
|
||||
const currentAssociation = this.modeService.getModeIdByFilenameOrFirstLine(basename);
|
||||
const currentAssociation = this.modeService.getModeIdByFilepathOrFirstLine(basename);
|
||||
|
||||
const languages = this.modeService.getRegisteredLanguageNames();
|
||||
const picks: IPickOpenEntry[] = languages.sort().map((lang, index) => {
|
||||
const picks: IQuickPickItem[] = languages.sort().map((lang, index) => {
|
||||
const id = this.modeService.getModeIdForLanguageName(lang.toLowerCase());
|
||||
|
||||
return <IPickOpenEntry>{
|
||||
return <IQuickPickItem>{
|
||||
id,
|
||||
label: lang,
|
||||
description: (id === currentAssociation) ? nls.localize('currentAssociation', "Current Association") : void 0
|
||||
};
|
||||
});
|
||||
|
||||
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 => {
|
||||
setTimeout(() => {
|
||||
this.quickInputService.pick(picks, { placeHolder: nls.localize('pickLanguageToConfigure', "Select Language Mode to Associate with '{0}'", extension || basename) }).then(language => {
|
||||
if (language) {
|
||||
const fileAssociationsConfig = this.configurationService.inspect(FILES_ASSOCIATIONS_CONFIG);
|
||||
|
||||
@@ -1037,11 +1045,11 @@ export class ChangeModeAction extends Action {
|
||||
this.configurationService.updateValue(FILES_ASSOCIATIONS_CONFIG, currentAssociations, target);
|
||||
}
|
||||
});
|
||||
});
|
||||
}, 50 /* quick open is sensitive to being opened so soon after another */);
|
||||
}
|
||||
}
|
||||
|
||||
export interface IChangeEOLEntry extends IPickOpenEntry {
|
||||
export interface IChangeEOLEntry extends IQuickPickItem {
|
||||
eol: EndOfLineSequence;
|
||||
}
|
||||
|
||||
@@ -1054,22 +1062,22 @@ class ChangeIndentationAction extends Action {
|
||||
actionId: string,
|
||||
actionLabel: string,
|
||||
@IEditorService private editorService: IEditorService,
|
||||
@IQuickOpenService private quickOpenService: IQuickOpenService
|
||||
@IQuickInputService private quickInputService: IQuickInputService
|
||||
) {
|
||||
super(actionId, actionLabel);
|
||||
}
|
||||
|
||||
run(): TPromise<any> {
|
||||
run(): Thenable<any> {
|
||||
const activeTextEditorWidget = getCodeEditor(this.editorService.activeTextEditorWidget);
|
||||
if (!activeTextEditorWidget) {
|
||||
return this.quickOpenService.pick([{ label: nls.localize('noEditor', "No text editor active at this time") }]);
|
||||
return this.quickInputService.pick([{ label: nls.localize('noEditor', "No text editor active at this time") }]);
|
||||
}
|
||||
|
||||
if (!isWritableCodeEditor(activeTextEditorWidget)) {
|
||||
return this.quickOpenService.pick([{ label: nls.localize('noWritableCodeEditor', "The active code editor is read-only.") }]);
|
||||
return this.quickInputService.pick([{ label: nls.localize('noWritableCodeEditor', "The active code editor is read-only.") }]);
|
||||
}
|
||||
|
||||
const picks = [
|
||||
const picks: QuickPickInput<IQuickPickItem & { run(): void }>[] = [
|
||||
activeTextEditorWidget.getAction(IndentUsingSpaces.ID),
|
||||
activeTextEditorWidget.getAction(IndentUsingTabs.ID),
|
||||
activeTextEditorWidget.getAction(DetectIndentation.ID),
|
||||
@@ -1088,10 +1096,10 @@ class ChangeIndentationAction extends Action {
|
||||
};
|
||||
});
|
||||
|
||||
(<IPickOpenEntry>picks[0]).separator = { label: nls.localize('indentView', "change view") };
|
||||
(<IPickOpenEntry>picks[3]).separator = { label: nls.localize('indentConvert', "convert file"), border: true };
|
||||
picks.splice(3, 0, { type: 'separator', label: nls.localize('indentConvert', "convert file") });
|
||||
picks.unshift({ type: 'separator', label: nls.localize('indentView', "change view") });
|
||||
|
||||
return this.quickOpenService.pick(picks, { placeHolder: nls.localize('pickAction', "Select Action"), matchOnDetail: true }).then(action => action && action.run());
|
||||
return this.quickInputService.pick(picks, { placeHolder: nls.localize('pickAction', "Select Action"), matchOnDetail: true }).then(action => action && action.run());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1104,19 +1112,19 @@ export class ChangeEOLAction extends Action {
|
||||
actionId: string,
|
||||
actionLabel: string,
|
||||
@IEditorService private editorService: IEditorService,
|
||||
@IQuickOpenService private quickOpenService: IQuickOpenService
|
||||
@IQuickInputService private quickInputService: IQuickInputService
|
||||
) {
|
||||
super(actionId, actionLabel);
|
||||
}
|
||||
|
||||
run(): TPromise<any> {
|
||||
run(): Thenable<any> {
|
||||
const activeTextEditorWidget = getCodeEditor(this.editorService.activeTextEditorWidget);
|
||||
if (!activeTextEditorWidget) {
|
||||
return this.quickOpenService.pick([{ label: nls.localize('noEditor', "No text editor active at this time") }]);
|
||||
return this.quickInputService.pick([{ label: nls.localize('noEditor', "No text editor active at this time") }]);
|
||||
}
|
||||
|
||||
if (!isWritableCodeEditor(activeTextEditorWidget)) {
|
||||
return this.quickOpenService.pick([{ label: nls.localize('noWritableCodeEditor', "The active code editor is read-only.") }]);
|
||||
return this.quickInputService.pick([{ label: nls.localize('noWritableCodeEditor', "The active code editor is read-only.") }]);
|
||||
}
|
||||
|
||||
const textModel = activeTextEditorWidget.getModel();
|
||||
@@ -1128,7 +1136,7 @@ export class ChangeEOLAction extends Action {
|
||||
|
||||
const selectedIndex = (textModel && textModel.getEOL() === '\n') ? 0 : 1;
|
||||
|
||||
return this.quickOpenService.pick(EOLOptions, { placeHolder: nls.localize('pickEndOfLine', "Select End of Line Sequence"), autoFocus: { autoFocusIndex: selectedIndex } }).then(eol => {
|
||||
return this.quickInputService.pick(EOLOptions, { placeHolder: nls.localize('pickEndOfLine', "Select End of Line Sequence"), activeItem: EOLOptions[selectedIndex] }).then(eol => {
|
||||
if (eol) {
|
||||
const activeCodeEditor = getCodeEditor(this.editorService.activeTextEditorWidget);
|
||||
if (activeCodeEditor && isWritableCodeEditor(activeCodeEditor)) {
|
||||
@@ -1149,28 +1157,26 @@ export class ChangeEncodingAction extends Action {
|
||||
actionId: string,
|
||||
actionLabel: string,
|
||||
@IEditorService private editorService: IEditorService,
|
||||
@IQuickOpenService private quickOpenService: IQuickOpenService,
|
||||
@IQuickInputService private quickInputService: IQuickInputService,
|
||||
@ITextResourceConfigurationService private textResourceConfigurationService: ITextResourceConfigurationService,
|
||||
@IFileService private fileService: IFileService
|
||||
) {
|
||||
super(actionId, actionLabel);
|
||||
}
|
||||
|
||||
run(): TPromise<any> {
|
||||
run(): Thenable<any> {
|
||||
if (!getCodeEditor(this.editorService.activeTextEditorWidget)) {
|
||||
return this.quickOpenService.pick([{ label: nls.localize('noEditor', "No text editor active at this time") }]);
|
||||
return this.quickInputService.pick([{ label: nls.localize('noEditor', "No text editor active at this time") }]);
|
||||
}
|
||||
|
||||
let activeControl = this.editorService.activeControl;
|
||||
let encodingSupport: IEncodingSupport = toEditorWithEncodingSupport(activeControl.input);
|
||||
if (!encodingSupport) {
|
||||
return this.quickOpenService.pick([{ label: nls.localize('noFileEditor', "No file active at this time") }]);
|
||||
return this.quickInputService.pick([{ label: nls.localize('noFileEditor', "No file active at this time") }]);
|
||||
}
|
||||
|
||||
let pickActionPromise: TPromise<IPickOpenEntry>;
|
||||
|
||||
let saveWithEncodingPick: IPickOpenEntry;
|
||||
let reopenWithEncodingPick: IPickOpenEntry;
|
||||
let saveWithEncodingPick: IQuickPickItem;
|
||||
let reopenWithEncodingPick: IQuickPickItem;
|
||||
if (language === LANGUAGE_DEFAULT) {
|
||||
saveWithEncodingPick = { label: nls.localize('saveWithEncoding', "Save with Encoding") };
|
||||
reopenWithEncodingPick = { label: nls.localize('reopenWithEncoding', "Reopen with Encoding") };
|
||||
@@ -1179,12 +1185,13 @@ export class ChangeEncodingAction extends Action {
|
||||
reopenWithEncodingPick = { label: nls.localize('reopenWithEncoding', "Reopen with Encoding"), detail: 'Reopen with Encoding' };
|
||||
}
|
||||
|
||||
let pickActionPromise: Promise<IQuickPickItem>;
|
||||
if (encodingSupport instanceof UntitledEditorInput) {
|
||||
pickActionPromise = TPromise.as(saveWithEncodingPick);
|
||||
pickActionPromise = Promise.resolve(saveWithEncodingPick);
|
||||
} else if (!isWritableBaseEditor(activeControl)) {
|
||||
pickActionPromise = TPromise.as(reopenWithEncodingPick);
|
||||
pickActionPromise = Promise.resolve(reopenWithEncodingPick);
|
||||
} else {
|
||||
pickActionPromise = this.quickOpenService.pick([reopenWithEncodingPick, saveWithEncodingPick], { placeHolder: nls.localize('pickAction', "Select Action"), matchOnDetail: true });
|
||||
pickActionPromise = this.quickInputService.pick([reopenWithEncodingPick, saveWithEncodingPick], { placeHolder: nls.localize('pickAction', "Select Action"), matchOnDetail: true });
|
||||
}
|
||||
|
||||
return pickActionPromise.then(action => {
|
||||
@@ -1194,10 +1201,10 @@ export class ChangeEncodingAction extends Action {
|
||||
|
||||
const resource = toResource(activeControl.input, { supportSideBySide: true });
|
||||
|
||||
return TPromise.timeout(50 /* quick open is sensitive to being opened so soon after another */)
|
||||
return timeout(50 /* quick open is sensitive to being opened so soon after another */)
|
||||
.then(() => {
|
||||
if (!resource || !this.fileService.canHandleResource(resource)) {
|
||||
return TPromise.as(null); // encoding detection only possible for resources the file service can handle
|
||||
return Promise.resolve(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);
|
||||
@@ -1211,7 +1218,7 @@ export class ChangeEncodingAction extends Action {
|
||||
let aliasMatchIndex: number;
|
||||
|
||||
// All encodings are valid picks
|
||||
const picks: IPickOpenEntry[] = Object.keys(SUPPORTED_ENCODINGS)
|
||||
const picks: QuickPickInput[] = Object.keys(SUPPORTED_ENCODINGS)
|
||||
.sort((k1, k2) => {
|
||||
if (k1 === configuredEncoding) {
|
||||
return -1;
|
||||
@@ -1238,15 +1245,17 @@ export class ChangeEncodingAction extends Action {
|
||||
return { id: key, label: SUPPORTED_ENCODINGS[key].labelLong, description: key };
|
||||
});
|
||||
|
||||
const items = picks.slice() as IQuickPickItem[];
|
||||
|
||||
// If we have a guessed encoding, show it first unless it matches the configured encoding
|
||||
if (guessedEncoding && configuredEncoding !== guessedEncoding && SUPPORTED_ENCODINGS[guessedEncoding]) {
|
||||
picks[0].separator = { border: true };
|
||||
picks.unshift({ type: 'separator' });
|
||||
picks.unshift({ id: guessedEncoding, label: SUPPORTED_ENCODINGS[guessedEncoding].labelLong, description: nls.localize('guessedEncoding', "Guessed from content") });
|
||||
}
|
||||
|
||||
return this.quickOpenService.pick(picks, {
|
||||
return this.quickInputService.pick(picks, {
|
||||
placeHolder: isReopenWithEncoding ? nls.localize('pickEncodingForReopen', "Select File Encoding to Reopen File") : nls.localize('pickEncodingForSave', "Select File Encoding to Save with"),
|
||||
autoFocus: { autoFocusIndex: typeof directMatchIndex === 'number' ? directMatchIndex : typeof aliasMatchIndex === 'number' ? aliasMatchIndex : void 0 }
|
||||
activeItem: items[typeof directMatchIndex === 'number' ? directMatchIndex : typeof aliasMatchIndex === 'number' ? aliasMatchIndex : -1]
|
||||
}).then(encoding => {
|
||||
if (encoding) {
|
||||
activeControl = this.editorService.activeControl;
|
||||
@@ -1260,149 +1269,3 @@ export class ChangeEncodingAction extends Action {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
class ScreenReaderDetectedExplanation extends Themable {
|
||||
private container: HTMLElement;
|
||||
private hrElement: HTMLHRElement;
|
||||
private _visible: boolean;
|
||||
|
||||
constructor(
|
||||
@IThemeService themeService: IThemeService,
|
||||
@IContextViewService private readonly contextViewService: IContextViewService,
|
||||
@IWorkspaceConfigurationService private readonly configurationService: IWorkspaceConfigurationService,
|
||||
) {
|
||||
super(themeService);
|
||||
}
|
||||
|
||||
get visible(): boolean {
|
||||
return this._visible;
|
||||
}
|
||||
|
||||
protected updateStyles(): void {
|
||||
if (this.container) {
|
||||
const background = this.getColor(editorWidgetBackground);
|
||||
this.container.style.backgroundColor = background ? background.toString() : null;
|
||||
|
||||
const widgetShadowColor = this.getColor(widgetShadow);
|
||||
this.container.style.boxShadow = widgetShadowColor ? `0 0px 8px ${widgetShadowColor}` : null;
|
||||
|
||||
const contrastBorderColor = this.getColor(contrastBorder);
|
||||
this.container.style.border = contrastBorderColor ? `1px solid ${contrastBorderColor}` : null;
|
||||
|
||||
const foregroundColor = this.getColor(foreground);
|
||||
this.hrElement.style.backgroundColor = foregroundColor ? foregroundColor.toString() : null;
|
||||
}
|
||||
}
|
||||
|
||||
show(anchorElement: HTMLElement): void {
|
||||
this._visible = true;
|
||||
|
||||
this.contextViewService.showContextView({
|
||||
getAnchor: () => {
|
||||
const res = getDomNodePagePosition(anchorElement);
|
||||
|
||||
return {
|
||||
x: res.left,
|
||||
y: res.top - 9, /* above the status bar */
|
||||
width: res.width,
|
||||
height: res.height
|
||||
} as IAnchor;
|
||||
},
|
||||
render: (container) => {
|
||||
return this.renderContents(container);
|
||||
},
|
||||
onDOMEvent: (e, activeElement) => { },
|
||||
onHide: () => {
|
||||
this._visible = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
hide(): void {
|
||||
this.contextViewService.hideContextView();
|
||||
}
|
||||
|
||||
protected renderContents(parent: HTMLElement): IDisposable {
|
||||
const toDispose: IDisposable[] = [];
|
||||
|
||||
this.container = $('div.screen-reader-detected-explanation', {
|
||||
'aria-hidden': 'true'
|
||||
});
|
||||
|
||||
const title = $('h2.title', {}, nls.localize('screenReaderDetectedExplanation.title', "Screen Reader Optimized"));
|
||||
this.container.appendChild(title);
|
||||
|
||||
const closeBtn = $('div.cancel');
|
||||
toDispose.push(addDisposableListener(closeBtn, 'click', () => {
|
||||
this.contextViewService.hideContextView();
|
||||
}));
|
||||
toDispose.push(addDisposableListener(closeBtn, 'mouseover', () => {
|
||||
const theme = this.themeService.getTheme();
|
||||
let darkenFactor: number;
|
||||
switch (theme.type) {
|
||||
case 'light':
|
||||
darkenFactor = 0.1;
|
||||
break;
|
||||
case 'dark':
|
||||
darkenFactor = 0.2;
|
||||
break;
|
||||
}
|
||||
|
||||
if (darkenFactor) {
|
||||
closeBtn.style.backgroundColor = this.getColor(editorWidgetBackground, (color, theme) => darken(color, darkenFactor)(theme));
|
||||
}
|
||||
}));
|
||||
toDispose.push(addDisposableListener(closeBtn, 'mouseout', () => {
|
||||
closeBtn.style.backgroundColor = null;
|
||||
}));
|
||||
this.container.appendChild(closeBtn);
|
||||
|
||||
// {{SQL CARBON EDIT}}
|
||||
const question = $('p.question', {}, nls.localize('screenReaderDetectedExplanation.question', "Are you using a screen reader to operate Azure Data Studio?"));
|
||||
this.container.appendChild(question);
|
||||
|
||||
const buttonContainer = $('div.buttons');
|
||||
this.container.appendChild(buttonContainer);
|
||||
|
||||
const yesBtn = new Button(buttonContainer);
|
||||
yesBtn.label = nls.localize('screenReaderDetectedExplanation.answerYes', "Yes");
|
||||
toDispose.push(attachButtonStyler(yesBtn, this.themeService));
|
||||
toDispose.push(yesBtn.onDidClick(e => {
|
||||
this.configurationService.updateValue('editor.accessibilitySupport', 'on', ConfigurationTarget.USER);
|
||||
this.contextViewService.hideContextView();
|
||||
}));
|
||||
|
||||
const noBtn = new Button(buttonContainer);
|
||||
noBtn.label = nls.localize('screenReaderDetectedExplanation.answerNo', "No");
|
||||
toDispose.push(attachButtonStyler(noBtn, this.themeService));
|
||||
toDispose.push(noBtn.onDidClick(e => {
|
||||
this.configurationService.updateValue('editor.accessibilitySupport', 'off', ConfigurationTarget.USER);
|
||||
this.contextViewService.hideContextView();
|
||||
}));
|
||||
|
||||
const clear = $('div');
|
||||
clear.style.clear = 'both';
|
||||
this.container.appendChild(clear);
|
||||
|
||||
const br = $('br');
|
||||
this.container.appendChild(br);
|
||||
|
||||
this.hrElement = $('hr');
|
||||
this.container.appendChild(this.hrElement);
|
||||
|
||||
// {{SQL CARBON EDIT}}
|
||||
const explanation1 = $('p.body1', {}, nls.localize('screenReaderDetectedExplanation.body1', "Azure Data Studio is now optimized for usage with a screen reader."));
|
||||
this.container.appendChild(explanation1);
|
||||
|
||||
const explanation2 = $('p.body2', {}, nls.localize('screenReaderDetectedExplanation.body2', "Some editor features will have different behaviour: e.g. word wrapping, folding, etc."));
|
||||
this.container.appendChild(explanation2);
|
||||
|
||||
parent.appendChild(this.container);
|
||||
|
||||
this.updateStyles();
|
||||
|
||||
return {
|
||||
dispose: () => dispose(toDispose)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
179
src/vs/workbench/browser/parts/editor/editorWidgets.ts
Normal file
@@ -0,0 +1,179 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Widget } from 'vs/base/browser/ui/widget';
|
||||
import { IOverlayWidget, ICodeEditor, IOverlayWidgetPosition, OverlayWidgetPositionPreference } from 'vs/editor/browser/editorBrowser';
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import { $, append } from 'vs/base/browser/dom';
|
||||
import { attachStylerCallback } from 'vs/platform/theme/common/styler';
|
||||
import { buttonBackground, buttonForeground, editorBackground, editorForeground, contrastBorder } from 'vs/platform/theme/common/colorRegistry';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IWindowService } from 'vs/platform/windows/common/windows';
|
||||
import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace';
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
import { WORKSPACE_EXTENSION } from 'vs/platform/workspaces/common/workspaces';
|
||||
import { extname } from 'vs/base/common/paths';
|
||||
import { Disposable, dispose } from 'vs/base/common/lifecycle';
|
||||
import { localize } from 'vs/nls';
|
||||
import { IEditorContribution } from 'vs/editor/common/editorCommon';
|
||||
import { isEqual } from 'vs/base/common/resources';
|
||||
|
||||
export class FloatingClickWidget extends Widget implements IOverlayWidget {
|
||||
|
||||
private _onClick: Emitter<void> = this._register(new Emitter<void>());
|
||||
get onClick(): Event<void> { return this._onClick.event; }
|
||||
|
||||
private _domNode: HTMLElement;
|
||||
|
||||
constructor(
|
||||
private editor: ICodeEditor,
|
||||
private label: string,
|
||||
keyBindingAction: string,
|
||||
@IKeybindingService keybindingService: IKeybindingService,
|
||||
@IThemeService private themeService: IThemeService
|
||||
) {
|
||||
super();
|
||||
|
||||
if (keyBindingAction) {
|
||||
const keybinding = keybindingService.lookupKeybinding(keyBindingAction);
|
||||
if (keybinding) {
|
||||
this.label += ` (${keybinding.getLabel()})`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getId(): string {
|
||||
return 'editor.overlayWidget.floatingClickWidget';
|
||||
}
|
||||
|
||||
getDomNode(): HTMLElement {
|
||||
return this._domNode;
|
||||
}
|
||||
|
||||
getPosition(): IOverlayWidgetPosition {
|
||||
return {
|
||||
preference: OverlayWidgetPositionPreference.BOTTOM_RIGHT_CORNER
|
||||
};
|
||||
}
|
||||
|
||||
render() {
|
||||
this._domNode = $('.floating-click-widget');
|
||||
|
||||
this._register(attachStylerCallback(this.themeService, { buttonBackground, buttonForeground, editorBackground, editorForeground, contrastBorder }, colors => {
|
||||
const backgroundColor = colors.buttonBackground ? colors.buttonBackground : colors.editorBackground;
|
||||
if (backgroundColor) {
|
||||
this._domNode.style.backgroundColor = backgroundColor.toString();
|
||||
}
|
||||
|
||||
const foregroundColor = colors.buttonForeground ? colors.buttonForeground : colors.editorForeground;
|
||||
if (foregroundColor) {
|
||||
this._domNode.style.color = foregroundColor.toString();
|
||||
}
|
||||
|
||||
const borderColor = colors.contrastBorder ? colors.contrastBorder.toString() : null;
|
||||
this._domNode.style.borderWidth = borderColor ? '1px' : null;
|
||||
this._domNode.style.borderStyle = borderColor ? 'solid' : null;
|
||||
this._domNode.style.borderColor = borderColor;
|
||||
}));
|
||||
|
||||
append(this._domNode, $('')).textContent = this.label;
|
||||
|
||||
this.onclick(this._domNode, e => this._onClick.fire());
|
||||
|
||||
this.editor.addOverlayWidget(this);
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this.editor.removeOverlayWidget(this);
|
||||
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
export class OpenWorkspaceButtonContribution extends Disposable implements IEditorContribution {
|
||||
|
||||
static get(editor: ICodeEditor): OpenWorkspaceButtonContribution {
|
||||
return editor.getContribution<OpenWorkspaceButtonContribution>(OpenWorkspaceButtonContribution.ID);
|
||||
}
|
||||
|
||||
private static readonly ID = 'editor.contrib.openWorkspaceButton';
|
||||
|
||||
private openWorkspaceButton: FloatingClickWidget;
|
||||
|
||||
constructor(
|
||||
private editor: ICodeEditor,
|
||||
@IInstantiationService private instantiationService: IInstantiationService,
|
||||
@IWindowService private windowService: IWindowService,
|
||||
@IWorkspaceContextService private contextService: IWorkspaceContextService
|
||||
) {
|
||||
super();
|
||||
|
||||
this.update();
|
||||
this.registerListeners();
|
||||
}
|
||||
|
||||
private registerListeners(): void {
|
||||
this._register(this.editor.onDidChangeModel(e => this.update()));
|
||||
}
|
||||
|
||||
getId(): string {
|
||||
return OpenWorkspaceButtonContribution.ID;
|
||||
}
|
||||
|
||||
private update(): void {
|
||||
if (!this.shouldShowButton(this.editor)) {
|
||||
this.disposeOpenWorkspaceWidgetRenderer();
|
||||
return;
|
||||
}
|
||||
|
||||
this.createOpenWorkspaceWidgetRenderer();
|
||||
}
|
||||
|
||||
private shouldShowButton(editor: ICodeEditor): boolean {
|
||||
const model = editor.getModel();
|
||||
if (!model) {
|
||||
return false; // we need a model
|
||||
}
|
||||
|
||||
if (model.uri.scheme !== Schemas.file || extname(model.uri.fsPath) !== `.${WORKSPACE_EXTENSION}`) {
|
||||
return false; // we need a local workspace file
|
||||
}
|
||||
|
||||
if (this.contextService.getWorkbenchState() === WorkbenchState.WORKSPACE) {
|
||||
const workspaceConfiguration = this.contextService.getWorkspace().configuration;
|
||||
if (workspaceConfiguration && isEqual(workspaceConfiguration, model.uri)) {
|
||||
return false; // already inside workspace
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private createOpenWorkspaceWidgetRenderer(): void {
|
||||
if (!this.openWorkspaceButton) {
|
||||
this.openWorkspaceButton = this.instantiationService.createInstance(FloatingClickWidget, this.editor, localize('openWorkspace', "Open Workspace"), null);
|
||||
this._register(this.openWorkspaceButton.onClick(() => {
|
||||
const model = this.editor.getModel();
|
||||
if (model) {
|
||||
this.windowService.openWindow([model.uri]);
|
||||
}
|
||||
}));
|
||||
|
||||
this.openWorkspaceButton.render();
|
||||
}
|
||||
}
|
||||
|
||||
private disposeOpenWorkspaceWidgetRenderer(): void {
|
||||
this.openWorkspaceButton = dispose(this.openWorkspaceButton);
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this.disposeOpenWorkspaceWidgetRenderer();
|
||||
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 381 B |
@@ -7,10 +7,6 @@
|
||||
display: none;
|
||||
}
|
||||
|
||||
.monaco-workbench>.part.editor>.content .editor-group-container:not(.active) .breadcrumbs-control {
|
||||
opacity: .8;
|
||||
}
|
||||
|
||||
.monaco-workbench>.part.editor>.content .editor-group-container .breadcrumbs-control .monaco-breadcrumb-item.selected .monaco-icon-label,
|
||||
.monaco-workbench>.part.editor>.content .editor-group-container .breadcrumbs-control .monaco-breadcrumb-item.focused .monaco-icon-label {
|
||||
text-decoration-line: underline;
|
||||
@@ -23,6 +19,17 @@
|
||||
|
||||
/* todo@joh move somewhere else */
|
||||
|
||||
.monaco-workbench .monaco-breadcrumbs-picker .arrow {
|
||||
position: absolute;
|
||||
width: 0;
|
||||
border-style: solid;
|
||||
}
|
||||
|
||||
.monaco-workbench .monaco-breadcrumbs-picker .picker-item {
|
||||
line-height: 22px;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.monaco-workbench .monaco-breadcrumbs-picker .highlighting-tree {
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
|
||||
@@ -44,15 +44,13 @@
|
||||
|
||||
.monaco-workbench > .part.editor > .content .editor-group-container > .title {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-wrap: nowrap;
|
||||
box-sizing: border-box;
|
||||
overflow: hidden;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.monaco-workbench > .part.editor > .content .editor-group-container > .title.tabs {
|
||||
flex-wrap: wrap;
|
||||
.monaco-workbench > .part.editor > .content .editor-group-container > .title:not(.tabs) {
|
||||
display: flex; /* when tabs are not shown, use flex layout */
|
||||
flex-wrap: nowrap;
|
||||
}
|
||||
|
||||
.monaco-workbench > .part.editor > .content .editor-group-container > .title.title-border-bottom::after {
|
||||
|
||||
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 381 B |
@@ -3,6 +3,8 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
/* Title Label */
|
||||
|
||||
.monaco-workbench > .part.editor > .content .editor-group-container > .title > .label-container {
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
@@ -11,8 +13,6 @@
|
||||
flex: auto;
|
||||
}
|
||||
|
||||
/* Title Label */
|
||||
|
||||
.monaco-workbench > .part.editor > .content .editor-group-container > .title .title-label {
|
||||
line-height: 35px;
|
||||
overflow: hidden;
|
||||
@@ -34,7 +34,7 @@
|
||||
.monaco-workbench > .part.editor > .content .editor-group-container > .title.breadcrumbs .breadcrumbs-control {
|
||||
flex: 1 50%;
|
||||
overflow: hidden;
|
||||
padding: 0 6px;
|
||||
margin-left: .45em;
|
||||
}
|
||||
|
||||
.monaco-workbench > .part.editor > .content .editor-group-container > .title.breadcrumbs .breadcrumbs-control .monaco-breadcrumb-item {
|
||||
@@ -67,8 +67,8 @@
|
||||
|
||||
.monaco-workbench > .part.editor > .content .editor-group-container > .title.breadcrumbs .breadcrumbs-control .monaco-breadcrumb-item.root_folder::after {
|
||||
/* use dot separator for workspace folder */
|
||||
content: '•';
|
||||
padding: 0 4px;
|
||||
content: '\00a0•\00a0';
|
||||
padding: 0px;
|
||||
}
|
||||
|
||||
.monaco-workbench > .part.editor > .content .editor-group-container > .title.breadcrumbs .breadcrumbs-control .monaco-breadcrumb-item:last-child {
|
||||
|
||||
@@ -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:#252526;opacity:0.5;}.icon-canvas-transparent{opacity:0;}.icon-vs-bg{fill:#c5c5c5;opacity:0.5;}</style></defs><title>Paragraph_16x</title><g id="canvas"><path class="icon-canvas-transparent" d="M16,0V16H0V0Z"/></g><g id="outline" style="display: none;"><path class="icon-vs-out" d="M13,1V4H12V16H6V9.973A4.5,4.5,0,0,1,6.5,1Z"/></g><g id="iconBg"><path class="icon-vs-bg" d="M12,2V3H11V15H10V3H8V15H7V8.95A3.588,3.588,0,0,1,6.5,9a3.5,3.5,0,0,1,0-7Z"/></g></svg>
|
||||
|
After Width: | Height: | Size: 576 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;opacity:0.5;}.icon-canvas-transparent{opacity:0;}.icon-vs-bg{fill:#424242;opacity:0.5;}</style></defs><title>Paragraph_16x</title><g id="canvas"><path class="icon-canvas-transparent" d="M16,0V16H0V0Z"/></g><g id="outline" style="display: none;"><path class="icon-vs-out" d="M13,1V4H12V16H6V9.973A4.5,4.5,0,0,1,6.5,1Z"/></g><g id="iconBg"><path class="icon-vs-bg" d="M12,2V3H11V15H10V3H8V15H7V8.95A3.588,3.588,0,0,1,6.5,9a3.5,3.5,0,0,1,0-7Z"/></g></svg>
|
||||
|
After Width: | Height: | Size: 576 B |
@@ -5,11 +5,15 @@
|
||||
|
||||
/* Title Container */
|
||||
|
||||
.monaco-workbench > .part.editor > .content .editor-group-container > .title.tabs > .monaco-scrollable-element {
|
||||
.monaco-workbench > .part.editor > .content .editor-group-container > .title.tabs > .tabs-and-actions-container {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.monaco-workbench > .part.editor > .content .editor-group-container > .title.tabs > .tabs-and-actions-container > .monaco-scrollable-element {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.monaco-workbench > .part.editor > .content .editor-group-container > .title.tabs > .monaco-scrollable-element .scrollbar {
|
||||
.monaco-workbench > .part.editor > .content .editor-group-container > .title.tabs > .tabs-and-actions-container > .monaco-scrollable-element .scrollbar {
|
||||
z-index: 3; /* on top of tabs */
|
||||
cursor: default;
|
||||
}
|
||||
@@ -92,28 +96,33 @@
|
||||
display: none; /* hidden by default until a color is provided (see below) */
|
||||
}
|
||||
|
||||
.monaco-workbench > .part.editor > .content .editor-group-container > .title .tabs-container > .tab.active.tab-border-top > .tab-border-top-container {
|
||||
.monaco-workbench > .part.editor > .content .editor-group-container > .title .tabs-container > .tab.active.tab-border-top > .tab-border-top-container,
|
||||
.monaco-workbench > .part.editor > .content .editor-group-container > .title .tabs-container > .tab.active.tab-border-bottom > .tab-border-bottom-container,
|
||||
.monaco-workbench > .part.editor > .content .editor-group-container > .title .tabs-container > .tab.dirty-border-top > .tab-border-top-container {
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 6; /* over possible title border */
|
||||
pointer-events: none;
|
||||
background-color: var(--tab-border-top-color);
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.monaco-workbench > .part.editor > .content .editor-group-container > .title .tabs-container > .tab.active.tab-border-top > .tab-border-top-container {
|
||||
top: 0;
|
||||
height: 1px;
|
||||
background-color: var(--tab-border-top-color);
|
||||
}
|
||||
|
||||
.monaco-workbench > .part.editor > .content .editor-group-container > .title .tabs-container > .tab.active.tab-border-bottom > .tab-border-bottom-container {
|
||||
display: block;
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
z-index: 6; /* over possible title border */
|
||||
pointer-events: none;
|
||||
background-color: var(--tab-border-bottom-color);
|
||||
width: 100%;
|
||||
height: 1px;
|
||||
background-color: var(--tab-border-bottom-color);
|
||||
}
|
||||
|
||||
.monaco-workbench > .part.editor > .content .editor-group-container > .title .tabs-container > .tab.dirty-border-top > .tab-border-top-container {
|
||||
top: 0;
|
||||
height: 2px;
|
||||
background-color: var(--tab-dirty-border-top-color);
|
||||
}
|
||||
|
||||
/* Tab Label */
|
||||
@@ -137,6 +146,10 @@
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.monaco-workbench > .part.editor > .content .editor-group-container > .title .tabs-container > .tab.sizing-shrink:focus > .tab-label::after {
|
||||
opacity: 0; /* when tab has the focus this shade breaks the tab border (fixes https://github.com/Microsoft/vscode/issues/57819) */
|
||||
}
|
||||
|
||||
.monaco-workbench > .part.editor > .content .editor-group-container > .title .tabs-container > .tab.sizing-fit .monaco-icon-label,
|
||||
.monaco-workbench > .part.editor > .content .editor-group-container > .title .tabs-container > .tab.sizing-fit .monaco-icon-label > .monaco-icon-label-description-container {
|
||||
overflow: visible; /* fixes https://github.com/Microsoft/vscode/issues/20182 */
|
||||
@@ -168,8 +181,9 @@
|
||||
}
|
||||
|
||||
.monaco-workbench > .part.editor > .content .editor-group-container > .title .tabs-container > .tab.dirty.close-button-right.sizing-shrink > .tab-close,
|
||||
.monaco-workbench > .part.editor > .content .editor-group-container > .title .tabs-container > .tab.close-button-right.sizing-shrink:hover > .tab-close {
|
||||
overflow: visible; /* ...but still show the close button on hover and when dirty */
|
||||
.monaco-workbench > .part.editor > .content .editor-group-container > .title .tabs-container > .tab.close-button-right.sizing-shrink:hover > .tab-close,
|
||||
.monaco-workbench > .part.editor > .content .editor-group-container > .title .tabs-container > .tab.close-button-right.sizing-shrink > .tab-close:focus-within {
|
||||
overflow: visible; /* ...but still show the close button on hover, focus and when dirty */
|
||||
}
|
||||
|
||||
.monaco-workbench > .part.editor > .content .editor-group-container > .title .tabs-container > .tab.close-button-off > .tab-close {
|
||||
@@ -259,23 +273,28 @@
|
||||
|
||||
.monaco-workbench > .part.editor > .content .editor-group-container > .title .tabs-breadcrumbs .breadcrumbs-control {
|
||||
flex: 1 100%;
|
||||
height: 25px;
|
||||
height: 22px;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.monaco-workbench > .part.editor > .content .editor-group-container > .title .tabs-breadcrumbs .breadcrumbs-control .monaco-icon-label {
|
||||
height: 22px;
|
||||
line-height: 22px;
|
||||
}
|
||||
|
||||
.monaco-workbench > .part.editor > .content .editor-group-container > .title .tabs-breadcrumbs .breadcrumbs-control .monaco-icon-label::before {
|
||||
height: 18px; /* tweak the icon size of the editor labels when icons are enabled */
|
||||
height: 22px; /* tweak the icon size of the editor labels when icons are enabled */
|
||||
}
|
||||
|
||||
.monaco-workbench > .part.editor > .content .editor-group-container > .title .tabs-breadcrumbs .breadcrumbs-control .monaco-breadcrumb-item {
|
||||
max-width: 260px;
|
||||
max-width: 80%;
|
||||
}
|
||||
|
||||
.monaco-workbench > .part.editor > .content .editor-group-container > .title .tabs-breadcrumbs .breadcrumbs-control .monaco-breadcrumb-item::before {
|
||||
min-width: 16px;
|
||||
height: 22px;
|
||||
}
|
||||
|
||||
.monaco-workbench > .part.editor > .content .editor-group-container > .title .tabs-breadcrumbs .breadcrumbs-control .monaco-breadcrumb-item:last-child {
|
||||
padding-right: 8px;
|
||||
}
|
||||
|
||||
/* .monaco-workbench > .part.editor > .content .editor-group-container > .title .tabs-breadcrumbs .breadcrumbs-control .monaco-breadcrumb-item:not(:last-child):not(:hover):not(.focused):not(.file) {
|
||||
min-width: 33px;
|
||||
} */
|
||||
|
||||
|
||||
@@ -1,40 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
.vs .monaco-workbench .textdiff-editor-action.next {
|
||||
background: url('next-diff.svg') center center no-repeat;
|
||||
}
|
||||
|
||||
.vs .monaco-workbench .textdiff-editor-action.previous {
|
||||
background: url('previous-diff.svg') center center no-repeat;
|
||||
}
|
||||
|
||||
.vs-dark .monaco-workbench .textdiff-editor-action.next,
|
||||
.hc-black .monaco-workbench .textdiff-editor-action.next {
|
||||
background: url('next-diff-inverse.svg') center center no-repeat;
|
||||
}
|
||||
|
||||
.vs-dark .monaco-workbench .textdiff-editor-action.previous,
|
||||
.hc-black .monaco-workbench .textdiff-editor-action.previous {
|
||||
background: url('previous-diff-inverse.svg') center center no-repeat;
|
||||
}
|
||||
|
||||
.vs .monaco-workbench .textdiff-editor-action.toggleIgnoreTrimWhitespace {
|
||||
opacity: 1;
|
||||
background: url('paragraph.svg') center center no-repeat;
|
||||
}
|
||||
.vs-dark .monaco-workbench .textdiff-editor-action.toggleIgnoreTrimWhitespace,
|
||||
.hc-black .monaco-workbench .textdiff-editor-action.toggleIgnoreTrimWhitespace {
|
||||
opacity: 1;
|
||||
background: url('paragraph-inverse.svg') center center no-repeat;
|
||||
}
|
||||
|
||||
.vs .monaco-workbench .textdiff-editor-action.toggleIgnoreTrimWhitespace.is-checked {
|
||||
opacity: 0.5;
|
||||
}
|
||||
.vs-dark .monaco-workbench .textdiff-editor-action.toggleIgnoreTrimWhitespace.is-checked,
|
||||
.hc-black .monaco-workbench .textdiff-editor-action.toggleIgnoreTrimWhitespace.is-checked {
|
||||
opacity: 0.5;
|
||||
}
|
||||
@@ -3,8 +3,6 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import 'vs/css!./media/notabstitlecontrol';
|
||||
import { toResource, Verbosity, IEditorInput } from 'vs/workbench/common/editor';
|
||||
import { TitleControl, IToolbarActions } from 'vs/workbench/browser/parts/editor/titleControl';
|
||||
@@ -15,11 +13,17 @@ import { addDisposableListener, EventType, addClass, EventHelper, removeClass, t
|
||||
import { IEditorPartOptions, EDITOR_TITLE_HEIGHT } from 'vs/workbench/browser/parts/editor/editor';
|
||||
import { IAction } from 'vs/base/common/actions';
|
||||
import { CLOSE_EDITOR_COMMAND_ID } from 'vs/workbench/browser/parts/editor/editorCommands';
|
||||
import { Color } from 'vs/base/common/color';
|
||||
|
||||
interface IRenderedEditorLabel {
|
||||
editor: IEditorInput;
|
||||
pinned: boolean;
|
||||
}
|
||||
|
||||
export class NoTabsTitleControl extends TitleControl {
|
||||
private titleContainer: HTMLElement;
|
||||
private editorLabel: ResourceLabel;
|
||||
private lastRenderedActiveEditor: IEditorInput;
|
||||
private activeLabel: IRenderedEditorLabel = Object.create(null);
|
||||
|
||||
protected create(parent: HTMLElement): void {
|
||||
this.titleContainer = parent;
|
||||
@@ -40,7 +44,9 @@ export class NoTabsTitleControl extends TitleControl {
|
||||
this._register(this.editorLabel.onClick(e => this.onTitleLabelClick(e)));
|
||||
|
||||
// Breadcrumbs
|
||||
this.createBreadcrumbsControl(labelContainer, { showFileIcons: false, showSymbolIcons: true, showDecorationColors: false, extraClasses: ['no-tabs-breadcrumbs'] });
|
||||
this.createBreadcrumbsControl(labelContainer, { showFileIcons: false, showSymbolIcons: true, showDecorationColors: false, breadcrumbsBackground: () => Color.transparent });
|
||||
toggleClass(this.titleContainer, 'breadcrumbs', Boolean(this.breadcrumbsControl));
|
||||
this.toDispose.push({ dispose: () => removeClass(this.titleContainer, 'breadcrumbs') }); // import to remove because the container is a shared dom node
|
||||
|
||||
// Right Actions Container
|
||||
const actionsContainer = document.createElement('div');
|
||||
@@ -60,7 +66,7 @@ export class NoTabsTitleControl extends TitleControl {
|
||||
this._register(addDisposableListener(this.titleContainer, EventType.DBLCLICK, (e: MouseEvent) => this.onTitleDoubleClick(e)));
|
||||
|
||||
// Detect mouse click
|
||||
this._register(addDisposableListener(this.titleContainer, EventType.CLICK, (e: MouseEvent) => this.onTitleClick(e)));
|
||||
this._register(addDisposableListener(this.titleContainer, EventType.MOUSE_UP, (e: MouseEvent) => this.onTitleClick(e)));
|
||||
|
||||
// Detect touch
|
||||
this._register(addDisposableListener(this.titleContainer, TouchEventType.Tap, (e: GestureEvent) => this.onTitleClick(e)));
|
||||
@@ -87,6 +93,8 @@ export class NoTabsTitleControl extends TitleControl {
|
||||
|
||||
// Close editor on middle mouse click
|
||||
if (e instanceof MouseEvent && e.button === 1 /* Middle Button */) {
|
||||
EventHelper.stop(e, true /* for https://github.com/Microsoft/vscode/issues/56715 */);
|
||||
|
||||
this.group.closeEditor(this.group.activeEditor);
|
||||
}
|
||||
}
|
||||
@@ -96,7 +104,10 @@ export class NoTabsTitleControl extends TitleControl {
|
||||
}
|
||||
|
||||
openEditor(editor: IEditorInput): void {
|
||||
this.ifActiveEditorChanged(() => this.redraw());
|
||||
const activeEditorChanged = this.ifActiveEditorChanged(() => this.redraw());
|
||||
if (!activeEditorChanged) {
|
||||
this.ifActiveEditorPropertiesChanged(() => this.redraw());
|
||||
}
|
||||
}
|
||||
|
||||
closeEditor(editor: IEditorInput): void {
|
||||
@@ -147,16 +158,36 @@ export class NoTabsTitleControl extends TitleControl {
|
||||
this.redraw();
|
||||
}
|
||||
|
||||
private ifActiveEditorChanged(fn: () => void): void {
|
||||
protected handleBreadcrumbsEnablementChange(): void {
|
||||
toggleClass(this.titleContainer, 'breadcrumbs', Boolean(this.breadcrumbsControl));
|
||||
this.redraw();
|
||||
}
|
||||
|
||||
private ifActiveEditorChanged(fn: () => void): boolean {
|
||||
if (
|
||||
!this.lastRenderedActiveEditor && this.group.activeEditor || // active editor changed from null => editor
|
||||
this.lastRenderedActiveEditor && !this.group.activeEditor || // active editor changed from editor => null
|
||||
!this.group.isActive(this.lastRenderedActiveEditor) // active editor changed from editorA => editorB
|
||||
!this.activeLabel.editor && this.group.activeEditor || // active editor changed from null => editor
|
||||
this.activeLabel.editor && !this.group.activeEditor || // active editor changed from editor => null
|
||||
!this.group.isActive(this.activeLabel.editor) // active editor changed from editorA => editorB
|
||||
) {
|
||||
fn();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private ifActiveEditorPropertiesChanged(fn: () => void): void {
|
||||
if (!this.activeLabel.editor || !this.group.activeEditor) {
|
||||
return; // need an active editor to check for properties changed
|
||||
}
|
||||
|
||||
if (this.activeLabel.pinned !== this.group.isPinned(this.group.activeEditor)) {
|
||||
fn(); // only run if pinned state has changed
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private ifEditorIsActive(editor: IEditorInput, fn: () => void): void {
|
||||
if (this.group.isActive(editor)) {
|
||||
fn(); // only run if editor is current active
|
||||
@@ -165,11 +196,12 @@ export class NoTabsTitleControl extends TitleControl {
|
||||
|
||||
private redraw(): void {
|
||||
const editor = this.group.activeEditor;
|
||||
this.lastRenderedActiveEditor = editor;
|
||||
|
||||
const isEditorPinned = this.group.isPinned(this.group.activeEditor);
|
||||
const isGroupActive = this.accessor.activeGroup === this.group;
|
||||
|
||||
this.activeLabel = { editor, pinned: isEditorPinned };
|
||||
|
||||
// Update Breadcrumbs
|
||||
if (this.breadcrumbsControl) {
|
||||
if (isGroupActive) {
|
||||
@@ -189,6 +221,7 @@ export class NoTabsTitleControl extends TitleControl {
|
||||
|
||||
// Otherwise render it
|
||||
else {
|
||||
|
||||
// Dirty state
|
||||
this.updateEditorDirty(editor);
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { IDisposable, Disposable } from 'vs/base/common/lifecycle';
|
||||
import URI from 'vs/base/common/uri';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { IRange } from 'vs/editor/common/core/range';
|
||||
@@ -21,8 +21,8 @@ export interface IRangeHighlightDecoration {
|
||||
|
||||
export class RangeHighlightDecorations extends Disposable {
|
||||
|
||||
private rangeHighlightDecorationId: string = null;
|
||||
private editor: ICodeEditor = null;
|
||||
private rangeHighlightDecorationId: string | null = null;
|
||||
private editor: ICodeEditor | null = null;
|
||||
private editorDisposables: IDisposable[] = [];
|
||||
|
||||
private readonly _onHighlightRemoved: Emitter<void> = this._register(new Emitter<void>());
|
||||
|
||||
@@ -6,20 +6,19 @@
|
||||
import 'vs/css!./media/resourceviewer';
|
||||
import * as nls from 'vs/nls';
|
||||
import * as mimes from 'vs/base/common/mime';
|
||||
import URI from 'vs/base/common/uri';
|
||||
import { Builder, $ } from 'vs/base/browser/builder';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import * as DOM from 'vs/base/browser/dom';
|
||||
import { DomScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement';
|
||||
import { LRUCache } from 'vs/base/common/map';
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
import { clamp } from 'vs/base/common/numbers';
|
||||
import { Themable } from 'vs/workbench/common/theme';
|
||||
import { IStatusbarItem, StatusbarItemDescriptor, IStatusbarRegistry, Extensions, StatusbarAlignment } from 'vs/workbench/browser/parts/statusbar/statusbar';
|
||||
import { IStatusbarItem, StatusbarItemDescriptor, IStatusbarRegistry, Extensions } from 'vs/workbench/browser/parts/statusbar/statusbar';
|
||||
import { StatusbarAlignment } from 'vs/platform/statusbar/common/statusbar';
|
||||
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
|
||||
import { IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { IDisposable, Disposable, combinedDisposable } from 'vs/base/common/lifecycle';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { Action } from 'vs/base/common/actions';
|
||||
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { memoize } from 'vs/base/common/decorators';
|
||||
@@ -61,8 +60,8 @@ class BinarySize {
|
||||
}
|
||||
}
|
||||
|
||||
export interface ResourceViewerContext {
|
||||
layout(dimension: DOM.Dimension): void;
|
||||
export interface ResourceViewerContext extends IDisposable {
|
||||
layout?(dimension: DOM.Dimension): void;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -81,10 +80,10 @@ export class ResourceViewer {
|
||||
openInternalClb: (uri: URI) => void,
|
||||
openExternalClb: (uri: URI) => void,
|
||||
metadataClb: (meta: string) => void
|
||||
): ResourceViewerContext | null {
|
||||
): ResourceViewerContext {
|
||||
|
||||
// Ensure CSS class
|
||||
$(container).setClass('monaco-resource-viewer');
|
||||
container.className = 'monaco-resource-viewer';
|
||||
|
||||
// Images
|
||||
if (ResourceViewer.isImageResource(descriptor)) {
|
||||
@@ -93,21 +92,20 @@ export class ResourceViewer {
|
||||
|
||||
// Large Files
|
||||
if (descriptor.size > ResourceViewer.MAX_OPEN_INTERNAL_SIZE) {
|
||||
FileTooLargeFileView.create(container, descriptor, scrollbar, metadataClb);
|
||||
return FileTooLargeFileView.create(container, descriptor, scrollbar, metadataClb);
|
||||
}
|
||||
|
||||
// Seemingly Binary Files
|
||||
else {
|
||||
FileSeemsBinaryFileView.create(container, descriptor, scrollbar, openInternalClb, metadataClb);
|
||||
return FileSeemsBinaryFileView.create(container, descriptor, scrollbar, openInternalClb, metadataClb);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static isImageResource(descriptor: IResourceDescriptor) {
|
||||
const mime = getMime(descriptor);
|
||||
|
||||
return mime.indexOf('image/') >= 0;
|
||||
// Chrome does not support tiffs
|
||||
return mime.indexOf('image/') >= 0 && mime !== 'image/tiff';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -122,14 +120,12 @@ class ImageView {
|
||||
scrollbar: DomScrollableElement,
|
||||
openExternalClb: (uri: URI) => void,
|
||||
metadataClb: (meta: string) => void
|
||||
): ResourceViewerContext | null {
|
||||
): ResourceViewerContext {
|
||||
if (ImageView.shouldShowImageInline(descriptor)) {
|
||||
return InlineImageView.create(container, descriptor, fileService, scrollbar, metadataClb);
|
||||
}
|
||||
|
||||
LargeImageView.create(container, descriptor, openExternalClb);
|
||||
|
||||
return null;
|
||||
return LargeImageView.create(container, descriptor, openExternalClb, metadataClb);
|
||||
}
|
||||
|
||||
private static shouldShowImageInline(descriptor: IResourceDescriptor): boolean {
|
||||
@@ -156,25 +152,29 @@ class LargeImageView {
|
||||
static create(
|
||||
container: HTMLElement,
|
||||
descriptor: IResourceDescriptor,
|
||||
openExternalClb: (uri: URI) => void
|
||||
openExternalClb: (uri: URI) => void,
|
||||
metadataClb: (meta: string) => void
|
||||
) {
|
||||
const size = BinarySize.formatSize(descriptor.size);
|
||||
metadataClb(size);
|
||||
|
||||
const imageContainer = $(container)
|
||||
.empty()
|
||||
.p({
|
||||
text: nls.localize('largeImageError', "The image is not displayed in the editor because it is too large ({0}).", size)
|
||||
});
|
||||
DOM.clearNode(container);
|
||||
|
||||
const disposables: IDisposable[] = [];
|
||||
|
||||
const label = document.createElement('p');
|
||||
label.textContent = nls.localize('largeImageError', "The image is not displayed in the editor because it is too large ({0}).", size);
|
||||
container.appendChild(label);
|
||||
|
||||
if (descriptor.resource.scheme !== Schemas.data) {
|
||||
imageContainer.append($('a', {
|
||||
role: 'button',
|
||||
class: 'embedded-link',
|
||||
text: nls.localize('resourceOpenExternalButton', "Open image using external program?")
|
||||
}).on(DOM.EventType.CLICK, (e) => {
|
||||
openExternalClb(descriptor.resource);
|
||||
}));
|
||||
const link = DOM.append(label, DOM.$('a.embedded-link'));
|
||||
link.setAttribute('role', 'button');
|
||||
link.textContent = nls.localize('resourceOpenExternalButton', "Open image using external program?");
|
||||
|
||||
disposables.push(DOM.addDisposableListener(link, DOM.EventType.CLICK, () => openExternalClb(descriptor.resource)));
|
||||
}
|
||||
|
||||
return combinedDisposable(disposables);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -186,18 +186,17 @@ class FileTooLargeFileView {
|
||||
metadataClb: (meta: string) => void
|
||||
) {
|
||||
const size = BinarySize.formatSize(descriptor.size);
|
||||
metadataClb(size);
|
||||
|
||||
$(container)
|
||||
.empty()
|
||||
.span({
|
||||
text: nls.localize('nativeFileTooLargeError', "The file is not displayed in the editor because it is too large ({0}).", size)
|
||||
});
|
||||
DOM.clearNode(container);
|
||||
|
||||
if (metadataClb) {
|
||||
metadataClb(size);
|
||||
}
|
||||
const label = document.createElement('span');
|
||||
label.textContent = nls.localize('nativeFileTooLargeError', "The file is not displayed in the editor because it is too large ({0}).", size);
|
||||
container.appendChild(label);
|
||||
|
||||
scrollbar.scanDomNode();
|
||||
|
||||
return Disposable.None;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -209,27 +208,27 @@ class FileSeemsBinaryFileView {
|
||||
openInternalClb: (uri: URI) => void,
|
||||
metadataClb: (meta: string) => void
|
||||
) {
|
||||
const binaryContainer = $(container)
|
||||
.empty()
|
||||
.p({
|
||||
text: nls.localize('nativeBinaryError', "The file is not displayed in the editor because it is either binary or uses an unsupported text encoding.")
|
||||
});
|
||||
metadataClb(typeof descriptor.size === 'number' ? BinarySize.formatSize(descriptor.size) : '');
|
||||
|
||||
DOM.clearNode(container);
|
||||
|
||||
const disposables: IDisposable[] = [];
|
||||
|
||||
const label = document.createElement('p');
|
||||
label.textContent = nls.localize('nativeBinaryError', "The file is not displayed in the editor because it is either binary or uses an unsupported text encoding.");
|
||||
container.appendChild(label);
|
||||
|
||||
if (descriptor.resource.scheme !== Schemas.data) {
|
||||
binaryContainer.append($('a', {
|
||||
role: 'button',
|
||||
class: 'embedded-link',
|
||||
text: nls.localize('openAsText', "Do you want to open it anyway?")
|
||||
}).on(DOM.EventType.CLICK, (e) => {
|
||||
openInternalClb(descriptor.resource);
|
||||
}));
|
||||
}
|
||||
const link = DOM.append(label, DOM.$('a.embedded-link'));
|
||||
link.setAttribute('role', 'button');
|
||||
link.textContent = nls.localize('openAsText', "Do you want to open it anyway?");
|
||||
|
||||
if (metadataClb) {
|
||||
metadataClb(BinarySize.formatSize(descriptor.size));
|
||||
disposables.push(DOM.addDisposableListener(link, DOM.EventType.CLICK, () => openInternalClb(descriptor.resource)));
|
||||
}
|
||||
|
||||
scrollbar.scanDomNode();
|
||||
|
||||
return combinedDisposable(disposables);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -239,7 +238,7 @@ class ZoomStatusbarItem extends Themable implements IStatusbarItem {
|
||||
|
||||
static instance: ZoomStatusbarItem;
|
||||
|
||||
showTimeout: number;
|
||||
showTimeout: any;
|
||||
|
||||
private statusBarItem: HTMLElement;
|
||||
private onSelectScale?: (scale: Scale) => void;
|
||||
@@ -276,16 +275,16 @@ class ZoomStatusbarItem extends Themable implements IStatusbarItem {
|
||||
|
||||
render(container: HTMLElement): IDisposable {
|
||||
if (!this.statusBarItem && container) {
|
||||
this.statusBarItem = $(container).a()
|
||||
.addClass('.zoom-statusbar-item')
|
||||
.on('click', () => {
|
||||
this.contextMenuService.showContextMenu({
|
||||
getAnchor: () => container,
|
||||
getActions: () => TPromise.as(this.zoomActions)
|
||||
});
|
||||
})
|
||||
.getHTMLElement();
|
||||
this.statusBarItem = DOM.append(container, DOM.$('a.zoom-statusbar-item'));
|
||||
this.statusBarItem.setAttribute('role', 'button');
|
||||
this.statusBarItem.style.display = 'none';
|
||||
|
||||
DOM.addDisposableListener(this.statusBarItem, DOM.EventType.CLICK, () => {
|
||||
this.contextMenuService.showContextMenu({
|
||||
getAnchor: () => container,
|
||||
getActions: () => this.zoomActions
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
return this;
|
||||
@@ -304,7 +303,7 @@ class ZoomStatusbarItem extends Themable implements IStatusbarItem {
|
||||
this.onSelectScale(scale);
|
||||
}
|
||||
|
||||
return null;
|
||||
return void 0;
|
||||
}));
|
||||
}
|
||||
|
||||
@@ -368,8 +367,11 @@ class InlineImageView {
|
||||
scrollbar: DomScrollableElement,
|
||||
metadataClb: (meta: string) => void
|
||||
) {
|
||||
const context = {
|
||||
layout(dimension: DOM.Dimension) { }
|
||||
const disposables: IDisposable[] = [];
|
||||
|
||||
const context: ResourceViewerContext = {
|
||||
layout(dimension: DOM.Dimension) { },
|
||||
dispose: () => combinedDisposable(disposables).dispose()
|
||||
};
|
||||
|
||||
const cacheKey = descriptor.resource.toString();
|
||||
@@ -379,41 +381,40 @@ class InlineImageView {
|
||||
|
||||
const initialState: ImageState = InlineImageView.imageStateCache.get(cacheKey) || { scale: 'fit', offsetX: 0, offsetY: 0 };
|
||||
let scale = initialState.scale;
|
||||
let img: Builder | null = null;
|
||||
let imgElement: HTMLImageElement | null = null;
|
||||
let image: HTMLImageElement | null = null;
|
||||
|
||||
function updateScale(newScale: Scale) {
|
||||
if (!img || !imgElement.parentElement) {
|
||||
if (!image || !image.parentElement) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (newScale === 'fit') {
|
||||
scale = 'fit';
|
||||
img.addClass('scale-to-fit');
|
||||
img.removeClass('pixelated');
|
||||
img.style('min-width', 'auto');
|
||||
img.style('width', 'auto');
|
||||
DOM.addClass(image, 'scale-to-fit');
|
||||
DOM.removeClass(image, 'pixelated');
|
||||
image.style.minWidth = 'auto';
|
||||
image.style.width = 'auto';
|
||||
InlineImageView.imageStateCache.set(cacheKey, null);
|
||||
} else {
|
||||
const oldWidth = imgElement.width;
|
||||
const oldHeight = imgElement.height;
|
||||
const oldWidth = image.width;
|
||||
const oldHeight = image.height;
|
||||
|
||||
scale = clamp(newScale, InlineImageView.MIN_SCALE, InlineImageView.MAX_SCALE);
|
||||
if (scale >= InlineImageView.PIXELATION_THRESHOLD) {
|
||||
img.addClass('pixelated');
|
||||
DOM.addClass(image, 'pixelated');
|
||||
} else {
|
||||
img.removeClass('pixelated');
|
||||
DOM.removeClass(image, 'pixelated');
|
||||
}
|
||||
|
||||
const { scrollTop, scrollLeft } = imgElement.parentElement;
|
||||
const dx = (scrollLeft + imgElement.parentElement.clientWidth / 2) / imgElement.parentElement.scrollWidth;
|
||||
const dy = (scrollTop + imgElement.parentElement.clientHeight / 2) / imgElement.parentElement.scrollHeight;
|
||||
const { scrollTop, scrollLeft } = image.parentElement;
|
||||
const dx = (scrollLeft + image.parentElement.clientWidth / 2) / image.parentElement.scrollWidth;
|
||||
const dy = (scrollTop + image.parentElement.clientHeight / 2) / image.parentElement.scrollHeight;
|
||||
|
||||
img.removeClass('scale-to-fit');
|
||||
img.style('min-width', `${(imgElement.naturalWidth * scale)}px`);
|
||||
img.style('width', `${(imgElement.naturalWidth * scale)}px`);
|
||||
DOM.removeClass(image, 'scale-to-fit');
|
||||
image.style.minWidth = `${(image.naturalWidth * scale)}px`;
|
||||
image.style.width = `${(image.naturalWidth * scale)}px`;
|
||||
|
||||
const newWidth = imgElement.width;
|
||||
const newWidth = image.width;
|
||||
const scaleFactor = (newWidth - oldWidth) / oldWidth;
|
||||
|
||||
const newScrollLeft = ((oldWidth * scaleFactor * dx) + scrollLeft);
|
||||
@@ -431,123 +432,131 @@ class InlineImageView {
|
||||
}
|
||||
|
||||
function firstZoom() {
|
||||
scale = imgElement.clientWidth / imgElement.naturalWidth;
|
||||
scale = image.clientWidth / image.naturalWidth;
|
||||
updateScale(scale);
|
||||
}
|
||||
|
||||
$(container)
|
||||
.on(DOM.EventType.KEY_DOWN, (e: KeyboardEvent, c) => {
|
||||
if (!img) {
|
||||
return;
|
||||
}
|
||||
ctrlPressed = e.ctrlKey;
|
||||
altPressed = e.altKey;
|
||||
disposables.push(DOM.addDisposableListener(window, DOM.EventType.KEY_DOWN, (e: KeyboardEvent) => {
|
||||
if (!image) {
|
||||
return;
|
||||
}
|
||||
ctrlPressed = e.ctrlKey;
|
||||
altPressed = e.altKey;
|
||||
|
||||
if (platform.isMacintosh ? altPressed : ctrlPressed) {
|
||||
c.removeClass('zoom-in').addClass('zoom-out');
|
||||
}
|
||||
})
|
||||
.on(DOM.EventType.KEY_UP, (e: KeyboardEvent, c) => {
|
||||
if (!img) {
|
||||
return;
|
||||
}
|
||||
if (platform.isMacintosh ? altPressed : ctrlPressed) {
|
||||
DOM.removeClass(container, 'zoom-in');
|
||||
DOM.addClass(container, 'zoom-out');
|
||||
}
|
||||
}));
|
||||
|
||||
ctrlPressed = e.ctrlKey;
|
||||
altPressed = e.altKey;
|
||||
disposables.push(DOM.addDisposableListener(window, DOM.EventType.KEY_UP, (e: KeyboardEvent) => {
|
||||
if (!image) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!(platform.isMacintosh ? altPressed : ctrlPressed)) {
|
||||
c.removeClass('zoom-out').addClass('zoom-in');
|
||||
}
|
||||
})
|
||||
.on(DOM.EventType.CLICK, (e: MouseEvent) => {
|
||||
if (!img) {
|
||||
return;
|
||||
}
|
||||
ctrlPressed = e.ctrlKey;
|
||||
altPressed = e.altKey;
|
||||
|
||||
if (e.button !== 0) {
|
||||
return;
|
||||
}
|
||||
if (!(platform.isMacintosh ? altPressed : ctrlPressed)) {
|
||||
DOM.removeClass(container, 'zoom-out');
|
||||
DOM.addClass(container, 'zoom-in');
|
||||
}
|
||||
}));
|
||||
|
||||
// left click
|
||||
if (scale === 'fit') {
|
||||
firstZoom();
|
||||
}
|
||||
disposables.push(DOM.addDisposableListener(container, DOM.EventType.CLICK, (e: MouseEvent) => {
|
||||
if (!image) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!(platform.isMacintosh ? altPressed : ctrlPressed)) { // zoom in
|
||||
let i = 0;
|
||||
for (; i < InlineImageView.zoomLevels.length; ++i) {
|
||||
if (InlineImageView.zoomLevels[i] > scale) {
|
||||
break;
|
||||
}
|
||||
if (e.button !== 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// left click
|
||||
if (scale === 'fit') {
|
||||
firstZoom();
|
||||
}
|
||||
|
||||
if (!(platform.isMacintosh ? altPressed : ctrlPressed)) { // zoom in
|
||||
let i = 0;
|
||||
for (; i < InlineImageView.zoomLevels.length; ++i) {
|
||||
if (InlineImageView.zoomLevels[i] > scale) {
|
||||
break;
|
||||
}
|
||||
updateScale(InlineImageView.zoomLevels[i] || InlineImageView.MAX_SCALE);
|
||||
} else {
|
||||
let i = InlineImageView.zoomLevels.length - 1;
|
||||
for (; i >= 0; --i) {
|
||||
if (InlineImageView.zoomLevels[i] < scale) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
updateScale(InlineImageView.zoomLevels[i] || InlineImageView.MAX_SCALE);
|
||||
} else {
|
||||
let i = InlineImageView.zoomLevels.length - 1;
|
||||
for (; i >= 0; --i) {
|
||||
if (InlineImageView.zoomLevels[i] < scale) {
|
||||
break;
|
||||
}
|
||||
updateScale(InlineImageView.zoomLevels[i] || InlineImageView.MIN_SCALE);
|
||||
}
|
||||
})
|
||||
.on(DOM.EventType.WHEEL, (e: WheelEvent) => {
|
||||
if (!img) {
|
||||
return;
|
||||
}
|
||||
updateScale(InlineImageView.zoomLevels[i] || InlineImageView.MIN_SCALE);
|
||||
}
|
||||
}));
|
||||
|
||||
const isScrollWhellKeyPressed = platform.isMacintosh ? altPressed : ctrlPressed;
|
||||
if (!isScrollWhellKeyPressed && !e.ctrlKey) { // pinching is reported as scroll wheel + ctrl
|
||||
return;
|
||||
}
|
||||
disposables.push(DOM.addDisposableListener(container, DOM.EventType.WHEEL, (e: WheelEvent) => {
|
||||
if (!image) {
|
||||
return;
|
||||
}
|
||||
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
const isScrollWhellKeyPressed = platform.isMacintosh ? altPressed : ctrlPressed;
|
||||
if (!isScrollWhellKeyPressed && !e.ctrlKey) { // pinching is reported as scroll wheel + ctrl
|
||||
return;
|
||||
}
|
||||
|
||||
if (scale === 'fit') {
|
||||
firstZoom();
|
||||
}
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
let delta = e.deltaY < 0 ? 1 : -1;
|
||||
if (scale === 'fit') {
|
||||
firstZoom();
|
||||
}
|
||||
|
||||
// Pinching should increase the scale
|
||||
if (e.ctrlKey && !isScrollWhellKeyPressed) {
|
||||
delta *= -1;
|
||||
}
|
||||
updateScale(scale as number * (1 - delta * InlineImageView.SCALE_PINCH_FACTOR));
|
||||
})
|
||||
.on(DOM.EventType.SCROLL, () => {
|
||||
if (!imgElement || !imgElement.parentElement || scale === 'fit') {
|
||||
return;
|
||||
}
|
||||
let delta = e.deltaY < 0 ? 1 : -1;
|
||||
|
||||
const entry = InlineImageView.imageStateCache.get(cacheKey);
|
||||
if (entry) {
|
||||
const { scrollTop, scrollLeft } = imgElement.parentElement;
|
||||
InlineImageView.imageStateCache.set(cacheKey, { scale: entry.scale, offsetX: scrollLeft, offsetY: scrollTop });
|
||||
}
|
||||
});
|
||||
// Pinching should increase the scale
|
||||
if (e.ctrlKey && !isScrollWhellKeyPressed) {
|
||||
delta *= -1;
|
||||
}
|
||||
updateScale(scale as number * (1 - delta * InlineImageView.SCALE_PINCH_FACTOR));
|
||||
}));
|
||||
|
||||
$(container)
|
||||
.empty()
|
||||
.addClass('image', 'zoom-in')
|
||||
.img({})
|
||||
.style('visibility', 'hidden')
|
||||
.addClass('scale-to-fit')
|
||||
.on(DOM.EventType.LOAD, (e, i) => {
|
||||
img = i;
|
||||
imgElement = img.getHTMLElement() as HTMLImageElement;
|
||||
metadataClb(nls.localize('imgMeta', '{0}x{1} {2}', imgElement.naturalWidth, imgElement.naturalHeight, BinarySize.formatSize(descriptor.size)));
|
||||
scrollbar.scanDomNode();
|
||||
img.style('visibility', 'visible');
|
||||
updateScale(scale);
|
||||
if (initialState.scale !== 'fit') {
|
||||
scrollbar.setScrollPosition({
|
||||
scrollLeft: initialState.offsetX,
|
||||
scrollTop: initialState.offsetY,
|
||||
});
|
||||
}
|
||||
});
|
||||
disposables.push(DOM.addDisposableListener(container, DOM.EventType.SCROLL, () => {
|
||||
if (!image || !image.parentElement || scale === 'fit') {
|
||||
return;
|
||||
}
|
||||
|
||||
const entry = InlineImageView.imageStateCache.get(cacheKey);
|
||||
if (entry) {
|
||||
const { scrollTop, scrollLeft } = image.parentElement;
|
||||
InlineImageView.imageStateCache.set(cacheKey, { scale: entry.scale, offsetX: scrollLeft, offsetY: scrollTop });
|
||||
}
|
||||
}));
|
||||
|
||||
DOM.clearNode(container);
|
||||
DOM.addClasses(container, 'image', 'zoom-in');
|
||||
|
||||
image = DOM.append(container, DOM.$('img.scale-to-fit'));
|
||||
image.style.visibility = 'hidden';
|
||||
|
||||
disposables.push(DOM.addDisposableListener(image, DOM.EventType.LOAD, e => {
|
||||
if (typeof descriptor.size === 'number') {
|
||||
metadataClb(nls.localize('imgMeta', '{0}x{1} {2}', image.naturalWidth, image.naturalHeight, BinarySize.formatSize(descriptor.size)));
|
||||
} else {
|
||||
metadataClb(nls.localize('imgMetaNoSize', '{0}x{1}', image.naturalWidth, image.naturalHeight));
|
||||
}
|
||||
|
||||
scrollbar.scanDomNode();
|
||||
image.style.visibility = 'visible';
|
||||
updateScale(scale);
|
||||
if (initialState.scale !== 'fit') {
|
||||
scrollbar.setScrollPosition({
|
||||
scrollLeft: initialState.offsetX,
|
||||
scrollTop: initialState.offsetY,
|
||||
});
|
||||
}
|
||||
}));
|
||||
|
||||
InlineImageView.imageSrc(descriptor, fileService).then(dataUri => {
|
||||
const imgs = container.getElementsByTagName('img');
|
||||
@@ -559,9 +568,9 @@ class InlineImageView {
|
||||
return context;
|
||||
}
|
||||
|
||||
private static imageSrc(descriptor: IResourceDescriptor, fileService: IFileService): TPromise<string> {
|
||||
private static imageSrc(descriptor: IResourceDescriptor, fileService: IFileService): Thenable<string> {
|
||||
if (descriptor.resource.scheme === Schemas.data) {
|
||||
return TPromise.as(descriptor.resource.toString(true /* skip encoding */));
|
||||
return Promise.resolve(descriptor.resource.toString(true /* skip encoding */));
|
||||
}
|
||||
|
||||
return fileService.resolveContent(descriptor.resource, { encoding: 'base64' }).then(data => {
|
||||
@@ -575,7 +584,8 @@ class InlineImageView {
|
||||
function getMime(descriptor: IResourceDescriptor) {
|
||||
let mime = descriptor.mime;
|
||||
if (!mime && descriptor.resource.scheme !== Schemas.data) {
|
||||
mime = mimes.getMediaMime(descriptor.resource.toString());
|
||||
mime = mimes.getMediaMime(descriptor.resource.path);
|
||||
}
|
||||
|
||||
return mime || mimes.MIME_BINARY;
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import * as DOM from 'vs/base/browser/dom';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { EditorInput, EditorOptions, SideBySideEditorInput, IEditorControl, IEditor } from 'vs/workbench/common/editor';
|
||||
@@ -17,6 +16,7 @@ import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { IEditorGroup } from 'vs/workbench/services/group/common/editorGroupsService';
|
||||
import { SplitView, Sizing, Orientation } from 'vs/base/browser/ui/splitview/splitview';
|
||||
import { Event, Relay, anyEvent, mapEvent, Emitter } from 'vs/base/common/event';
|
||||
import { IStorageService } from 'vs/platform/storage/common/storage';
|
||||
|
||||
export class SideBySideEditor extends BaseEditor {
|
||||
|
||||
@@ -59,9 +59,10 @@ export class SideBySideEditor extends BaseEditor {
|
||||
constructor(
|
||||
@ITelemetryService telemetryService: ITelemetryService,
|
||||
@IInstantiationService private instantiationService: IInstantiationService,
|
||||
@IThemeService themeService: IThemeService
|
||||
@IThemeService themeService: IThemeService,
|
||||
@IStorageService storageService: IStorageService
|
||||
) {
|
||||
super(SideBySideEditor.ID, telemetryService, themeService);
|
||||
super(SideBySideEditor.ID, telemetryService, themeService, storageService);
|
||||
}
|
||||
|
||||
protected createEditor(parent: HTMLElement): void {
|
||||
@@ -107,9 +108,11 @@ export class SideBySideEditor extends BaseEditor {
|
||||
if (this.masterEditor) {
|
||||
this.masterEditor.setVisible(visible, group);
|
||||
}
|
||||
|
||||
if (this.detailsEditor) {
|
||||
this.detailsEditor.setVisible(visible, group);
|
||||
}
|
||||
|
||||
super.setEditorVisible(visible, group);
|
||||
}
|
||||
|
||||
@@ -117,10 +120,13 @@ export class SideBySideEditor extends BaseEditor {
|
||||
if (this.masterEditor) {
|
||||
this.masterEditor.clearInput();
|
||||
}
|
||||
|
||||
if (this.detailsEditor) {
|
||||
this.detailsEditor.clearInput();
|
||||
}
|
||||
|
||||
this.disposeEditors();
|
||||
|
||||
super.clearInput();
|
||||
}
|
||||
|
||||
@@ -139,6 +145,7 @@ export class SideBySideEditor extends BaseEditor {
|
||||
if (this.masterEditor) {
|
||||
return this.masterEditor.getControl();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -159,7 +166,10 @@ export class SideBySideEditor extends BaseEditor {
|
||||
return this.setNewInput(newInput, options, token);
|
||||
}
|
||||
|
||||
return TPromise.join([this.detailsEditor.setInput(newInput.details, null, token), this.masterEditor.setInput(newInput.master, options, token)]).then(() => void 0);
|
||||
return Promise.all([
|
||||
this.detailsEditor.setInput(newInput.details, null, token),
|
||||
this.masterEditor.setInput(newInput.master, options, token)]
|
||||
).then(() => void 0);
|
||||
}
|
||||
|
||||
private setNewInput(newInput: SideBySideEditorInput, options: EditorOptions, token: CancellationToken): Thenable<void> {
|
||||
@@ -179,7 +189,7 @@ export class SideBySideEditor extends BaseEditor {
|
||||
return editor;
|
||||
}
|
||||
|
||||
private onEditorsCreated(details: BaseEditor, master: BaseEditor, detailsInput: EditorInput, masterInput: EditorInput, options: EditorOptions, token: CancellationToken): TPromise<void> {
|
||||
private onEditorsCreated(details: BaseEditor, master: BaseEditor, detailsInput: EditorInput, masterInput: EditorInput, options: EditorOptions, token: CancellationToken): Promise<void> {
|
||||
this.detailsEditor = details;
|
||||
this.masterEditor = master;
|
||||
|
||||
@@ -190,7 +200,7 @@ export class SideBySideEditor extends BaseEditor {
|
||||
|
||||
this.onDidCreateEditors.fire();
|
||||
|
||||
return TPromise.join([this.detailsEditor.setInput(detailsInput, null, token), this.masterEditor.setInput(masterInput, options, token)]).then(() => this.focus());
|
||||
return Promise.all([this.detailsEditor.setInput(detailsInput, null, token), this.masterEditor.setInput(masterInput, options, token)]).then(() => this.focus());
|
||||
}
|
||||
|
||||
updateStyles(): void {
|
||||
|
||||
@@ -3,8 +3,6 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import 'vs/css!./media/tabstitlecontrol';
|
||||
import { isMacintosh } from 'vs/base/common/platform';
|
||||
import { shorten } from 'vs/base/common/labels';
|
||||
@@ -26,11 +24,10 @@ import { IDisposable, dispose, combinedDisposable } from 'vs/base/common/lifecyc
|
||||
import { ScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement';
|
||||
import { ScrollbarVisibility } from 'vs/base/common/scrollable';
|
||||
import { getOrSet } from 'vs/base/common/map';
|
||||
|
||||
// {{SQL CARBON EDIT}} -- Display the editor's tab color
|
||||
import { IThemeService, registerThemingParticipant, ITheme, ICssStyleCollector, HIGH_CONTRAST } 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, TAB_HOVER_BACKGROUND, TAB_HOVER_BORDER, TAB_UNFOCUSED_HOVER_BACKGROUND, TAB_UNFOCUSED_HOVER_BORDER, EDITOR_GROUP_HEADER_TABS_BACKGROUND, WORKBENCH_BACKGROUND, TAB_ACTIVE_BORDER_TOP, TAB_UNFOCUSED_ACTIVE_BORDER_TOP } from 'vs/workbench/common/theme';
|
||||
import { activeContrastBorder, contrastBorder, editorBackground } from 'vs/platform/theme/common/colorRegistry';
|
||||
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, TAB_HOVER_BACKGROUND, TAB_HOVER_BORDER, TAB_UNFOCUSED_HOVER_BACKGROUND, TAB_UNFOCUSED_HOVER_BORDER, EDITOR_GROUP_HEADER_TABS_BACKGROUND, WORKBENCH_BACKGROUND, TAB_ACTIVE_BORDER_TOP, TAB_UNFOCUSED_ACTIVE_BORDER_TOP, TAB_ACTIVE_MODIFIED_BORDER, TAB_INACTIVE_MODIFIED_BORDER, TAB_UNFOCUSED_ACTIVE_MODIFIED_BORDER, TAB_UNFOCUSED_INACTIVE_MODIFIED_BORDER } from 'vs/workbench/common/theme';
|
||||
import { activeContrastBorder, contrastBorder, editorBackground, breadcrumbsBackground } from 'vs/platform/theme/common/colorRegistry';
|
||||
import { ResourcesDropHandler, fillResourceDataTransfers, DraggedEditorIdentifier, DraggedEditorGroupIdentifier, DragAndDropObserver } from 'vs/workbench/browser/dnd';
|
||||
import { Color } from 'vs/base/common/color';
|
||||
import { INotificationService } from 'vs/platform/notification/common/notification';
|
||||
@@ -43,9 +40,7 @@ import { IEditorGroupsAccessor, IEditorPartOptions, IEditorGroupView } from 'vs/
|
||||
import { CloseOneEditorAction } from 'vs/workbench/browser/parts/editor/editorActions';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { BreadcrumbsControl } from 'vs/workbench/browser/parts/editor/breadcrumbsControl';
|
||||
import { IWorkspaceConfigurationService } from 'vs/workbench/services/configuration/common/configuration';
|
||||
import * as QueryConstants from 'sql/parts/query/common/constants';
|
||||
import * as WorkbenchUtils from 'sql/workbench/common/sqlWorkbenchUtils';
|
||||
import { IFileService } from 'vs/platform/files/common/files';
|
||||
|
||||
// {{SQL CARBON EDIT}} -- Display the editor's tab color
|
||||
import { IConnectionManagementService } from 'sql/platform/connection/common/connectionManagement';
|
||||
@@ -53,6 +48,10 @@ import { IQueryEditorService } from 'sql/workbench/services/queryEditor/common/q
|
||||
import { IObjectExplorerService } from 'sql/workbench/services/objectExplorer/common/objectExplorerService';
|
||||
import { ICommandService } from 'vs/platform/commands/common/commands';
|
||||
import { GlobalNewUntitledFileAction } from 'vs/workbench/parts/files/electron-browser/fileActions';
|
||||
import { IWorkspaceConfigurationService } from 'vs/workbench/services/configuration/common/configuration';
|
||||
import * as QueryConstants from 'sql/parts/query/common/constants';
|
||||
import * as WorkbenchUtils from 'sql/workbench/common/sqlWorkbenchUtils';
|
||||
// {{SQL CARBON EDIT}} -- End
|
||||
|
||||
interface IEditorInputLabel {
|
||||
name: string;
|
||||
@@ -67,7 +66,7 @@ export class TabsTitleControl extends TitleControl {
|
||||
private titleContainer: HTMLElement;
|
||||
private tabsContainer: HTMLElement;
|
||||
private editorToolbarContainer: HTMLElement;
|
||||
private scrollbar: ScrollableElement;
|
||||
private tabsScrollbar: ScrollableElement;
|
||||
private closeOneEditorAction: CloseOneEditorAction;
|
||||
|
||||
private tabLabelWidgets: ResourceLabel[] = [];
|
||||
@@ -94,19 +93,26 @@ export class TabsTitleControl extends TitleControl {
|
||||
@IThemeService themeService: IThemeService,
|
||||
@IExtensionService extensionService: IExtensionService,
|
||||
@IConfigurationService configurationService: IConfigurationService,
|
||||
@IFileService fileService: IFileService,
|
||||
// {{SQL CARBON EDIT}} -- Display the editor's tab color
|
||||
@IWorkspaceConfigurationService private workspaceConfigurationService: IWorkspaceConfigurationService,
|
||||
@ICommandService private commandService: ICommandService,
|
||||
@IConnectionManagementService private connectionService: IConnectionManagementService,
|
||||
@IQueryEditorService private queryEditorService: IQueryEditorService,
|
||||
@IObjectExplorerService private objectExplorerService: IObjectExplorerService
|
||||
@IObjectExplorerService private objectExplorerService: IObjectExplorerService,
|
||||
// {{SQL CARBON EDIT}} -- End
|
||||
) {
|
||||
super(parent, accessor, group, contextMenuService, instantiationService, contextKeyService, keybindingService, telemetryService, notificationService, menuService, quickOpenService, themeService, extensionService, configurationService);
|
||||
super(parent, accessor, group, contextMenuService, instantiationService, contextKeyService, keybindingService, telemetryService, notificationService, menuService, quickOpenService, themeService, extensionService, configurationService, fileService);
|
||||
}
|
||||
|
||||
protected create(parent: HTMLElement): void {
|
||||
this.titleContainer = parent;
|
||||
|
||||
// Tabs and Actions Container (are on a single row with flex side-by-side)
|
||||
const tabsAndActionsContainer = document.createElement('div');
|
||||
addClass(tabsAndActionsContainer, 'tabs-and-actions-container');
|
||||
this.titleContainer.appendChild(tabsAndActionsContainer);
|
||||
|
||||
// Tabs Container
|
||||
this.tabsContainer = document.createElement('div');
|
||||
this.tabsContainer.setAttribute('role', 'tablist');
|
||||
@@ -114,15 +120,16 @@ export class TabsTitleControl extends TitleControl {
|
||||
addClass(this.tabsContainer, 'tabs-container');
|
||||
|
||||
// Tabs Container listeners
|
||||
this.registerContainerListeners();
|
||||
this.registerTabsContainerListeners();
|
||||
|
||||
// Scrollbar
|
||||
this.createScrollbar();
|
||||
// Tabs Scrollbar
|
||||
this.tabsScrollbar = this.createTabsScrollbar(this.tabsContainer);
|
||||
tabsAndActionsContainer.appendChild(this.tabsScrollbar.getDomNode());
|
||||
|
||||
// Editor Toolbar Container
|
||||
this.editorToolbarContainer = document.createElement('div');
|
||||
addClass(this.editorToolbarContainer, 'editor-actions');
|
||||
this.titleContainer.appendChild(this.editorToolbarContainer);
|
||||
tabsAndActionsContainer.appendChild(this.editorToolbarContainer);
|
||||
|
||||
// Editor Actions Toolbar
|
||||
this.createEditorActionsToolBar(this.editorToolbarContainer);
|
||||
@@ -130,17 +137,15 @@ export class TabsTitleControl extends TitleControl {
|
||||
// Close Action
|
||||
this.closeOneEditorAction = this._register(this.instantiationService.createInstance(CloseOneEditorAction, CloseOneEditorAction.ID, CloseOneEditorAction.LABEL));
|
||||
|
||||
// Breadcrumbs
|
||||
// Breadcrumbs (are on a separate row below tabs and actions)
|
||||
const breadcrumbsContainer = document.createElement('div');
|
||||
addClass(breadcrumbsContainer, 'tabs-breadcrumbs');
|
||||
this.titleContainer.appendChild(breadcrumbsContainer);
|
||||
this.createBreadcrumbsControl(breadcrumbsContainer, { showFileIcons: true, showSymbolIcons: true, showDecorationColors: false, extraClasses: [] });
|
||||
this.createBreadcrumbsControl(breadcrumbsContainer, { showFileIcons: true, showSymbolIcons: true, showDecorationColors: false, breadcrumbsBackground: breadcrumbsBackground });
|
||||
}
|
||||
|
||||
private createScrollbar(): void {
|
||||
|
||||
// Custom Scrollbar
|
||||
this.scrollbar = new ScrollableElement(this.tabsContainer, {
|
||||
private createTabsScrollbar(scrollable: HTMLElement): ScrollableElement {
|
||||
const tabsScrollbar = new ScrollableElement(scrollable, {
|
||||
horizontal: ScrollbarVisibility.Auto,
|
||||
vertical: ScrollbarVisibility.Hidden,
|
||||
scrollYToX: true,
|
||||
@@ -148,11 +153,11 @@ export class TabsTitleControl extends TitleControl {
|
||||
horizontalScrollbarSize: 3
|
||||
});
|
||||
|
||||
this.scrollbar.onScroll(e => {
|
||||
this.tabsContainer.scrollLeft = e.scrollLeft;
|
||||
tabsScrollbar.onScroll(e => {
|
||||
scrollable.scrollLeft = e.scrollLeft;
|
||||
});
|
||||
|
||||
this.titleContainer.appendChild(this.scrollbar.getDomNode());
|
||||
return tabsScrollbar;
|
||||
}
|
||||
|
||||
private updateBreadcrumbsControl(): void {
|
||||
@@ -163,7 +168,12 @@ export class TabsTitleControl extends TitleControl {
|
||||
}
|
||||
}
|
||||
|
||||
private registerContainerListeners(): void {
|
||||
protected handleBreadcrumbsEnablementChange(): void {
|
||||
// relayout when breadcrumbs are enable/disabled
|
||||
this.group.relayout();
|
||||
}
|
||||
|
||||
private registerTabsContainerListeners(): void {
|
||||
|
||||
// Group dragging
|
||||
this.enableGroupDragging(this.tabsContainer);
|
||||
@@ -171,7 +181,7 @@ export class TabsTitleControl extends TitleControl {
|
||||
// Forward scrolling inside the container to our custom scrollbar
|
||||
this._register(addDisposableListener(this.tabsContainer, EventType.SCROLL, () => {
|
||||
if (hasClass(this.tabsContainer, 'scroll')) {
|
||||
this.scrollbar.setScrollPosition({
|
||||
this.tabsScrollbar.setScrollPosition({
|
||||
scrollLeft: this.tabsContainer.scrollLeft // during DND the container gets scrolled so we need to update the custom scrollbar
|
||||
});
|
||||
}
|
||||
@@ -182,7 +192,7 @@ export class TabsTitleControl extends TitleControl {
|
||||
if (e.target === this.tabsContainer) {
|
||||
EventHelper.stop(e);
|
||||
// {{SQL CARBON EDIT}}
|
||||
this.commandService.executeCommand(GlobalNewUntitledFileAction.ID).done(undefined, err => this.notificationService.warn(err));
|
||||
this.commandService.executeCommand(GlobalNewUntitledFileAction.ID).then(undefined, err => this.notificationService.warn(err));
|
||||
}
|
||||
}));
|
||||
|
||||
@@ -354,7 +364,7 @@ export class TabsTitleControl extends TitleControl {
|
||||
|
||||
// Activity has an impact on each tab
|
||||
this.forEachTab((editor, index, tabContainer, tabLabelWidget, tabLabel) => {
|
||||
this.redrawEditorActive(isGroupActive, editor, tabContainer, tabLabelWidget);
|
||||
this.redrawEditorActiveAndDirty(isGroupActive, editor, tabContainer, tabLabelWidget);
|
||||
});
|
||||
|
||||
// Activity has an impact on the toolbar, so we need to update and layout
|
||||
@@ -377,7 +387,7 @@ export class TabsTitleControl extends TitleControl {
|
||||
}
|
||||
|
||||
updateEditorDirty(editor: IEditorInput): void {
|
||||
this.withTab(editor, tabContainer => this.redrawEditorDirty(editor, tabContainer));
|
||||
this.withTab(editor, (tabContainer, tabLabelWidget) => this.redrawEditorActiveAndDirty(this.accessor.activeGroup === this.group, editor, tabContainer, tabLabelWidget));
|
||||
}
|
||||
|
||||
updateOptions(oldOptions: IEditorPartOptions, newOptions: IEditorPartOptions): void {
|
||||
@@ -393,7 +403,8 @@ export class TabsTitleControl extends TitleControl {
|
||||
oldOptions.tabCloseButton !== newOptions.tabCloseButton ||
|
||||
oldOptions.tabSizing !== newOptions.tabSizing ||
|
||||
oldOptions.showIcons !== newOptions.showIcons ||
|
||||
oldOptions.iconTheme !== newOptions.iconTheme
|
||||
oldOptions.iconTheme !== newOptions.iconTheme ||
|
||||
oldOptions.highlightModifiedTabs !== newOptions.highlightModifiedTabs
|
||||
) {
|
||||
this.redraw();
|
||||
}
|
||||
@@ -493,7 +504,7 @@ export class TabsTitleControl extends TitleControl {
|
||||
|
||||
// Touch Scroll Support
|
||||
disposables.push(addDisposableListener(tab, TouchEventType.Change, (e: GestureEvent) => {
|
||||
this.scrollbar.setScrollPosition({ scrollLeft: this.scrollbar.getScrollPosition().scrollLeft - e.translationX });
|
||||
this.tabsScrollbar.setScrollPosition({ scrollLeft: this.tabsScrollbar.getScrollPosition().scrollLeft - e.translationX });
|
||||
}));
|
||||
|
||||
// Close on mouse middle click
|
||||
@@ -502,7 +513,9 @@ export class TabsTitleControl extends TitleControl {
|
||||
|
||||
tab.blur();
|
||||
|
||||
if (e.button === 1 /* Middle Button*/ && !this.originatesFromTabActionBar(e)) {
|
||||
if (e.button === 1 /* Middle Button*/) {
|
||||
e.stopPropagation(); // for https://github.com/Microsoft/vscode/issues/56715
|
||||
|
||||
this.blockRevealActiveTabOnce();
|
||||
this.closeOneEditorAction.run({ groupId: this.group.id, editorIndex: index });
|
||||
}
|
||||
@@ -558,7 +571,7 @@ export class TabsTitleControl extends TitleControl {
|
||||
}
|
||||
|
||||
// moving in the tabs container can have an impact on scrolling position, so we need to update the custom scrollbar
|
||||
this.scrollbar.setScrollPosition({
|
||||
this.tabsScrollbar.setScrollPosition({
|
||||
scrollLeft: this.tabsContainer.scrollLeft
|
||||
});
|
||||
}));
|
||||
@@ -838,9 +851,7 @@ export class TabsTitleControl extends TitleControl {
|
||||
this.redrawLabel(editor, tabContainer, tabLabelWidget, tabLabel);
|
||||
|
||||
// Borders / Outline
|
||||
const borderLeftColor = (index !== 0) ? (this.getColor(TAB_BORDER) || this.getColor(contrastBorder)) : null;
|
||||
const borderRightColor = (index === this.group.count - 1) ? (this.getColor(TAB_BORDER) || this.getColor(contrastBorder)) : null;
|
||||
tabContainer.style.borderLeft = borderLeftColor ? `1px solid ${borderLeftColor}` : null;
|
||||
const borderRightColor = (this.getColor(TAB_BORDER) || this.getColor(contrastBorder));
|
||||
tabContainer.style.borderRight = borderRightColor ? `1px solid ${borderRightColor}` : null;
|
||||
tabContainer.style.outlineColor = this.getColor(activeContrastBorder);
|
||||
|
||||
@@ -863,12 +874,8 @@ export class TabsTitleControl extends TitleControl {
|
||||
removeClass(tabContainer, 'has-icon-theme');
|
||||
}
|
||||
|
||||
// Active state
|
||||
this.redrawEditorActive(this.accessor.activeGroup === this.group, editor, tabContainer, tabLabelWidget);
|
||||
|
||||
// Dirty State
|
||||
this.redrawEditorDirty(editor, tabContainer);
|
||||
|
||||
// Active / dirty state
|
||||
this.redrawEditorActiveAndDirty(this.accessor.activeGroup === this.group, editor, tabContainer, tabLabelWidget);
|
||||
// {{SQL CARBON EDIT}} -- Display the editor's tab color
|
||||
this.setEditorTabColor(editor, tabContainer, this.group.isActive(editor));
|
||||
}
|
||||
@@ -883,15 +890,22 @@ export class TabsTitleControl extends TitleControl {
|
||||
tabContainer.title = title;
|
||||
|
||||
// Label
|
||||
// {{SQL CARBON EDIT}} -- add title in options passed
|
||||
tabLabelWidget.setLabel({ name, description, resource: toResource(editor, { supportSideBySide: true }) }, { extraClasses: ['tab-label'], italic: !this.group.isPinned(editor), title });
|
||||
tabLabelWidget.setLabel({ name, description, resource: toResource(editor, { supportSideBySide: true }) }, { title, extraClasses: ['tab-label'], italic: !this.group.isPinned(editor) });
|
||||
|
||||
// {{SQL CARBON EDIT}} -- Display the editor's tab color
|
||||
const isTabActive = this.group.isActive(editor);
|
||||
this.setEditorTabColor(editor, tabContainer, isTabActive);
|
||||
}
|
||||
|
||||
private redrawEditorActive(isGroupActive: boolean, editor: IEditorInput, tabContainer: HTMLElement, tabLabelWidget: ResourceLabel): void {
|
||||
private redrawEditorActiveAndDirty(isGroupActive: boolean, editor: IEditorInput, tabContainer: HTMLElement, tabLabelWidget: ResourceLabel): void {
|
||||
const isTabActive = this.group.isActive(editor);
|
||||
|
||||
const hasModifiedBorderTop = this.doRedrawEditorDirty(isGroupActive, isTabActive, editor, tabContainer);
|
||||
|
||||
this.doRedrawEditorActive(isGroupActive, !hasModifiedBorderTop, editor, tabContainer, tabLabelWidget);
|
||||
}
|
||||
|
||||
private doRedrawEditorActive(isGroupActive: boolean, allowBorderTop: boolean, editor: IEditorInput, tabContainer: HTMLElement, tabLabelWidget: ResourceLabel): void {
|
||||
|
||||
// Tab is active
|
||||
if (this.group.isActive(editor)) {
|
||||
@@ -910,7 +924,7 @@ export class TabsTitleControl extends TitleControl {
|
||||
tabContainer.style.removeProperty('--tab-border-bottom-color');
|
||||
}
|
||||
|
||||
const activeTabBorderColorTop = this.getColor(isGroupActive ? TAB_ACTIVE_BORDER_TOP : TAB_UNFOCUSED_ACTIVE_BORDER_TOP);
|
||||
const activeTabBorderColorTop = allowBorderTop ? this.getColor(isGroupActive ? TAB_ACTIVE_BORDER_TOP : TAB_UNFOCUSED_ACTIVE_BORDER_TOP) : void 0;
|
||||
if (activeTabBorderColorTop) {
|
||||
addClass(tabContainer, 'tab-border-top');
|
||||
tabContainer.style.setProperty('--tab-border-top-color', activeTabBorderColorTop.toString());
|
||||
@@ -926,7 +940,7 @@ export class TabsTitleControl extends TitleControl {
|
||||
// Tab is inactive
|
||||
else {
|
||||
|
||||
// Containr
|
||||
// Container
|
||||
removeClass(tabContainer, 'active');
|
||||
tabContainer.setAttribute('aria-selected', 'false');
|
||||
tabContainer.style.backgroundColor = this.getColor(TAB_INACTIVE_BACKGROUND);
|
||||
@@ -937,22 +951,57 @@ export class TabsTitleControl extends TitleControl {
|
||||
}
|
||||
}
|
||||
|
||||
private redrawEditorDirty(editor: IEditorInput, tabContainer: HTMLElement): void {
|
||||
private doRedrawEditorDirty(isGroupActive: boolean, isTabActive: boolean, editor: IEditorInput, tabContainer: HTMLElement): boolean {
|
||||
let hasModifiedBorderColor = false;
|
||||
|
||||
// Tab: dirty
|
||||
if (editor.isDirty()) {
|
||||
addClass(tabContainer, 'dirty');
|
||||
} else {
|
||||
removeClass(tabContainer, 'dirty');
|
||||
|
||||
// Highlight modified tabs with a border if configured
|
||||
if (this.accessor.partOptions.highlightModifiedTabs) {
|
||||
let modifiedBorderColor: string;
|
||||
if (isGroupActive && isTabActive) {
|
||||
modifiedBorderColor = this.getColor(TAB_ACTIVE_MODIFIED_BORDER);
|
||||
} else if (isGroupActive && !isTabActive) {
|
||||
modifiedBorderColor = this.getColor(TAB_INACTIVE_MODIFIED_BORDER);
|
||||
} else if (!isGroupActive && isTabActive) {
|
||||
modifiedBorderColor = this.getColor(TAB_UNFOCUSED_ACTIVE_MODIFIED_BORDER);
|
||||
} else {
|
||||
modifiedBorderColor = this.getColor(TAB_UNFOCUSED_INACTIVE_MODIFIED_BORDER);
|
||||
}
|
||||
|
||||
if (modifiedBorderColor) {
|
||||
hasModifiedBorderColor = true;
|
||||
|
||||
addClass(tabContainer, 'dirty-border-top');
|
||||
tabContainer.style.setProperty('--tab-dirty-border-top-color', modifiedBorderColor);
|
||||
}
|
||||
} else {
|
||||
removeClass(tabContainer, 'dirty-border-top');
|
||||
tabContainer.style.removeProperty('--tab-dirty-border-top-color');
|
||||
}
|
||||
}
|
||||
|
||||
// Tab: not dirty
|
||||
else {
|
||||
removeClass(tabContainer, 'dirty');
|
||||
|
||||
removeClass(tabContainer, 'dirty-border-top');
|
||||
tabContainer.style.removeProperty('--tab-dirty-border-top-color');
|
||||
}
|
||||
|
||||
return hasModifiedBorderColor;
|
||||
}
|
||||
|
||||
layout(dimension: Dimension): void {
|
||||
this.dimension = dimension;
|
||||
|
||||
const activeTab = this.getTab(this.group.activeEditor);
|
||||
if (!activeTab || !dimension) {
|
||||
if (!activeTab || !this.dimension) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.dimension = dimension;
|
||||
|
||||
// The layout of tabs can be an expensive operation because we access DOM properties
|
||||
// that can result in the browser doing a full page layout to validate them. To buffer
|
||||
// this a little bit we try at least to schedule this work on the next animation frame.
|
||||
@@ -972,7 +1021,7 @@ export class TabsTitleControl extends TitleControl {
|
||||
|
||||
if (this.breadcrumbsControl && !this.breadcrumbsControl.isHidden()) {
|
||||
this.breadcrumbsControl.layout({ width: dimension.width, height: BreadcrumbsControl.HEIGHT });
|
||||
this.scrollbar.getDomNode().style.height = `${dimension.height - BreadcrumbsControl.HEIGHT}px`;
|
||||
this.tabsScrollbar.getDomNode().style.height = `${dimension.height - BreadcrumbsControl.HEIGHT}px`;
|
||||
}
|
||||
|
||||
const visibleContainerWidth = this.tabsContainer.offsetWidth;
|
||||
@@ -987,7 +1036,7 @@ export class TabsTitleControl extends TitleControl {
|
||||
}
|
||||
|
||||
// Update scrollbar
|
||||
this.scrollbar.setScrollDimensions({
|
||||
this.tabsScrollbar.setScrollDimensions({
|
||||
width: visibleContainerWidth,
|
||||
scrollWidth: totalContainerWidth
|
||||
});
|
||||
@@ -999,20 +1048,20 @@ export class TabsTitleControl extends TitleControl {
|
||||
}
|
||||
|
||||
// Reveal the active one
|
||||
const containerScrollPosX = this.scrollbar.getScrollPosition().scrollLeft;
|
||||
const containerScrollPosX = this.tabsScrollbar.getScrollPosition().scrollLeft;
|
||||
const activeTabFits = activeTabWidth <= visibleContainerWidth;
|
||||
|
||||
// Tab is overflowing to the right: Scroll minimally until the element is fully visible to the right
|
||||
// Note: only try to do this if we actually have enough width to give to show the tab fully!
|
||||
if (activeTabFits && containerScrollPosX + visibleContainerWidth < activeTabPosX + activeTabWidth) {
|
||||
this.scrollbar.setScrollPosition({
|
||||
this.tabsScrollbar.setScrollPosition({
|
||||
scrollLeft: containerScrollPosX + ((activeTabPosX + activeTabWidth) /* right corner of tab */ - (containerScrollPosX + visibleContainerWidth) /* right corner of view port */)
|
||||
});
|
||||
}
|
||||
|
||||
// Tab is overlflowng to the left or does not fit: Scroll it into view to the left
|
||||
else if (containerScrollPosX > activeTabPosX || !activeTabFits) {
|
||||
this.scrollbar.setScrollPosition({
|
||||
this.tabsScrollbar.setScrollPosition({
|
||||
scrollLeft: activeTabPosX
|
||||
});
|
||||
}
|
||||
@@ -1247,7 +1296,7 @@ registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => {
|
||||
const adjustedColorDrag = editorDragAndDropBackground.flatten(adjustedTabDragBackground);
|
||||
collector.addRule(`
|
||||
.monaco-workbench > .part.editor > .content.dragged-over .editor-group-container.active > .title .tabs-container > .tab.sizing-shrink.dragged-over:not(.active):not(.dragged) > .tab-label::after,
|
||||
.monaco-workbench > .part.editor > .content.dragged-over .editor-group-container > .title .tabs-container > .tab.sizing-shrink.dragged-over:not(.dragged) > .tab-label::after {
|
||||
.monaco-workbench > .part.editor > .content.dragged-over .editor-group-container:not(.active) > .title .tabs-container > .tab.sizing-shrink.dragged-over:not(.dragged) > .tab-label::after {
|
||||
background: linear-gradient(to left, ${adjustedColorDrag}, transparent) !important;
|
||||
}
|
||||
`);
|
||||
|
||||
@@ -3,13 +3,8 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import 'vs/css!./media/textdiffeditor';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import * as nls from 'vs/nls';
|
||||
import * as objects from 'vs/base/common/objects';
|
||||
import { Action, IAction } from 'vs/base/common/actions';
|
||||
import * as types from 'vs/base/common/types';
|
||||
import { IDiffEditor } from 'vs/editor/browser/editorBrowser';
|
||||
import { IDiffEditorOptions, IEditorOptions as ICodeEditorOptions } from 'vs/editor/common/config/editorOptions';
|
||||
@@ -28,15 +23,15 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
|
||||
import { ScrollType, IDiffEditorViewState, IDiffEditorModel } from 'vs/editor/common/editorCommon';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import URI from 'vs/base/common/uri';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { once } from 'vs/base/common/event';
|
||||
import { IEditorGroupsService } from 'vs/workbench/services/group/common/editorGroupsService';
|
||||
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { EditorMemento } from 'vs/workbench/browser/parts/editor/baseEditor';
|
||||
import { IWindowService } from 'vs/platform/windows/common/windows';
|
||||
|
||||
/**
|
||||
* The text editor that leverages the diff text editor for the editing experience.
|
||||
@@ -47,31 +42,22 @@ export class TextDiffEditor extends BaseTextEditor implements ITextDiffEditor {
|
||||
|
||||
private diffNavigator: DiffNavigator;
|
||||
private diffNavigatorDisposables: IDisposable[] = [];
|
||||
private nextDiffAction: NavigateAction;
|
||||
private previousDiffAction: NavigateAction;
|
||||
private toggleIgnoreTrimWhitespaceAction: ToggleIgnoreTrimWhitespaceAction;
|
||||
|
||||
constructor(
|
||||
@ITelemetryService telemetryService: ITelemetryService,
|
||||
@IInstantiationService instantiationService: IInstantiationService,
|
||||
@IStorageService storageService: IStorageService,
|
||||
@ITextResourceConfigurationService configurationService: ITextResourceConfigurationService,
|
||||
@IConfigurationService private readonly _actualConfigurationService: IConfigurationService,
|
||||
@IEditorService editorService: IEditorService,
|
||||
@IThemeService themeService: IThemeService,
|
||||
@IEditorGroupsService editorGroupService: IEditorGroupsService,
|
||||
@ITextFileService textFileService: ITextFileService,
|
||||
@IWindowService windowService: IWindowService
|
||||
) {
|
||||
super(TextDiffEditor.ID, telemetryService, instantiationService, storageService, configurationService, themeService, textFileService, editorService, editorGroupService);
|
||||
|
||||
this._register(this._actualConfigurationService.onDidChangeConfiguration((e) => {
|
||||
if (e.affectsConfiguration('diffEditor.ignoreTrimWhitespace')) {
|
||||
this.updateIgnoreTrimWhitespaceAction();
|
||||
}
|
||||
}));
|
||||
super(TextDiffEditor.ID, telemetryService, instantiationService, storageService, configurationService, themeService, textFileService, editorService, editorGroupService, windowService);
|
||||
}
|
||||
|
||||
protected getEditorMemento<T>(storageService: IStorageService, editorGroupService: IEditorGroupsService, key: string, limit: number = 10): IEditorMemento<T> {
|
||||
protected getEditorMemento<T>(editorGroupService: IEditorGroupsService, key: string, limit: number = 10): IEditorMemento<T> {
|
||||
return new EditorMemento(this.getId(), key, Object.create(null), limit, editorGroupService); // do not persist in storage as diff editors are never persisted
|
||||
}
|
||||
|
||||
@@ -84,13 +70,6 @@ export class TextDiffEditor extends BaseTextEditor implements ITextDiffEditor {
|
||||
}
|
||||
|
||||
createEditorControl(parent: HTMLElement, configuration: ICodeEditorOptions): IDiffEditor {
|
||||
|
||||
// Actions
|
||||
this.nextDiffAction = new NavigateAction(this, true);
|
||||
this.previousDiffAction = new NavigateAction(this, false);
|
||||
this.toggleIgnoreTrimWhitespaceAction = new ToggleIgnoreTrimWhitespaceAction(this._actualConfigurationService);
|
||||
this.updateIgnoreTrimWhitespaceAction();
|
||||
|
||||
return this.instantiationService.createInstance(DiffEditorWidget, parent, configuration);
|
||||
}
|
||||
|
||||
@@ -139,14 +118,6 @@ export class TextDiffEditor extends BaseTextEditor implements ITextDiffEditor {
|
||||
});
|
||||
this.diffNavigatorDisposables.push(this.diffNavigator);
|
||||
|
||||
this.diffNavigatorDisposables.push(this.diffNavigator.onDidUpdate(() => {
|
||||
this.nextDiffAction.updateEnablement();
|
||||
this.previousDiffAction.updateEnablement();
|
||||
}));
|
||||
|
||||
// Enablement of actions
|
||||
this.updateIgnoreTrimWhitespaceAction();
|
||||
|
||||
// Readonly flag
|
||||
diffEditor.updateOptions({ readOnly: resolvedDiffEditorModel.isReadonly() });
|
||||
}, error => {
|
||||
@@ -157,7 +128,7 @@ export class TextDiffEditor extends BaseTextEditor implements ITextDiffEditor {
|
||||
}
|
||||
|
||||
// Otherwise make sure the error bubbles up
|
||||
return TPromise.wrapError(error);
|
||||
return Promise.reject(error);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -185,13 +156,6 @@ export class TextDiffEditor extends BaseTextEditor implements ITextDiffEditor {
|
||||
return false;
|
||||
}
|
||||
|
||||
private updateIgnoreTrimWhitespaceAction(): void {
|
||||
const ignoreTrimWhitespace = this.configurationService.getValue<boolean>(this.getResource(), 'diffEditor.ignoreTrimWhitespace');
|
||||
if (this.toggleIgnoreTrimWhitespaceAction) {
|
||||
this.toggleIgnoreTrimWhitespaceAction.updateClassName(ignoreTrimWhitespace);
|
||||
}
|
||||
}
|
||||
|
||||
private openAsBinary(input: EditorInput, options: EditorOptions): boolean {
|
||||
if (input instanceof DiffEditorInput) {
|
||||
const originalInput = input.originalInput;
|
||||
@@ -232,6 +196,7 @@ export class TextDiffEditor extends BaseTextEditor implements ITextDiffEditor {
|
||||
const options: IDiffEditorOptions = super.getConfigurationOverrides();
|
||||
|
||||
options.readOnly = this.isReadOnly();
|
||||
options.lineDecorationsWidth = '2ch';
|
||||
|
||||
return options;
|
||||
}
|
||||
@@ -289,14 +254,6 @@ export class TextDiffEditor extends BaseTextEditor implements ITextDiffEditor {
|
||||
return this.diffNavigator;
|
||||
}
|
||||
|
||||
getActions(): IAction[] {
|
||||
return [
|
||||
this.toggleIgnoreTrimWhitespaceAction,
|
||||
this.previousDiffAction,
|
||||
this.nextDiffAction
|
||||
];
|
||||
}
|
||||
|
||||
getControl(): IDiffEditor {
|
||||
return super.getControl() as IDiffEditor;
|
||||
}
|
||||
@@ -380,59 +337,3 @@ export class TextDiffEditor extends BaseTextEditor implements ITextDiffEditor {
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
class NavigateAction extends Action {
|
||||
static ID_NEXT = 'workbench.action.compareEditor.nextChange';
|
||||
static ID_PREV = 'workbench.action.compareEditor.previousChange';
|
||||
|
||||
private editor: TextDiffEditor;
|
||||
private next: boolean;
|
||||
|
||||
constructor(editor: TextDiffEditor, next: boolean) {
|
||||
super(next ? NavigateAction.ID_NEXT : NavigateAction.ID_PREV);
|
||||
|
||||
this.editor = editor;
|
||||
this.next = next;
|
||||
|
||||
this.label = this.next ? nls.localize('navigate.next.label', "Next Change") : nls.localize('navigate.prev.label', "Previous Change");
|
||||
this.class = this.next ? 'textdiff-editor-action next' : 'textdiff-editor-action previous';
|
||||
this.enabled = false;
|
||||
}
|
||||
|
||||
run(): TPromise<any> {
|
||||
if (this.next) {
|
||||
this.editor.getDiffNavigator().next();
|
||||
} else {
|
||||
this.editor.getDiffNavigator().previous();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
updateEnablement(): void {
|
||||
this.enabled = this.editor.getDiffNavigator().canNavigate();
|
||||
}
|
||||
}
|
||||
|
||||
class ToggleIgnoreTrimWhitespaceAction extends Action {
|
||||
static ID = 'workbench.action.compareEditor.toggleIgnoreTrimWhitespace';
|
||||
|
||||
private _isChecked: boolean;
|
||||
|
||||
constructor(
|
||||
@IConfigurationService private readonly _configurationService: IConfigurationService
|
||||
) {
|
||||
super(ToggleIgnoreTrimWhitespaceAction.ID);
|
||||
this.label = nls.localize('toggleIgnoreTrimWhitespace.label', "Ignore Trim Whitespace");
|
||||
}
|
||||
|
||||
updateClassName(ignoreTrimWhitespace: boolean): void {
|
||||
this._isChecked = ignoreTrimWhitespace;
|
||||
this.class = `textdiff-editor-action toggleIgnoreTrimWhitespace${this._isChecked ? ' is-checked' : ''}`;
|
||||
}
|
||||
|
||||
run(): TPromise<any> {
|
||||
this._configurationService.updateValue(`diffEditor.ignoreTrimWhitespace`, !this._isChecked);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,13 +3,10 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import * as nls from 'vs/nls';
|
||||
import URI from 'vs/base/common/uri';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import * as objects from 'vs/base/common/objects';
|
||||
import * as types from 'vs/base/common/types';
|
||||
import * as errors from 'vs/base/common/errors';
|
||||
import * as DOM from 'vs/base/browser/dom';
|
||||
import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget';
|
||||
import { EditorInput, EditorOptions, IEditorMemento, ITextEditor } from 'vs/workbench/common/editor';
|
||||
@@ -26,6 +23,7 @@ import { isDiffEditor, isCodeEditor, ICodeEditor, getCodeEditor } from 'vs/edito
|
||||
import { IEditorGroupsService, IEditorGroup } from 'vs/workbench/services/group/common/editorGroupsService';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { IWindowService } from 'vs/platform/windows/common/windows';
|
||||
|
||||
const TEXT_EDITOR_VIEW_STATE_PREFERENCE_KEY = 'textEditorViewState';
|
||||
|
||||
@@ -55,10 +53,11 @@ export abstract class BaseTextEditor extends BaseEditor implements ITextEditor {
|
||||
@ITextFileService private readonly _textFileService: ITextFileService,
|
||||
@IEditorService protected editorService: IEditorService,
|
||||
@IEditorGroupsService protected editorGroupService: IEditorGroupsService,
|
||||
@IWindowService private windowService: IWindowService
|
||||
) {
|
||||
super(id, telemetryService, themeService);
|
||||
super(id, telemetryService, themeService, storageService);
|
||||
|
||||
this.editorMemento = this.getEditorMemento<IEditorViewState>(storageService, editorGroupService, TEXT_EDITOR_VIEW_STATE_PREFERENCE_KEY, 100);
|
||||
this.editorMemento = this.getEditorMemento<IEditorViewState>(editorGroupService, TEXT_EDITOR_VIEW_STATE_PREFERENCE_KEY, 100);
|
||||
|
||||
this._register(this.configurationService.onDidChangeConfiguration(e => this.handleConfigurationChangeEvent(this.configurationService.getValue<IEditorConfiguration>(this.getResource()))));
|
||||
}
|
||||
@@ -75,7 +74,7 @@ export abstract class BaseTextEditor extends BaseEditor implements ITextEditor {
|
||||
return this._textFileService;
|
||||
}
|
||||
|
||||
private handleConfigurationChangeEvent(configuration?: IEditorConfiguration): void {
|
||||
protected handleConfigurationChangeEvent(configuration?: IEditorConfiguration): void {
|
||||
if (this.isVisible()) {
|
||||
this.updateEditorConfiguration(configuration);
|
||||
} else {
|
||||
@@ -148,15 +147,17 @@ export abstract class BaseTextEditor extends BaseEditor implements ITextEditor {
|
||||
}
|
||||
|
||||
this._register(this.editorService.onDidActiveEditorChange(() => this.onEditorFocusLost()));
|
||||
this._register(DOM.addDisposableListener(window, DOM.EventType.BLUR, () => this.onWindowFocusLost()));
|
||||
this._register(this.windowService.onDidChangeFocus(focused => this.onWindowFocusChange(focused)));
|
||||
}
|
||||
|
||||
private onEditorFocusLost(): void {
|
||||
this.maybeTriggerSaveAll(SaveReason.FOCUS_CHANGE);
|
||||
}
|
||||
|
||||
private onWindowFocusLost(): void {
|
||||
this.maybeTriggerSaveAll(SaveReason.WINDOW_CHANGE);
|
||||
private onWindowFocusChange(focused: boolean): void {
|
||||
if (!focused) {
|
||||
this.maybeTriggerSaveAll(SaveReason.WINDOW_CHANGE);
|
||||
}
|
||||
}
|
||||
|
||||
private maybeTriggerSaveAll(reason: SaveReason): void {
|
||||
@@ -169,7 +170,7 @@ export abstract class BaseTextEditor extends BaseEditor implements ITextEditor {
|
||||
(reason === SaveReason.FOCUS_CHANGE && mode === AutoSaveMode.ON_FOCUS_CHANGE)
|
||||
) {
|
||||
if (this.textFileService.isDirty()) {
|
||||
this.textFileService.saveAll(void 0, { reason }).done(null, errors.onUnexpectedError);
|
||||
this.textFileService.saveAll(void 0, { reason });
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -232,7 +233,7 @@ export abstract class BaseTextEditor extends BaseEditor implements ITextEditor {
|
||||
return;
|
||||
}
|
||||
|
||||
this.editorMemento.saveState(this.group, resource, editorViewState);
|
||||
this.editorMemento.saveEditorState(this.group, resource, editorViewState);
|
||||
}
|
||||
|
||||
protected retrieveTextEditorViewState(resource: URI): IEditorViewState {
|
||||
@@ -257,9 +258,9 @@ export abstract class BaseTextEditor extends BaseEditor implements ITextEditor {
|
||||
/**
|
||||
* Clears the text editor view state for the given resources.
|
||||
*/
|
||||
protected clearTextEditorViewState(resources: URI[]): void {
|
||||
protected clearTextEditorViewState(resources: URI[], group?: IEditorGroup): void {
|
||||
resources.forEach(resource => {
|
||||
this.editorMemento.clearState(resource);
|
||||
this.editorMemento.clearEditorState(resource, group);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -267,7 +268,7 @@ export abstract class BaseTextEditor extends BaseEditor implements ITextEditor {
|
||||
* Loads the text editor view state for the given resource and returns it.
|
||||
*/
|
||||
protected loadTextEditorViewState(resource: URI): IEditorViewState {
|
||||
return this.editorMemento.loadState(this.group, resource);
|
||||
return this.editorMemento.loadEditorState(this.group, resource);
|
||||
}
|
||||
|
||||
private updateEditorConfiguration(configuration = this.configurationService.getValue<IEditorConfiguration>(this.getResource())): void {
|
||||
|
||||
@@ -2,9 +2,7 @@
|
||||
* 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 { TPromise } from 'vs/base/common/winjs.base';
|
||||
import * as nls from 'vs/nls';
|
||||
import * as types from 'vs/base/common/types';
|
||||
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
|
||||
@@ -25,6 +23,7 @@ import { ScrollType } from 'vs/editor/common/editorCommon';
|
||||
import { IEditorGroupsService } from 'vs/workbench/services/group/common/editorGroupsService';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { IWindowService } from 'vs/platform/windows/common/windows';
|
||||
|
||||
/**
|
||||
* An editor implementation that is capable of showing the contents of resource inputs. Uses
|
||||
@@ -41,9 +40,10 @@ export class AbstractTextResourceEditor extends BaseTextEditor {
|
||||
@IThemeService themeService: IThemeService,
|
||||
@IEditorGroupsService editorGroupService: IEditorGroupsService,
|
||||
@ITextFileService textFileService: ITextFileService,
|
||||
@IEditorService editorService: IEditorService
|
||||
@IEditorService editorService: IEditorService,
|
||||
@IWindowService windowService: IWindowService
|
||||
) {
|
||||
super(id, telemetryService, instantiationService, storageService, configurationService, themeService, textFileService, editorService, editorGroupService);
|
||||
super(id, telemetryService, instantiationService, storageService, configurationService, themeService, textFileService, editorService, editorGroupService, windowService);
|
||||
}
|
||||
|
||||
getTitle(): string {
|
||||
@@ -70,7 +70,7 @@ export class AbstractTextResourceEditor extends BaseTextEditor {
|
||||
|
||||
// Assert Model instance
|
||||
if (!(resolvedModel instanceof BaseTextEditorModel)) {
|
||||
return TPromise.wrapError<void>(new Error('Unable to open file as text'));
|
||||
return Promise.reject(new Error('Unable to open file as text'));
|
||||
}
|
||||
|
||||
// Set Editor Model
|
||||
@@ -163,15 +163,14 @@ export class AbstractTextResourceEditor extends BaseTextEditor {
|
||||
super.clearInput();
|
||||
}
|
||||
|
||||
shutdown(): void {
|
||||
protected saveState(): void {
|
||||
|
||||
// Save View State (only for untitled)
|
||||
if (this.input instanceof UntitledEditorInput) {
|
||||
this.saveTextResourceEditorViewState(this.input);
|
||||
}
|
||||
|
||||
// Call Super
|
||||
super.shutdown();
|
||||
super.saveState();
|
||||
}
|
||||
|
||||
private saveTextResourceEditorViewState(input: EditorInput): void {
|
||||
@@ -210,8 +209,9 @@ export class TextResourceEditor extends AbstractTextResourceEditor {
|
||||
@IThemeService themeService: IThemeService,
|
||||
@ITextFileService textFileService: ITextFileService,
|
||||
@IEditorService editorService: IEditorService,
|
||||
@IEditorGroupsService editorGroupService: IEditorGroupsService
|
||||
@IEditorGroupsService editorGroupService: IEditorGroupsService,
|
||||
@IWindowService windowService: IWindowService
|
||||
) {
|
||||
super(TextResourceEditor.ID, telemetryService, instantiationService, storageService, configurationService, themeService, editorGroupService, textFileService, editorService);
|
||||
super(TextResourceEditor.ID, telemetryService, instantiationService, storageService, configurationService, themeService, editorGroupService, textFileService, editorService, windowService);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,8 +3,6 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import { applyDragImage } from 'vs/base/browser/dnd';
|
||||
import { addDisposableListener, Dimension, EventType } from 'vs/base/browser/dom';
|
||||
import { StandardMouseEvent } from 'vs/base/browser/mouseEvent';
|
||||
@@ -14,7 +12,6 @@ import { Action, IAction, IRunEvent } from 'vs/base/common/actions';
|
||||
import * as arrays from 'vs/base/common/arrays';
|
||||
import { ResolvedKeybinding } from 'vs/base/common/keyCodes';
|
||||
import { dispose, IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import 'vs/css!./media/titlecontrol';
|
||||
import { getCodeEditor } from 'vs/editor/browser/editorBrowser';
|
||||
import { localize } from 'vs/nls';
|
||||
@@ -40,6 +37,8 @@ import { EditorCommandsContextActionRunner, IEditorCommandsContext, IEditorInput
|
||||
import { ResourceContextKey } from 'vs/workbench/common/resources';
|
||||
import { Themable } from 'vs/workbench/common/theme';
|
||||
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
|
||||
import { AnchorAlignment } from 'vs/base/browser/ui/contextview/contextview';
|
||||
import { IFileService } from 'vs/platform/files/common/files';
|
||||
|
||||
export interface IToolbarActions {
|
||||
primary: IAction[];
|
||||
@@ -57,8 +56,6 @@ export abstract class TitleControl extends Themable {
|
||||
private currentSecondaryEditorActionIds: string[] = [];
|
||||
protected editorActionsToolbar: ToolBar;
|
||||
|
||||
private mapEditorToActions: Map<string, IToolbarActions> = new Map();
|
||||
|
||||
private resourceContext: ResourceContextKey;
|
||||
private editorToolBarMenuDisposables: IDisposable[] = [];
|
||||
|
||||
@@ -79,11 +76,12 @@ export abstract class TitleControl extends Themable {
|
||||
@IQuickOpenService protected quickOpenService: IQuickOpenService,
|
||||
@IThemeService themeService: IThemeService,
|
||||
@IExtensionService private extensionService: IExtensionService,
|
||||
@IConfigurationService protected configurationService: IConfigurationService
|
||||
@IConfigurationService protected configurationService: IConfigurationService,
|
||||
@IFileService private readonly fileService: IFileService,
|
||||
) {
|
||||
super(themeService);
|
||||
|
||||
this.resourceContext = instantiationService.createInstance(ResourceContextKey);
|
||||
this.resourceContext = this._register(instantiationService.createInstance(ResourceContextKey));
|
||||
this.contextMenu = this._register(this.menuService.createMenu(MenuId.EditorTitleContext, this.contextKeyService));
|
||||
|
||||
this.create(parent);
|
||||
@@ -98,22 +96,31 @@ export abstract class TitleControl extends Themable {
|
||||
|
||||
protected createBreadcrumbsControl(container: HTMLElement, options: IBreadcrumbsControlOptions): void {
|
||||
const config = this._register(BreadcrumbsConfig.IsEnabled.bindTo(this.configurationService));
|
||||
config.onDidChange(value => {
|
||||
this._register(config.onDidChange(() => {
|
||||
const value = config.getValue();
|
||||
if (!value && this.breadcrumbsControl) {
|
||||
this.breadcrumbsControl.dispose();
|
||||
this.breadcrumbsControl = undefined;
|
||||
this.group.relayout();
|
||||
this.handleBreadcrumbsEnablementChange();
|
||||
} else if (value && !this.breadcrumbsControl) {
|
||||
this.breadcrumbsControl = this.instantiationService.createInstance(BreadcrumbsControl, container, options, this.group);
|
||||
this.breadcrumbsControl.update();
|
||||
this.group.relayout();
|
||||
this.handleBreadcrumbsEnablementChange();
|
||||
}
|
||||
});
|
||||
if (config.value) {
|
||||
}));
|
||||
if (config.getValue()) {
|
||||
this.breadcrumbsControl = this.instantiationService.createInstance(BreadcrumbsControl, container, options, this.group);
|
||||
}
|
||||
|
||||
this._register(this.fileService.onDidChangeFileSystemProviderRegistrations(() => {
|
||||
if (this.breadcrumbsControl && this.breadcrumbsControl.update()) {
|
||||
this.handleBreadcrumbsEnablementChange();
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
protected abstract handleBreadcrumbsEnablementChange(): void;
|
||||
|
||||
protected createEditorActionsToolBar(container: HTMLElement): void {
|
||||
const context = { groupId: this.group.id } as IEditorCommandsContext;
|
||||
|
||||
@@ -122,7 +129,8 @@ export abstract class TitleControl extends Themable {
|
||||
orientation: ActionsOrientation.HORIZONTAL,
|
||||
ariaLabel: localize('araLabelEditorActions', "Editor actions"),
|
||||
getKeyBinding: action => this.getKeybinding(action),
|
||||
actionRunner: this._register(new EditorCommandsContextActionRunner(context))
|
||||
actionRunner: this._register(new EditorCommandsContextActionRunner(context)),
|
||||
anchorAlignmentProvider: () => AnchorAlignment.RIGHT
|
||||
}));
|
||||
|
||||
// Context
|
||||
@@ -215,17 +223,6 @@ export abstract class TitleControl extends Themable {
|
||||
// Editor actions require the editor control to be there, so we retrieve it via service
|
||||
const activeControl = this.group.activeControl;
|
||||
if (activeControl instanceof BaseEditor) {
|
||||
|
||||
// Editor Control Actions
|
||||
let editorActions = this.mapEditorToActions.get(activeControl.getId());
|
||||
if (!editorActions) {
|
||||
editorActions = { primary: activeControl.getActions(), secondary: activeControl.getSecondaryActions() };
|
||||
this.mapEditorToActions.set(activeControl.getId(), editorActions);
|
||||
}
|
||||
primary.push(...editorActions.primary);
|
||||
secondary.push(...editorActions.secondary);
|
||||
|
||||
// Contributed Actions
|
||||
const codeEditor = getCodeEditor(activeControl.getControl());
|
||||
const scopedContextKeyService = codeEditor && codeEditor.invokeWithinContext(accessor => accessor.get(IContextKeyService)) || this.contextKeyService;
|
||||
const titleBarMenu = this.menuService.createMenu(MenuId.EditorTitle, scopedContextKeyService);
|
||||
@@ -302,7 +299,7 @@ export abstract class TitleControl extends Themable {
|
||||
// Show it
|
||||
this.contextMenuService.showContextMenu({
|
||||
getAnchor: () => anchor,
|
||||
getActions: () => TPromise.as(actions),
|
||||
getActions: () => actions,
|
||||
getActionsContext: () => ({ groupId: this.group.id, editorIndex: this.group.getIndexOfEditor(editor) } as IEditorCommandsContext),
|
||||
getKeyBinding: (action) => this.getKeybinding(action),
|
||||
onHide: () => {
|
||||
@@ -316,7 +313,7 @@ export abstract class TitleControl extends Themable {
|
||||
});
|
||||
}
|
||||
|
||||
protected getKeybinding(action: IAction): ResolvedKeybinding {
|
||||
private getKeybinding(action: IAction): ResolvedKeybinding {
|
||||
return this.keybindingService.lookupKeybinding(action.id);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,50 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
.monaco-workbench .part.menubar {
|
||||
display: flex;
|
||||
flex-shrink: 1;
|
||||
box-sizing: border-box;
|
||||
height: 30px;
|
||||
-webkit-app-region: no-drag;
|
||||
overflow: hidden;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.monaco-workbench.fullscreen .part.menubar {
|
||||
margin: 0px;
|
||||
padding: 0px 5px;
|
||||
}
|
||||
|
||||
.monaco-workbench .part.menubar > .menubar-menu-button {
|
||||
align-items: center;
|
||||
box-sizing: border-box;
|
||||
padding: 0px 8px;
|
||||
cursor: default;
|
||||
-webkit-app-region: no-drag;
|
||||
zoom: 1;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.monaco-workbench .part.menubar .menubar-menu-items-holder {
|
||||
position: absolute;
|
||||
left: 0px;
|
||||
opacity: 1;
|
||||
z-index: 2000;
|
||||
}
|
||||
|
||||
.monaco-workbench .part.menubar .menubar-menu-items-holder.monaco-menu-container {
|
||||
font-family: "Segoe WPC", "Segoe UI", ".SFNSDisplay-Light", "SFUIText-Light", "HelveticaNeue-Light", sans-serif, "Droid Sans Fallback";
|
||||
outline: 0;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.monaco-workbench .part.menubar .menubar-menu-items-holder.monaco-menu-container :focus {
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
.hc-black .monaco-workbench .part.menubar .menubar-menu-items-holder.monaco-menu-container {
|
||||
border: 2px solid #6FC3DF;
|
||||
}
|
||||
@@ -1,402 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 { MenuRegistry, MenuId } from 'vs/platform/actions/common/actions';
|
||||
import { isMacintosh } from 'vs/base/common/platform';
|
||||
|
||||
goMenuRegistration();
|
||||
|
||||
if (isMacintosh) {
|
||||
windowMenuRegistration();
|
||||
}
|
||||
|
||||
helpMenuRegistration();
|
||||
|
||||
// Menu registration
|
||||
function goMenuRegistration() {
|
||||
// Forward/Back
|
||||
MenuRegistry.appendMenuItem(MenuId.MenubarGoMenu, {
|
||||
group: '1_fwd_back',
|
||||
command: {
|
||||
id: 'workbench.action.navigateBack',
|
||||
title: nls.localize({ key: 'miBack', comment: ['&& denotes a mnemonic'] }, "&&Back")
|
||||
},
|
||||
order: 1
|
||||
});
|
||||
|
||||
MenuRegistry.appendMenuItem(MenuId.MenubarGoMenu, {
|
||||
group: '1_fwd_back',
|
||||
command: {
|
||||
id: 'workbench.action.navigateForward',
|
||||
title: nls.localize({ key: 'miForward', comment: ['&& denotes a mnemonic'] }, "&&Forward")
|
||||
},
|
||||
order: 2
|
||||
});
|
||||
|
||||
// Switch Editor
|
||||
MenuRegistry.appendMenuItem(MenuId.MenubarSwitchEditorMenu, {
|
||||
group: '1_any',
|
||||
command: {
|
||||
id: 'workbench.action.nextEditor',
|
||||
title: nls.localize({ key: 'miNextEditor', comment: ['&& denotes a mnemonic'] }, "&&Next Editor")
|
||||
},
|
||||
order: 1
|
||||
});
|
||||
|
||||
MenuRegistry.appendMenuItem(MenuId.MenubarSwitchEditorMenu, {
|
||||
group: '1_any',
|
||||
command: {
|
||||
id: 'workbench.action.previousEditor',
|
||||
title: nls.localize({ key: 'miPreviousEditor', comment: ['&& denotes a mnemonic'] }, "&&Previous Editor")
|
||||
},
|
||||
order: 2
|
||||
});
|
||||
|
||||
MenuRegistry.appendMenuItem(MenuId.MenubarSwitchEditorMenu, {
|
||||
group: '2_used',
|
||||
command: {
|
||||
id: 'workbench.action.openNextRecentlyUsedEditorInGroup',
|
||||
title: nls.localize({ key: 'miNextEditorInGroup', comment: ['&& denotes a mnemonic'] }, "&&Next Used Editor in Group")
|
||||
},
|
||||
order: 1
|
||||
});
|
||||
|
||||
MenuRegistry.appendMenuItem(MenuId.MenubarSwitchEditorMenu, {
|
||||
group: '2_used',
|
||||
command: {
|
||||
id: 'workbench.action.openPreviousRecentlyUsedEditorInGroup',
|
||||
title: nls.localize({ key: 'miPreviousEditorInGroup', comment: ['&& denotes a mnemonic'] }, "&&Previous Used Editor in Group")
|
||||
},
|
||||
order: 2
|
||||
});
|
||||
|
||||
MenuRegistry.appendMenuItem(MenuId.MenubarGoMenu, {
|
||||
group: '2_switch',
|
||||
title: nls.localize({ key: 'miSwitchEditor', comment: ['&& denotes a mnemonic'] }, "Switch &&Editor"),
|
||||
submenu: MenuId.MenubarSwitchEditorMenu,
|
||||
order: 1
|
||||
});
|
||||
|
||||
// Switch Group
|
||||
MenuRegistry.appendMenuItem(MenuId.MenubarSwitchGroupMenu, {
|
||||
group: '1_focus_index',
|
||||
command: {
|
||||
id: 'workbench.action.focusFirstEditorGroup',
|
||||
title: nls.localize({ key: 'miFocusFirstGroup', comment: ['&& denotes a mnemonic'] }, "Group &&1")
|
||||
},
|
||||
order: 1
|
||||
});
|
||||
|
||||
MenuRegistry.appendMenuItem(MenuId.MenubarSwitchGroupMenu, {
|
||||
group: '1_focus_index',
|
||||
command: {
|
||||
id: 'workbench.action.focusSecondEditorGroup',
|
||||
title: nls.localize({ key: 'miFocusSecondGroup', comment: ['&& denotes a mnemonic'] }, "Group &&2")
|
||||
},
|
||||
order: 2
|
||||
});
|
||||
|
||||
MenuRegistry.appendMenuItem(MenuId.MenubarSwitchGroupMenu, {
|
||||
group: '1_focus_index',
|
||||
command: {
|
||||
id: 'workbench.action.focusThirdEditorGroup',
|
||||
title: nls.localize({ key: 'miFocusThirdGroup', comment: ['&& denotes a mnemonic'] }, "Group &&3")
|
||||
},
|
||||
order: 3
|
||||
});
|
||||
|
||||
MenuRegistry.appendMenuItem(MenuId.MenubarSwitchGroupMenu, {
|
||||
group: '1_focus_index',
|
||||
command: {
|
||||
id: 'workbench.action.focusFourthEditorGroup',
|
||||
title: nls.localize({ key: 'miFocusFourthGroup', comment: ['&& denotes a mnemonic'] }, "Group &&4")
|
||||
},
|
||||
order: 4
|
||||
});
|
||||
|
||||
MenuRegistry.appendMenuItem(MenuId.MenubarSwitchGroupMenu, {
|
||||
group: '1_focus_index',
|
||||
command: {
|
||||
id: 'workbench.action.focusFifthEditorGroup',
|
||||
title: nls.localize({ key: 'miFocusFifthGroup', comment: ['&& denotes a mnemonic'] }, "Group &&5")
|
||||
},
|
||||
order: 5
|
||||
});
|
||||
|
||||
MenuRegistry.appendMenuItem(MenuId.MenubarSwitchGroupMenu, {
|
||||
group: '2_next_prev',
|
||||
command: {
|
||||
id: 'workbench.action.focusNextGroup',
|
||||
title: nls.localize({ key: 'miNextGroup', comment: ['&& denotes a mnemonic'] }, "&&Next Group")
|
||||
},
|
||||
order: 1
|
||||
});
|
||||
|
||||
MenuRegistry.appendMenuItem(MenuId.MenubarSwitchGroupMenu, {
|
||||
group: '2_next_prev',
|
||||
command: {
|
||||
id: 'workbench.action.focusPreviousGroup',
|
||||
title: nls.localize({ key: 'miPreviousGroup', comment: ['&& denotes a mnemonic'] }, "&&Previous Group")
|
||||
},
|
||||
order: 2
|
||||
});
|
||||
|
||||
MenuRegistry.appendMenuItem(MenuId.MenubarSwitchGroupMenu, {
|
||||
group: '3_directional',
|
||||
command: {
|
||||
id: 'workbench.action.focusLeftGroup',
|
||||
title: nls.localize({ key: 'miFocusLeftGroup', comment: ['&& denotes a mnemonic'] }, "Group &&Left")
|
||||
},
|
||||
order: 1
|
||||
});
|
||||
|
||||
MenuRegistry.appendMenuItem(MenuId.MenubarSwitchGroupMenu, {
|
||||
group: '3_directional',
|
||||
command: {
|
||||
id: 'workbench.action.focusRightGroup',
|
||||
title: nls.localize({ key: 'miFocusRightGroup', comment: ['&& denotes a mnemonic'] }, "Group &&Right")
|
||||
},
|
||||
order: 2
|
||||
});
|
||||
|
||||
MenuRegistry.appendMenuItem(MenuId.MenubarSwitchGroupMenu, {
|
||||
group: '3_directional',
|
||||
command: {
|
||||
id: 'workbench.action.focusAboveGroup',
|
||||
title: nls.localize({ key: 'miFocusAboveGroup', comment: ['&& denotes a mnemonic'] }, "Group &&Above")
|
||||
},
|
||||
order: 3
|
||||
});
|
||||
|
||||
MenuRegistry.appendMenuItem(MenuId.MenubarSwitchGroupMenu, {
|
||||
group: '3_directional',
|
||||
command: {
|
||||
id: 'workbench.action.focusBelowGroup',
|
||||
title: nls.localize({ key: 'miFocusBelowGroup', comment: ['&& denotes a mnemonic'] }, "Group &&Below")
|
||||
},
|
||||
order: 4
|
||||
});
|
||||
|
||||
MenuRegistry.appendMenuItem(MenuId.MenubarGoMenu, {
|
||||
group: '2_switch',
|
||||
title: nls.localize({ key: 'miSwitchGroup', comment: ['&& denotes a mnemonic'] }, "Switch &&Group"),
|
||||
submenu: MenuId.MenubarSwitchGroupMenu,
|
||||
order: 2
|
||||
});
|
||||
|
||||
// Go to
|
||||
MenuRegistry.appendMenuItem(MenuId.MenubarGoMenu, {
|
||||
group: 'z_go_to',
|
||||
command: {
|
||||
id: 'workbench.action.quickOpen',
|
||||
title: nls.localize({ key: 'miGotoFile', comment: ['&& denotes a mnemonic'] }, "Go to &&File...")
|
||||
},
|
||||
order: 1
|
||||
});
|
||||
|
||||
MenuRegistry.appendMenuItem(MenuId.MenubarGoMenu, {
|
||||
group: 'z_go_to',
|
||||
command: {
|
||||
id: 'workbench.action.gotoSymbol',
|
||||
title: nls.localize({ key: 'miGotoSymbolInFile', comment: ['&& denotes a mnemonic'] }, "Go to &&Symbol in File...")
|
||||
},
|
||||
order: 2
|
||||
});
|
||||
|
||||
MenuRegistry.appendMenuItem(MenuId.MenubarGoMenu, {
|
||||
group: 'z_go_to',
|
||||
command: {
|
||||
id: 'workbench.action.showAllSymbols',
|
||||
title: nls.localize({ key: 'miGotoSymbolInWorkspace', comment: ['&& denotes a mnemonic'] }, "Go to Symbol in &&Workspace...")
|
||||
},
|
||||
order: 3
|
||||
});
|
||||
|
||||
MenuRegistry.appendMenuItem(MenuId.MenubarGoMenu, {
|
||||
group: 'z_go_to',
|
||||
command: {
|
||||
id: 'editor.action.goToDeclaration',
|
||||
title: nls.localize({ key: 'miGotoDefinition', comment: ['&& denotes a mnemonic'] }, "Go to &&Definition")
|
||||
},
|
||||
order: 4
|
||||
});
|
||||
|
||||
MenuRegistry.appendMenuItem(MenuId.MenubarGoMenu, {
|
||||
group: 'z_go_to',
|
||||
command: {
|
||||
id: 'editor.action.goToTypeDefinition',
|
||||
title: nls.localize({ key: 'miGotoTypeDefinition', comment: ['&& denotes a mnemonic'] }, "Go to &&Type Definition")
|
||||
},
|
||||
order: 5
|
||||
});
|
||||
|
||||
MenuRegistry.appendMenuItem(MenuId.MenubarGoMenu, {
|
||||
group: 'z_go_to',
|
||||
command: {
|
||||
id: 'editor.action.goToImplementation',
|
||||
title: nls.localize({ key: 'miGotoImplementation', comment: ['&& denotes a mnemonic'] }, "Go to &&Implementation")
|
||||
},
|
||||
order: 6
|
||||
});
|
||||
|
||||
MenuRegistry.appendMenuItem(MenuId.MenubarGoMenu, {
|
||||
group: 'z_go_to',
|
||||
command: {
|
||||
id: 'workbench.action.gotoLine',
|
||||
title: nls.localize({ key: 'miGotoLine', comment: ['&& denotes a mnemonic'] }, "Go to &&Line...")
|
||||
},
|
||||
order: 7
|
||||
});
|
||||
}
|
||||
|
||||
function windowMenuRegistration() {
|
||||
|
||||
}
|
||||
|
||||
function helpMenuRegistration() {
|
||||
// Welcome
|
||||
MenuRegistry.appendMenuItem(MenuId.MenubarHelpMenu, {
|
||||
group: '1_welcome',
|
||||
command: {
|
||||
id: 'workbench.action.showWelcomePage',
|
||||
title: nls.localize({ key: 'miWelcome', comment: ['&& denotes a mnemonic'] }, "&&Welcome")
|
||||
},
|
||||
order: 1
|
||||
});
|
||||
|
||||
MenuRegistry.appendMenuItem(MenuId.MenubarHelpMenu, {
|
||||
group: '1_welcome',
|
||||
command: {
|
||||
id: 'workbench.action.showInteractivePlayground',
|
||||
title: nls.localize({ key: 'miInteractivePlayground', comment: ['&& denotes a mnemonic'] }, "&&Interactive Playground")
|
||||
},
|
||||
order: 2
|
||||
});
|
||||
|
||||
MenuRegistry.appendMenuItem(MenuId.MenubarHelpMenu, {
|
||||
group: '1_welcome',
|
||||
command: {
|
||||
id: 'workbench.action.openDocumentationUrl',
|
||||
title: nls.localize({ key: 'miDocumentation', comment: ['&& denotes a mnemonic'] }, "&&Documentation")
|
||||
},
|
||||
order: 3
|
||||
});
|
||||
|
||||
MenuRegistry.appendMenuItem(MenuId.MenubarHelpMenu, {
|
||||
group: '1_welcome',
|
||||
command: {
|
||||
id: 'update.showCurrentReleaseNotes',
|
||||
title: nls.localize({ key: 'miReleaseNotes', comment: ['&& denotes a mnemonic'] }, "&&Release Notes")
|
||||
},
|
||||
order: 4
|
||||
});
|
||||
|
||||
// Reference
|
||||
MenuRegistry.appendMenuItem(MenuId.MenubarHelpMenu, {
|
||||
group: '2_reference',
|
||||
command: {
|
||||
id: 'workbench.action.keybindingsReference',
|
||||
title: nls.localize({ key: 'miKeyboardShortcuts', comment: ['&& denotes a mnemonic'] }, "&&Keyboard Shortcuts Reference")
|
||||
},
|
||||
order: 1
|
||||
});
|
||||
|
||||
MenuRegistry.appendMenuItem(MenuId.MenubarHelpMenu, {
|
||||
group: '2_reference',
|
||||
command: {
|
||||
id: 'workbench.action.openIntroductoryVideosUrl',
|
||||
title: nls.localize({ key: 'miIntroductoryVideos', comment: ['&& denotes a mnemonic'] }, "Introductory &&Videos")
|
||||
},
|
||||
order: 2
|
||||
});
|
||||
|
||||
MenuRegistry.appendMenuItem(MenuId.MenubarHelpMenu, {
|
||||
group: '2_reference',
|
||||
command: {
|
||||
id: 'workbench.action.openTipsAndTricksUrl',
|
||||
title: nls.localize({ key: 'miTipsAndTricks', comment: ['&& denotes a mnemonic'] }, "&&Tips and Tricks")
|
||||
},
|
||||
order: 3
|
||||
});
|
||||
|
||||
// Feedback
|
||||
MenuRegistry.appendMenuItem(MenuId.MenubarHelpMenu, {
|
||||
group: '3_feedback',
|
||||
command: {
|
||||
id: 'workbench.action.openTwitterUrl',
|
||||
title: nls.localize({ key: 'miTwitter', comment: ['&& denotes a mnemonic'] }, "&&Join us on Twitter")
|
||||
},
|
||||
order: 1
|
||||
});
|
||||
|
||||
MenuRegistry.appendMenuItem(MenuId.MenubarHelpMenu, {
|
||||
group: '3_feedback',
|
||||
command: {
|
||||
id: 'workbench.action.openRequestFeatureUrl',
|
||||
title: nls.localize({ key: 'miUserVoice', comment: ['&& denotes a mnemonic'] }, "&&Search Feature Requests")
|
||||
},
|
||||
order: 2
|
||||
});
|
||||
|
||||
MenuRegistry.appendMenuItem(MenuId.MenubarHelpMenu, {
|
||||
group: '3_feedback',
|
||||
command: {
|
||||
id: 'workbench.action.openIssueReporter',
|
||||
title: nls.localize({ key: 'miReportIssue', comment: ['&& denotes a mnemonic', 'Translate this to "Report Issue in English" in all languages please!'] }, "Report &&Issue")
|
||||
},
|
||||
order: 3
|
||||
});
|
||||
|
||||
// Legal
|
||||
MenuRegistry.appendMenuItem(MenuId.MenubarHelpMenu, {
|
||||
group: '4_legal',
|
||||
command: {
|
||||
id: 'workbench.action.openLicenseUrl',
|
||||
title: nls.localize({ key: 'miLicense', comment: ['&& denotes a mnemonic'] }, "View &&License")
|
||||
},
|
||||
order: 1
|
||||
});
|
||||
|
||||
MenuRegistry.appendMenuItem(MenuId.MenubarHelpMenu, {
|
||||
group: '4_legal',
|
||||
command: {
|
||||
id: 'workbench.action.openPrivacyStatementUrl',
|
||||
title: nls.localize({ key: 'miPrivacyStatement', comment: ['&& denotes a mnemonic'] }, "&&Privacy Statement")
|
||||
},
|
||||
order: 2
|
||||
});
|
||||
|
||||
// Tools
|
||||
MenuRegistry.appendMenuItem(MenuId.MenubarHelpMenu, {
|
||||
group: '5_tools',
|
||||
command: {
|
||||
id: 'workbench.action.toggleDevTools',
|
||||
title: nls.localize({ key: 'miToggleDevTools', comment: ['&& denotes a mnemonic'] }, "&&Toggle Developer Tools")
|
||||
},
|
||||
order: 1
|
||||
});
|
||||
|
||||
MenuRegistry.appendMenuItem(MenuId.MenubarHelpMenu, {
|
||||
group: '5_tools',
|
||||
command: {
|
||||
id: 'workbench.action.openProcessExplorer',
|
||||
title: nls.localize({ key: 'miOpenProcessExplorerer', comment: ['&& denotes a mnemonic'] }, "Open &&Process Explorer")
|
||||
},
|
||||
order: 2
|
||||
});
|
||||
|
||||
if (!isMacintosh) {
|
||||
// About
|
||||
MenuRegistry.appendMenuItem(MenuId.MenubarHelpMenu, {
|
||||
group: 'z_about',
|
||||
command: {
|
||||
id: 'workbench.action.showAboutDialog',
|
||||
title: nls.localize({ key: 'miAbout', comment: ['&& denotes a mnemonic'] }, "&&About")
|
||||
},
|
||||
order: 1
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -104,7 +104,7 @@
|
||||
.monaco-workbench .notifications-list-container .notification-list-item > .notification-list-item-details-row {
|
||||
display: none;
|
||||
align-items: center;
|
||||
padding: 0 5px;
|
||||
padding-left: 5px;
|
||||
overflow: hidden; /* details row should never overflow */
|
||||
}
|
||||
|
||||
@@ -131,7 +131,7 @@
|
||||
.monaco-workbench .notifications-list-container .notification-list-item .notification-list-item-buttons-container .monaco-button {
|
||||
max-width: fit-content;
|
||||
padding: 5px 10px;
|
||||
margin-left: 10px;
|
||||
margin: 4px 5px; /* allows button focus outline to be visible */
|
||||
font-size: 12px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
|
||||
@@ -3,13 +3,10 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import 'vs/css!./media/notificationsActions';
|
||||
import { INotificationViewItem } from 'vs/workbench/common/notifications';
|
||||
import { localize } from 'vs/nls';
|
||||
import { Action, IAction, ActionRunner } from 'vs/base/common/actions';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { INotificationService } from 'vs/platform/notification/common/notification';
|
||||
import { CLEAR_NOTIFICATION, EXPAND_NOTIFICATION, COLLAPSE_NOTIFICATION, CLEAR_ALL_NOTIFICATIONS, HIDE_NOTIFICATIONS_CENTER } from 'vs/workbench/browser/parts/notifications/notificationsCommands';
|
||||
@@ -29,10 +26,10 @@ export class ClearNotificationAction extends Action {
|
||||
super(id, label, 'clear-notification-action');
|
||||
}
|
||||
|
||||
run(notification: INotificationViewItem): TPromise<any> {
|
||||
run(notification: INotificationViewItem): Promise<any> {
|
||||
this.commandService.executeCommand(CLEAR_NOTIFICATION, notification);
|
||||
|
||||
return TPromise.as(void 0);
|
||||
return Promise.resolve(void 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,10 +46,10 @@ export class ClearAllNotificationsAction extends Action {
|
||||
super(id, label, 'clear-all-notifications-action');
|
||||
}
|
||||
|
||||
run(notification: INotificationViewItem): TPromise<any> {
|
||||
run(notification: INotificationViewItem): Promise<any> {
|
||||
this.commandService.executeCommand(CLEAR_ALL_NOTIFICATIONS);
|
||||
|
||||
return TPromise.as(void 0);
|
||||
return Promise.resolve(void 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,10 +66,10 @@ export class HideNotificationsCenterAction extends Action {
|
||||
super(id, label, 'hide-all-notifications-action');
|
||||
}
|
||||
|
||||
run(notification: INotificationViewItem): TPromise<any> {
|
||||
run(notification: INotificationViewItem): Promise<any> {
|
||||
this.commandService.executeCommand(HIDE_NOTIFICATIONS_CENTER);
|
||||
|
||||
return TPromise.as(void 0);
|
||||
return Promise.resolve(void 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -89,10 +86,10 @@ export class ExpandNotificationAction extends Action {
|
||||
super(id, label, 'expand-notification-action');
|
||||
}
|
||||
|
||||
run(notification: INotificationViewItem): TPromise<any> {
|
||||
run(notification: INotificationViewItem): Promise<any> {
|
||||
this.commandService.executeCommand(EXPAND_NOTIFICATION, notification);
|
||||
|
||||
return TPromise.as(void 0);
|
||||
return Promise.resolve(void 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -109,10 +106,10 @@ export class CollapseNotificationAction extends Action {
|
||||
super(id, label, 'collapse-notification-action');
|
||||
}
|
||||
|
||||
run(notification: INotificationViewItem): TPromise<any> {
|
||||
run(notification: INotificationViewItem): Promise<any> {
|
||||
this.commandService.executeCommand(COLLAPSE_NOTIFICATION, notification);
|
||||
|
||||
return TPromise.as(void 0);
|
||||
return Promise.resolve(void 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -147,10 +144,10 @@ export class CopyNotificationMessageAction extends Action {
|
||||
super(id, label);
|
||||
}
|
||||
|
||||
run(notification: INotificationViewItem): TPromise<any> {
|
||||
run(notification: INotificationViewItem): Promise<any> {
|
||||
this.clipboardService.writeText(notification.message.raw);
|
||||
|
||||
return TPromise.as(void 0);
|
||||
return Promise.resolve(void 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -163,7 +160,7 @@ export class NotificationActionRunner extends ActionRunner {
|
||||
super();
|
||||
}
|
||||
|
||||
protected runAction(action: IAction, context: INotificationViewItem): TPromise<any> {
|
||||
protected runAction(action: IAction, context: INotificationViewItem): Promise<any> {
|
||||
|
||||
/* __GDPR__
|
||||
"workbenchActionExecuted" : {
|
||||
@@ -174,8 +171,8 @@ export class NotificationActionRunner extends ActionRunner {
|
||||
this.telemetryService.publicLog('workbenchActionExecuted', { id: action.id, from: 'message' });
|
||||
|
||||
// Run and make sure to notify on any error again
|
||||
super.runAction(action, context).done(null, error => this.notificationService.error(error));
|
||||
super.runAction(action, context).then(null, error => this.notificationService.error(error));
|
||||
|
||||
return TPromise.as(void 0);
|
||||
return Promise.resolve(void 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,14 +3,13 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import { alert } from 'vs/base/browser/ui/aria/aria';
|
||||
import { localize } from 'vs/nls';
|
||||
import { INotificationViewItem, INotificationsModel, NotificationChangeType, INotificationChangeEvent } from 'vs/workbench/common/notifications';
|
||||
import { INotificationViewItem, INotificationsModel, NotificationChangeType, INotificationChangeEvent, NotificationViewItemLabelKind } from 'vs/workbench/common/notifications';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { toErrorMessage } from 'vs/base/common/errorMessage';
|
||||
import { Severity } from 'vs/platform/notification/common/notification';
|
||||
import { once } from 'vs/base/common/event';
|
||||
|
||||
export class NotificationsAlerts extends Disposable {
|
||||
|
||||
@@ -18,7 +17,7 @@ export class NotificationsAlerts extends Disposable {
|
||||
super();
|
||||
|
||||
// Alert initial notifications if any
|
||||
model.notifications.forEach(n => this.ariaAlert(n));
|
||||
model.notifications.forEach(n => this.triggerAriaAlert(n));
|
||||
|
||||
this.registerListeners();
|
||||
}
|
||||
@@ -31,7 +30,7 @@ export class NotificationsAlerts extends Disposable {
|
||||
if (e.kind === NotificationChangeType.ADD) {
|
||||
|
||||
// ARIA alert for screen readers
|
||||
this.ariaAlert(e.item);
|
||||
this.triggerAriaAlert(e.item);
|
||||
|
||||
// Always log errors to console with full details
|
||||
if (e.item.severity === Severity.Error) {
|
||||
@@ -44,7 +43,21 @@ export class NotificationsAlerts extends Disposable {
|
||||
}
|
||||
}
|
||||
|
||||
private ariaAlert(notifiation: INotificationViewItem): void {
|
||||
private triggerAriaAlert(notifiation: INotificationViewItem): void {
|
||||
|
||||
// Trigger the alert again whenever the label changes
|
||||
const listener = notifiation.onDidLabelChange(e => {
|
||||
if (e.kind === NotificationViewItemLabelKind.MESSAGE) {
|
||||
this.doTriggerAriaAlert(notifiation);
|
||||
}
|
||||
});
|
||||
|
||||
once(notifiation.onDidClose)(() => listener.dispose());
|
||||
|
||||
this.doTriggerAriaAlert(notifiation);
|
||||
}
|
||||
|
||||
private doTriggerAriaAlert(notifiation: INotificationViewItem): void {
|
||||
let alertText: string;
|
||||
if (notifiation.severity === Severity.Error) {
|
||||
alertText = localize('alertErrorMessage', "Error: {0}", notifiation.message.value);
|
||||
|
||||
@@ -3,8 +3,6 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import 'vs/css!./media/notificationsCenter';
|
||||
import 'vs/css!./media/notificationsActions';
|
||||
import { Themable, NOTIFICATIONS_BORDER, NOTIFICATIONS_CENTER_HEADER_FOREGROUND, NOTIFICATIONS_CENTER_HEADER_BACKGROUND, NOTIFICATIONS_CENTER_BORDER } from 'vs/workbench/common/theme';
|
||||
@@ -106,9 +104,9 @@ export class NotificationsCenter extends Themable {
|
||||
|
||||
private updateTitle(): void {
|
||||
if (this.model.notifications.length === 0) {
|
||||
this.notificationsCenterTitle.innerText = localize('notificationsEmpty', "No new notifications");
|
||||
this.notificationsCenterTitle.textContent = localize('notificationsEmpty', "No new notifications");
|
||||
} else {
|
||||
this.notificationsCenterTitle.innerText = localize('notifications', "Notifications");
|
||||
this.notificationsCenterTitle.textContent = localize('notifications', "Notifications");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,8 +3,6 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import { CommandsRegistry } from 'vs/platform/commands/common/commands';
|
||||
import { RawContextKey, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
|
||||
@@ -226,7 +224,7 @@ export function registerNotificationCommands(center: INotificationsCenterControl
|
||||
|
||||
// Commands for Command Palette
|
||||
const category = localize('notifications', "Notifications");
|
||||
MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: SHOW_NOTIFICATIONS_CENTER, title: localize('showNotifications', "Show Notifications"), category }, when: NotificationsCenterVisibleContext.toNegated() });
|
||||
MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: HIDE_NOTIFICATIONS_CENTER, title: localize('hideNotifications', "Hide Notifications"), category }, when: NotificationsCenterVisibleContext });
|
||||
MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: CLEAR_ALL_NOTIFICATIONS, title: localize('clearAllNotifications', "Clear All Notifications"), category } });
|
||||
MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: SHOW_NOTIFICATIONS_CENTER, title: { value: localize('showNotifications', "Show Notifications"), original: 'Notifications: Show Notifications' }, category }, when: NotificationsCenterVisibleContext.toNegated() });
|
||||
MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: HIDE_NOTIFICATIONS_CENTER, title: { value: localize('hideNotifications', "Hide Notifications"), original: 'Notifications: Hide Notifications' }, category }, when: NotificationsCenterVisibleContext });
|
||||
MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: CLEAR_ALL_NOTIFICATIONS, title: { value: localize('clearAllNotifications', "Clear All Notifications"), original: 'Notifications: Clear All Notifications' }, category } });
|
||||
}
|
||||
@@ -3,8 +3,6 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import 'vs/css!./media/notificationsList';
|
||||
import { addClass, isAncestor, trackFocus } from 'vs/base/browser/dom';
|
||||
import { WorkbenchList } from 'vs/platform/list/browser/listService';
|
||||
@@ -18,7 +16,6 @@ import { NotificationsListDelegate, NotificationRenderer } from 'vs/workbench/br
|
||||
import { NotificationActionRunner, CopyNotificationMessageAction } from 'vs/workbench/browser/parts/notifications/notificationsActions';
|
||||
import { NotificationFocusedContext } from 'vs/workbench/browser/parts/notifications/notificationsCommands';
|
||||
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
|
||||
export class NotificationsList extends Themable {
|
||||
private listContainer: HTMLElement;
|
||||
@@ -78,15 +75,22 @@ export class NotificationsList extends Themable {
|
||||
this.listContainer,
|
||||
new NotificationsListDelegate(this.listContainer),
|
||||
[renderer],
|
||||
this.options
|
||||
{
|
||||
...this.options,
|
||||
setRowLineHeight: false
|
||||
}
|
||||
));
|
||||
|
||||
// Context menu to copy message
|
||||
const copyAction = this._register(this.instantiationService.createInstance(CopyNotificationMessageAction, CopyNotificationMessageAction.ID, CopyNotificationMessageAction.LABEL));
|
||||
this._register((this.list.onContextMenu(e => {
|
||||
if (!e.element) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.contextMenuService.showContextMenu({
|
||||
getAnchor: () => e.anchor,
|
||||
getActions: () => TPromise.as([copyAction]),
|
||||
getActions: () => [copyAction],
|
||||
getActionsContext: () => e.element,
|
||||
actionRunner
|
||||
});
|
||||
|
||||
@@ -3,8 +3,6 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import { INotificationsModel, INotificationChangeEvent, NotificationChangeType, INotificationViewItem } from 'vs/workbench/common/notifications';
|
||||
import { IStatusbarService, StatusbarAlignment } from 'vs/platform/statusbar/common/statusbar';
|
||||
import { IDisposable, Disposable } from 'vs/base/common/lifecycle';
|
||||
|
||||
@@ -3,8 +3,6 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import 'vs/css!./media/notificationsToasts';
|
||||
import { INotificationsModel, NotificationChangeType, INotificationChangeEvent, INotificationViewItem, NotificationViewItemLabelKind } from 'vs/workbench/common/notifications';
|
||||
import { IDisposable, dispose, toDisposable } from 'vs/base/common/lifecycle';
|
||||
@@ -23,6 +21,7 @@ import { localize } from 'vs/nls';
|
||||
import { Severity } from 'vs/platform/notification/common/notification';
|
||||
import { ScrollbarVisibility } from 'vs/base/common/scrollable';
|
||||
import { ILifecycleService, LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle';
|
||||
import { IWindowService } from 'vs/platform/windows/common/windows';
|
||||
|
||||
interface INotificationToast {
|
||||
item: INotificationViewItem;
|
||||
@@ -45,15 +44,16 @@ export class NotificationsToasts extends Themable {
|
||||
|
||||
private static PURGE_TIMEOUT: { [severity: number]: number } = (() => {
|
||||
const intervals = Object.create(null);
|
||||
intervals[Severity.Info] = 10000;
|
||||
intervals[Severity.Warning] = 12000;
|
||||
intervals[Severity.Error] = 15000;
|
||||
intervals[Severity.Info] = 15000;
|
||||
intervals[Severity.Warning] = 18000;
|
||||
intervals[Severity.Error] = 20000;
|
||||
|
||||
return intervals;
|
||||
})();
|
||||
|
||||
private notificationsToastsContainer: HTMLElement;
|
||||
private workbenchDimensions: Dimension;
|
||||
private windowHasFocus: boolean;
|
||||
private isNotificationsCenterVisible: boolean;
|
||||
private mapNotificationToToast: Map<INotificationViewItem, INotificationToast>;
|
||||
private notificationsToastsVisibleContextKey: IContextKey<boolean>;
|
||||
@@ -66,21 +66,33 @@ export class NotificationsToasts extends Themable {
|
||||
@IThemeService themeService: IThemeService,
|
||||
@IEditorGroupsService private editorGroupService: IEditorGroupsService,
|
||||
@IContextKeyService contextKeyService: IContextKeyService,
|
||||
@ILifecycleService private lifecycleService: ILifecycleService
|
||||
@ILifecycleService private lifecycleService: ILifecycleService,
|
||||
@IWindowService private windowService: IWindowService
|
||||
) {
|
||||
super(themeService);
|
||||
|
||||
this.mapNotificationToToast = new Map<INotificationViewItem, INotificationToast>();
|
||||
this.notificationsToastsVisibleContextKey = NotificationsToastsVisibleContext.bindTo(contextKeyService);
|
||||
|
||||
// Show toast for initial notifications if any
|
||||
model.notifications.forEach(notification => this.addToast(notification));
|
||||
this.windowService.isFocused().then(isFocused => this.windowHasFocus = isFocused);
|
||||
|
||||
this.registerListeners();
|
||||
}
|
||||
|
||||
private registerListeners(): void {
|
||||
this._register(this.model.onDidNotificationChange(e => this.onDidNotificationChange(e)));
|
||||
|
||||
// Wait for the running phase to ensure we can draw notifications properly
|
||||
this.lifecycleService.when(LifecyclePhase.Ready).then(() => {
|
||||
|
||||
// Show toast for initial notifications if any
|
||||
this.model.notifications.forEach(notification => this.addToast(notification));
|
||||
|
||||
// Update toasts on notification changes
|
||||
this._register(this.model.onDidNotificationChange(e => this.onDidNotificationChange(e)));
|
||||
});
|
||||
|
||||
// Track window focus
|
||||
this.windowService.onDidChangeFocus(hasFocus => this.windowHasFocus = hasFocus);
|
||||
}
|
||||
|
||||
private onDidNotificationChange(e: INotificationChangeEvent): void {
|
||||
@@ -97,6 +109,10 @@ export class NotificationsToasts extends Themable {
|
||||
return; // do not show toasts while notification center is visibles
|
||||
}
|
||||
|
||||
if (item.silent) {
|
||||
return; // do not show toats for silenced notifications
|
||||
}
|
||||
|
||||
// Lazily create toasts containers
|
||||
if (!this.notificationsToastsContainer) {
|
||||
this.notificationsToastsContainer = document.createElement('div');
|
||||
@@ -173,31 +189,8 @@ export class NotificationsToasts extends Themable {
|
||||
this.removeToast(item);
|
||||
});
|
||||
|
||||
// Automatically hide collapsed notifications
|
||||
if (!item.expanded) {
|
||||
|
||||
// Track mouse over item
|
||||
let isMouseOverToast = false;
|
||||
itemDisposeables.push(addDisposableListener(notificationToastContainer, EventType.MOUSE_OVER, () => isMouseOverToast = true));
|
||||
itemDisposeables.push(addDisposableListener(notificationToastContainer, EventType.MOUSE_OUT, () => isMouseOverToast = false));
|
||||
|
||||
// Install Timers
|
||||
let timeoutHandle: number;
|
||||
const hideAfterTimeout = () => {
|
||||
timeoutHandle = setTimeout(() => {
|
||||
const showsProgress = item.hasProgress() && !item.progress.state.done;
|
||||
if (!notificationList.hasFocus() && !item.expanded && !isMouseOverToast && !showsProgress) {
|
||||
this.removeToast(item);
|
||||
} else {
|
||||
hideAfterTimeout(); // push out disposal if item has focus or is expanded
|
||||
}
|
||||
}, NotificationsToasts.PURGE_TIMEOUT[item.severity]);
|
||||
};
|
||||
|
||||
hideAfterTimeout();
|
||||
|
||||
itemDisposeables.push(toDisposable(() => clearTimeout(timeoutHandle)));
|
||||
}
|
||||
// Automatically purge non-sticky notifications
|
||||
this.purgeNotification(item, notificationToastContainer, notificationList, itemDisposeables);
|
||||
|
||||
// Theming
|
||||
this.updateStyles();
|
||||
@@ -205,16 +198,61 @@ export class NotificationsToasts extends Themable {
|
||||
// Context Key
|
||||
this.notificationsToastsVisibleContextKey.set(true);
|
||||
|
||||
// Animate In if we are in a running session (otherwise just show directly)
|
||||
if (this.lifecycleService.phase >= LifecyclePhase.Running) {
|
||||
addClass(notificationToast, 'notification-fade-in');
|
||||
itemDisposeables.push(addDisposableListener(notificationToast, 'transitionend', () => {
|
||||
removeClass(notificationToast, 'notification-fade-in');
|
||||
addClass(notificationToast, 'notification-fade-in-done');
|
||||
}));
|
||||
} else {
|
||||
// Animate in
|
||||
addClass(notificationToast, 'notification-fade-in');
|
||||
itemDisposeables.push(addDisposableListener(notificationToast, 'transitionend', () => {
|
||||
removeClass(notificationToast, 'notification-fade-in');
|
||||
addClass(notificationToast, 'notification-fade-in-done');
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
private purgeNotification(item: INotificationViewItem, notificationToastContainer: HTMLElement, notificationList: NotificationsList, disposables: IDisposable[]): void {
|
||||
|
||||
// Track mouse over item
|
||||
let isMouseOverToast = false;
|
||||
disposables.push(addDisposableListener(notificationToastContainer, EventType.MOUSE_OVER, () => isMouseOverToast = true));
|
||||
disposables.push(addDisposableListener(notificationToastContainer, EventType.MOUSE_OUT, () => isMouseOverToast = false));
|
||||
|
||||
// Install Timers to Purge Notification
|
||||
let purgeTimeoutHandle: any;
|
||||
let listener: IDisposable;
|
||||
|
||||
const hideAfterTimeout = () => {
|
||||
|
||||
purgeTimeoutHandle = setTimeout(() => {
|
||||
|
||||
// If the notification is sticky or prompting and the window does not have
|
||||
// focus, we wait for the window to gain focus again before triggering
|
||||
// the timeout again. This prevents an issue where focussing the window
|
||||
// could immediately hide the notification because the timeout was triggered
|
||||
// again.
|
||||
if ((item.sticky || item.hasPrompt()) && !this.windowHasFocus) {
|
||||
if (!listener) {
|
||||
listener = this.windowService.onDidChangeFocus(focus => {
|
||||
if (focus) {
|
||||
hideAfterTimeout();
|
||||
}
|
||||
});
|
||||
disposables.push(listener);
|
||||
}
|
||||
}
|
||||
|
||||
// Otherwise...
|
||||
else if (
|
||||
item.sticky || // never hide sticky notifications
|
||||
notificationList.hasFocus() || // never hide notifications with focus
|
||||
isMouseOverToast // never hide notifications under mouse
|
||||
) {
|
||||
hideAfterTimeout();
|
||||
} else {
|
||||
this.removeToast(item);
|
||||
}
|
||||
}, NotificationsToasts.PURGE_TIMEOUT[item.severity]);
|
||||
};
|
||||
|
||||
hideAfterTimeout();
|
||||
|
||||
disposables.push(toDisposable(() => clearTimeout(purgeTimeoutHandle)));
|
||||
}
|
||||
|
||||
private removeToast(item: INotificationViewItem): void {
|
||||
@@ -481,4 +519,4 @@ export class NotificationsToasts extends Themable {
|
||||
private isVisible(toast: INotificationToast): boolean {
|
||||
return !!toast.container.parentElement;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,13 +3,10 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import { IVirtualDelegate, IRenderer } from 'vs/base/browser/ui/list/list';
|
||||
import { IListVirtualDelegate, IListRenderer } from 'vs/base/browser/ui/list/list';
|
||||
import { clearNode, addClass, removeClass, toggleClass, addDisposableListener, EventType, EventHelper } from 'vs/base/browser/dom';
|
||||
import { IOpenerService } from 'vs/platform/opener/common/opener';
|
||||
import URI from 'vs/base/common/uri';
|
||||
import { onUnexpectedError } from 'vs/base/common/errors';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { localize } from 'vs/nls';
|
||||
import { ButtonGroup } from 'vs/base/browser/ui/button/button';
|
||||
import { attachButtonStyler, attachProgressBarStyler } from 'vs/platform/theme/common/styler';
|
||||
@@ -20,13 +17,13 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti
|
||||
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
|
||||
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
|
||||
import { DropdownMenuActionItem } from 'vs/base/browser/ui/dropdown/dropdown';
|
||||
import { INotificationViewItem, NotificationViewItem, NotificationViewItemLabelKind, INotificationMessage } from 'vs/workbench/common/notifications';
|
||||
import { INotificationViewItem, NotificationViewItem, NotificationViewItemLabelKind, INotificationMessage, ChoiceAction } from 'vs/workbench/common/notifications';
|
||||
import { ClearNotificationAction, ExpandNotificationAction, CollapseNotificationAction, ConfigureNotificationAction } from 'vs/workbench/browser/parts/notifications/notificationsActions';
|
||||
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
||||
import { ProgressBar } from 'vs/base/browser/ui/progressbar/progressbar';
|
||||
import { Severity } from 'vs/platform/notification/common/notification';
|
||||
|
||||
export class NotificationsListDelegate implements IVirtualDelegate<INotificationViewItem> {
|
||||
export class NotificationsListDelegate implements IListVirtualDelegate<INotificationViewItem> {
|
||||
|
||||
private static readonly ROW_HEIGHT = 42;
|
||||
private static readonly LINE_HEIGHT = 22;
|
||||
@@ -179,7 +176,7 @@ class NotificationMessageRenderer {
|
||||
}
|
||||
}
|
||||
|
||||
export class NotificationRenderer implements IRenderer<INotificationViewItem, INotificationTemplateData> {
|
||||
export class NotificationRenderer implements IListRenderer<INotificationViewItem, INotificationTemplateData> {
|
||||
|
||||
static readonly TEMPLATE_ID = 'notification';
|
||||
|
||||
@@ -371,7 +368,7 @@ export class NotificationTemplateRenderer {
|
||||
private renderMessage(notification: INotificationViewItem): boolean {
|
||||
clearNode(this.template.message);
|
||||
this.template.message.appendChild(NotificationMessageRenderer.render(notification.message, {
|
||||
callback: link => this.openerService.open(URI.parse(link)).then(void 0, onUnexpectedError),
|
||||
callback: link => this.openerService.open(URI.parse(link)),
|
||||
disposeables: this.inputDisposeables
|
||||
}));
|
||||
|
||||
@@ -426,10 +423,10 @@ export class NotificationTemplateRenderer {
|
||||
|
||||
private renderSource(notification): void {
|
||||
if (notification.expanded && notification.source) {
|
||||
this.template.source.innerText = localize('notificationSource', "Source: {0}", notification.source);
|
||||
this.template.source.textContent = localize('notificationSource', "Source: {0}", notification.source);
|
||||
this.template.source.title = notification.source;
|
||||
} else {
|
||||
this.template.source.innerText = '';
|
||||
this.template.source.textContent = '';
|
||||
this.template.source.removeAttribute('title');
|
||||
}
|
||||
}
|
||||
@@ -449,8 +446,10 @@ export class NotificationTemplateRenderer {
|
||||
// Run action
|
||||
this.actionRunner.run(action, notification);
|
||||
|
||||
// Hide notification
|
||||
notification.close();
|
||||
// Hide notification (unless explicitly prevented)
|
||||
if (!(action instanceof ChoiceAction) || !action.keepOpen) {
|
||||
notification.close();
|
||||
}
|
||||
}));
|
||||
|
||||
this.inputDisposeables.push(attachButtonStyler(button, this.themeService));
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
<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>DockBottom_16x</title><path class="icon-canvas-transparent" d="M16,0V16H0V0Z"/><path class="icon-vs-out" d="M16,1V15H0V1Z" style="display: none;"/><path class="icon-vs-fg" d="M14,3V9H2V3Z" style="display: none;"/><path class="icon-vs-bg" d="M1,2V14H15V2ZM14,9H2V3H14Z"/></svg>
|
||||
|
Before Width: | Height: | Size: 511 B |
@@ -1 +0,0 @@
|
||||
<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>DockBottom_16x</title><path class="icon-canvas-transparent" d="M16,0V16H0V0Z"/><path class="icon-vs-out" d="M16,1V15H0V1Z" style="display: none;"/><path class="icon-vs-fg" d="M14,3V9H2V3Z" style="display: none;"/><path class="icon-vs-bg" d="M1,2V14H15V2ZM14,9H2V3H14Z"/></svg>
|
||||
|
Before Width: | Height: | Size: 511 B |
@@ -1 +0,0 @@
|
||||
<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>DockRight_16x</title><path class="icon-canvas-transparent" d="M16,0V16H0V0Z"/><path class="icon-vs-out" d="M16,1V15H0V1Z" style="display: none;"/><path class="icon-vs-fg" d="M10,3V13H2V3Z" style="display: none;"/><path class="icon-vs-bg" d="M1,2V14H15V2Zm9,11H2V3h8Z"/></svg>
|
||||
|
Before Width: | Height: | Size: 510 B |
@@ -1 +0,0 @@
|
||||
<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>DockRight_16x</title><path class="icon-canvas-transparent" d="M16,0V16H0V0Z"/><path class="icon-vs-out" d="M16,1V15H0V1Z" style="display: none;"/><path class="icon-vs-fg" d="M10,3V13H2V3Z" style="display: none;"/><path class="icon-vs-bg" d="M1,2V14H15V2Zm9,11H2V3h8Z"/></svg>
|
||||
|
Before Width: | Height: | Size: 510 B |
@@ -147,24 +147,6 @@
|
||||
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;
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
|
||||
import 'vs/css!./media/panelpart';
|
||||
import * as nls from 'vs/nls';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
|
||||
import { KeyMod, KeyCode } from 'vs/base/common/keyCodes';
|
||||
import { Action } from 'vs/base/common/actions';
|
||||
@@ -14,7 +13,7 @@ import { SyncActionDescriptor, MenuId, MenuRegistry } from 'vs/platform/actions/
|
||||
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 { ActivityAction } from 'vs/workbench/browser/parts/compositeBarActions';
|
||||
import { IActivity } from 'vs/workbench/common/activity';
|
||||
|
||||
export class ClosePanelAction extends Action {
|
||||
@@ -30,8 +29,9 @@ export class ClosePanelAction extends Action {
|
||||
super(id, name, 'hide-panel-action');
|
||||
}
|
||||
|
||||
run(): TPromise<any> {
|
||||
return this.partService.setPanelHidden(true);
|
||||
run(): Thenable<any> {
|
||||
this.partService.setPanelHidden(true);
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,8 +48,9 @@ export class TogglePanelAction extends Action {
|
||||
super(id, name, partService.isVisible(Parts.PANEL_PART) ? 'panel expanded' : 'panel');
|
||||
}
|
||||
|
||||
run(): TPromise<any> {
|
||||
return this.partService.setPanelHidden(this.partService.isVisible(Parts.PANEL_PART));
|
||||
run(): Thenable<any> {
|
||||
this.partService.setPanelHidden(this.partService.isVisible(Parts.PANEL_PART));
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,11 +68,12 @@ class FocusPanelAction extends Action {
|
||||
super(id, label);
|
||||
}
|
||||
|
||||
run(): TPromise<any> {
|
||||
run(): Thenable<any> {
|
||||
|
||||
// Show panel
|
||||
if (!this.partService.isVisible(Parts.PANEL_PART)) {
|
||||
return this.partService.setPanelHidden(false);
|
||||
this.partService.setPanelHidden(false);
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
|
||||
// Focus into active panel
|
||||
@@ -79,7 +81,8 @@ class FocusPanelAction extends Action {
|
||||
if (panel) {
|
||||
panel.focus();
|
||||
}
|
||||
return TPromise.as(true);
|
||||
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -88,8 +91,8 @@ export class TogglePanelPositionAction extends Action {
|
||||
static readonly ID = 'workbench.action.togglePanelPosition';
|
||||
static readonly LABEL = nls.localize('toggledPanelPosition', "Toggle Panel Position");
|
||||
|
||||
private static readonly MOVE_TO_RIGHT_LABEL = nls.localize('moveToRight', "Move to Right");
|
||||
private static readonly MOVE_TO_BOTTOM_LABEL = nls.localize('moveToBottom', "Move to Bottom");
|
||||
private static readonly MOVE_TO_RIGHT_LABEL = nls.localize('moveToRight', "Move Panel Right");
|
||||
private static readonly MOVE_TO_BOTTOM_LABEL = nls.localize('moveToBottom', "Move Panel to Bottom");
|
||||
|
||||
private toDispose: IDisposable[];
|
||||
|
||||
@@ -97,26 +100,32 @@ export class TogglePanelPositionAction extends Action {
|
||||
id: string,
|
||||
label: string,
|
||||
@IPartService private partService: IPartService,
|
||||
|
||||
) {
|
||||
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();
|
||||
}
|
||||
|
||||
run(): TPromise<any> {
|
||||
run(): Thenable<any> {
|
||||
const position = this.partService.getPanelPosition();
|
||||
return this.partService.setPanelPosition(position === Position.BOTTOM ? Position.RIGHT : Position.BOTTOM);
|
||||
|
||||
this.partService.setPanelPosition(position === Position.BOTTOM ? Position.RIGHT : Position.BOTTOM);
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
super.dispose();
|
||||
|
||||
this.toDispose = dispose(this.toDispose);
|
||||
}
|
||||
}
|
||||
@@ -137,7 +146,9 @@ export class ToggleMaximizedPanelAction extends Action {
|
||||
@IPartService private partService: IPartService
|
||||
) {
|
||||
super(id, label, partService.isPanelMaximized() ? 'minimize-panel-action' : 'maximize-panel-action');
|
||||
|
||||
this.toDispose = [];
|
||||
|
||||
this.toDispose.push(partService.onEditorLayout(() => {
|
||||
const maximized = this.partService.isPanelMaximized();
|
||||
this.class = maximized ? 'minimize-panel-action' : 'maximize-panel-action';
|
||||
@@ -145,13 +156,18 @@ export class ToggleMaximizedPanelAction extends Action {
|
||||
}));
|
||||
}
|
||||
|
||||
run(): TPromise<any> {
|
||||
return (!this.partService.isVisible(Parts.PANEL_PART) ? this.partService.setPanelHidden(false) : TPromise.as(null))
|
||||
.then(() => this.partService.toggleMaximizedPanel());
|
||||
run(): Thenable<any> {
|
||||
if (!this.partService.isVisible(Parts.PANEL_PART)) {
|
||||
this.partService.setPanelHidden(false);
|
||||
}
|
||||
|
||||
this.partService.toggleMaximizedPanel();
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
super.dispose();
|
||||
|
||||
this.toDispose = dispose(this.toDispose);
|
||||
}
|
||||
}
|
||||
@@ -165,8 +181,74 @@ export class PanelActivityAction extends ActivityAction {
|
||||
super(activity);
|
||||
}
|
||||
|
||||
run(event: any): TPromise<any> {
|
||||
return this.panelService.openPanel(this.activity.id, true).then(() => this.activate());
|
||||
run(event: any): Thenable<any> {
|
||||
this.panelService.openPanel(this.activity.id, true);
|
||||
this.activate();
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
}
|
||||
|
||||
export class SwitchPanelViewAction extends Action {
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
name: string,
|
||||
@IPanelService private panelService: IPanelService
|
||||
) {
|
||||
super(id, name);
|
||||
}
|
||||
|
||||
run(offset: number): Thenable<any> {
|
||||
const pinnedPanels = this.panelService.getPinnedPanels();
|
||||
const activePanel = this.panelService.getActivePanel();
|
||||
if (!activePanel) {
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
let targetPanelId: string;
|
||||
for (let i = 0; i < pinnedPanels.length; i++) {
|
||||
if (pinnedPanels[i].id === activePanel.getId()) {
|
||||
targetPanelId = pinnedPanels[(i + pinnedPanels.length + offset) % pinnedPanels.length].id;
|
||||
break;
|
||||
}
|
||||
}
|
||||
this.panelService.openPanel(targetPanelId, true);
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
}
|
||||
|
||||
export class PreviousPanelViewAction extends SwitchPanelViewAction {
|
||||
|
||||
static readonly ID = 'workbench.action.previousPanelView';
|
||||
static LABEL = nls.localize('previousPanelView', 'Previous Panel View');
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
name: string,
|
||||
@IPanelService panelService: IPanelService
|
||||
) {
|
||||
super(id, name, panelService);
|
||||
}
|
||||
|
||||
run(): Thenable<any> {
|
||||
return super.run(-1);
|
||||
}
|
||||
}
|
||||
|
||||
export class NextPanelViewAction extends SwitchPanelViewAction {
|
||||
|
||||
static readonly ID = 'workbench.action.nextPanelView';
|
||||
static LABEL = nls.localize('nextPanelView', 'Next Panel View');
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
name: string,
|
||||
@IPanelService panelService: IPanelService
|
||||
) {
|
||||
super(id, name, panelService);
|
||||
}
|
||||
|
||||
public run(): Thenable<any> {
|
||||
return super.run(1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -177,6 +259,8 @@ actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(ToggleMaximizedP
|
||||
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"));
|
||||
actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(PreviousPanelViewAction, PreviousPanelViewAction.ID, PreviousPanelViewAction.LABEL), 'View: Open Previous Panel View', nls.localize('view', "View"));
|
||||
actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(NextPanelViewAction, NextPanelViewAction.ID, NextPanelViewAction.LABEL), 'View: Open Next Panel View', nls.localize('view', "View"));
|
||||
|
||||
MenuRegistry.appendMenuItem(MenuId.MenubarAppearanceMenu, {
|
||||
group: '2_workbench_layout',
|
||||
@@ -186,3 +270,12 @@ MenuRegistry.appendMenuItem(MenuId.MenubarAppearanceMenu, {
|
||||
},
|
||||
order: 5
|
||||
});
|
||||
|
||||
MenuRegistry.appendMenuItem(MenuId.MenubarAppearanceMenu, {
|
||||
group: '2_workbench_layout',
|
||||
command: {
|
||||
id: TogglePanelPositionAction.ID,
|
||||
title: TogglePanelPositionAction.LABEL
|
||||
},
|
||||
order: 3
|
||||
});
|
||||
|
||||
@@ -4,10 +4,8 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import 'vs/css!./media/panelpart';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { IAction } from 'vs/base/common/actions';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { $ } from 'vs/base/browser/builder';
|
||||
import { Event, mapEvent } from 'vs/base/common/event';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { ActionsOrientation } from 'vs/base/browser/ui/actionbar/actionbar';
|
||||
import { IPanel } from 'vs/workbench/common/panel';
|
||||
@@ -24,17 +22,17 @@ import { ClosePanelAction, TogglePanelPositionAction, PanelActivityAction, Toggl
|
||||
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, 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 { CompositeBar } from 'vs/workbench/browser/parts/compositeBar';
|
||||
import { ToggleCompositePinnedAction } from 'vs/workbench/browser/parts/compositeBarActions';
|
||||
import { IBadge } from 'vs/workbench/services/activity/common/activity';
|
||||
import { INotificationService } from 'vs/platform/notification/common/notification';
|
||||
import { Dimension } from 'vs/base/browser/dom';
|
||||
import { Dimension, trackFocus } from 'vs/base/browser/dom';
|
||||
import { localize } from 'vs/nls';
|
||||
import { IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { RawContextKey, IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||
|
||||
const ActivePanleContextId = 'activePanel';
|
||||
export const ActivePanelContext = new RawContextKey<string>(ActivePanleContextId, '');
|
||||
export const ActivePanelContext = new RawContextKey<string>('activePanel', '');
|
||||
export const PanelFocusContext = new RawContextKey<boolean>('panelFocus', false);
|
||||
|
||||
export class PanelPart extends CompositePart<Panel> implements IPanelService {
|
||||
|
||||
@@ -46,6 +44,7 @@ export class PanelPart extends CompositePart<Panel> implements IPanelService {
|
||||
_serviceBrand: any;
|
||||
|
||||
private activePanelContextKey: IContextKey<string>;
|
||||
private panelFocusContextKey: IContextKey<boolean>;
|
||||
private blockOpeningPanel: boolean;
|
||||
private compositeBar: CompositeBar;
|
||||
private compositeActions: { [compositeId: string]: { activityAction: PanelActivityAction, pinnedAction: ToggleCompositePinnedAction } } = Object.create(null);
|
||||
@@ -86,21 +85,28 @@ export class PanelPart extends CompositePart<Panel> implements IPanelService {
|
||||
icon: false,
|
||||
storageId: PanelPart.PINNED_PANELS,
|
||||
orientation: ActionsOrientation.HORIZONTAL,
|
||||
openComposite: (compositeId: string) => this.openPanel(compositeId, true),
|
||||
openComposite: (compositeId: string) => Promise.resolve(this.openPanel(compositeId, true)),
|
||||
getActivityAction: (compositeId: string) => this.getCompositeActions(compositeId).activityAction,
|
||||
getCompositePinnedAction: (compositeId: string) => this.getCompositeActions(compositeId).pinnedAction,
|
||||
getOnCompositeClickAction: (compositeId: string) => this.instantiationService.createInstance(PanelActivityAction, this.getPanel(compositeId)),
|
||||
getContextMenuActions: () => [this.instantiationService.createInstance(TogglePanelAction, TogglePanelAction.ID, localize('hidePanel', "Hide Panel"))],
|
||||
getContextMenuActions: () => [
|
||||
this.instantiationService.createInstance(TogglePanelPositionAction, TogglePanelPositionAction.ID, TogglePanelPositionAction.LABEL),
|
||||
this.instantiationService.createInstance(TogglePanelAction, TogglePanelAction.ID, localize('hidePanel', "Hide Panel"))
|
||||
],
|
||||
getDefaultCompositeId: () => Registry.as<PanelRegistry>(PanelExtensions.Panels).getDefaultPanelId(),
|
||||
hidePart: () => this.partService.setPanelHidden(true),
|
||||
compositeSize: 0,
|
||||
overflowActionSize: 44,
|
||||
colors: {
|
||||
backgroundColor: PANEL_BACKGROUND,
|
||||
badgeBackground,
|
||||
badgeForeground,
|
||||
dragAndDropBackground: PANEL_DRAG_AND_DROP_BACKGROUND
|
||||
}
|
||||
colors: theme => ({
|
||||
activeBackgroundColor: theme.getColor(PANEL_BACKGROUND), // Background color for overflow action
|
||||
inactiveBackgroundColor: theme.getColor(PANEL_BACKGROUND), // Background color for overflow action
|
||||
activeBorderBottomColor: theme.getColor(PANEL_ACTIVE_TITLE_BORDER),
|
||||
activeForegroundColor: theme.getColor(PANEL_ACTIVE_TITLE_FOREGROUND),
|
||||
inactiveForegroundColor: theme.getColor(PANEL_INACTIVE_TITLE_FOREGROUND),
|
||||
badgeBackground: theme.getColor(badgeBackground),
|
||||
badgeForeground: theme.getColor(badgeForeground),
|
||||
dragAndDropBackground: theme.getColor(PANEL_DRAG_AND_DROP_BACKGROUND)
|
||||
})
|
||||
}));
|
||||
|
||||
for (const panel of this.getPanels()) {
|
||||
@@ -108,18 +114,32 @@ export class PanelPart extends CompositePart<Panel> implements IPanelService {
|
||||
}
|
||||
|
||||
this.activePanelContextKey = ActivePanelContext.bindTo(contextKeyService);
|
||||
this.panelFocusContextKey = PanelFocusContext.bindTo(contextKeyService);
|
||||
|
||||
this.registerListeners();
|
||||
}
|
||||
|
||||
create(parent: HTMLElement): void {
|
||||
super.create(parent);
|
||||
|
||||
const focusTracker = trackFocus(parent);
|
||||
|
||||
focusTracker.onDidFocus(() => {
|
||||
this.panelFocusContextKey.set(true);
|
||||
});
|
||||
focusTracker.onDidBlur(() => {
|
||||
this.panelFocusContextKey.set(false);
|
||||
});
|
||||
}
|
||||
|
||||
private registerListeners(): void {
|
||||
this._register(this.onDidPanelOpen(this._onDidPanelOpen, this));
|
||||
this._register(this.onDidPanelOpen(({ panel }) => this._onDidPanelOpen(panel)));
|
||||
this._register(this.onDidPanelClose(this._onDidPanelClose, this));
|
||||
|
||||
this._register(this.registry.onDidRegister(panelDescriptor => this.compositeBar.addComposite(panelDescriptor)));
|
||||
|
||||
// Activate panel action on opening of a panel
|
||||
this._register(this.onDidPanelOpen(panel => {
|
||||
this._register(this.onDidPanelOpen(({ panel }) => {
|
||||
this.compositeBar.activateComposite(panel.getId());
|
||||
this.layoutCompositeBar(); // Need to relayout composite bar since different panels have different action bar width
|
||||
}));
|
||||
@@ -140,8 +160,8 @@ export class PanelPart extends CompositePart<Panel> implements IPanelService {
|
||||
}
|
||||
}
|
||||
|
||||
get onDidPanelOpen(): Event<IPanel> {
|
||||
return this._onDidCompositeOpen.event;
|
||||
get onDidPanelOpen(): Event<{ panel: IPanel, focus: boolean }> {
|
||||
return mapEvent(this._onDidCompositeOpen.event, compositeOpen => ({ panel: compositeOpen.composite, focus: compositeOpen.focus }));
|
||||
}
|
||||
|
||||
get onDidPanelClose(): Event<IPanel> {
|
||||
@@ -151,31 +171,30 @@ export class PanelPart extends CompositePart<Panel> implements IPanelService {
|
||||
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 container = this.getContainer();
|
||||
container.style.backgroundColor = this.getColor(PANEL_BACKGROUND);
|
||||
container.style.borderLeftColor = this.getColor(PANEL_BORDER) || this.getColor(contrastBorder);
|
||||
|
||||
const title = $(this.getTitleArea());
|
||||
title.style('border-top-color', this.getColor(PANEL_BORDER) || this.getColor(contrastBorder));
|
||||
const title = this.getTitleArea();
|
||||
title.style.borderTopColor = this.getColor(PANEL_BORDER) || this.getColor(contrastBorder);
|
||||
}
|
||||
|
||||
openPanel(id: string, focus?: boolean): TPromise<Panel> {
|
||||
openPanel(id: string, focus?: boolean): Panel {
|
||||
if (this.blockOpeningPanel) {
|
||||
return TPromise.as(null); // Workaround against a potential race condition
|
||||
return null; // Workaround against a potential race condition
|
||||
}
|
||||
|
||||
// First check if panel is hidden and show if so
|
||||
let promise = TPromise.wrap(null);
|
||||
if (!this.partService.isVisible(Parts.PANEL_PART)) {
|
||||
try {
|
||||
this.blockOpeningPanel = true;
|
||||
promise = this.partService.setPanelHidden(false);
|
||||
this.partService.setPanelHidden(false);
|
||||
} finally {
|
||||
this.blockOpeningPanel = false;
|
||||
}
|
||||
}
|
||||
|
||||
return promise.then(() => this.openComposite(id, focus));
|
||||
return this.openComposite(id, focus);
|
||||
}
|
||||
|
||||
showActivity(panelId: string, badge: IBadge, clazz?: string): IDisposable {
|
||||
@@ -192,6 +211,13 @@ export class PanelPart extends CompositePart<Panel> implements IPanelService {
|
||||
.sort((v1, v2) => v1.order - v2.order);
|
||||
}
|
||||
|
||||
getPinnedPanels(): PanelDescriptor[] {
|
||||
const pinnedCompositeIds = this.compositeBar.getPinnedComposites().map(c => c.id);
|
||||
return this.getPanels()
|
||||
.filter(p => pinnedCompositeIds.indexOf(p.id) !== -1)
|
||||
.sort((p1, p2) => pinnedCompositeIds.indexOf(p1.id) - pinnedCompositeIds.indexOf(p2.id));
|
||||
}
|
||||
|
||||
setPanelEnablement(id: string, enabled: boolean): void {
|
||||
const descriptor = Registry.as<PanelRegistry>(PanelExtensions.Panels).getPanels().filter(p => p.id === id).pop();
|
||||
if (descriptor && descriptor.enabled !== enabled) {
|
||||
@@ -207,7 +233,6 @@ 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)
|
||||
];
|
||||
}
|
||||
@@ -220,8 +245,8 @@ export class PanelPart extends CompositePart<Panel> implements IPanelService {
|
||||
return this.getLastActiveCompositetId();
|
||||
}
|
||||
|
||||
hideActivePanel(): TPromise<void> {
|
||||
return this.hideActiveComposite().then(composite => void 0);
|
||||
hideActivePanel(): void {
|
||||
this.hideActiveComposite();
|
||||
}
|
||||
|
||||
protected createTitleLabel(parent: HTMLElement): ICompositeTitleLabel {
|
||||
@@ -282,7 +307,7 @@ export class PanelPart extends CompositePart<Panel> implements IPanelService {
|
||||
}
|
||||
|
||||
private removeComposite(compositeId: string): void {
|
||||
this.compositeBar.removeComposite(compositeId);
|
||||
this.compositeBar.hideComposite(compositeId);
|
||||
const compositeActions = this.compositeActions[compositeId];
|
||||
if (compositeActions) {
|
||||
compositeActions.activityAction.dispose();
|
||||
@@ -321,20 +346,9 @@ registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => {
|
||||
const titleActiveBorder = theme.getColor(PANEL_ACTIVE_TITLE_BORDER);
|
||||
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.checked .action-label {
|
||||
color: ${titleActive};
|
||||
border-bottom-color: ${titleActiveBorder};
|
||||
}
|
||||
`);
|
||||
}
|
||||
|
||||
// Title Inactive
|
||||
const titleInactive = theme.getColor(PANEL_INACTIVE_TITLE_FOREGROUND);
|
||||
if (titleInactive) {
|
||||
collector.addRule(`
|
||||
.monaco-workbench > .part.panel > .title > .panel-switcher-container > .monaco-action-bar .action-item .action-label {
|
||||
color: ${titleInactive};
|
||||
.monaco-workbench > .part.panel > .title > .panel-switcher-container > .monaco-action-bar .action-item:hover .action-label {
|
||||
color: ${titleActive} !important;
|
||||
border-bottom-color: ${titleActiveBorder} !important;
|
||||
}
|
||||
`);
|
||||
}
|
||||
@@ -344,7 +358,7 @@ registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => {
|
||||
if (focusBorderColor) {
|
||||
collector.addRule(`
|
||||
.monaco-workbench > .part.panel > .title > .panel-switcher-container > .monaco-action-bar .action-item:focus .action-label {
|
||||
color: ${titleActive};
|
||||
color: ${titleActive} !important;
|
||||
border-bottom-color: ${focusBorderColor} !important;
|
||||
border-bottom: 1px solid;
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
* 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 { QuickPickManyToggle, BackAction } from 'vs/workbench/browser/parts/quickinput/quickInput';
|
||||
import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
|
||||
@@ -15,4 +14,4 @@ import { inQuickOpenContext } from 'vs/workbench/browser/parts/quickopen/quickop
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule(QuickPickManyToggle);
|
||||
|
||||
const registry = Registry.as<IWorkbenchActionRegistry>(ActionExtensions.WorkbenchActions);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(BackAction, BackAction.ID, BackAction.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 } }, inQuickOpenContext, KeybindingWeight.WorkbenchContrib + 50), 'Back');
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(BackAction, BackAction.ID, BackAction.LABEL, { primary: 0, win: { primary: KeyMod.Alt | KeyCode.LeftArrow }, mac: { primary: KeyMod.WinCtrl | KeyCode.US_MINUS }, linux: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.US_MINUS } }, inQuickOpenContext, KeybindingWeight.WorkbenchContrib + 50), 'Back');
|
||||
|
||||
@@ -98,6 +98,10 @@
|
||||
margin: 0px 11px;
|
||||
}
|
||||
|
||||
.quick-input-progress.monaco-progress-container {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.quick-input-progress.monaco-progress-container,
|
||||
.quick-input-progress.monaco-progress-container .progress-bit {
|
||||
height: 2px;
|
||||
@@ -113,12 +117,22 @@
|
||||
}
|
||||
|
||||
.quick-input-list .quick-input-list-entry {
|
||||
box-sizing: border-box;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
height: 100%;
|
||||
padding: 0 6px;
|
||||
}
|
||||
|
||||
.quick-input-list .quick-input-list-entry.quick-input-list-separator-border {
|
||||
border-top-width: 1px;
|
||||
border-top-style: solid;
|
||||
}
|
||||
|
||||
.quick-input-list .monaco-list-row:first-child .quick-input-list-entry.quick-input-list-separator-border {
|
||||
border-top-style: none;
|
||||
}
|
||||
|
||||
.quick-input-list .quick-input-list-label {
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
@@ -164,8 +178,50 @@
|
||||
.quick-input-list .quick-input-list-label-meta {
|
||||
opacity: 0.7;
|
||||
line-height: normal;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.quick-input-list .monaco-highlighted-label .highlight {
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
.quick-input-list .quick-input-list-separator {
|
||||
margin-right: 18px;
|
||||
}
|
||||
|
||||
.quick-input-list .quick-input-list-entry.has-actions:hover .quick-input-list-separator,
|
||||
.quick-input-list .monaco-list-row.focused .quick-input-list-entry.has-actions .quick-input-list-separator {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
.quick-input-list .quick-input-list-entry-action-bar {
|
||||
display: none;
|
||||
flex: 0;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.quick-input-list .quick-input-list-entry-action-bar .action-label.icon {
|
||||
margin: 0;
|
||||
width: 19px;
|
||||
height: 100%;
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
|
||||
.quick-input-list .quick-input-list-entry-action-bar {
|
||||
margin-top: 1px;
|
||||
}
|
||||
|
||||
.quick-input-list .quick-input-list-entry-action-bar ul:first-child .action-label.icon {
|
||||
margin-left: 2px;
|
||||
}
|
||||
|
||||
.quick-input-list .quick-input-list-entry-action-bar ul:last-child .action-label.icon {
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.quick-input-list .quick-input-list-entry:hover .quick-input-list-entry-action-bar,
|
||||
.quick-input-list .monaco-list-row.focused .quick-input-list-entry-action-bar {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
@@ -3,11 +3,9 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import 'vs/css!./quickInput';
|
||||
import { Component } from 'vs/workbench/common/component';
|
||||
import { IQuickInputService, IQuickPickItem, IPickOptions, IInputOptions, IQuickNavigateConfiguration, IQuickPick, IQuickInput, IQuickInputButton, IInputBox } from 'vs/platform/quickinput/common/quickInput';
|
||||
import { IQuickInputService, IQuickPickItem, IPickOptions, IInputOptions, IQuickNavigateConfiguration, IQuickPick, IQuickInput, IQuickInputButton, IInputBox, IQuickPickItemButtonEvent, QuickPickInput, IQuickPickSeparator, IKeyMods } from 'vs/platform/quickinput/common/quickInput';
|
||||
import { IPartService } from 'vs/workbench/services/part/common/partService';
|
||||
import * as dom from 'vs/base/browser/dom';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
@@ -15,7 +13,6 @@ import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import { contrastBorder, widgetShadow } from 'vs/platform/theme/common/colorRegistry';
|
||||
import { SIDE_BAR_BACKGROUND, SIDE_BAR_FOREGROUND } from 'vs/workbench/common/theme';
|
||||
import { IQuickOpenService } from 'vs/platform/quickOpen/common/quickOpen';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { QuickInputList } from './quickInputList';
|
||||
import { QuickInputBox } from './quickInputBox';
|
||||
@@ -38,14 +35,20 @@ import { ICommandAndKeybindingRule, KeybindingWeight } from 'vs/platform/keybind
|
||||
import { inQuickOpenContext } from 'vs/workbench/browser/parts/quickopen/quickopen';
|
||||
import { ActionBar, ActionItem } from 'vs/base/browser/ui/actionbar/actionbar';
|
||||
import { Action } from 'vs/base/common/actions';
|
||||
import URI from 'vs/base/common/uri';
|
||||
import { IdGenerator } from 'vs/base/common/idGenerator';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
||||
import { equals } from 'vs/base/common/arrays';
|
||||
import { TimeoutTimer } from 'vs/base/common/async';
|
||||
import { getIconClass } from 'vs/workbench/browser/parts/quickinput/quickInputUtils';
|
||||
import { AccessibilitySupport } from 'vs/base/common/platform';
|
||||
import * as browser from 'vs/base/browser/browser';
|
||||
import { IEditorOptions } from 'vs/editor/common/config/editorOptions';
|
||||
import { IStorageService } from 'vs/platform/storage/common/storage';
|
||||
|
||||
const $ = dom.$;
|
||||
|
||||
type Writeable<T> = { -readonly [P in keyof T]: T[P] };
|
||||
|
||||
const backButton = {
|
||||
iconPath: {
|
||||
dark: URI.parse(require.toUrl('vs/workbench/browser/parts/quickinput/media/dark/arrow-left.svg')),
|
||||
@@ -70,9 +73,13 @@ interface QuickInputUI {
|
||||
onDidAccept: Event<void>;
|
||||
onDidTriggerButton: Event<IQuickInputButton>;
|
||||
ignoreFocusOut: boolean;
|
||||
keyMods: Writeable<IKeyMods>;
|
||||
isScreenReaderOptimized(): boolean;
|
||||
show(controller: QuickInput): void;
|
||||
setVisibilities(visibilities: Visibilities): void;
|
||||
setComboboxAccessibility(enabled: boolean): void;
|
||||
setEnabled(enabled: boolean): void;
|
||||
setContextKey(contextKey?: string): void;
|
||||
hide(): void;
|
||||
}
|
||||
|
||||
@@ -94,6 +101,7 @@ class QuickInput implements IQuickInput {
|
||||
private _totalSteps: number;
|
||||
protected visible = false;
|
||||
private _enabled = true;
|
||||
private _contextKey: string;
|
||||
private _busy = false;
|
||||
private _ignoreFocusOut = false;
|
||||
private _buttons: IQuickInputButton[] = [];
|
||||
@@ -148,6 +156,15 @@ class QuickInput implements IQuickInput {
|
||||
this.update();
|
||||
}
|
||||
|
||||
get contextKey() {
|
||||
return this._contextKey;
|
||||
}
|
||||
|
||||
set contextKey(contextKey: string) {
|
||||
this._contextKey = contextKey;
|
||||
this.update();
|
||||
}
|
||||
|
||||
get busy() {
|
||||
return this._busy;
|
||||
}
|
||||
@@ -182,7 +199,7 @@ class QuickInput implements IQuickInput {
|
||||
if (this.visible) {
|
||||
return;
|
||||
}
|
||||
this.disposables.push(
|
||||
this.visibleDisposables.push(
|
||||
this.ui.onDidTriggerButton(button => {
|
||||
if (this.buttons.indexOf(button) !== -1) {
|
||||
this.onDidTriggerButtonEmitter.fire(button);
|
||||
@@ -235,20 +252,21 @@ class QuickInput implements IQuickInput {
|
||||
this.ui.leftActionBar.clear();
|
||||
const leftButtons = this.buttons.filter(button => button === backButton);
|
||||
this.ui.leftActionBar.push(leftButtons.map((button, index) => {
|
||||
const action = new Action(`id-${index}`, '', getIconClass(button.iconPath), true, () => this.onDidTriggerButtonEmitter.fire(button));
|
||||
const action = new Action(`id-${index}`, '', button.iconClass || getIconClass(button.iconPath), true, () => this.onDidTriggerButtonEmitter.fire(button));
|
||||
action.tooltip = button.tooltip;
|
||||
return action;
|
||||
}), { icon: true, label: false });
|
||||
this.ui.rightActionBar.clear();
|
||||
const rightButtons = this.buttons.filter(button => button !== backButton);
|
||||
this.ui.rightActionBar.push(rightButtons.map((button, index) => {
|
||||
const action = new Action(`id-${index}`, '', getIconClass(button.iconPath), true, () => this.onDidTriggerButtonEmitter.fire(button));
|
||||
const action = new Action(`id-${index}`, '', button.iconClass || getIconClass(button.iconPath), true, () => this.onDidTriggerButtonEmitter.fire(button));
|
||||
action.tooltip = button.tooltip;
|
||||
return action;
|
||||
}), { icon: true, label: false });
|
||||
}
|
||||
this.ui.ignoreFocusOut = this.ignoreFocusOut;
|
||||
this.ui.setEnabled(this.enabled);
|
||||
this.ui.setContextKey(this.contextKey);
|
||||
}
|
||||
|
||||
private getTitle() {
|
||||
@@ -282,11 +300,13 @@ class QuickInput implements IQuickInput {
|
||||
|
||||
class QuickPick<T extends IQuickPickItem> extends QuickInput implements IQuickPick<T> {
|
||||
|
||||
private static INPUT_BOX_ARIA_LABEL = localize('quickInputBox.ariaLabel', "Type to narrow down results.");
|
||||
|
||||
private _value = '';
|
||||
private _placeholder;
|
||||
private onDidChangeValueEmitter = new Emitter<string>();
|
||||
private onDidAcceptEmitter = new Emitter<string>();
|
||||
private _items: T[] = [];
|
||||
private _items: (T | IQuickPickSeparator)[] = [];
|
||||
private itemsUpdated = false;
|
||||
private _canSelectMany = false;
|
||||
private _matchOnDescription = false;
|
||||
@@ -299,7 +319,9 @@ class QuickPick<T extends IQuickPickItem> extends QuickInput implements IQuickPi
|
||||
private selectedItemsUpdated = false;
|
||||
private selectedItemsToConfirm: T[] = [];
|
||||
private onDidChangeSelectionEmitter = new Emitter<T[]>();
|
||||
private quickNavigate = false;
|
||||
private onDidTriggerItemButtonEmitter = new Emitter<IQuickPickItemButtonEvent<T>>();
|
||||
|
||||
quickNavigate: IQuickNavigateConfiguration;
|
||||
|
||||
constructor(ui: QuickInputUI) {
|
||||
super(ui);
|
||||
@@ -308,6 +330,7 @@ class QuickPick<T extends IQuickPickItem> extends QuickInput implements IQuickPi
|
||||
this.onDidAcceptEmitter,
|
||||
this.onDidChangeActiveEmitter,
|
||||
this.onDidChangeSelectionEmitter,
|
||||
this.onDidTriggerItemButtonEmitter,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -337,7 +360,7 @@ class QuickPick<T extends IQuickPickItem> extends QuickInput implements IQuickPi
|
||||
return this._items;
|
||||
}
|
||||
|
||||
set items(items: T[]) {
|
||||
set items(items: (T | IQuickPickSeparator)[]) {
|
||||
this._items = items;
|
||||
this.itemsUpdated = true;
|
||||
this.update();
|
||||
@@ -392,8 +415,14 @@ class QuickPick<T extends IQuickPickItem> extends QuickInput implements IQuickPi
|
||||
this.update();
|
||||
}
|
||||
|
||||
get keyMods() {
|
||||
return this.ui.keyMods;
|
||||
}
|
||||
|
||||
onDidChangeSelection = this.onDidChangeSelectionEmitter.event;
|
||||
|
||||
onDidTriggerItemButton = this.onDidTriggerItemButtonEmitter.event;
|
||||
|
||||
show() {
|
||||
if (!this.visible) {
|
||||
this.visibleDisposables.push(
|
||||
@@ -403,7 +432,7 @@ class QuickPick<T extends IQuickPickItem> extends QuickInput implements IQuickPi
|
||||
}
|
||||
this._value = value;
|
||||
this.ui.list.filter(this.ui.inputBox.value);
|
||||
if (!this.canSelectMany) {
|
||||
if (!this.ui.isScreenReaderOptimized() && !this.canSelectMany) {
|
||||
this.ui.list.focus('First');
|
||||
}
|
||||
this.onDidChangeValueEmitter.fire(value);
|
||||
@@ -467,6 +496,9 @@ class QuickPick<T extends IQuickPickItem> extends QuickInput implements IQuickPi
|
||||
}),
|
||||
this.ui.list.onDidChangeSelection(selectedItems => {
|
||||
if (this.canSelectMany) {
|
||||
if (selectedItems.length) {
|
||||
this.ui.list.setSelectedElements([]);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (this.selectedItemsToConfirm !== this._selectedItems && equals(selectedItems, this._selectedItems, (a, b) => a === b)) {
|
||||
@@ -474,7 +506,9 @@ class QuickPick<T extends IQuickPickItem> extends QuickInput implements IQuickPi
|
||||
}
|
||||
this._selectedItems = selectedItems as T[];
|
||||
this.onDidChangeSelectionEmitter.fire(selectedItems as T[]);
|
||||
this.onDidAcceptEmitter.fire();
|
||||
if (selectedItems.length) {
|
||||
this.onDidAcceptEmitter.fire();
|
||||
}
|
||||
}),
|
||||
this.ui.list.onChangedCheckedElements(checkedItems => {
|
||||
if (!this.canSelectMany) {
|
||||
@@ -486,77 +520,24 @@ class QuickPick<T extends IQuickPickItem> extends QuickInput implements IQuickPi
|
||||
this._selectedItems = checkedItems as T[];
|
||||
this.onDidChangeSelectionEmitter.fire(checkedItems as T[]);
|
||||
}),
|
||||
this.ui.list.onButtonTriggered(event => this.onDidTriggerItemButtonEmitter.fire(event as IQuickPickItemButtonEvent<T>)),
|
||||
this.registerQuickNavigation()
|
||||
);
|
||||
}
|
||||
super.show();
|
||||
super.show(); // TODO: Why have show() bubble up while update() trickles down? (Could move setComboboxAccessibility() here.)
|
||||
}
|
||||
|
||||
protected update() {
|
||||
super.update();
|
||||
if (!this.visible) {
|
||||
return;
|
||||
}
|
||||
if (this.ui.inputBox.value !== this.value) {
|
||||
this.ui.inputBox.value = this.value;
|
||||
}
|
||||
if (this.ui.inputBox.placeholder !== (this.placeholder || '')) {
|
||||
this.ui.inputBox.placeholder = (this.placeholder || '');
|
||||
}
|
||||
if (this.itemsUpdated) {
|
||||
this.itemsUpdated = false;
|
||||
this.ui.list.setElements(this.items);
|
||||
this.ui.list.filter(this.ui.inputBox.value);
|
||||
this.ui.checkAll.checked = this.ui.list.getAllVisibleChecked();
|
||||
this.ui.visibleCount.setCount(this.ui.list.getVisibleCount());
|
||||
this.ui.count.setCount(this.ui.list.getCheckedCount());
|
||||
if (!this.canSelectMany) {
|
||||
this.ui.list.focus('First');
|
||||
private registerQuickNavigation() {
|
||||
return dom.addDisposableListener(this.ui.container, dom.EventType.KEY_UP, e => {
|
||||
if (this.canSelectMany || !this.quickNavigate) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (this.ui.container.classList.contains('show-checkboxes') !== this.canSelectMany) {
|
||||
if (this.canSelectMany) {
|
||||
this.ui.list.clearFocus();
|
||||
} else {
|
||||
this.ui.list.focus('First');
|
||||
}
|
||||
}
|
||||
if (this.activeItemsUpdated) {
|
||||
this.activeItemsUpdated = false;
|
||||
this.activeItemsToConfirm = this._activeItems;
|
||||
this.ui.list.setFocusedElements(this.activeItems);
|
||||
if (this.activeItemsToConfirm === this._activeItems) {
|
||||
this.activeItemsToConfirm = null;
|
||||
}
|
||||
}
|
||||
if (this.selectedItemsUpdated) {
|
||||
this.selectedItemsUpdated = false;
|
||||
this.selectedItemsToConfirm = this._selectedItems;
|
||||
if (this.canSelectMany) {
|
||||
this.ui.list.setCheckedElements(this.selectedItems);
|
||||
} else {
|
||||
this.ui.list.setSelectedElements(this.selectedItems);
|
||||
}
|
||||
if (this.selectedItemsToConfirm === this._selectedItems) {
|
||||
this.selectedItemsToConfirm = null;
|
||||
}
|
||||
}
|
||||
this.ui.list.matchOnDescription = this.matchOnDescription;
|
||||
this.ui.list.matchOnDetail = this.matchOnDetail;
|
||||
this.ui.setVisibilities(this.canSelectMany ? { title: !!this.title || !!this.step, checkAll: true, inputBox: true, visibleCount: true, count: true, ok: true, list: true } : { title: !!this.title || !!this.step, inputBox: true, visibleCount: true, list: true });
|
||||
}
|
||||
|
||||
configureQuickNavigate(quickNavigate: IQuickNavigateConfiguration) {
|
||||
if (this.canSelectMany || this.quickNavigate) {
|
||||
return;
|
||||
}
|
||||
this.quickNavigate = true;
|
||||
|
||||
this.disposables.push(dom.addDisposableListener(this.ui.container, dom.EventType.KEY_UP, (e: KeyboardEvent) => {
|
||||
const keyboardEvent: StandardKeyboardEvent = new StandardKeyboardEvent(e as KeyboardEvent);
|
||||
const keyboardEvent: StandardKeyboardEvent = new StandardKeyboardEvent(e);
|
||||
const keyCode = keyboardEvent.keyCode;
|
||||
|
||||
// Select element when keys are pressed that signal it
|
||||
const quickNavKeys = quickNavigate.keybindings;
|
||||
const quickNavKeys = this.quickNavigate.keybindings;
|
||||
const wasTriggerKeyPressed = keyCode === KeyCode.Enter || quickNavKeys.some(k => {
|
||||
const [firstPart, chordPart] = k.getParts();
|
||||
if (chordPart) {
|
||||
@@ -591,7 +572,63 @@ class QuickPick<T extends IQuickPickItem> extends QuickInput implements IQuickPi
|
||||
this.onDidChangeSelectionEmitter.fire(this.selectedItems);
|
||||
this.onDidAcceptEmitter.fire();
|
||||
}
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
protected update() {
|
||||
super.update();
|
||||
if (!this.visible) {
|
||||
return;
|
||||
}
|
||||
if (this.ui.inputBox.value !== this.value) {
|
||||
this.ui.inputBox.value = this.value;
|
||||
}
|
||||
if (this.ui.inputBox.placeholder !== (this.placeholder || '')) {
|
||||
this.ui.inputBox.placeholder = (this.placeholder || '');
|
||||
}
|
||||
if (this.itemsUpdated) {
|
||||
this.itemsUpdated = false;
|
||||
this.ui.list.setElements(this.items);
|
||||
this.ui.list.filter(this.ui.inputBox.value);
|
||||
this.ui.checkAll.checked = this.ui.list.getAllVisibleChecked();
|
||||
this.ui.visibleCount.setCount(this.ui.list.getVisibleCount());
|
||||
this.ui.count.setCount(this.ui.list.getCheckedCount());
|
||||
if (!this.ui.isScreenReaderOptimized() && !this.canSelectMany) {
|
||||
this.ui.list.focus('First');
|
||||
}
|
||||
}
|
||||
if (this.ui.container.classList.contains('show-checkboxes') !== !!this.canSelectMany) {
|
||||
if (this.canSelectMany) {
|
||||
this.ui.list.clearFocus();
|
||||
} else if (!this.ui.isScreenReaderOptimized()) {
|
||||
this.ui.list.focus('First');
|
||||
}
|
||||
}
|
||||
if (this.activeItemsUpdated) {
|
||||
this.activeItemsUpdated = false;
|
||||
this.activeItemsToConfirm = this._activeItems;
|
||||
this.ui.list.setFocusedElements(this.activeItems);
|
||||
if (this.activeItemsToConfirm === this._activeItems) {
|
||||
this.activeItemsToConfirm = null;
|
||||
}
|
||||
}
|
||||
if (this.selectedItemsUpdated) {
|
||||
this.selectedItemsUpdated = false;
|
||||
this.selectedItemsToConfirm = this._selectedItems;
|
||||
if (this.canSelectMany) {
|
||||
this.ui.list.setCheckedElements(this.selectedItems);
|
||||
} else {
|
||||
this.ui.list.setSelectedElements(this.selectedItems);
|
||||
}
|
||||
if (this.selectedItemsToConfirm === this._selectedItems) {
|
||||
this.selectedItemsToConfirm = null;
|
||||
}
|
||||
}
|
||||
this.ui.list.matchOnDescription = this.matchOnDescription;
|
||||
this.ui.list.matchOnDetail = this.matchOnDetail;
|
||||
this.ui.setComboboxAccessibility(true);
|
||||
this.ui.inputBox.setAttribute('aria-label', QuickPick.INPUT_BOX_ARIA_LABEL);
|
||||
this.ui.setVisibilities(this.canSelectMany ? { title: !!this.title || !!this.step, checkAll: true, inputBox: true, visibleCount: true, count: true, ok: true, list: true } : { title: !!this.title || !!this.step, inputBox: true, visibleCount: true, list: true });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -688,6 +725,7 @@ class InputBox extends QuickInput implements IInputBox {
|
||||
}),
|
||||
this.ui.onDidAccept(() => this.onDidAcceptEmitter.fire()),
|
||||
);
|
||||
this.valueSelectionUpdated = true;
|
||||
}
|
||||
super.show();
|
||||
}
|
||||
@@ -729,6 +767,7 @@ export class QuickInputService extends Component implements IQuickInputService {
|
||||
private static readonly ID = 'workbench.component.quickinput';
|
||||
private static readonly MAX_WIDTH = 600; // Max total width of quick open widget
|
||||
|
||||
private idPrefix = 'quickInput_'; // Constant since there is still only one.
|
||||
private layoutDimensions: dom.Dimension;
|
||||
private titleBar: HTMLElement;
|
||||
private filterContainer: HTMLElement;
|
||||
@@ -737,11 +776,14 @@ export class QuickInputService extends Component implements IQuickInputService {
|
||||
private okContainer: HTMLElement;
|
||||
private ok: Button;
|
||||
private ui: QuickInputUI;
|
||||
private comboboxAccessibility = false;
|
||||
private enabled = true;
|
||||
private inQuickOpenWidgets: Record<string, boolean> = {};
|
||||
private inQuickOpenContext: IContextKey<boolean>;
|
||||
private contexts: { [id: string]: IContextKey<boolean>; } = Object.create(null);
|
||||
private onDidAcceptEmitter = this._register(new Emitter<void>());
|
||||
private onDidTriggerButtonEmitter = this._register(new Emitter<IQuickInputButton>());
|
||||
private keyMods: Writeable<IKeyMods> = { ctrlCmd: false, alt: false };
|
||||
|
||||
private controller: QuickInput;
|
||||
|
||||
@@ -753,13 +795,15 @@ export class QuickInputService extends Component implements IQuickInputService {
|
||||
@IQuickOpenService private quickOpenService: IQuickOpenService,
|
||||
@IEditorGroupsService private editorGroupService: IEditorGroupsService,
|
||||
@IKeybindingService private keybindingService: IKeybindingService,
|
||||
@IContextKeyService contextKeyService: IContextKeyService,
|
||||
@IThemeService themeService: IThemeService
|
||||
@IContextKeyService private contextKeyService: IContextKeyService,
|
||||
@IThemeService themeService: IThemeService,
|
||||
@IStorageService storageService: IStorageService
|
||||
) {
|
||||
super(QuickInputService.ID, themeService);
|
||||
super(QuickInputService.ID, themeService, storageService);
|
||||
this.inQuickOpenContext = new RawContextKey<boolean>('inQuickOpen', false).bindTo(contextKeyService);
|
||||
this._register(this.quickOpenService.onShow(() => this.inQuickOpen('quickOpen', true)));
|
||||
this._register(this.quickOpenService.onHide(() => this.inQuickOpen('quickOpen', false)));
|
||||
this.registerKeyModsListeners();
|
||||
}
|
||||
|
||||
private inQuickOpen(widget: 'quickInput' | 'quickOpen', open: boolean) {
|
||||
@@ -779,13 +823,71 @@ export class QuickInputService extends Component implements IQuickInputService {
|
||||
}
|
||||
}
|
||||
|
||||
private setContextKey(id?: string) {
|
||||
let key: IContextKey<boolean>;
|
||||
if (id) {
|
||||
key = this.contexts[id];
|
||||
if (!key) {
|
||||
key = new RawContextKey<boolean>(id, false)
|
||||
.bindTo(this.contextKeyService);
|
||||
this.contexts[id] = key;
|
||||
}
|
||||
}
|
||||
|
||||
if (key && key.get()) {
|
||||
return; // already active context
|
||||
}
|
||||
|
||||
this.resetContextKeys();
|
||||
|
||||
if (key) {
|
||||
key.set(true);
|
||||
}
|
||||
}
|
||||
|
||||
private resetContextKeys() {
|
||||
for (const key in this.contexts) {
|
||||
if (this.contexts[key].get()) {
|
||||
this.contexts[key].reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private registerKeyModsListeners() {
|
||||
const workbench = this.partService.getWorkbenchElement();
|
||||
this._register(dom.addDisposableListener(workbench, dom.EventType.KEY_DOWN, (e: KeyboardEvent) => {
|
||||
const event = new StandardKeyboardEvent(e);
|
||||
switch (event.keyCode) {
|
||||
case KeyCode.Ctrl:
|
||||
case KeyCode.Meta:
|
||||
this.keyMods.ctrlCmd = true;
|
||||
break;
|
||||
case KeyCode.Alt:
|
||||
this.keyMods.alt = true;
|
||||
break;
|
||||
}
|
||||
}));
|
||||
this._register(dom.addDisposableListener(workbench, dom.EventType.KEY_UP, (e: KeyboardEvent) => {
|
||||
const event = new StandardKeyboardEvent(e);
|
||||
switch (event.keyCode) {
|
||||
case KeyCode.Ctrl:
|
||||
case KeyCode.Meta:
|
||||
this.keyMods.ctrlCmd = false;
|
||||
break;
|
||||
case KeyCode.Alt:
|
||||
this.keyMods.alt = false;
|
||||
break;
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
private create() {
|
||||
if (this.ui) {
|
||||
return;
|
||||
}
|
||||
|
||||
const workbench = document.getElementById(this.partService.getWorkbenchElementId());
|
||||
const container = dom.append(workbench, $('.quick-input-widget'));
|
||||
const workbench = this.partService.getWorkbenchElement();
|
||||
const container = dom.append(workbench, $('.quick-input-widget.show-file-icons'));
|
||||
container.tabIndex = -1;
|
||||
container.style.display = 'none';
|
||||
|
||||
@@ -816,9 +918,11 @@ export class QuickInputService extends Component implements IQuickInputService {
|
||||
this.filterContainer = dom.append(headerContainer, $('.quick-input-filter'));
|
||||
|
||||
const inputBox = this._register(new QuickInputBox(this.filterContainer));
|
||||
inputBox.setAttribute('aria-describedby', `${this.idPrefix}message`);
|
||||
|
||||
this.visibleCountContainer = dom.append(this.filterContainer, $('.quick-input-visible-count'));
|
||||
this.visibleCountContainer.setAttribute('aria-live', 'polite');
|
||||
this.visibleCountContainer.setAttribute('aria-atomic', 'true');
|
||||
const visibleCount = new CountBadge(this.visibleCountContainer, { countFormat: localize({ key: 'quickInput.visibleCount', comment: ['This tells the user how many items are shown in a list of items to select from. The items can be anything. Currently not visible, but read by screen readers.'] }, "{0} Results") });
|
||||
|
||||
this.countContainer = dom.append(this.filterContainer, $('.quick-input-count'));
|
||||
@@ -834,13 +938,13 @@ export class QuickInputService extends Component implements IQuickInputService {
|
||||
this.onDidAcceptEmitter.fire();
|
||||
}));
|
||||
|
||||
const message = dom.append(container, $('.quick-input-message'));
|
||||
const message = dom.append(container, $(`#${this.idPrefix}message.quick-input-message`));
|
||||
|
||||
const progressBar = new ProgressBar(container);
|
||||
dom.addClass(progressBar.getContainer(), 'quick-input-progress');
|
||||
this._register(attachProgressBarStyler(progressBar, this.themeService));
|
||||
|
||||
const list = this._register(this.instantiationService.createInstance(QuickInputList, container));
|
||||
const list = this._register(this.instantiationService.createInstance(QuickInputList, container, this.idPrefix + 'list'));
|
||||
this._register(list.onChangedAllVisibleChecked(checked => {
|
||||
checkAll.checked = checked;
|
||||
}));
|
||||
@@ -859,6 +963,11 @@ export class QuickInputService extends Component implements IQuickInputService {
|
||||
}
|
||||
}, 0);
|
||||
}));
|
||||
this._register(list.onDidChangeFocus(() => {
|
||||
if (this.comboboxAccessibility) {
|
||||
this.ui.inputBox.setAttribute('aria-activedescendant', this.ui.list.getActiveDescendant());
|
||||
}
|
||||
}));
|
||||
|
||||
const focusTracker = dom.trackFocus(container);
|
||||
this._register(focusTracker);
|
||||
@@ -919,21 +1028,33 @@ export class QuickInputService extends Component implements IQuickInputService {
|
||||
onDidAccept: this.onDidAcceptEmitter.event,
|
||||
onDidTriggerButton: this.onDidTriggerButtonEmitter.event,
|
||||
ignoreFocusOut: false,
|
||||
keyMods: this.keyMods,
|
||||
isScreenReaderOptimized: () => this.isScreenReaderOptimized(),
|
||||
show: controller => this.show(controller),
|
||||
hide: () => this.hide(),
|
||||
setVisibilities: visibilities => this.setVisibilities(visibilities),
|
||||
setComboboxAccessibility: enabled => this.setComboboxAccessibility(enabled),
|
||||
setEnabled: enabled => this.setEnabled(enabled),
|
||||
setContextKey: contextKey => this.setContextKey(contextKey),
|
||||
};
|
||||
this.updateStyles();
|
||||
}
|
||||
|
||||
pick<T extends IQuickPickItem, O extends IPickOptions<T>>(picks: TPromise<T[]>, options: O = <O>{}, token: CancellationToken = CancellationToken.None): TPromise<O extends { canPickMany: true } ? T[] : T> {
|
||||
return new TPromise<O extends { canPickMany: true } ? T[] : T>((resolve, reject) => {
|
||||
pick<T extends IQuickPickItem, O extends IPickOptions<T>>(picks: Promise<QuickPickInput<T>[]> | QuickPickInput<T>[], options: O = <O>{}, token: CancellationToken = CancellationToken.None): Promise<O extends { canPickMany: true } ? T[] : T> {
|
||||
return new Promise<O extends { canPickMany: true } ? T[] : T>((doResolve, reject) => {
|
||||
let resolve = (result: any) => {
|
||||
resolve = doResolve;
|
||||
if (options.onKeyMods) {
|
||||
options.onKeyMods(input.keyMods);
|
||||
}
|
||||
doResolve(result);
|
||||
};
|
||||
if (token.isCancellationRequested) {
|
||||
resolve(undefined);
|
||||
return;
|
||||
}
|
||||
const input = this.createQuickPick<T>();
|
||||
let activeItem: T;
|
||||
const disposables = [
|
||||
input,
|
||||
input.onDidAccept(() => {
|
||||
@@ -963,6 +1084,22 @@ export class QuickInputService extends Component implements IQuickInputService {
|
||||
}
|
||||
}
|
||||
}),
|
||||
input.onDidTriggerItemButton(event => options.onDidTriggerItemButton && options.onDidTriggerItemButton({
|
||||
...event,
|
||||
removeItem: () => {
|
||||
const index = input.items.indexOf(event.item);
|
||||
if (index !== -1) {
|
||||
const items = input.items.slice();
|
||||
items.splice(index, 1);
|
||||
input.items = items;
|
||||
}
|
||||
}
|
||||
})),
|
||||
input.onDidChangeValue(value => {
|
||||
if (activeItem && !value && (input.activeItems.length !== 1 || input.activeItems[0] !== activeItem)) {
|
||||
input.activeItems = [activeItem];
|
||||
}
|
||||
}),
|
||||
token.onCancellationRequested(() => {
|
||||
input.hide();
|
||||
}),
|
||||
@@ -976,38 +1113,45 @@ export class QuickInputService extends Component implements IQuickInputService {
|
||||
input.ignoreFocusOut = options.ignoreFocusLost;
|
||||
input.matchOnDescription = options.matchOnDescription;
|
||||
input.matchOnDetail = options.matchOnDetail;
|
||||
input.quickNavigate = options.quickNavigate;
|
||||
input.contextKey = options.contextKey;
|
||||
input.busy = true;
|
||||
picks.then(items => {
|
||||
input.busy = false;
|
||||
input.items = items;
|
||||
if (input.canSelectMany) {
|
||||
input.selectedItems = items.filter(item => item.picked);
|
||||
}
|
||||
});
|
||||
Promise.all([picks, options.activeItem])
|
||||
.then(([items, _activeItem]) => {
|
||||
activeItem = _activeItem;
|
||||
input.busy = false;
|
||||
input.items = items;
|
||||
if (input.canSelectMany) {
|
||||
input.selectedItems = items.filter(item => item.type !== 'separator' && item.picked) as T[];
|
||||
}
|
||||
if (activeItem) {
|
||||
input.activeItems = [activeItem];
|
||||
}
|
||||
});
|
||||
input.show();
|
||||
picks.then(null, err => {
|
||||
Promise.resolve(picks).then(null, err => {
|
||||
reject(err);
|
||||
input.hide();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
input(options: IInputOptions = {}, token: CancellationToken = CancellationToken.None): TPromise<string> {
|
||||
return new TPromise<string>((resolve, reject) => {
|
||||
input(options: IInputOptions = {}, token: CancellationToken = CancellationToken.None): Promise<string> {
|
||||
return new Promise<string>((resolve, reject) => {
|
||||
if (token.isCancellationRequested) {
|
||||
resolve(undefined);
|
||||
return;
|
||||
}
|
||||
const input = this.createInputBox();
|
||||
const validateInput = options.validateInput || (() => TPromise.as(undefined));
|
||||
const validateInput = options.validateInput || (() => <Thenable<undefined>>Promise.resolve(undefined));
|
||||
const onDidValueChange = debounceEvent(input.onDidChangeValue, (last, cur) => cur, 100);
|
||||
let validationValue = options.value || '';
|
||||
let validation = TPromise.wrap(validateInput(validationValue));
|
||||
let validation = Promise.resolve(validateInput(validationValue));
|
||||
const disposables = [
|
||||
input,
|
||||
onDidValueChange(value => {
|
||||
if (value !== validationValue) {
|
||||
validation = TPromise.wrap(validateInput(value));
|
||||
validation = Promise.resolve(validateInput(value));
|
||||
validationValue = value;
|
||||
}
|
||||
validation.then(result => {
|
||||
@@ -1019,7 +1163,7 @@ export class QuickInputService extends Component implements IQuickInputService {
|
||||
input.onDidAccept(() => {
|
||||
const value = input.value;
|
||||
if (value !== validationValue) {
|
||||
validation = TPromise.wrap(validateInput(value));
|
||||
validation = Promise.resolve(validateInput(value));
|
||||
validationValue = value;
|
||||
}
|
||||
validation.then(result => {
|
||||
@@ -1087,11 +1231,14 @@ export class QuickInputService extends Component implements IQuickInputService {
|
||||
this.ui.list.matchOnDescription = false;
|
||||
this.ui.list.matchOnDetail = false;
|
||||
this.ui.ignoreFocusOut = false;
|
||||
this.setComboboxAccessibility(false);
|
||||
this.ui.inputBox.removeAttribute('aria-label');
|
||||
|
||||
const keybinding = this.keybindingService.lookupKeybinding(BackAction.ID);
|
||||
backButton.tooltip = keybinding ? localize('quickInput.backWithKeybinding', "Back ({0})", keybinding.getLabel()) : localize('quickInput.back', "Back");
|
||||
|
||||
this.inQuickOpen('quickInput', true);
|
||||
this.resetContextKeys();
|
||||
|
||||
this.ui.container.style.display = '';
|
||||
this.updateLayout();
|
||||
@@ -1111,6 +1258,29 @@ export class QuickInputService extends Component implements IQuickInputService {
|
||||
this.updateLayout(); // TODO
|
||||
}
|
||||
|
||||
private setComboboxAccessibility(enabled: boolean) {
|
||||
if (enabled !== this.comboboxAccessibility) {
|
||||
this.comboboxAccessibility = enabled;
|
||||
if (this.comboboxAccessibility) {
|
||||
this.ui.inputBox.setAttribute('role', 'combobox');
|
||||
this.ui.inputBox.setAttribute('aria-haspopup', 'true');
|
||||
this.ui.inputBox.setAttribute('aria-autocomplete', 'list');
|
||||
this.ui.inputBox.setAttribute('aria-activedescendant', this.ui.list.getActiveDescendant());
|
||||
} else {
|
||||
this.ui.inputBox.removeAttribute('role');
|
||||
this.ui.inputBox.removeAttribute('aria-haspopup');
|
||||
this.ui.inputBox.removeAttribute('aria-autocomplete');
|
||||
this.ui.inputBox.removeAttribute('aria-activedescendant');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private isScreenReaderOptimized() {
|
||||
const detected = browser.getAccessibilitySupport() === AccessibilitySupport.Enabled;
|
||||
const config = this.configurationService.getValue<IEditorOptions>('editor').accessibilitySupport;
|
||||
return config === 'on' || (config === 'auto' && detected);
|
||||
}
|
||||
|
||||
private setEnabled(enabled: boolean) {
|
||||
if (enabled !== this.enabled) {
|
||||
this.enabled = enabled;
|
||||
@@ -1132,6 +1302,7 @@ export class QuickInputService extends Component implements IQuickInputService {
|
||||
if (controller) {
|
||||
this.controller = null;
|
||||
this.inQuickOpen('quickInput', false);
|
||||
this.resetContextKeys();
|
||||
this.ui.container.style.display = 'none';
|
||||
if (!focusLost) {
|
||||
this.editorGroupService.activeGroup.focus();
|
||||
@@ -1156,24 +1327,24 @@ export class QuickInputService extends Component implements IQuickInputService {
|
||||
if (this.isDisplayed() && this.ui.list.isDisplayed()) {
|
||||
this.ui.list.focus(next ? 'Next' : 'Previous');
|
||||
if (quickNavigate && this.controller instanceof QuickPick) {
|
||||
this.controller.configureQuickNavigate(quickNavigate);
|
||||
this.controller.quickNavigate = quickNavigate;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
accept() {
|
||||
this.onDidAcceptEmitter.fire();
|
||||
return TPromise.as(undefined);
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
|
||||
back() {
|
||||
this.onDidTriggerButtonEmitter.fire(this.backButton);
|
||||
return TPromise.as(undefined);
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
|
||||
cancel() {
|
||||
this.hide();
|
||||
return TPromise.as(undefined);
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
|
||||
layout(dimension: dom.Dimension): void {
|
||||
@@ -1201,16 +1372,16 @@ export class QuickInputService extends Component implements IQuickInputService {
|
||||
if (this.ui) {
|
||||
// TODO
|
||||
const titleColor = { dark: 'rgba(255, 255, 255, 0.105)', light: 'rgba(0,0,0,.06)', hc: 'black' }[theme.type];
|
||||
this.titleBar.style.backgroundColor = titleColor ? titleColor.toString() : undefined;
|
||||
this.titleBar.style.backgroundColor = titleColor ? titleColor.toString() : null;
|
||||
this.ui.inputBox.style(theme);
|
||||
const sideBarBackground = theme.getColor(SIDE_BAR_BACKGROUND);
|
||||
this.ui.container.style.backgroundColor = sideBarBackground ? sideBarBackground.toString() : undefined;
|
||||
this.ui.container.style.backgroundColor = sideBarBackground ? sideBarBackground.toString() : null;
|
||||
const sideBarForeground = theme.getColor(SIDE_BAR_FOREGROUND);
|
||||
this.ui.container.style.color = sideBarForeground ? sideBarForeground.toString() : undefined;
|
||||
this.ui.container.style.color = sideBarForeground ? sideBarForeground.toString() : null;
|
||||
const contrastBorderColor = theme.getColor(contrastBorder);
|
||||
this.ui.container.style.border = contrastBorderColor ? `1px solid ${contrastBorderColor}` : undefined;
|
||||
this.ui.container.style.border = contrastBorderColor ? `1px solid ${contrastBorderColor}` : null;
|
||||
const widgetShadowColor = theme.getColor(widgetShadow);
|
||||
this.ui.container.style.boxShadow = widgetShadowColor ? `0 5px 8px ${widgetShadowColor}` : undefined;
|
||||
this.ui.container.style.boxShadow = widgetShadowColor ? `0 5px 8px ${widgetShadowColor}` : null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1219,25 +1390,6 @@ export class QuickInputService extends Component implements IQuickInputService {
|
||||
}
|
||||
}
|
||||
|
||||
const iconPathToClass = {};
|
||||
const iconClassGenerator = new IdGenerator('quick-input-button-icon-');
|
||||
|
||||
function getIconClass(iconPath: { dark: URI; light?: URI; }) {
|
||||
let iconClass: string;
|
||||
|
||||
const key = iconPath.dark.toString();
|
||||
if (iconPathToClass[key]) {
|
||||
iconClass = iconPathToClass[key];
|
||||
} else {
|
||||
iconClass = iconClassGenerator.nextId();
|
||||
dom.createCSSRule(`.${iconClass}`, `background-image: url("${(iconPath.light || iconPath.dark).toString()}")`);
|
||||
dom.createCSSRule(`.vs-dark .${iconClass}, .hc-black .${iconClass}`, `background-image: url("${iconPath.dark.toString()}")`);
|
||||
iconPathToClass[key] = iconClass;
|
||||
}
|
||||
|
||||
return iconClass;
|
||||
}
|
||||
|
||||
export const QuickPickManyToggle: ICommandAndKeybindingRule = {
|
||||
id: 'workbench.action.quickPickManyToggle',
|
||||
weight: KeybindingWeight.WorkbenchContrib,
|
||||
@@ -1258,8 +1410,8 @@ export class BackAction extends Action {
|
||||
super(id, label);
|
||||
}
|
||||
|
||||
public run(): TPromise<any> {
|
||||
public run(): Promise<any> {
|
||||
this.quickInputService.back();
|
||||
return TPromise.as(null);
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,13 +3,10 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import 'vs/css!./quickInput';
|
||||
import * as dom from 'vs/base/browser/dom';
|
||||
import { InputBox, IRange, MessageType } from 'vs/base/browser/ui/inputbox/inputBox';
|
||||
import { localize } from 'vs/nls';
|
||||
import { inputBackground, inputForeground, inputBorder, inputValidationInfoBackground, inputValidationInfoBorder, inputValidationWarningBackground, inputValidationWarningBorder, inputValidationErrorBackground, inputValidationErrorBorder } from 'vs/platform/theme/common/colorRegistry';
|
||||
import { inputBackground, inputForeground, inputBorder, inputValidationInfoBackground, inputValidationInfoForeground, inputValidationInfoBorder, inputValidationWarningBackground, inputValidationWarningForeground, inputValidationWarningBorder, inputValidationErrorBackground, inputValidationErrorForeground, inputValidationErrorBorder } from 'vs/platform/theme/common/colorRegistry';
|
||||
import { ITheme } from 'vs/platform/theme/common/themeService';
|
||||
import { dispose, IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
|
||||
@@ -17,8 +14,6 @@ import Severity from 'vs/base/common/severity';
|
||||
|
||||
const $ = dom.$;
|
||||
|
||||
const DEFAULT_INPUT_ARIA_LABEL = localize('quickInputBox.ariaLabel', "Type to narrow down results.");
|
||||
|
||||
export class QuickInputBox {
|
||||
|
||||
private container: HTMLElement;
|
||||
@@ -29,16 +24,8 @@ export class QuickInputBox {
|
||||
private parent: HTMLElement
|
||||
) {
|
||||
this.container = dom.append(this.parent, $('.quick-input-box'));
|
||||
this.inputBox = new InputBox(this.container, null, {
|
||||
ariaLabel: DEFAULT_INPUT_ARIA_LABEL
|
||||
});
|
||||
this.inputBox = new InputBox(this.container, null);
|
||||
this.disposables.push(this.inputBox);
|
||||
|
||||
// ARIA
|
||||
const inputElement = this.inputBox.inputElement;
|
||||
inputElement.setAttribute('role', 'combobox');
|
||||
inputElement.setAttribute('aria-haspopup', 'false');
|
||||
inputElement.setAttribute('aria-autocomplete', 'list');
|
||||
}
|
||||
|
||||
onKeyDown = (handler: (event: StandardKeyboardEvent) => void): IDisposable => {
|
||||
@@ -59,7 +46,7 @@ export class QuickInputBox {
|
||||
this.inputBox.value = value;
|
||||
}
|
||||
|
||||
select(range: IRange = null): void {
|
||||
select(range: IRange | null = null): void {
|
||||
this.inputBox.select(range);
|
||||
}
|
||||
|
||||
@@ -87,6 +74,14 @@ export class QuickInputBox {
|
||||
this.inputBox.setEnabled(enabled);
|
||||
}
|
||||
|
||||
setAttribute(name: string, value: string) {
|
||||
this.inputBox.inputElement.setAttribute(name, value);
|
||||
}
|
||||
|
||||
removeAttribute(name: string) {
|
||||
this.inputBox.inputElement.removeAttribute(name);
|
||||
}
|
||||
|
||||
showDecoration(decoration: Severity): void {
|
||||
if (decoration === Severity.Ignore) {
|
||||
this.inputBox.hideMessage();
|
||||
@@ -109,10 +104,13 @@ export class QuickInputBox {
|
||||
inputBackground: theme.getColor(inputBackground),
|
||||
inputBorder: theme.getColor(inputBorder),
|
||||
inputValidationInfoBackground: theme.getColor(inputValidationInfoBackground),
|
||||
inputValidationInfoForeground: theme.getColor(inputValidationInfoForeground),
|
||||
inputValidationInfoBorder: theme.getColor(inputValidationInfoBorder),
|
||||
inputValidationWarningBackground: theme.getColor(inputValidationWarningBackground),
|
||||
inputValidationWarningForeground: theme.getColor(inputValidationWarningForeground),
|
||||
inputValidationWarningBorder: theme.getColor(inputValidationWarningBorder),
|
||||
inputValidationErrorBackground: theme.getColor(inputValidationErrorBackground),
|
||||
inputValidationErrorForeground: theme.getColor(inputValidationErrorForeground),
|
||||
inputValidationErrorBorder: theme.getColor(inputValidationErrorBorder),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -3,15 +3,13 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import 'vs/css!./quickInput';
|
||||
import { IVirtualDelegate, IRenderer } from 'vs/base/browser/ui/list/list';
|
||||
import { IListVirtualDelegate, IListRenderer } from 'vs/base/browser/ui/list/list';
|
||||
import * as dom from 'vs/base/browser/dom';
|
||||
import { dispose, IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { WorkbenchList } from 'vs/platform/list/browser/listService';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IQuickPickItem } from 'vs/platform/quickinput/common/quickInput';
|
||||
import { IQuickPickItem, IQuickPickItemButtonEvent, IQuickPickSeparator } from 'vs/platform/quickinput/common/quickInput';
|
||||
import { IMatch } from 'vs/base/common/filters';
|
||||
import { matchesFuzzyOcticonAware, parseOcticons } from 'vs/base/common/octicon';
|
||||
import { compareAnything } from 'vs/base/common/comparers';
|
||||
@@ -24,21 +22,32 @@ import { HighlightedLabel } from 'vs/base/browser/ui/highlightedlabel/highlighte
|
||||
import { memoize } from 'vs/base/common/decorators';
|
||||
import { range } from 'vs/base/common/arrays';
|
||||
import * as platform from 'vs/base/common/platform';
|
||||
import { listFocusBackground } from 'vs/platform/theme/common/colorRegistry';
|
||||
import { listFocusBackground, pickerGroupBorder, pickerGroupForeground } from 'vs/platform/theme/common/colorRegistry';
|
||||
import { registerThemingParticipant } from 'vs/platform/theme/common/themeService';
|
||||
import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar';
|
||||
import { Action } from 'vs/base/common/actions';
|
||||
import { getIconClass } from 'vs/workbench/browser/parts/quickinput/quickInputUtils';
|
||||
import { IListOptions } from 'vs/base/browser/ui/list/listWidget';
|
||||
|
||||
const $ = dom.$;
|
||||
|
||||
interface IListElement {
|
||||
index: number;
|
||||
item: IQuickPickItem;
|
||||
saneLabel: string;
|
||||
saneDescription?: string;
|
||||
saneDetail?: string;
|
||||
checked: boolean;
|
||||
separator: IQuickPickSeparator;
|
||||
fireButtonTriggered: (event: IQuickPickItemButtonEvent<IQuickPickItem>) => void;
|
||||
}
|
||||
|
||||
class ListElement implements IListElement {
|
||||
index: number;
|
||||
item: IQuickPickItem;
|
||||
shouldAlwaysShow = false;
|
||||
saneLabel: string;
|
||||
saneDescription?: string;
|
||||
saneDetail?: string;
|
||||
hidden = false;
|
||||
private _onChecked = new Emitter<boolean>();
|
||||
onChecked = this._onChecked.event;
|
||||
@@ -52,9 +61,11 @@ class ListElement implements IListElement {
|
||||
this._onChecked.fire(value);
|
||||
}
|
||||
}
|
||||
separator: IQuickPickSeparator;
|
||||
labelHighlights?: IMatch[];
|
||||
descriptionHighlights?: IMatch[];
|
||||
detailHighlights?: IMatch[];
|
||||
fireButtonTriggered: (event: IQuickPickItemButtonEvent<IQuickPickItem>) => void;
|
||||
|
||||
constructor(init: IListElement) {
|
||||
assign(this, init);
|
||||
@@ -62,15 +73,18 @@ class ListElement implements IListElement {
|
||||
}
|
||||
|
||||
interface IListElementTemplateData {
|
||||
entry: HTMLDivElement;
|
||||
checkbox: HTMLInputElement;
|
||||
label: IconLabel;
|
||||
detail: HighlightedLabel;
|
||||
separator: HTMLDivElement;
|
||||
actionBar: ActionBar;
|
||||
element: ListElement;
|
||||
toDisposeElement: IDisposable[];
|
||||
toDisposeTemplate: IDisposable[];
|
||||
}
|
||||
|
||||
class ListElementRenderer implements IRenderer<ListElement, IListElementTemplateData> {
|
||||
class ListElementRenderer implements IListRenderer<ListElement, IListElementTemplateData> {
|
||||
|
||||
static readonly ID = 'listelement';
|
||||
|
||||
@@ -83,10 +97,10 @@ class ListElementRenderer implements IRenderer<ListElement, IListElementTemplate
|
||||
data.toDisposeElement = [];
|
||||
data.toDisposeTemplate = [];
|
||||
|
||||
const entry = dom.append(container, $('.quick-input-list-entry'));
|
||||
data.entry = dom.append(container, $('.quick-input-list-entry'));
|
||||
|
||||
// Checkbox
|
||||
const label = dom.append(entry, $('label.quick-input-list-label'));
|
||||
const label = dom.append(data.entry, $('label.quick-input-list-label'));
|
||||
data.checkbox = <HTMLInputElement>dom.append(label, $('input.quick-input-list-checkbox'));
|
||||
data.checkbox.type = 'checkbox';
|
||||
data.toDisposeTemplate.push(dom.addStandardDisposableListener(data.checkbox, dom.EventType.CHANGE, e => {
|
||||
@@ -103,7 +117,15 @@ class ListElementRenderer implements IRenderer<ListElement, IListElementTemplate
|
||||
|
||||
// Detail
|
||||
const detailContainer = dom.append(row2, $('.quick-input-list-label-meta'));
|
||||
data.detail = new HighlightedLabel(detailContainer);
|
||||
data.detail = new HighlightedLabel(detailContainer, true);
|
||||
|
||||
// Separator
|
||||
data.separator = dom.append(data.entry, $('.quick-input-list-separator'));
|
||||
|
||||
// Actions
|
||||
data.actionBar = new ActionBar(data.entry);
|
||||
data.actionBar.domNode.classList.add('quick-input-list-entry-action-bar');
|
||||
data.toDisposeTemplate.push(data.actionBar);
|
||||
|
||||
return data;
|
||||
}
|
||||
@@ -119,16 +141,56 @@ class ListElementRenderer implements IRenderer<ListElement, IListElementTemplate
|
||||
// Label
|
||||
const options: IIconLabelValueOptions = Object.create(null);
|
||||
options.matches = labelHighlights || [];
|
||||
options.descriptionTitle = element.item.description;
|
||||
options.descriptionTitle = element.saneDescription;
|
||||
options.descriptionMatches = descriptionHighlights || [];
|
||||
data.label.setValue(element.item.label, element.item.description, options);
|
||||
options.extraClasses = element.item.iconClasses;
|
||||
data.label.setValue(element.saneLabel, element.saneDescription, options);
|
||||
|
||||
// Meta
|
||||
data.detail.set(element.item.detail, detailHighlights);
|
||||
data.detail.set(element.saneDetail, detailHighlights);
|
||||
|
||||
// ARIA label
|
||||
data.entry.setAttribute('aria-label', [element.saneLabel, element.saneDescription, element.saneDetail]
|
||||
.map(s => s && parseOcticons(s).text)
|
||||
.filter(s => !!s)
|
||||
.join(', '));
|
||||
|
||||
// Separator
|
||||
if (element.separator && element.separator.label) {
|
||||
data.separator.textContent = element.separator.label;
|
||||
data.separator.style.display = null;
|
||||
} else {
|
||||
data.separator.style.display = 'none';
|
||||
}
|
||||
if (element.separator) {
|
||||
dom.addClass(data.entry, 'quick-input-list-separator-border');
|
||||
} else {
|
||||
dom.removeClass(data.entry, 'quick-input-list-separator-border');
|
||||
}
|
||||
|
||||
// Actions
|
||||
data.actionBar.clear();
|
||||
const buttons = element.item.buttons;
|
||||
if (buttons && buttons.length) {
|
||||
data.actionBar.push(buttons.map((button, index) => {
|
||||
const action = new Action(`id-${index}`, '', button.iconClass || getIconClass(button.iconPath), true, () => {
|
||||
element.fireButtonTriggered({
|
||||
button,
|
||||
item: element.item
|
||||
});
|
||||
return null;
|
||||
});
|
||||
action.tooltip = button.tooltip;
|
||||
return action;
|
||||
}), { icon: true, label: false });
|
||||
dom.addClass(data.entry, 'has-actions');
|
||||
} else {
|
||||
dom.removeClass(data.entry, 'has-actions');
|
||||
}
|
||||
}
|
||||
|
||||
disposeElement(): void {
|
||||
// noop
|
||||
disposeElement(element: ListElement, index: number, data: IListElementTemplateData): void {
|
||||
data.toDisposeElement = dispose(data.toDisposeElement);
|
||||
}
|
||||
|
||||
disposeTemplate(data: IListElementTemplateData): void {
|
||||
@@ -137,10 +199,10 @@ class ListElementRenderer implements IRenderer<ListElement, IListElementTemplate
|
||||
}
|
||||
}
|
||||
|
||||
class ListElementDelegate implements IVirtualDelegate<ListElement> {
|
||||
class ListElementDelegate implements IListVirtualDelegate<ListElement> {
|
||||
|
||||
getHeight(element: ListElement): number {
|
||||
return element.item.detail ? 44 : 22;
|
||||
return element.saneDetail ? 44 : 22;
|
||||
}
|
||||
|
||||
getTemplateId(element: ListElement): string {
|
||||
@@ -150,8 +212,10 @@ class ListElementDelegate implements IVirtualDelegate<ListElement> {
|
||||
|
||||
export class QuickInputList {
|
||||
|
||||
readonly id: string;
|
||||
private container: HTMLElement;
|
||||
private list: WorkbenchList<ListElement>;
|
||||
private inputElements: (IQuickPickItem | IQuickPickSeparator)[];
|
||||
private elements: ListElement[] = [];
|
||||
private elementsToIndexes = new Map<IQuickPickItem, number>();
|
||||
matchOnDescription = false;
|
||||
@@ -164,6 +228,8 @@ export class QuickInputList {
|
||||
onChangedVisibleCount: Event<number> = this._onChangedVisibleCount.event;
|
||||
private _onChangedCheckedElements = new Emitter<IQuickPickItem[]>();
|
||||
onChangedCheckedElements: Event<IQuickPickItem[]> = this._onChangedCheckedElements.event;
|
||||
private _onButtonTriggered = new Emitter<IQuickPickItemButtonEvent<IQuickPickItem>>();
|
||||
onButtonTriggered = this._onButtonTriggered.event;
|
||||
private _onLeave = new Emitter<void>();
|
||||
onLeave: Event<void> = this._onLeave.event;
|
||||
private _fireCheckedEvents = true;
|
||||
@@ -172,14 +238,19 @@ export class QuickInputList {
|
||||
|
||||
constructor(
|
||||
private parent: HTMLElement,
|
||||
id: string,
|
||||
@IInstantiationService private instantiationService: IInstantiationService
|
||||
) {
|
||||
this.id = id;
|
||||
this.container = dom.append(this.parent, $('.quick-input-list'));
|
||||
const delegate = new ListElementDelegate();
|
||||
this.list = this.instantiationService.createInstance(WorkbenchList, this.container, delegate, [new ListElementRenderer()], {
|
||||
identityProvider: element => element.label,
|
||||
identityProvider: { getId: element => element.saneLabel },
|
||||
openController: { shouldOpen: () => false }, // Workaround #58124
|
||||
setRowLineHeight: false,
|
||||
multipleSelectionSupport: false
|
||||
}) as WorkbenchList<ListElement>;
|
||||
} as IListOptions<ListElement>) as WorkbenchList<ListElement>;
|
||||
this.list.getHTMLElement().id = id;
|
||||
this.disposables.push(this.list);
|
||||
this.disposables.push(this.list.onKeyDown(e => {
|
||||
const event = new StandardKeyboardEvent(e);
|
||||
@@ -208,16 +279,17 @@ export class QuickInputList {
|
||||
break;
|
||||
}
|
||||
}));
|
||||
this.disposables.push(this.list.onMouseDown(e => {
|
||||
if (e.browserEvent.button !== 2) {
|
||||
// Works around / fixes #64350.
|
||||
e.browserEvent.preventDefault();
|
||||
}
|
||||
}));
|
||||
this.disposables.push(dom.addDisposableListener(this.container, dom.EventType.CLICK, e => {
|
||||
if (e.x || e.y) { // Avoid 'click' triggered by 'space' on checkbox.
|
||||
this._onLeave.fire();
|
||||
}
|
||||
}));
|
||||
this.disposables.push(this.list.onSelectionChange(e => {
|
||||
if (e.elements.length) {
|
||||
this.list.setSelection([]);
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
@memoize
|
||||
@@ -284,21 +356,34 @@ export class QuickInputList {
|
||||
}
|
||||
}
|
||||
|
||||
setElements(elements: IQuickPickItem[]): void {
|
||||
setElements(inputElements: (IQuickPickItem | IQuickPickSeparator)[]): void {
|
||||
this.elementDisposables = dispose(this.elementDisposables);
|
||||
this.elements = elements.map((item, index) => new ListElement({
|
||||
index,
|
||||
item,
|
||||
checked: false
|
||||
}));
|
||||
const fireButtonTriggered = (event: IQuickPickItemButtonEvent<IQuickPickItem>) => this.fireButtonTriggered(event);
|
||||
this.inputElements = inputElements;
|
||||
this.elements = inputElements.reduce((result, item, index) => {
|
||||
if (item.type !== 'separator') {
|
||||
const previous = index && inputElements[index - 1];
|
||||
result.push(new ListElement({
|
||||
index,
|
||||
item,
|
||||
saneLabel: item.label && item.label.replace(/\r?\n/g, ' '),
|
||||
saneDescription: item.description && item.description.replace(/\r?\n/g, ' '),
|
||||
saneDetail: item.detail && item.detail.replace(/\r?\n/g, ' '),
|
||||
checked: false,
|
||||
separator: previous && previous.type === 'separator' ? previous : undefined,
|
||||
fireButtonTriggered
|
||||
}));
|
||||
}
|
||||
return result;
|
||||
}, [] as ListElement[]);
|
||||
this.elementDisposables.push(...this.elements.map(element => element.onChecked(() => this.fireCheckedEvents())));
|
||||
|
||||
this.elementsToIndexes = this.elements.reduce((map, element, index) => {
|
||||
map.set(element.item, index);
|
||||
return map;
|
||||
}, new Map<IQuickPickItem, number>());
|
||||
this.list.splice(0, this.list.length); // Clear focus and selection first, sending the events when the list is empty.
|
||||
this.list.splice(0, this.list.length, this.elements);
|
||||
this.list.setFocus([]);
|
||||
this._onChangedVisibleCount.fire(this.elements.length);
|
||||
}
|
||||
|
||||
@@ -313,6 +398,10 @@ export class QuickInputList {
|
||||
.map(item => this.elementsToIndexes.get(item)));
|
||||
}
|
||||
|
||||
getActiveDescendant() {
|
||||
return this.list.getHTMLElement().getAttribute('aria-activedescendant');
|
||||
}
|
||||
|
||||
getSelectedElements() {
|
||||
return this.list.getSelectedElements()
|
||||
.map(e => e.item);
|
||||
@@ -387,17 +476,19 @@ export class QuickInputList {
|
||||
element.descriptionHighlights = undefined;
|
||||
element.detailHighlights = undefined;
|
||||
element.hidden = false;
|
||||
const previous = element.index && this.inputElements[element.index - 1];
|
||||
element.separator = previous && previous.type === 'separator' ? previous : undefined;
|
||||
});
|
||||
}
|
||||
|
||||
// Filter by value (since we support octicons, use octicon aware fuzzy matching)
|
||||
else {
|
||||
this.elements.forEach(element => {
|
||||
const labelHighlights = matchesFuzzyOcticonAware(query, parseOcticons(element.item.label));
|
||||
const descriptionHighlights = this.matchOnDescription ? matchesFuzzyOcticonAware(query, parseOcticons(element.item.description || '')) : undefined;
|
||||
const detailHighlights = this.matchOnDetail ? matchesFuzzyOcticonAware(query, parseOcticons(element.item.detail || '')) : undefined;
|
||||
const labelHighlights = matchesFuzzyOcticonAware(query, parseOcticons(element.saneLabel));
|
||||
const descriptionHighlights = this.matchOnDescription ? matchesFuzzyOcticonAware(query, parseOcticons(element.saneDescription || '')) : undefined;
|
||||
const detailHighlights = this.matchOnDetail ? matchesFuzzyOcticonAware(query, parseOcticons(element.saneDetail || '')) : undefined;
|
||||
|
||||
if (element.shouldAlwaysShow || labelHighlights || descriptionHighlights || detailHighlights) {
|
||||
if (labelHighlights || descriptionHighlights || detailHighlights) {
|
||||
element.labelHighlights = labelHighlights;
|
||||
element.descriptionHighlights = descriptionHighlights;
|
||||
element.detailHighlights = detailHighlights;
|
||||
@@ -406,21 +497,21 @@ export class QuickInputList {
|
||||
element.labelHighlights = undefined;
|
||||
element.descriptionHighlights = undefined;
|
||||
element.detailHighlights = undefined;
|
||||
element.hidden = true;
|
||||
element.hidden = !element.item.alwaysShow;
|
||||
}
|
||||
element.separator = undefined;
|
||||
});
|
||||
}
|
||||
|
||||
const shownElements = this.elements.filter(element => !element.hidden);
|
||||
|
||||
// Sort by value
|
||||
const normalizedSearchValue = query.toLowerCase();
|
||||
shownElements.sort((a, b) => {
|
||||
if (!query) {
|
||||
return a.index - b.index; // restore natural order
|
||||
}
|
||||
return compareEntries(a, b, normalizedSearchValue);
|
||||
});
|
||||
if (query) {
|
||||
const normalizedSearchValue = query.toLowerCase();
|
||||
shownElements.sort((a, b) => {
|
||||
return compareEntries(a, b, normalizedSearchValue);
|
||||
});
|
||||
}
|
||||
|
||||
this.elementsToIndexes = shownElements.reduce((map, element, index) => {
|
||||
map.set(element.item, index);
|
||||
@@ -468,6 +559,10 @@ export class QuickInputList {
|
||||
this._onChangedCheckedElements.fire(this.getCheckedElements());
|
||||
}
|
||||
}
|
||||
|
||||
private fireButtonTriggered(event: IQuickPickItemButtonEvent<IQuickPickItem>) {
|
||||
this._onButtonTriggered.fire(event);
|
||||
}
|
||||
}
|
||||
|
||||
function compareEntries(elementA: ListElement, elementB: ListElement, lookFor: string): number {
|
||||
@@ -482,7 +577,7 @@ function compareEntries(elementA: ListElement, elementB: ListElement, lookFor: s
|
||||
return 1;
|
||||
}
|
||||
|
||||
return compareAnything(elementA.item.label, elementB.item.label, lookFor);
|
||||
return compareAnything(elementA.saneLabel, elementB.saneLabel, lookFor);
|
||||
}
|
||||
|
||||
registerThemingParticipant((theme, collector) => {
|
||||
@@ -492,4 +587,12 @@ registerThemingParticipant((theme, collector) => {
|
||||
collector.addRule(`.quick-input-list .monaco-list .monaco-list-row.focused { background-color: ${listInactiveFocusBackground}; }`);
|
||||
collector.addRule(`.quick-input-list .monaco-list .monaco-list-row.focused:hover { background-color: ${listInactiveFocusBackground}; }`);
|
||||
}
|
||||
const pickerGroupBorderColor = theme.getColor(pickerGroupBorder);
|
||||
if (pickerGroupBorderColor) {
|
||||
collector.addRule(`.quick-input-list .quick-input-list-entry { border-top-color: ${pickerGroupBorderColor}; }`);
|
||||
}
|
||||
const pickerGroupForegroundColor = theme.getColor(pickerGroupForeground);
|
||||
if (pickerGroupForegroundColor) {
|
||||
collector.addRule(`.quick-input-list .quick-input-list-separator { color: ${pickerGroupForegroundColor}; }`);
|
||||
}
|
||||
});
|
||||
|
||||
28
src/vs/workbench/browser/parts/quickinput/quickInputUtils.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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!./quickInput';
|
||||
import * as dom from 'vs/base/browser/dom';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { IdGenerator } from 'vs/base/common/idGenerator';
|
||||
|
||||
const iconPathToClass = {};
|
||||
const iconClassGenerator = new IdGenerator('quick-input-button-icon-');
|
||||
|
||||
export function getIconClass(iconPath: { dark: URI; light?: URI; }) {
|
||||
let iconClass: string;
|
||||
|
||||
const key = iconPath.dark.toString();
|
||||
if (iconPathToClass[key]) {
|
||||
iconClass = iconPathToClass[key];
|
||||
} else {
|
||||
iconClass = iconClassGenerator.nextId();
|
||||
dom.createCSSRule(`.${iconClass}`, `background-image: url("${(iconPath.light || iconPath.dark).toString()}")`);
|
||||
dom.createCSSRule(`.vs-dark .${iconClass}, .hc-black .${iconClass}`, `background-image: url("${iconPath.dark.toString()}")`);
|
||||
iconPathToClass[key] = iconClass;
|
||||
}
|
||||
|
||||
return iconClass;
|
||||
}
|
||||
@@ -2,7 +2,6 @@
|
||||
* 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 { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { IQuickOpenService } from 'vs/platform/quickOpen/common/quickOpen';
|
||||
@@ -31,7 +30,7 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
id: 'workbench.action.acceptSelectedQuickOpenItem',
|
||||
weight: KeybindingWeight.WorkbenchContrib,
|
||||
when: inQuickOpenContext,
|
||||
primary: null,
|
||||
primary: 0,
|
||||
handler: accessor => {
|
||||
const quickOpenService = accessor.get(IQuickOpenService);
|
||||
quickOpenService.accept();
|
||||
@@ -44,7 +43,7 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
id: 'workbench.action.focusQuickOpen',
|
||||
weight: KeybindingWeight.WorkbenchContrib,
|
||||
when: inQuickOpenContext,
|
||||
primary: null,
|
||||
primary: 0,
|
||||
handler: accessor => {
|
||||
const quickOpenService = accessor.get(IQuickOpenService);
|
||||
quickOpenService.focus();
|
||||
@@ -67,11 +66,11 @@ KeybindingsRegistry.registerKeybindingRule({
|
||||
});
|
||||
|
||||
MenuRegistry.appendMenuItem(MenuId.CommandPalette, {
|
||||
command: { id: QUICKOPEN_ACTION_ID, title: QUICKOPEN_ACION_LABEL }
|
||||
command: { id: QUICKOPEN_ACTION_ID, title: { value: QUICKOPEN_ACION_LABEL, original: 'Go to File...' } }
|
||||
});
|
||||
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(QuickOpenSelectNextAction, QuickOpenSelectNextAction.ID, QuickOpenSelectNextAction.LABEL, { primary: null, mac: { primary: KeyMod.WinCtrl | KeyCode.KEY_N } }, inQuickOpenContext, KeybindingWeight.WorkbenchContrib + 50), 'Select Next in Quick Open');
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(QuickOpenSelectPreviousAction, QuickOpenSelectPreviousAction.ID, QuickOpenSelectPreviousAction.LABEL, { primary: null, mac: { primary: KeyMod.WinCtrl | KeyCode.KEY_P } }, inQuickOpenContext, KeybindingWeight.WorkbenchContrib + 50), 'Select Previous in Quick Open');
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(QuickOpenSelectNextAction, QuickOpenSelectNextAction.ID, QuickOpenSelectNextAction.LABEL, { primary: 0, mac: { primary: KeyMod.WinCtrl | KeyCode.KEY_N } }, inQuickOpenContext, KeybindingWeight.WorkbenchContrib + 50), 'Select Next in Quick Open');
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(QuickOpenSelectPreviousAction, QuickOpenSelectPreviousAction.ID, QuickOpenSelectPreviousAction.LABEL, { primary: 0, mac: { primary: KeyMod.WinCtrl | KeyCode.KEY_P } }, inQuickOpenContext, KeybindingWeight.WorkbenchContrib + 50), 'Select Previous in Quick Open');
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(QuickOpenNavigateNextAction, QuickOpenNavigateNextAction.ID, QuickOpenNavigateNextAction.LABEL), 'Navigate Next in Quick Open');
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(QuickOpenNavigatePreviousAction, QuickOpenNavigatePreviousAction.ID, QuickOpenNavigatePreviousAction.LABEL), 'Navigate Previous in Quick Open');
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(RemoveFromEditorHistoryAction, RemoveFromEditorHistoryAction.ID, RemoveFromEditorHistoryAction.LABEL), 'Remove From History');
|
||||
|
||||
@@ -3,9 +3,6 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import * as nls from 'vs/nls';
|
||||
import { Action } from 'vs/base/common/actions';
|
||||
import { IQuickOpenService } from 'vs/platform/quickOpen/common/quickOpen';
|
||||
@@ -22,19 +19,19 @@ export const defaultQuickOpenContext = ContextKeyExpr.and(inQuickOpenContext, Co
|
||||
export const QUICKOPEN_ACTION_ID = 'workbench.action.quickOpen';
|
||||
export const QUICKOPEN_ACION_LABEL = nls.localize('quickOpen', "Go to File...");
|
||||
|
||||
CommandsRegistry.registerCommand(QUICKOPEN_ACTION_ID, function (accessor: ServicesAccessor, prefix: string = null) {
|
||||
CommandsRegistry.registerCommand(QUICKOPEN_ACTION_ID, function (accessor: ServicesAccessor, prefix: string | null = null) {
|
||||
const quickOpenService = accessor.get(IQuickOpenService);
|
||||
|
||||
return quickOpenService.show(typeof prefix === 'string' ? prefix : null).then(() => {
|
||||
return quickOpenService.show(typeof prefix === 'string' ? prefix : undefined).then(() => {
|
||||
return void 0;
|
||||
});
|
||||
});
|
||||
|
||||
export const QUICKOPEN_FOCUS_SECONDARY_ACTION_ID = 'workbench.action.quickOpenPreviousEditor';
|
||||
CommandsRegistry.registerCommand(QUICKOPEN_FOCUS_SECONDARY_ACTION_ID, function (accessor: ServicesAccessor, prefix: string = null) {
|
||||
CommandsRegistry.registerCommand(QUICKOPEN_FOCUS_SECONDARY_ACTION_ID, function (accessor: ServicesAccessor, prefix: string | null = null) {
|
||||
const quickOpenService = accessor.get(IQuickOpenService);
|
||||
|
||||
return quickOpenService.show(null, { autoFocus: { autoFocusSecondEntry: true } }).then(() => {
|
||||
return quickOpenService.show(undefined, { autoFocus: { autoFocusSecondEntry: true } }).then(() => {
|
||||
return void 0;
|
||||
});
|
||||
});
|
||||
@@ -53,14 +50,14 @@ export class BaseQuickOpenNavigateAction extends Action {
|
||||
super(id, label);
|
||||
}
|
||||
|
||||
run(event?: any): TPromise<any> {
|
||||
run(event?: any): Thenable<any> {
|
||||
const keys = this.keybindingService.lookupKeybindings(this.id);
|
||||
const quickNavigate = this.quickNavigate ? { keybindings: keys } : void 0;
|
||||
|
||||
this.quickOpenService.navigate(this.next, quickNavigate);
|
||||
this.quickInputService.navigate(this.next, quickNavigate);
|
||||
|
||||
return TPromise.as(true);
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,8 +70,8 @@ export function getQuickNavigateHandler(id: string, next?: boolean): ICommandHan
|
||||
const keys = keybindingService.lookupKeybindings(id);
|
||||
const quickNavigate = { keybindings: keys };
|
||||
|
||||
quickOpenService.navigate(next, quickNavigate);
|
||||
quickInputService.navigate(next, quickNavigate);
|
||||
quickOpenService.navigate(!!next, quickNavigate);
|
||||
quickInputService.navigate(!!next, quickNavigate);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -140,4 +137,4 @@ export class QuickOpenSelectPreviousAction extends BaseQuickOpenNavigateAction {
|
||||
) {
|
||||
super(id, label, false, false, quickOpenService, quickInputService, keybindingService);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import 'vs/css!./media/sidebarpart';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import * as nls from 'vs/nls';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { Action } from 'vs/base/common/actions';
|
||||
@@ -21,19 +20,24 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
||||
import { KeyMod, KeyCode } from 'vs/base/common/keyCodes';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { Event, mapEvent } from 'vs/base/common/event';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import { contrastBorder } from 'vs/platform/theme/common/colorRegistry';
|
||||
import { SIDE_BAR_TITLE_FOREGROUND, SIDE_BAR_BACKGROUND, SIDE_BAR_FOREGROUND, SIDE_BAR_BORDER } from 'vs/workbench/common/theme';
|
||||
import { INotificationService } from 'vs/platform/notification/common/notification';
|
||||
import { Dimension, EventType } from 'vs/base/browser/dom';
|
||||
import { $ } from 'vs/base/browser/builder';
|
||||
import { Dimension, EventType, addDisposableListener, trackFocus } from 'vs/base/browser/dom';
|
||||
import { StandardMouseEvent } from 'vs/base/browser/mouseEvent';
|
||||
import { RawContextKey, IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { AnchorAlignment } from 'vs/base/browser/ui/contextview/contextview';
|
||||
|
||||
const SideBarFocusContextId = 'sideBarFocus';
|
||||
export const SidebarFocusContext = new RawContextKey<boolean>(SideBarFocusContextId, false);
|
||||
|
||||
export class SidebarPart extends CompositePart<Viewlet> {
|
||||
|
||||
static readonly activeViewletSettingsKey = 'workbench.sidebar.activeviewletid';
|
||||
|
||||
private sideBarFocusContextKey: IContextKey<boolean>;
|
||||
private blockOpeningViewlet: boolean;
|
||||
|
||||
constructor(
|
||||
@@ -45,7 +49,8 @@ export class SidebarPart extends CompositePart<Viewlet> {
|
||||
@IPartService partService: IPartService,
|
||||
@IKeybindingService keybindingService: IKeybindingService,
|
||||
@IInstantiationService instantiationService: IInstantiationService,
|
||||
@IThemeService themeService: IThemeService
|
||||
@IThemeService themeService: IThemeService,
|
||||
@IContextKeyService contextKeyService: IContextKeyService,
|
||||
) {
|
||||
super(
|
||||
notificationService,
|
||||
@@ -65,19 +70,37 @@ export class SidebarPart extends CompositePart<Viewlet> {
|
||||
id,
|
||||
{ hasTitle: true, borderWidth: () => (this.getColor(SIDE_BAR_BORDER) || this.getColor(contrastBorder)) ? 1 : 0 }
|
||||
);
|
||||
|
||||
this.sideBarFocusContextKey = SidebarFocusContext.bindTo(contextKeyService);
|
||||
}
|
||||
|
||||
get onDidViewletOpen(): Event<IViewlet> {
|
||||
return this._onDidCompositeOpen.event as Event<IViewlet>;
|
||||
return mapEvent(this._onDidCompositeOpen.event, compositeEvent => <IViewlet>compositeEvent.composite);
|
||||
}
|
||||
|
||||
get onDidViewletClose(): Event<IViewlet> {
|
||||
return this._onDidCompositeClose.event as Event<IViewlet>;
|
||||
}
|
||||
|
||||
create(parent: HTMLElement): void {
|
||||
super.create(parent);
|
||||
|
||||
const focusTracker = trackFocus(parent);
|
||||
|
||||
focusTracker.onDidFocus(() => {
|
||||
this.sideBarFocusContextKey.set(true);
|
||||
});
|
||||
focusTracker.onDidBlur(() => {
|
||||
this.sideBarFocusContextKey.set(false);
|
||||
});
|
||||
}
|
||||
|
||||
createTitleArea(parent: HTMLElement): HTMLElement {
|
||||
const titleArea = super.createTitleArea(parent);
|
||||
$(titleArea).on(EventType.CONTEXT_MENU, (e: MouseEvent) => this.onTitleAreaContextMenu(new StandardMouseEvent(e)), this.toDispose);
|
||||
|
||||
this._register(addDisposableListener(titleArea, EventType.CONTEXT_MENU, e => {
|
||||
this.onTitleAreaContextMenu(new StandardMouseEvent(e));
|
||||
}));
|
||||
|
||||
return titleArea;
|
||||
}
|
||||
@@ -86,38 +109,37 @@ export class SidebarPart extends CompositePart<Viewlet> {
|
||||
super.updateStyles();
|
||||
|
||||
// Part container
|
||||
const container = $(this.getContainer());
|
||||
const container = this.getContainer();
|
||||
|
||||
container.style('background-color', this.getColor(SIDE_BAR_BACKGROUND));
|
||||
container.style('color', this.getColor(SIDE_BAR_FOREGROUND));
|
||||
container.style.backgroundColor = this.getColor(SIDE_BAR_BACKGROUND);
|
||||
container.style.color = this.getColor(SIDE_BAR_FOREGROUND);
|
||||
|
||||
const borderColor = this.getColor(SIDE_BAR_BORDER) || this.getColor(contrastBorder);
|
||||
const isPositionLeft = this.partService.getSideBarPosition() === SideBarPosition.LEFT;
|
||||
container.style('border-right-width', borderColor && isPositionLeft ? '1px' : null);
|
||||
container.style('border-right-style', borderColor && isPositionLeft ? 'solid' : null);
|
||||
container.style('border-right-color', isPositionLeft ? borderColor : null);
|
||||
container.style('border-left-width', borderColor && !isPositionLeft ? '1px' : null);
|
||||
container.style('border-left-style', borderColor && !isPositionLeft ? 'solid' : null);
|
||||
container.style('border-left-color', !isPositionLeft ? borderColor : null);
|
||||
container.style.borderRightWidth = borderColor && isPositionLeft ? '1px' : null;
|
||||
container.style.borderRightStyle = borderColor && isPositionLeft ? 'solid' : null;
|
||||
container.style.borderRightColor = isPositionLeft ? borderColor : null;
|
||||
container.style.borderLeftWidth = borderColor && !isPositionLeft ? '1px' : null;
|
||||
container.style.borderLeftStyle = borderColor && !isPositionLeft ? 'solid' : null;
|
||||
container.style.borderLeftColor = !isPositionLeft ? borderColor : null;
|
||||
}
|
||||
|
||||
openViewlet(id: string, focus?: boolean): TPromise<Viewlet> {
|
||||
openViewlet(id: string, focus?: boolean): Viewlet {
|
||||
if (this.blockOpeningViewlet) {
|
||||
return TPromise.as(null); // Workaround against a potential race condition
|
||||
return null; // Workaround against a potential race condition
|
||||
}
|
||||
|
||||
// First check if sidebar is hidden and show if so
|
||||
let promise = TPromise.wrap<void>(null);
|
||||
if (!this.partService.isVisible(Parts.SIDEBAR_PART)) {
|
||||
try {
|
||||
this.blockOpeningViewlet = true;
|
||||
promise = this.partService.setSideBarHidden(false);
|
||||
this.partService.setSideBarHidden(false);
|
||||
} finally {
|
||||
this.blockOpeningViewlet = false;
|
||||
}
|
||||
}
|
||||
|
||||
return promise.then(() => this.openComposite(id, focus)) as TPromise<Viewlet>;
|
||||
return this.openComposite(id, focus) as Viewlet;
|
||||
}
|
||||
|
||||
getActiveViewlet(): IViewlet {
|
||||
@@ -128,8 +150,8 @@ export class SidebarPart extends CompositePart<Viewlet> {
|
||||
return this.getLastActiveCompositetId();
|
||||
}
|
||||
|
||||
hideActiveViewlet(): TPromise<void> {
|
||||
return this.hideActiveComposite().then(composite => void 0);
|
||||
hideActiveViewlet(): void {
|
||||
this.hideActiveComposite();
|
||||
}
|
||||
|
||||
layout(dimension: Dimension): Dimension[] {
|
||||
@@ -140,6 +162,10 @@ export class SidebarPart extends CompositePart<Viewlet> {
|
||||
return super.layout(dimension);
|
||||
}
|
||||
|
||||
protected getTitleAreaDropDownAnchorAlignment(): AnchorAlignment {
|
||||
return this.partService.getSideBarPosition() === SideBarPosition.LEFT ? AnchorAlignment.LEFT : AnchorAlignment.RIGHT;
|
||||
}
|
||||
|
||||
private onTitleAreaContextMenu(event: StandardMouseEvent): void {
|
||||
const activeViewlet = this.getActiveViewlet() as Viewlet;
|
||||
if (activeViewlet) {
|
||||
@@ -148,10 +174,9 @@ export class SidebarPart extends CompositePart<Viewlet> {
|
||||
const anchor: { x: number, y: number } = { x: event.posx, y: event.posy };
|
||||
this.contextMenuService.showContextMenu({
|
||||
getAnchor: () => anchor,
|
||||
getActions: () => TPromise.as(contextMenuActions),
|
||||
getActions: () => contextMenuActions,
|
||||
getActionItem: action => this.actionItemProvider(action as Action),
|
||||
actionRunner: activeViewlet.getActionRunner(),
|
||||
getKeyBinding: action => this.keybindingService.lookupKeybinding(action.id)
|
||||
actionRunner: activeViewlet.getActionRunner()
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -172,11 +197,11 @@ class FocusSideBarAction extends Action {
|
||||
super(id, label);
|
||||
}
|
||||
|
||||
run(): TPromise<any> {
|
||||
run(): Thenable<any> {
|
||||
|
||||
// Show side bar
|
||||
if (!this.partService.isVisible(Parts.SIDEBAR_PART)) {
|
||||
return this.partService.setSideBarHidden(false);
|
||||
return Promise.resolve(this.partService.setSideBarHidden(false));
|
||||
}
|
||||
|
||||
// Focus into active viewlet
|
||||
@@ -184,7 +209,8 @@ class FocusSideBarAction extends Action {
|
||||
if (viewlet) {
|
||||
viewlet.focus();
|
||||
}
|
||||
return TPromise.as(true);
|
||||
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
width: 100%;
|
||||
height: 22px;
|
||||
font-size: 12px;
|
||||
padding: 0 10px;
|
||||
}
|
||||
|
||||
.monaco-workbench > .part.statusbar > .statusbar-item {
|
||||
@@ -46,6 +45,15 @@
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
/* adding padding to the most left status bar item */
|
||||
.monaco-workbench > .part.statusbar > .statusbar-item.left:first-child, .monaco-workbench > .part.statusbar > .statusbar-item.right + .statusbar-item.left {
|
||||
padding-left: 10px;
|
||||
}
|
||||
/* adding padding to the most right status bar item */
|
||||
.monaco-workbench > .part.statusbar > .statusbar-item.right:first-child {
|
||||
padding-right: 10px;
|
||||
}
|
||||
|
||||
.monaco-workbench > .part.statusbar > .statusbar-item a {
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
|
||||
@@ -2,11 +2,10 @@
|
||||
* 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 { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { IDisposable } from 'vs/base/common/lifecycle';
|
||||
import * as statusbarService from 'vs/platform/statusbar/common/statusbar';
|
||||
import { StatusbarAlignment } from 'vs/platform/statusbar/common/statusbar';
|
||||
import { SyncDescriptor0, createSyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
|
||||
import { IConstructorSignature0 } from 'vs/platform/instantiation/common/instantiation';
|
||||
|
||||
@@ -14,8 +13,6 @@ export interface IStatusbarItem {
|
||||
render(element: HTMLElement): IDisposable;
|
||||
}
|
||||
|
||||
export import StatusbarAlignment = statusbarService.StatusbarAlignment;
|
||||
|
||||
export class StatusbarItemDescriptor {
|
||||
syncDescriptor: SyncDescriptor0<IStatusbarItem>;
|
||||
alignment: StatusbarAlignment;
|
||||
|
||||
@@ -3,23 +3,19 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import 'vs/css!./media/statusbarpart';
|
||||
import * as nls from 'vs/nls';
|
||||
import { toErrorMessage } from 'vs/base/common/errorMessage';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { dispose, IDisposable, toDisposable } from 'vs/base/common/lifecycle';
|
||||
import { $ } from 'vs/base/browser/builder';
|
||||
import { OcticonLabel } from 'vs/base/browser/ui/octiconLabel/octiconLabel';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { ICommandService } from 'vs/platform/commands/common/commands';
|
||||
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { Part } from 'vs/workbench/browser/part';
|
||||
import { StatusbarAlignment, IStatusbarRegistry, Extensions, IStatusbarItem } from 'vs/workbench/browser/parts/statusbar/statusbar';
|
||||
import { 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';
|
||||
import { IStatusbarService, IStatusbarEntry } from 'vs/platform/statusbar/common/statusbar';
|
||||
import { StatusbarAlignment, IStatusbarService, IStatusbarEntry } from 'vs/platform/statusbar/common/statusbar';
|
||||
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';
|
||||
@@ -28,15 +24,16 @@ import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/
|
||||
import { contrastBorder } from 'vs/platform/theme/common/colorRegistry';
|
||||
import { isThemeColor } from 'vs/editor/common/editorCommon';
|
||||
import { Color } from 'vs/base/common/color';
|
||||
import { addClass, EventHelper, createStyleSheet } from 'vs/base/browser/dom';
|
||||
import { addClass, EventHelper, createStyleSheet, addDisposableListener } from 'vs/base/browser/dom';
|
||||
import { INotificationService } from 'vs/platform/notification/common/notification';
|
||||
import { IStorageService } from 'vs/platform/storage/common/storage';
|
||||
|
||||
export class StatusbarPart extends Part implements IStatusbarService {
|
||||
|
||||
_serviceBrand: any;
|
||||
|
||||
private static readonly PRIORITY_PROP = 'priority';
|
||||
private static readonly ALIGNMENT_PROP = 'alignment';
|
||||
private static readonly PRIORITY_PROP = 'statusbar-entry-priority';
|
||||
private static readonly ALIGNMENT_PROP = 'statusbar-entry-alignment';
|
||||
|
||||
private statusItemsContainer: HTMLElement;
|
||||
private statusMsgDispose: IDisposable;
|
||||
@@ -47,9 +44,10 @@ export class StatusbarPart extends Part implements IStatusbarService {
|
||||
id: string,
|
||||
@IInstantiationService private instantiationService: IInstantiationService,
|
||||
@IThemeService themeService: IThemeService,
|
||||
@IWorkspaceContextService private contextService: IWorkspaceContextService
|
||||
@IWorkspaceContextService private contextService: IWorkspaceContextService,
|
||||
@IStorageService storageService: IStorageService
|
||||
) {
|
||||
super(id, { hasTitle: false }, themeService);
|
||||
super(id, { hasTitle: false }, themeService, storageService);
|
||||
|
||||
this.registerListeners();
|
||||
}
|
||||
@@ -71,7 +69,7 @@ export class StatusbarPart extends Part implements IStatusbarService {
|
||||
let inserted = false;
|
||||
for (let i = 0; i < neighbours.length; i++) {
|
||||
const neighbour = neighbours[i];
|
||||
const nPriority = $(neighbour).getProperty(StatusbarPart.PRIORITY_PROP);
|
||||
const nPriority = Number(neighbour.getAttribute(StatusbarPart.PRIORITY_PROP));
|
||||
if (
|
||||
alignment === StatusbarAlignment.LEFT && nPriority < priority ||
|
||||
alignment === StatusbarAlignment.RIGHT && nPriority > priority
|
||||
@@ -87,7 +85,7 @@ export class StatusbarPart extends Part implements IStatusbarService {
|
||||
}
|
||||
|
||||
return toDisposable(() => {
|
||||
$(el).destroy();
|
||||
el.remove();
|
||||
|
||||
if (toDispose) {
|
||||
toDispose.dispose();
|
||||
@@ -102,7 +100,7 @@ export class StatusbarPart extends Part implements IStatusbarService {
|
||||
const children = container.children;
|
||||
for (let i = 0; i < children.length; i++) {
|
||||
const childElement = <HTMLElement>children.item(i);
|
||||
if ($(childElement).getProperty(StatusbarPart.ALIGNMENT_PROP) === alignment) {
|
||||
if (Number(childElement.getAttribute(StatusbarPart.ALIGNMENT_PROP)) === alignment) {
|
||||
entries.push(childElement);
|
||||
}
|
||||
}
|
||||
@@ -116,17 +114,29 @@ export class StatusbarPart extends Part implements IStatusbarService {
|
||||
// Fill in initial items that were contributed from the registry
|
||||
const registry = Registry.as<IStatusbarRegistry>(Extensions.Statusbar);
|
||||
|
||||
const leftDescriptors = registry.items.filter(d => d.alignment === StatusbarAlignment.LEFT).sort((a, b) => b.priority - a.priority);
|
||||
const rightDescriptors = registry.items.filter(d => d.alignment === StatusbarAlignment.RIGHT).sort((a, b) => a.priority - b.priority);
|
||||
const descriptors = registry.items.slice().sort((a, b) => {
|
||||
if (a.alignment === b.alignment) {
|
||||
if (a.alignment === StatusbarAlignment.LEFT) {
|
||||
return b.priority - a.priority;
|
||||
} else {
|
||||
return a.priority - b.priority;
|
||||
}
|
||||
} else if (a.alignment === StatusbarAlignment.LEFT) {
|
||||
return 1;
|
||||
} else if (a.alignment === StatusbarAlignment.RIGHT) {
|
||||
return -1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
});
|
||||
|
||||
const descriptors = rightDescriptors.concat(leftDescriptors); // right first because they float
|
||||
descriptors.forEach(descriptor => {
|
||||
for (const descriptor of descriptors) {
|
||||
const item = this.instantiationService.createInstance(descriptor.syncDescriptor);
|
||||
const el = this.doCreateStatusItem(descriptor.alignment, descriptor.priority);
|
||||
|
||||
this._register(item.render(el));
|
||||
this.statusItemsContainer.appendChild(el);
|
||||
});
|
||||
}
|
||||
|
||||
return this.statusItemsContainer;
|
||||
}
|
||||
@@ -134,22 +144,22 @@ export class StatusbarPart extends Part implements IStatusbarService {
|
||||
protected updateStyles(): void {
|
||||
super.updateStyles();
|
||||
|
||||
const container = $(this.getContainer());
|
||||
const container = this.getContainer();
|
||||
|
||||
// Background colors
|
||||
const backgroundColor = this.getColor(this.contextService.getWorkbenchState() !== WorkbenchState.EMPTY ? STATUS_BAR_BACKGROUND : STATUS_BAR_NO_FOLDER_BACKGROUND);
|
||||
container.style('background-color', backgroundColor);
|
||||
container.style('color', this.getColor(this.contextService.getWorkbenchState() !== WorkbenchState.EMPTY ? STATUS_BAR_FOREGROUND : STATUS_BAR_NO_FOLDER_FOREGROUND));
|
||||
container.style.backgroundColor = backgroundColor;
|
||||
container.style.color = this.getColor(this.contextService.getWorkbenchState() !== WorkbenchState.EMPTY ? STATUS_BAR_FOREGROUND : STATUS_BAR_NO_FOLDER_FOREGROUND);
|
||||
|
||||
// Border color
|
||||
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);
|
||||
container.style.borderTopWidth = borderColor ? '1px' : null;
|
||||
container.style.borderTopStyle = borderColor ? 'solid' : null;
|
||||
container.style.borderTopColor = borderColor;
|
||||
|
||||
// Notification Beak
|
||||
if (!this.styleElement) {
|
||||
this.styleElement = createStyleSheet(container.getHTMLElement());
|
||||
this.styleElement = createStyleSheet(container);
|
||||
}
|
||||
|
||||
this.styleElement.innerHTML = `.monaco-workbench > .part.statusbar > .statusbar-item.has-beak:before { border-bottom-color: ${backgroundColor}; }`;
|
||||
@@ -168,8 +178,8 @@ export class StatusbarPart extends Part implements IStatusbarService {
|
||||
addClass(el, 'left');
|
||||
}
|
||||
|
||||
$(el).setProperty(StatusbarPart.PRIORITY_PROP, priority);
|
||||
$(el).setProperty(StatusbarPart.ALIGNMENT_PROP, alignment);
|
||||
el.setAttribute(StatusbarPart.PRIORITY_PROP, String(priority));
|
||||
el.setAttribute(StatusbarPart.ALIGNMENT_PROP, String(alignment));
|
||||
|
||||
return el;
|
||||
}
|
||||
@@ -185,7 +195,7 @@ export class StatusbarPart extends Part implements IStatusbarService {
|
||||
statusDispose = this.addEntry({ text: message }, StatusbarAlignment.LEFT, -Number.MAX_VALUE /* far right on left hand side */);
|
||||
showHandle = null;
|
||||
}, delayBy);
|
||||
let hideHandle: number;
|
||||
let hideHandle: any;
|
||||
|
||||
// Dispose function takes care of timeouts and actual entry
|
||||
const dispose = {
|
||||
@@ -242,7 +252,7 @@ class StatusBarEntryItem implements IStatusbarItem {
|
||||
if (this.entry.command) {
|
||||
textContainer = document.createElement('a');
|
||||
|
||||
$(textContainer).on('click', () => this.executeCommand(this.entry.command, this.entry.arguments), toDispose);
|
||||
toDispose.push(addDisposableListener(textContainer, 'click', () => this.executeCommand(this.entry.command, this.entry.arguments)));
|
||||
} else {
|
||||
textContainer = document.createElement('span');
|
||||
}
|
||||
@@ -252,7 +262,7 @@ class StatusBarEntryItem implements IStatusbarItem {
|
||||
|
||||
// Tooltip
|
||||
if (this.entry.tooltip) {
|
||||
$(textContainer).title(this.entry.tooltip);
|
||||
textContainer.title = this.entry.tooltip;
|
||||
}
|
||||
|
||||
// Color
|
||||
@@ -263,23 +273,23 @@ class StatusBarEntryItem implements IStatusbarItem {
|
||||
color = (this.themeService.getTheme().getColor(colorId) || Color.transparent).toString();
|
||||
toDispose.push(this.themeService.onThemeChange(theme => {
|
||||
let colorValue = (this.themeService.getTheme().getColor(colorId) || Color.transparent).toString();
|
||||
$(textContainer).color(colorValue);
|
||||
textContainer.style.color = colorValue;
|
||||
}));
|
||||
}
|
||||
$(textContainer).color(color);
|
||||
textContainer.style.color = color;
|
||||
}
|
||||
|
||||
// Context Menu
|
||||
if (this.entry.extensionId) {
|
||||
$(textContainer).on('contextmenu', e => {
|
||||
toDispose.push(addDisposableListener(textContainer, 'contextmenu', e => {
|
||||
EventHelper.stop(e, true);
|
||||
|
||||
this.contextMenuService.showContextMenu({
|
||||
getAnchor: () => el,
|
||||
getActionsContext: () => this.entry.extensionId,
|
||||
getActions: () => TPromise.as([manageExtensionAction])
|
||||
getActions: () => [manageExtensionAction]
|
||||
});
|
||||
}, toDispose);
|
||||
}));
|
||||
}
|
||||
|
||||
el.appendChild(textContainer);
|
||||
@@ -307,7 +317,7 @@ class StatusBarEntryItem implements IStatusbarItem {
|
||||
}
|
||||
*/
|
||||
this.telemetryService.publicLog('workbenchActionExecuted', { id, from: 'status bar' });
|
||||
this.commandService.executeCommand(id, ...args).done(undefined, err => this.notificationService.error(toErrorMessage(err)));
|
||||
this.commandService.executeCommand(id, ...args).then(undefined, err => this.notificationService.error(toErrorMessage(err)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -319,7 +329,7 @@ class ManageExtensionAction extends Action {
|
||||
super('statusbar.manage.extension', nls.localize('manageExtension', "Manage Extension"));
|
||||
}
|
||||
|
||||
run(extensionId: string): TPromise<any> {
|
||||
run(extensionId: string): Thenable<any> {
|
||||
return this.commandService.executeCommand('_extensions.manage', extensionId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,6 +51,11 @@
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.monaco-workbench.windows > .part.titlebar > .window-title,
|
||||
.monaco-workbench.linux > .part.titlebar > .window-title {
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.monaco-workbench.linux > .part.titlebar > .window-title {
|
||||
font-size: inherit;
|
||||
}
|
||||
@@ -96,6 +101,7 @@
|
||||
-webkit-app-region: no-drag;
|
||||
height: 100%;
|
||||
width: 138px;
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.monaco-workbench.fullscreen > .part.titlebar > .window-controls-container {
|
||||
|
||||
708
src/vs/workbench/browser/parts/titlebar/menubarControl.ts
Normal file
@@ -0,0 +1,708 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 { IMenubarMenu, IMenubarMenuItemAction, IMenubarMenuItemSubmenu, IMenubarKeybinding, IMenubarService, IMenubarData } from 'vs/platform/menubar/common/menubar';
|
||||
import { IMenuService, MenuId, IMenu, SubmenuItemAction } from 'vs/platform/actions/common/actions';
|
||||
import { registerThemingParticipant, ITheme, ICssStyleCollector, IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import { IWindowService, MenuBarVisibility, IWindowsService, getTitleBarStyle } from 'vs/platform/windows/common/windows';
|
||||
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { IAction, Action } from 'vs/base/common/actions';
|
||||
import { Separator } from 'vs/base/browser/ui/actionbar/actionbar';
|
||||
import * as DOM from 'vs/base/browser/dom';
|
||||
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
||||
import { isMacintosh, isLinux } from 'vs/base/common/platform';
|
||||
import { IConfigurationService, IConfigurationChangeEvent } from 'vs/platform/configuration/common/configuration';
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { IRecentlyOpened } from 'vs/platform/history/common/history';
|
||||
import { IWorkspaceIdentifier, ISingleFolderWorkspaceIdentifier, isSingleFolderWorkspaceIdentifier, isWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces';
|
||||
import { RunOnceScheduler } from 'vs/base/common/async';
|
||||
import { MENUBAR_SELECTION_FOREGROUND, MENUBAR_SELECTION_BACKGROUND, MENUBAR_SELECTION_BORDER, TITLE_BAR_ACTIVE_FOREGROUND, TITLE_BAR_INACTIVE_FOREGROUND } from 'vs/workbench/common/theme';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { ILabelService } from 'vs/platform/label/common/label';
|
||||
import { IUpdateService, StateType } from 'vs/platform/update/common/update';
|
||||
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
|
||||
import { INotificationService, Severity } from 'vs/platform/notification/common/notification';
|
||||
import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences';
|
||||
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
import { MenuBar } from 'vs/base/browser/ui/menu/menubar';
|
||||
import { SubmenuAction } from 'vs/base/browser/ui/menu/menu';
|
||||
import { attachMenuStyler } from 'vs/platform/theme/common/styler';
|
||||
|
||||
export class MenubarControl extends Disposable {
|
||||
|
||||
private keys = [
|
||||
'files.autoSave',
|
||||
'window.menuBarVisibility',
|
||||
'editor.multiCursorModifier',
|
||||
'workbench.sideBar.location',
|
||||
'workbench.statusBar.visible',
|
||||
'workbench.activityBar.visible',
|
||||
'window.enableMenuBarMnemonics',
|
||||
'window.nativeTabs'
|
||||
];
|
||||
|
||||
private topLevelMenus: {
|
||||
'File': IMenu;
|
||||
'Edit': IMenu;
|
||||
'Selection': IMenu;
|
||||
'View': IMenu;
|
||||
'Go': IMenu;
|
||||
'Debug': IMenu;
|
||||
'Terminal': IMenu;
|
||||
'Window'?: IMenu;
|
||||
'Help': IMenu;
|
||||
[index: string]: IMenu;
|
||||
};
|
||||
|
||||
private topLevelTitles = {
|
||||
'File': nls.localize({ key: 'mFile', comment: ['&& denotes a mnemonic'] }, "&&File"),
|
||||
'Edit': nls.localize({ key: 'mEdit', comment: ['&& denotes a mnemonic'] }, "&&Edit"),
|
||||
'Selection': nls.localize({ key: 'mSelection', comment: ['&& denotes a mnemonic'] }, "&&Selection"),
|
||||
'View': nls.localize({ key: 'mView', comment: ['&& denotes a mnemonic'] }, "&&View"),
|
||||
'Go': nls.localize({ key: 'mGoto', comment: ['&& denotes a mnemonic'] }, "&&Go"),
|
||||
'Debug': nls.localize({ key: 'mDebug', comment: ['&& denotes a mnemonic'] }, "&&Debug"),
|
||||
'Terminal': nls.localize({ key: 'mTerminal', comment: ['&& denotes a mnemonic'] }, "&&Terminal"),
|
||||
'Help': nls.localize({ key: 'mHelp', comment: ['&& denotes a mnemonic'] }, "&&Help")
|
||||
};
|
||||
|
||||
private menubar: MenuBar;
|
||||
private menuUpdater: RunOnceScheduler;
|
||||
private container: HTMLElement;
|
||||
private recentlyOpened: IRecentlyOpened;
|
||||
|
||||
private _onVisibilityChange: Emitter<boolean>;
|
||||
private _onFocusStateChange: Emitter<boolean>;
|
||||
|
||||
private static MAX_MENU_RECENT_ENTRIES = 10;
|
||||
|
||||
constructor(
|
||||
@IThemeService private themeService: IThemeService,
|
||||
@IMenubarService private menubarService: IMenubarService,
|
||||
@IMenuService private menuService: IMenuService,
|
||||
@IWindowService private windowService: IWindowService,
|
||||
@IWindowsService private windowsService: IWindowsService,
|
||||
@IContextKeyService private contextKeyService: IContextKeyService,
|
||||
@IKeybindingService private keybindingService: IKeybindingService,
|
||||
@IConfigurationService private configurationService: IConfigurationService,
|
||||
@ILabelService private labelService: ILabelService,
|
||||
@IUpdateService private updateService: IUpdateService,
|
||||
@IStorageService private storageService: IStorageService,
|
||||
@INotificationService private notificationService: INotificationService,
|
||||
@IPreferencesService private preferencesService: IPreferencesService,
|
||||
@IEnvironmentService private environmentService: IEnvironmentService
|
||||
) {
|
||||
|
||||
super();
|
||||
|
||||
this.topLevelMenus = {
|
||||
'File': this._register(this.menuService.createMenu(MenuId.MenubarFileMenu, this.contextKeyService)),
|
||||
'Edit': this._register(this.menuService.createMenu(MenuId.MenubarEditMenu, this.contextKeyService)),
|
||||
'Selection': this._register(this.menuService.createMenu(MenuId.MenubarSelectionMenu, this.contextKeyService)),
|
||||
'View': this._register(this.menuService.createMenu(MenuId.MenubarViewMenu, this.contextKeyService)),
|
||||
'Go': this._register(this.menuService.createMenu(MenuId.MenubarGoMenu, this.contextKeyService)),
|
||||
'Debug': this._register(this.menuService.createMenu(MenuId.MenubarDebugMenu, this.contextKeyService)),
|
||||
'Terminal': this._register(this.menuService.createMenu(MenuId.MenubarTerminalMenu, this.contextKeyService)),
|
||||
'Help': this._register(this.menuService.createMenu(MenuId.MenubarHelpMenu, this.contextKeyService))
|
||||
};
|
||||
|
||||
if (isMacintosh) {
|
||||
this.topLevelMenus['Preferences'] = this._register(this.menuService.createMenu(MenuId.MenubarPreferencesMenu, this.contextKeyService));
|
||||
}
|
||||
|
||||
this.menuUpdater = this._register(new RunOnceScheduler(() => this.doUpdateMenubar(false), 200));
|
||||
|
||||
if (isMacintosh || this.currentTitlebarStyleSetting !== 'custom') {
|
||||
for (let topLevelMenuName of Object.keys(this.topLevelMenus)) {
|
||||
this._register(this.topLevelMenus[topLevelMenuName].onDidChange(() => this.updateMenubar()));
|
||||
}
|
||||
|
||||
this.doUpdateMenubar(true);
|
||||
}
|
||||
|
||||
this._onVisibilityChange = this._register(new Emitter<boolean>());
|
||||
this._onFocusStateChange = this._register(new Emitter<boolean>());
|
||||
|
||||
this.windowService.getRecentlyOpened().then((recentlyOpened) => {
|
||||
this.recentlyOpened = recentlyOpened;
|
||||
});
|
||||
|
||||
this.detectAndRecommendCustomTitlebar();
|
||||
|
||||
this.registerListeners();
|
||||
}
|
||||
|
||||
private get currentEnableMenuBarMnemonics(): boolean {
|
||||
let enableMenuBarMnemonics = this.configurationService.getValue<boolean>('window.enableMenuBarMnemonics');
|
||||
if (typeof enableMenuBarMnemonics !== 'boolean') {
|
||||
enableMenuBarMnemonics = true;
|
||||
}
|
||||
|
||||
return enableMenuBarMnemonics;
|
||||
}
|
||||
|
||||
private get currentSidebarPosition(): string {
|
||||
return this.configurationService.getValue<string>('workbench.sideBar.location');
|
||||
}
|
||||
|
||||
private get currentStatusBarVisibility(): boolean {
|
||||
let setting = this.configurationService.getValue<boolean>('workbench.statusBar.visible');
|
||||
if (typeof setting !== 'boolean') {
|
||||
setting = true;
|
||||
}
|
||||
|
||||
return setting;
|
||||
}
|
||||
|
||||
private get currentActivityBarVisibility(): boolean {
|
||||
let setting = this.configurationService.getValue<boolean>('workbench.activityBar.visible');
|
||||
if (typeof setting !== 'boolean') {
|
||||
setting = true;
|
||||
}
|
||||
|
||||
return setting;
|
||||
}
|
||||
|
||||
private get currentMenubarVisibility(): MenuBarVisibility {
|
||||
return this.configurationService.getValue<MenuBarVisibility>('window.menuBarVisibility');
|
||||
}
|
||||
|
||||
private get currentTitlebarStyleSetting(): string {
|
||||
return getTitleBarStyle(this.configurationService, this.environmentService);
|
||||
}
|
||||
|
||||
private onDidChangeWindowFocus(hasFocus: boolean): void {
|
||||
if (this.container) {
|
||||
if (hasFocus) {
|
||||
DOM.removeClass(this.container, 'inactive');
|
||||
} else {
|
||||
DOM.addClass(this.container, 'inactive');
|
||||
this.menubar.blur();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private onConfigurationUpdated(event: IConfigurationChangeEvent): void {
|
||||
if (this.keys.some(key => event.affectsConfiguration(key))) {
|
||||
this.updateMenubar();
|
||||
}
|
||||
|
||||
if (event.affectsConfiguration('window.menuBarVisibility')) {
|
||||
this.detectAndRecommendCustomTitlebar();
|
||||
}
|
||||
}
|
||||
|
||||
private onRecentlyOpenedChange(): void {
|
||||
this.windowService.getRecentlyOpened().then(recentlyOpened => {
|
||||
this.recentlyOpened = recentlyOpened;
|
||||
this.updateMenubar();
|
||||
});
|
||||
}
|
||||
|
||||
private detectAndRecommendCustomTitlebar(): void {
|
||||
if (!isLinux) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.storageService.getBoolean('menubar/electronFixRecommended', StorageScope.GLOBAL, false)) {
|
||||
if (this.currentMenubarVisibility === 'hidden' || this.currentTitlebarStyleSetting === 'custom') {
|
||||
// Issue will not arise for user, abort notification
|
||||
return;
|
||||
}
|
||||
|
||||
const message = nls.localize('menubar.electronFixRecommendation', "If you experience hard to read text in the menu bar, we recommend trying out the custom title bar.");
|
||||
this.notificationService.prompt(Severity.Info, message, [
|
||||
{
|
||||
label: nls.localize('goToSetting', "Open Settings"),
|
||||
run: () => {
|
||||
return this.preferencesService.openGlobalSettings(undefined, { query: 'window.titleBarStyle' });
|
||||
}
|
||||
},
|
||||
{
|
||||
label: nls.localize('moreInfo', "More Info"),
|
||||
run: () => {
|
||||
window.open('https://go.microsoft.com/fwlink/?linkid=2038566');
|
||||
}
|
||||
},
|
||||
{
|
||||
label: nls.localize('neverShowAgain', "Don't Show Again"),
|
||||
run: () => {
|
||||
this.storageService.store('menubar/electronFixRecommended', true, StorageScope.GLOBAL);
|
||||
}
|
||||
}
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
private registerListeners(): void {
|
||||
// Update when config changes
|
||||
this._register(this.configurationService.onDidChangeConfiguration(e => this.onConfigurationUpdated(e)));
|
||||
|
||||
// Listen to update service
|
||||
this.updateService.onStateChange(() => this.updateMenubar());
|
||||
|
||||
// Listen for changes in recently opened menu
|
||||
this._register(this.windowsService.onRecentlyOpenedChange(() => { this.onRecentlyOpenedChange(); }));
|
||||
|
||||
// Listen to keybindings change
|
||||
this._register(this.keybindingService.onDidUpdateKeybindings(() => this.updateMenubar()));
|
||||
|
||||
// These listeners only apply when the custom menubar is being used
|
||||
if (!isMacintosh && this.currentTitlebarStyleSetting === 'custom') {
|
||||
// Listen for window focus changes
|
||||
this._register(this.windowService.onDidChangeFocus(e => this.onDidChangeWindowFocus(e)));
|
||||
|
||||
this._register(this.windowService.onDidChangeMaximize(e => this.updateMenubar()));
|
||||
|
||||
this._register(DOM.addDisposableListener(window, DOM.EventType.RESIZE, () => {
|
||||
this.menubar.blur();
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
private doUpdateMenubar(firstTime: boolean): void {
|
||||
if (!isMacintosh && this.currentTitlebarStyleSetting === 'custom') {
|
||||
this.setupCustomMenubar(firstTime);
|
||||
} else {
|
||||
// Send menus to main process to be rendered by Electron
|
||||
const menubarData = { menus: {}, keybindings: {} };
|
||||
if (this.getMenubarMenus(menubarData)) {
|
||||
this.menubarService.updateMenubar(this.windowService.getCurrentWindowId(), menubarData);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private updateMenubar(): void {
|
||||
this.menuUpdater.schedule();
|
||||
}
|
||||
|
||||
private calculateActionLabel(action: IAction | IMenubarMenuItemAction): string {
|
||||
let label = action.label;
|
||||
switch (action.id) {
|
||||
case 'workbench.action.toggleSidebarPosition':
|
||||
if (this.currentSidebarPosition !== 'right') {
|
||||
label = nls.localize({ key: 'miMoveSidebarRight', comment: ['&& denotes a mnemonic'] }, "&&Move Side Bar Right");
|
||||
} else {
|
||||
label = nls.localize({ key: 'miMoveSidebarLeft', comment: ['&& denotes a mnemonic'] }, "&&Move Side Bar Left");
|
||||
}
|
||||
break;
|
||||
|
||||
case 'workbench.action.toggleStatusbarVisibility':
|
||||
if (this.currentStatusBarVisibility) {
|
||||
label = nls.localize({ key: 'miHideStatusbar', comment: ['&& denotes a mnemonic'] }, "&&Hide Status Bar");
|
||||
} else {
|
||||
label = nls.localize({ key: 'miShowStatusbar', comment: ['&& denotes a mnemonic'] }, "&&Show Status Bar");
|
||||
}
|
||||
break;
|
||||
|
||||
case 'workbench.action.toggleActivityBarVisibility':
|
||||
if (this.currentActivityBarVisibility) {
|
||||
label = nls.localize({ key: 'miHideActivityBar', comment: ['&& denotes a mnemonic'] }, "Hide &&Activity Bar");
|
||||
} else {
|
||||
label = nls.localize({ key: 'miShowActivityBar', comment: ['&& denotes a mnemonic'] }, "Show &&Activity Bar");
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return label;
|
||||
}
|
||||
|
||||
private createOpenRecentMenuAction(workspace: IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier | URI, commandId: string, isFile: boolean): IAction {
|
||||
|
||||
let label: string;
|
||||
let uri: URI;
|
||||
|
||||
if (isSingleFolderWorkspaceIdentifier(workspace) && !isFile) {
|
||||
label = this.labelService.getWorkspaceLabel(workspace, { verbose: true });
|
||||
uri = workspace;
|
||||
} else if (isWorkspaceIdentifier(workspace)) {
|
||||
label = this.labelService.getWorkspaceLabel(workspace, { verbose: true });
|
||||
uri = URI.file(workspace.configPath);
|
||||
} else {
|
||||
uri = workspace;
|
||||
label = this.labelService.getUriLabel(uri);
|
||||
}
|
||||
|
||||
return new Action(commandId, label, undefined, undefined, (event) => {
|
||||
const openInNewWindow = event && ((!isMacintosh && (event.ctrlKey || event.shiftKey)) || (isMacintosh && (event.metaKey || event.altKey)));
|
||||
|
||||
return this.windowService.openWindow([uri], {
|
||||
forceNewWindow: openInNewWindow,
|
||||
forceOpenWorkspaceAsFile: isFile
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private getOpenRecentActions(): IAction[] {
|
||||
if (!this.recentlyOpened) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const { workspaces, files } = this.recentlyOpened;
|
||||
|
||||
const result: IAction[] = [];
|
||||
|
||||
if (workspaces.length > 0) {
|
||||
for (let i = 0; i < MenubarControl.MAX_MENU_RECENT_ENTRIES && i < workspaces.length; i++) {
|
||||
result.push(this.createOpenRecentMenuAction(workspaces[i], 'openRecentWorkspace', false));
|
||||
}
|
||||
|
||||
result.push(new Separator());
|
||||
}
|
||||
|
||||
if (files.length > 0) {
|
||||
for (let i = 0; i < MenubarControl.MAX_MENU_RECENT_ENTRIES && i < files.length; i++) {
|
||||
result.push(this.createOpenRecentMenuAction(files[i], 'openRecentFile', false));
|
||||
}
|
||||
|
||||
result.push(new Separator());
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private getUpdateAction(): IAction | null {
|
||||
const state = this.updateService.state;
|
||||
|
||||
switch (state.type) {
|
||||
case StateType.Uninitialized:
|
||||
return null;
|
||||
|
||||
case StateType.Idle:
|
||||
const windowId = this.windowService.getCurrentWindowId();
|
||||
return new Action('update.check', nls.localize({ key: 'checkForUpdates', comment: ['&& denotes a mnemonic'] }, "Check for &&Updates..."), undefined, true, () =>
|
||||
this.updateService.checkForUpdates({ windowId }));
|
||||
|
||||
case StateType.CheckingForUpdates:
|
||||
return new Action('update.checking', nls.localize('checkingForUpdates', "Checking For Updates..."), undefined, false);
|
||||
|
||||
case StateType.AvailableForDownload:
|
||||
return new Action('update.downloadNow', nls.localize({ key: 'download now', comment: ['&& denotes a mnemonic'] }, "D&&ownload Now"), null, true, () =>
|
||||
this.updateService.downloadUpdate());
|
||||
|
||||
case StateType.Downloading:
|
||||
return new Action('update.downloading', nls.localize('DownloadingUpdate', "Downloading Update..."), undefined, false);
|
||||
|
||||
case StateType.Downloaded:
|
||||
return new Action('update.install', nls.localize({ key: 'installUpdate...', comment: ['&& denotes a mnemonic'] }, "Install &&Update..."), undefined, true, () =>
|
||||
this.updateService.applyUpdate());
|
||||
|
||||
case StateType.Updating:
|
||||
return new Action('update.updating', nls.localize('installingUpdate', "Installing Update..."), undefined, false);
|
||||
|
||||
case StateType.Ready:
|
||||
return new Action('update.restart', nls.localize({ key: 'restartToUpdate', comment: ['&& denotes a mnemonic'] }, "Restart to &&Update..."), undefined, true, () =>
|
||||
this.updateService.quitAndInstall());
|
||||
}
|
||||
}
|
||||
|
||||
private insertActionsBefore(nextAction: IAction, target: IAction[]): void {
|
||||
switch (nextAction.id) {
|
||||
case 'workbench.action.openRecent':
|
||||
target.push(...this.getOpenRecentActions());
|
||||
break;
|
||||
|
||||
case 'workbench.action.showAboutDialog':
|
||||
if (!isMacintosh) {
|
||||
const updateAction = this.getUpdateAction();
|
||||
if (updateAction) {
|
||||
target.push(updateAction);
|
||||
target.push(new Separator());
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private setupCustomMenubar(firstTime: boolean): void {
|
||||
if (firstTime) {
|
||||
this.menubar = this._register(new MenuBar(
|
||||
this.container, {
|
||||
enableMnemonics: this.currentEnableMenuBarMnemonics,
|
||||
visibility: this.currentMenubarVisibility,
|
||||
getKeybinding: (action) => this.keybindingService.lookupKeybinding(action.id),
|
||||
}
|
||||
));
|
||||
|
||||
this._register(this.menubar.onFocusStateChange(e => this._onFocusStateChange.fire(e)));
|
||||
this._register(this.menubar.onVisibilityChange(e => this._onVisibilityChange.fire(e)));
|
||||
|
||||
this._register(attachMenuStyler(this.menubar, this.themeService));
|
||||
} else {
|
||||
this.menubar.update({ enableMnemonics: this.currentEnableMenuBarMnemonics, visibility: this.currentMenubarVisibility, getKeybinding: (action) => this.keybindingService.lookupKeybinding(action.id) });
|
||||
}
|
||||
|
||||
// Update the menu actions
|
||||
const updateActions = (menu: IMenu, target: IAction[]) => {
|
||||
target.splice(0);
|
||||
let groups = menu.getActions();
|
||||
for (let group of groups) {
|
||||
const [, actions] = group;
|
||||
|
||||
for (let action of actions) {
|
||||
this.insertActionsBefore(action, target);
|
||||
if (action instanceof SubmenuItemAction) {
|
||||
const submenu = this.menuService.createMenu(action.item.submenu, this.contextKeyService);
|
||||
const submenuActions: SubmenuAction[] = [];
|
||||
updateActions(submenu, submenuActions);
|
||||
target.push(new SubmenuAction(action.label, submenuActions));
|
||||
submenu.dispose();
|
||||
} else {
|
||||
action.label = this.calculateActionLabel(action);
|
||||
target.push(action);
|
||||
}
|
||||
}
|
||||
|
||||
target.push(new Separator());
|
||||
}
|
||||
|
||||
target.pop();
|
||||
};
|
||||
|
||||
for (let title of Object.keys(this.topLevelMenus)) {
|
||||
const menu = this.topLevelMenus[title];
|
||||
if (firstTime) {
|
||||
this._register(menu.onDidChange(() => {
|
||||
const actions = [];
|
||||
updateActions(menu, actions);
|
||||
this.menubar.updateMenu({ actions: actions, label: this.topLevelTitles[title] });
|
||||
}));
|
||||
}
|
||||
|
||||
const actions = [];
|
||||
updateActions(menu, actions);
|
||||
|
||||
if (!firstTime) {
|
||||
this.menubar.updateMenu({ actions: actions, label: this.topLevelTitles[title] });
|
||||
} else {
|
||||
this.menubar.push({ actions: actions, label: this.topLevelTitles[title] });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private getMenubarKeybinding(id: string): IMenubarKeybinding {
|
||||
const binding = this.keybindingService.lookupKeybinding(id);
|
||||
if (!binding) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// first try to resolve a native accelerator
|
||||
const electronAccelerator = binding.getElectronAccelerator();
|
||||
if (electronAccelerator) {
|
||||
return { label: electronAccelerator };
|
||||
}
|
||||
|
||||
// we need this fallback to support keybindings that cannot show in electron menus (e.g. chords)
|
||||
const acceleratorLabel = binding.getLabel();
|
||||
if (acceleratorLabel) {
|
||||
return { label: acceleratorLabel, isNative: false };
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private populateMenuItems(menu: IMenu, menuToPopulate: IMenubarMenu, keybindings: { [id: string]: IMenubarKeybinding }) {
|
||||
let groups = menu.getActions();
|
||||
for (let group of groups) {
|
||||
const [, actions] = group;
|
||||
|
||||
actions.forEach(menuItem => {
|
||||
|
||||
if (menuItem instanceof SubmenuItemAction) {
|
||||
const submenu = { items: [] };
|
||||
const menuToDispose = this.menuService.createMenu(menuItem.item.submenu, this.contextKeyService);
|
||||
this.populateMenuItems(menuToDispose, submenu, keybindings);
|
||||
|
||||
let menubarSubmenuItem: IMenubarMenuItemSubmenu = {
|
||||
id: menuItem.id,
|
||||
label: menuItem.label,
|
||||
submenu: submenu
|
||||
};
|
||||
|
||||
menuToPopulate.items.push(menubarSubmenuItem);
|
||||
menuToDispose.dispose();
|
||||
} else {
|
||||
let menubarMenuItem: IMenubarMenuItemAction = {
|
||||
id: menuItem.id,
|
||||
label: menuItem.label
|
||||
};
|
||||
|
||||
if (menuItem.checked) {
|
||||
menubarMenuItem.checked = true;
|
||||
}
|
||||
|
||||
if (!menuItem.enabled) {
|
||||
menubarMenuItem.enabled = false;
|
||||
}
|
||||
|
||||
menubarMenuItem.label = this.calculateActionLabel(menubarMenuItem);
|
||||
keybindings[menuItem.id] = this.getMenubarKeybinding(menuItem.id);
|
||||
menuToPopulate.items.push(menubarMenuItem);
|
||||
}
|
||||
});
|
||||
|
||||
menuToPopulate.items.push({ id: 'vscode.menubar.separator' });
|
||||
}
|
||||
|
||||
if (menuToPopulate.items.length > 0) {
|
||||
menuToPopulate.items.pop();
|
||||
}
|
||||
}
|
||||
|
||||
private getAdditionalKeybindings(): { [id: string]: IMenubarKeybinding } {
|
||||
const keybindings = {};
|
||||
if (isMacintosh) {
|
||||
keybindings['workbench.action.quit'] = (this.getMenubarKeybinding('workbench.action.quit'));
|
||||
}
|
||||
|
||||
return keybindings;
|
||||
}
|
||||
|
||||
private getMenubarMenus(menubarData: IMenubarData): boolean {
|
||||
if (!menubarData) {
|
||||
return false;
|
||||
}
|
||||
|
||||
menubarData.keybindings = this.getAdditionalKeybindings();
|
||||
for (let topLevelMenuName of Object.keys(this.topLevelMenus)) {
|
||||
const menu = this.topLevelMenus[topLevelMenuName];
|
||||
let menubarMenu: IMenubarMenu = { items: [] };
|
||||
this.populateMenuItems(menu, menubarMenu, menubarData.keybindings);
|
||||
if (menubarMenu.items.length === 0) {
|
||||
// Menus are incomplete
|
||||
return false;
|
||||
}
|
||||
menubarData.menus[topLevelMenuName] = menubarMenu;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public get onVisibilityChange(): Event<boolean> {
|
||||
return this._onVisibilityChange.event;
|
||||
}
|
||||
|
||||
public get onFocusStateChange(): Event<boolean> {
|
||||
return this._onFocusStateChange.event;
|
||||
}
|
||||
|
||||
public layout(dimension: DOM.Dimension) {
|
||||
if (this.container) {
|
||||
this.container.style.height = `${dimension.height}px`;
|
||||
}
|
||||
|
||||
if (this.menubar) {
|
||||
this.menubar.update({ enableMnemonics: this.currentEnableMenuBarMnemonics, visibility: this.currentMenubarVisibility, getKeybinding: (action) => this.keybindingService.lookupKeybinding(action.id) });
|
||||
}
|
||||
}
|
||||
|
||||
public getMenubarItemsDimensions(): DOM.Dimension {
|
||||
if (this.menubar) {
|
||||
return new DOM.Dimension(this.menubar.getWidth(), this.menubar.getHeight());
|
||||
}
|
||||
|
||||
return new DOM.Dimension(0, 0);
|
||||
}
|
||||
|
||||
public create(parent: HTMLElement): HTMLElement {
|
||||
this.container = parent;
|
||||
|
||||
// Build the menubar
|
||||
if (this.container) {
|
||||
|
||||
if (!isMacintosh && this.currentTitlebarStyleSetting === 'custom') {
|
||||
this.doUpdateMenubar(true);
|
||||
}
|
||||
}
|
||||
|
||||
return this.container;
|
||||
}
|
||||
}
|
||||
|
||||
registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => {
|
||||
const menubarActiveWindowFgColor = theme.getColor(TITLE_BAR_ACTIVE_FOREGROUND);
|
||||
if (menubarActiveWindowFgColor) {
|
||||
collector.addRule(`
|
||||
.monaco-workbench .menubar > .menubar-menu-button {
|
||||
color: ${menubarActiveWindowFgColor};
|
||||
}
|
||||
|
||||
.monaco-workbench .menubar .toolbar-toggle-more {
|
||||
background-color: ${menubarActiveWindowFgColor}
|
||||
}
|
||||
`);
|
||||
}
|
||||
|
||||
const menubarInactiveWindowFgColor = theme.getColor(TITLE_BAR_INACTIVE_FOREGROUND);
|
||||
if (menubarInactiveWindowFgColor) {
|
||||
collector.addRule(`
|
||||
.monaco-workbench .menubar.inactive > .menubar-menu-button {
|
||||
color: ${menubarInactiveWindowFgColor};
|
||||
}
|
||||
|
||||
.monaco-workbench .menubar.inactive > .menubar-menu-button .toolbar-toggle-more {
|
||||
background-color: ${menubarInactiveWindowFgColor}
|
||||
}
|
||||
`);
|
||||
}
|
||||
|
||||
|
||||
const menubarSelectedFgColor = theme.getColor(MENUBAR_SELECTION_FOREGROUND);
|
||||
if (menubarSelectedFgColor) {
|
||||
collector.addRule(`
|
||||
.monaco-workbench .menubar > .menubar-menu-button.open,
|
||||
.monaco-workbench .menubar > .menubar-menu-button:focus,
|
||||
.monaco-workbench .menubar:not(:focus-within) > .menubar-menu-button:hover {
|
||||
color: ${menubarSelectedFgColor};
|
||||
}
|
||||
|
||||
.monaco-workbench .menubar > .menubar-menu-button.open .toolbar-toggle-more,
|
||||
.monaco-workbench .menubar > .menubar-menu-button:focus .toolbar-toggle-more,
|
||||
.monaco-workbench .menubar:not(:focus-within) > .menubar-menu-button:hover .toolbar-toggle-more {
|
||||
background-color: ${menubarSelectedFgColor}
|
||||
}
|
||||
`);
|
||||
}
|
||||
|
||||
const menubarSelectedBgColor = theme.getColor(MENUBAR_SELECTION_BACKGROUND);
|
||||
if (menubarSelectedBgColor) {
|
||||
collector.addRule(`
|
||||
.monaco-workbench .menubar > .menubar-menu-button.open,
|
||||
.monaco-workbench .menubar > .menubar-menu-button:focus,
|
||||
.monaco-workbench .menubar:not(:focus-within) > .menubar-menu-button:hover {
|
||||
background-color: ${menubarSelectedBgColor};
|
||||
}
|
||||
`);
|
||||
}
|
||||
|
||||
const menubarSelectedBorderColor = theme.getColor(MENUBAR_SELECTION_BORDER);
|
||||
if (menubarSelectedBorderColor) {
|
||||
collector.addRule(`
|
||||
.monaco-workbench .menubar > .menubar-menu-button:hover {
|
||||
outline: dashed 1px;
|
||||
}
|
||||
|
||||
.monaco-workbench .menubar > .menubar-menu-button.open,
|
||||
.monaco-workbench .menubar > .menubar-menu-button:focus {
|
||||
outline: solid 1px;
|
||||
}
|
||||
|
||||
.monaco-workbench .menubar > .menubar-menu-button.open,
|
||||
.monaco-workbench .menubar > .menubar-menu-button:focus,
|
||||
.monaco-workbench .menubar > .menubar-menu-button:hover {
|
||||
outline-offset: -1px;
|
||||
outline-color: ${menubarSelectedBorderColor};
|
||||
}
|
||||
`);
|
||||
}
|
||||
});
|
||||
@@ -3,17 +3,12 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import 'vs/css!./media/titlebarpart';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { Builder, $ } from 'vs/base/browser/builder';
|
||||
import * as paths from 'vs/base/common/paths';
|
||||
import { Part } from 'vs/workbench/browser/part';
|
||||
import { ITitleService, ITitleProperties } from 'vs/workbench/services/title/common/titleService';
|
||||
import { getZoomFactor } from 'vs/base/browser/browser';
|
||||
import { IWindowService, IWindowsService, MenuBarVisibility } from 'vs/platform/windows/common/windows';
|
||||
import * as errors from 'vs/base/common/errors';
|
||||
import { IWindowService, IWindowsService, MenuBarVisibility, getTitleBarStyle } from 'vs/platform/windows/common/windows';
|
||||
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
|
||||
import { StandardMouseEvent } from 'vs/base/browser/mouseEvent';
|
||||
import { IAction, Action } from 'vs/base/common/actions';
|
||||
@@ -27,14 +22,16 @@ import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/
|
||||
import { IThemeService, registerThemingParticipant, ITheme, ICssStyleCollector } 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 { isMacintosh, isWindows, isLinux } from 'vs/base/common/platform';
|
||||
import URI from 'vs/base/common/uri';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { Color } from 'vs/base/common/color';
|
||||
import { trim } from 'vs/base/common/strings';
|
||||
import { addDisposableListener, EventType, EventHelper, Dimension } from 'vs/base/browser/dom';
|
||||
import { MenubarPart } from 'vs/workbench/browser/parts/menubar/menubarPart';
|
||||
import { EventType, EventHelper, Dimension, isAncestor, hide, show, removeClass, addClass, append, $, addDisposableListener } from 'vs/base/browser/dom';
|
||||
import { MenubarControl } from 'vs/workbench/browser/parts/titlebar/menubarControl';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { template, getBaseLabel } from 'vs/base/common/labels';
|
||||
import { IUriDisplayService } from 'vs/platform/uriDisplay/common/uriDisplay';
|
||||
import { ILabelService } from 'vs/platform/label/common/label';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { IStorageService } from 'vs/platform/storage/common/storage';
|
||||
|
||||
export class TitlebarPart extends Part implements ITitleService {
|
||||
|
||||
@@ -46,27 +43,19 @@ export class TitlebarPart extends Part implements ITitleService {
|
||||
private static readonly TITLE_DIRTY = '\u25cf ';
|
||||
private static readonly TITLE_SEPARATOR = isMacintosh ? ' — ' : ' - '; // macOS uses special - separator
|
||||
|
||||
private titleContainer: Builder;
|
||||
private title: Builder;
|
||||
private dragRegion: Builder;
|
||||
private windowControls: Builder;
|
||||
private maxRestoreControl: Builder;
|
||||
private appIcon: Builder;
|
||||
private menubarPart: MenubarPart;
|
||||
private menubar: Builder;
|
||||
private resizer: Builder;
|
||||
private titleContainer: HTMLElement;
|
||||
private title: HTMLElement;
|
||||
private dragRegion: HTMLElement;
|
||||
private windowControls: HTMLElement;
|
||||
private maxRestoreControl: HTMLElement;
|
||||
private appIcon: HTMLElement;
|
||||
private menubarPart: MenubarControl;
|
||||
private menubar: HTMLElement;
|
||||
private resizer: HTMLElement;
|
||||
|
||||
private pendingTitle: string;
|
||||
private representedFileName: string;
|
||||
|
||||
private initialSizing: {
|
||||
titleFontSize?: number;
|
||||
titlebarHeight?: number;
|
||||
controlsWidth?: number;
|
||||
appIconSize?: number;
|
||||
appIconWidth?: number;
|
||||
} = Object.create(null);
|
||||
|
||||
private isInactive: boolean;
|
||||
|
||||
private properties: ITitleProperties;
|
||||
@@ -83,9 +72,10 @@ export class TitlebarPart extends Part implements ITitleService {
|
||||
@IWorkspaceContextService private contextService: IWorkspaceContextService,
|
||||
@IInstantiationService private instantiationService: IInstantiationService,
|
||||
@IThemeService themeService: IThemeService,
|
||||
@IUriDisplayService private uriDisplayService: IUriDisplayService
|
||||
@ILabelService private labelService: ILabelService,
|
||||
@IStorageService storageService: IStorageService
|
||||
) {
|
||||
super(id, { hasTitle: false }, themeService);
|
||||
super(id, { hasTitle: false }, themeService, storageService);
|
||||
|
||||
this.properties = { isPure: true, isAdmin: false };
|
||||
this.activeEditorListeners = [];
|
||||
@@ -94,13 +84,13 @@ export class TitlebarPart extends Part implements ITitleService {
|
||||
}
|
||||
|
||||
private registerListeners(): void {
|
||||
this._register(addDisposableListener(window, EventType.BLUR, () => this.onBlur()));
|
||||
this._register(addDisposableListener(window, EventType.FOCUS, () => this.onFocus()));
|
||||
this._register(this.windowService.onDidChangeFocus(focused => focused ? this.onFocus() : this.onBlur()));
|
||||
this._register(this.configurationService.onDidChangeConfiguration(e => this.onConfigurationChanged(e)));
|
||||
this._register(this.editorService.onDidActiveEditorChange(() => this.onActiveEditorChange()));
|
||||
this._register(this.contextService.onDidChangeWorkspaceFolders(() => this.setTitle(this.getWindowTitle())));
|
||||
this._register(this.contextService.onDidChangeWorkbenchState(() => this.setTitle(this.getWindowTitle())));
|
||||
this._register(this.contextService.onDidChangeWorkspaceName(() => this.setTitle(this.getWindowTitle())));
|
||||
this._register(this.contextService.onDidChangeWorkspaceFolders(() => this.doUpdateTitle()));
|
||||
this._register(this.contextService.onDidChangeWorkbenchState(() => this.doUpdateTitle()));
|
||||
this._register(this.contextService.onDidChangeWorkspaceName(() => this.doUpdateTitle()));
|
||||
this._register(this.labelService.onDidRegisterFormatter(() => this.doUpdateTitle()));
|
||||
}
|
||||
|
||||
private onBlur(): void {
|
||||
@@ -115,7 +105,7 @@ export class TitlebarPart extends Part implements ITitleService {
|
||||
|
||||
private onConfigurationChanged(event: IConfigurationChangeEvent): void {
|
||||
if (event.affectsConfiguration('window.title')) {
|
||||
this.setTitle(this.getWindowTitle());
|
||||
this.doUpdateTitle();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -123,17 +113,29 @@ export class TitlebarPart extends Part implements ITitleService {
|
||||
if (isWindows || isLinux) {
|
||||
// Hide title when toggling menu bar
|
||||
if (this.configurationService.getValue<MenuBarVisibility>('window.menuBarVisibility') === 'toggle' && visible) {
|
||||
this.title.style('visibility', 'hidden');
|
||||
|
||||
// Hack to fix issue #52522 with layered webkit-app-region elements appearing under cursor
|
||||
this.dragRegion.hide();
|
||||
this.dragRegion.showDelayed(50);
|
||||
hide(this.dragRegion);
|
||||
setTimeout(() => show(this.dragRegion), 50);
|
||||
}
|
||||
|
||||
this.adjustTitleMarginToCenter();
|
||||
}
|
||||
}
|
||||
|
||||
private onMenubarFocusChanged(focused: boolean) {
|
||||
if (isWindows || isLinux) {
|
||||
if (focused) {
|
||||
hide(this.dragRegion);
|
||||
} else {
|
||||
this.title.style('visibility', null);
|
||||
show(this.dragRegion);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onMenubarVisibilityChange(): Event<boolean> {
|
||||
return this.menubarPart.onVisibilityChange;
|
||||
}
|
||||
|
||||
private onActiveEditorChange(): void {
|
||||
|
||||
// Dispose old listeners
|
||||
@@ -141,18 +143,13 @@ export class TitlebarPart extends Part implements ITitleService {
|
||||
this.activeEditorListeners = [];
|
||||
|
||||
// Calculate New Window Title
|
||||
this.setTitle(this.getWindowTitle());
|
||||
this.doUpdateTitle();
|
||||
|
||||
// Apply listener for dirty and label changes
|
||||
const activeEditor = this.editorService.activeEditor;
|
||||
if (activeEditor instanceof EditorInput) {
|
||||
this.activeEditorListeners.push(activeEditor.onDidChangeDirty(() => {
|
||||
this.setTitle(this.getWindowTitle());
|
||||
}));
|
||||
|
||||
this.activeEditorListeners.push(activeEditor.onDidChangeLabel(() => {
|
||||
this.setTitle(this.getWindowTitle());
|
||||
}));
|
||||
this.activeEditorListeners.push(activeEditor.onDidChangeDirty(() => this.doUpdateTitle()));
|
||||
this.activeEditorListeners.push(activeEditor.onDidChangeLabel(() => this.doUpdateTitle()));
|
||||
}
|
||||
|
||||
// Represented File Name
|
||||
@@ -170,23 +167,41 @@ export class TitlebarPart extends Part implements ITitleService {
|
||||
this.representedFileName = path;
|
||||
}
|
||||
|
||||
private getWindowTitle(): string {
|
||||
let title = this.doGetWindowTitle();
|
||||
if (!trim(title)) {
|
||||
title = this.environmentService.appNameLong;
|
||||
private doUpdateTitle(): void {
|
||||
const title = this.getWindowTitle();
|
||||
|
||||
// Always set the native window title to identify us properly to the OS
|
||||
let nativeTitle = title;
|
||||
if (!trim(nativeTitle)) {
|
||||
nativeTitle = this.environmentService.appNameLong;
|
||||
}
|
||||
window.document.title = nativeTitle;
|
||||
|
||||
// Apply custom title if we can
|
||||
if (this.title) {
|
||||
this.title.innerText = title;
|
||||
} else {
|
||||
this.pendingTitle = title;
|
||||
}
|
||||
|
||||
if (isWindows || isLinux) {
|
||||
this.adjustTitleMarginToCenter();
|
||||
}
|
||||
}
|
||||
|
||||
private getWindowTitle(): string {
|
||||
let title = this.doGetWindowTitle();
|
||||
|
||||
if (this.properties.isAdmin) {
|
||||
title = `${title} ${TitlebarPart.NLS_USER_IS_ADMIN}`;
|
||||
title = `${title || this.environmentService.appNameLong} ${TitlebarPart.NLS_USER_IS_ADMIN}`;
|
||||
}
|
||||
|
||||
if (!this.properties.isPure) {
|
||||
title = `${title} ${TitlebarPart.NLS_UNSUPPORTED}`;
|
||||
title = `${title || this.environmentService.appNameLong} ${TitlebarPart.NLS_UNSUPPORTED}`;
|
||||
}
|
||||
|
||||
// Extension Development Host gets a special title to identify itself
|
||||
if (this.environmentService.isExtensionDevelopment) {
|
||||
title = `${TitlebarPart.NLS_EXTENSION_HOST} - ${title}`;
|
||||
title = `${TitlebarPart.NLS_EXTENSION_HOST} - ${title || this.environmentService.appNameLong}`;
|
||||
}
|
||||
|
||||
return title;
|
||||
@@ -200,7 +215,7 @@ export class TitlebarPart extends Part implements ITitleService {
|
||||
this.properties.isAdmin = isAdmin;
|
||||
this.properties.isPure = isPure;
|
||||
|
||||
this.setTitle(this.getWindowTitle());
|
||||
this.doUpdateTitle();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -238,10 +253,10 @@ export class TitlebarPart extends Part implements ITitleService {
|
||||
const activeEditorShort = editor ? editor.getTitle(Verbosity.SHORT) : '';
|
||||
const activeEditorMedium = editor ? editor.getTitle(Verbosity.MEDIUM) : activeEditorShort;
|
||||
const activeEditorLong = editor ? editor.getTitle(Verbosity.LONG) : activeEditorMedium;
|
||||
const rootName = workspace.name;
|
||||
const rootPath = root ? this.uriDisplayService.getLabel(root) : '';
|
||||
const rootName = this.labelService.getWorkspaceLabel(workspace);
|
||||
const rootPath = root ? this.labelService.getUriLabel(root) : '';
|
||||
const folderName = folder ? folder.name : '';
|
||||
const folderPath = folder ? this.uriDisplayService.getLabel(folder.uri) : '';
|
||||
const folderPath = folder ? this.labelService.getUriLabel(folder.uri) : '';
|
||||
const dirty = editor && editor.isDirty() ? TitlebarPart.TITLE_DIRTY : '';
|
||||
const appName = this.environmentService.appNameLong;
|
||||
const separator = TitlebarPart.TITLE_SEPARATOR;
|
||||
@@ -262,83 +277,94 @@ export class TitlebarPart extends Part implements ITitleService {
|
||||
}
|
||||
|
||||
createContentArea(parent: HTMLElement): HTMLElement {
|
||||
this.titleContainer = $(parent);
|
||||
this.titleContainer = parent;
|
||||
|
||||
// Draggable region that we can manipulate for #52522
|
||||
this.dragRegion = $(this.titleContainer).div({ class: 'titlebar-drag-region' });
|
||||
this.dragRegion = append(this.titleContainer, $('div.titlebar-drag-region'));
|
||||
|
||||
// App Icon (Windows/Linux)
|
||||
if (!isMacintosh) {
|
||||
this.appIcon = $(this.titleContainer).div({ class: 'window-appicon' });
|
||||
this.appIcon = append(this.titleContainer, $('div.window-appicon'));
|
||||
}
|
||||
|
||||
// Menubar: the menubar part which is responsible for populating both the custom and native menubars
|
||||
this.menubarPart = this.instantiationService.createInstance(MenubarPart, 'workbench.parts.menubar');
|
||||
this.menubar = $(this.titleContainer).div({
|
||||
'class': ['part', 'menubar'],
|
||||
id: 'workbench.parts.menubar',
|
||||
role: 'menubar'
|
||||
});
|
||||
this.menubarPart = this.instantiationService.createInstance(MenubarControl);
|
||||
this.menubar = append(this.titleContainer, $('div.menubar'));
|
||||
this.menubar.setAttribute('role', 'menubar');
|
||||
|
||||
this.menubarPart.create(this.menubar.getHTMLElement());
|
||||
this.menubarPart.create(this.menubar);
|
||||
|
||||
if (!isMacintosh) {
|
||||
this._register(this.menubarPart.onVisibilityChange(e => this.onMenubarVisibilityChanged(e)));
|
||||
this._register(this.menubarPart.onFocusStateChange(e => this.onMenubarFocusChanged(e)));
|
||||
}
|
||||
|
||||
// Title
|
||||
this.title = $(this.titleContainer).div({ class: 'window-title' });
|
||||
this.title = append(this.titleContainer, $('div.window-title'));
|
||||
if (this.pendingTitle) {
|
||||
this.title.text(this.pendingTitle);
|
||||
this.title.innerText = this.pendingTitle;
|
||||
} else {
|
||||
this.setTitle(this.getWindowTitle());
|
||||
this.doUpdateTitle();
|
||||
}
|
||||
|
||||
// Maximize/Restore on doubleclick
|
||||
if (isMacintosh) {
|
||||
this.titleContainer.on(EventType.DBLCLICK, (e) => {
|
||||
this._register(addDisposableListener(this.titleContainer, EventType.DBLCLICK, e => {
|
||||
EventHelper.stop(e);
|
||||
|
||||
this.onTitleDoubleclick();
|
||||
});
|
||||
}));
|
||||
}
|
||||
|
||||
// Context menu on title
|
||||
this.title.on([EventType.CONTEXT_MENU, EventType.MOUSE_DOWN], (e: MouseEvent) => {
|
||||
if (e.type === EventType.CONTEXT_MENU || e.metaKey) {
|
||||
EventHelper.stop(e);
|
||||
[EventType.CONTEXT_MENU, EventType.MOUSE_DOWN].forEach(event => {
|
||||
this._register(addDisposableListener(this.title, event, e => {
|
||||
if (e.type === EventType.CONTEXT_MENU || e.metaKey) {
|
||||
EventHelper.stop(e);
|
||||
|
||||
this.onContextMenu(e);
|
||||
}
|
||||
this.onContextMenu(e);
|
||||
}
|
||||
}));
|
||||
});
|
||||
|
||||
// Window Controls (Windows/Linux)
|
||||
if (!isMacintosh) {
|
||||
this.windowControls = $(this.titleContainer).div({ class: 'window-controls-container' });
|
||||
this.windowControls = append(this.titleContainer, $('div.window-controls-container'));
|
||||
|
||||
|
||||
// Minimize
|
||||
$($(this.windowControls).div({ class: 'window-icon-bg' })).div({ class: 'window-icon window-minimize' }).on(EventType.CLICK, () => {
|
||||
this.windowService.minimizeWindow().then(null, errors.onUnexpectedError);
|
||||
});
|
||||
const minimizeIconContainer = append(this.windowControls, $('div.window-icon-bg'));
|
||||
const minimizeIcon = append(minimizeIconContainer, $('div.window-icon'));
|
||||
addClass(minimizeIcon, 'window-minimize');
|
||||
this._register(addDisposableListener(minimizeIcon, EventType.CLICK, e => {
|
||||
this.windowService.minimizeWindow();
|
||||
}));
|
||||
|
||||
// Restore
|
||||
this.maxRestoreControl = $($(this.windowControls).div({ class: 'window-icon-bg' })).div({ class: 'window-icon window-max-restore' }).on(EventType.CLICK, () => {
|
||||
const restoreIconContainer = append(this.windowControls, $('div.window-icon-bg'));
|
||||
this.maxRestoreControl = append(restoreIconContainer, $('div.window-icon'));
|
||||
addClass(this.maxRestoreControl, 'window-max-restore');
|
||||
this._register(addDisposableListener(this.maxRestoreControl, EventType.CLICK, e => {
|
||||
this.windowService.isMaximized().then((maximized) => {
|
||||
if (maximized) {
|
||||
return this.windowService.unmaximizeWindow();
|
||||
}
|
||||
|
||||
return this.windowService.maximizeWindow();
|
||||
}).then(null, errors.onUnexpectedError);
|
||||
});
|
||||
});
|
||||
}));
|
||||
|
||||
// Close
|
||||
$($(this.windowControls).div({ class: 'window-icon-bg window-close-bg' })).div({ class: 'window-icon window-close' }).on(EventType.CLICK, () => {
|
||||
this.windowService.closeWindow().then(null, errors.onUnexpectedError);
|
||||
});
|
||||
const closeIconContainer = append(this.windowControls, $('div.window-icon-bg'));
|
||||
addClass(closeIconContainer, 'window-close-bg');
|
||||
const closeIcon = append(closeIconContainer, $('div.window-icon'));
|
||||
addClass(closeIcon, 'window-close');
|
||||
this._register(addDisposableListener(closeIcon, EventType.CLICK, e => {
|
||||
this.windowService.closeWindow();
|
||||
}));
|
||||
|
||||
// Resizer
|
||||
this.resizer = $(this.titleContainer).div({ class: 'resizer' });
|
||||
this.resizer = append(this.titleContainer, $('div.resizer'));
|
||||
|
||||
const isMaximized = this.windowService.getConfiguration().maximized ? true : false;
|
||||
this.onDidChangeMaximized(isMaximized);
|
||||
@@ -347,36 +373,44 @@ 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([EventType.MOUSE_DOWN], () => {
|
||||
this._register(addDisposableListener(this.titleContainer, EventType.MOUSE_DOWN, e => {
|
||||
if (e.target && isAncestor(e.target as HTMLElement, this.menubar)) {
|
||||
return;
|
||||
}
|
||||
|
||||
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 */);
|
||||
}, true /* use capture to know the currently active element properly */));
|
||||
|
||||
return this.titleContainer.getHTMLElement();
|
||||
this.updateStyles();
|
||||
|
||||
return this.titleContainer;
|
||||
}
|
||||
|
||||
private onDidChangeMaximized(maximized: boolean) {
|
||||
if (this.maxRestoreControl) {
|
||||
if (maximized) {
|
||||
this.maxRestoreControl.removeClass('window-maximize');
|
||||
this.maxRestoreControl.addClass('window-unmaximize');
|
||||
removeClass(this.maxRestoreControl, 'window-maximize');
|
||||
addClass(this.maxRestoreControl, 'window-unmaximize');
|
||||
} else {
|
||||
this.maxRestoreControl.removeClass('window-unmaximize');
|
||||
this.maxRestoreControl.addClass('window-maximize');
|
||||
removeClass(this.maxRestoreControl, 'window-unmaximize');
|
||||
addClass(this.maxRestoreControl, 'window-maximize');
|
||||
}
|
||||
}
|
||||
|
||||
if (this.resizer) {
|
||||
if (maximized) {
|
||||
this.resizer.hide();
|
||||
hide(this.resizer);
|
||||
} else {
|
||||
this.resizer.show();
|
||||
show(this.resizer);
|
||||
}
|
||||
}
|
||||
|
||||
this.adjustTitleMarginToCenter();
|
||||
}
|
||||
|
||||
protected updateStyles(): void {
|
||||
@@ -385,29 +419,29 @@ export class TitlebarPart extends Part implements ITitleService {
|
||||
// Part container
|
||||
if (this.titleContainer) {
|
||||
if (this.isInactive) {
|
||||
this.titleContainer.addClass('inactive');
|
||||
addClass(this.titleContainer, 'inactive');
|
||||
} else {
|
||||
this.titleContainer.removeClass('inactive');
|
||||
removeClass(this.titleContainer, 'inactive');
|
||||
}
|
||||
|
||||
const titleBackground = this.getColor(this.isInactive ? TITLE_BAR_INACTIVE_BACKGROUND : TITLE_BAR_ACTIVE_BACKGROUND);
|
||||
this.titleContainer.style('background-color', titleBackground);
|
||||
this.titleContainer.style.backgroundColor = titleBackground;
|
||||
if (Color.fromHex(titleBackground).isLighter()) {
|
||||
this.titleContainer.addClass('light');
|
||||
addClass(this.titleContainer, 'light');
|
||||
} else {
|
||||
this.titleContainer.removeClass('light');
|
||||
removeClass(this.titleContainer, 'light');
|
||||
}
|
||||
|
||||
const titleForeground = this.getColor(this.isInactive ? TITLE_BAR_INACTIVE_FOREGROUND : TITLE_BAR_ACTIVE_FOREGROUND);
|
||||
this.titleContainer.style('color', titleForeground);
|
||||
this.titleContainer.style.color = titleForeground;
|
||||
|
||||
const titleBorder = this.getColor(TITLE_BAR_BORDER);
|
||||
this.titleContainer.style('border-bottom', titleBorder ? `1px solid ${titleBorder}` : null);
|
||||
this.titleContainer.style.borderBottom = titleBorder ? `1px solid ${titleBorder}` : null;
|
||||
}
|
||||
}
|
||||
|
||||
private onTitleDoubleclick(): void {
|
||||
this.windowService.onWindowTitleDoubleClick().then(null, errors.onUnexpectedError);
|
||||
this.windowService.onWindowTitleDoubleClick();
|
||||
}
|
||||
|
||||
private onContextMenu(e: MouseEvent): void {
|
||||
@@ -421,7 +455,7 @@ export class TitlebarPart extends Part implements ITitleService {
|
||||
if (actions.length) {
|
||||
this.contextMenuService.showContextMenu({
|
||||
getAnchor: () => anchor,
|
||||
getActions: () => TPromise.as(actions),
|
||||
getActions: () => actions,
|
||||
onHide: () => actions.forEach(a => a.dispose())
|
||||
});
|
||||
}
|
||||
@@ -456,97 +490,47 @@ export class TitlebarPart extends Part implements ITitleService {
|
||||
return actions;
|
||||
}
|
||||
|
||||
setTitle(title: string): void {
|
||||
|
||||
// Always set the native window title to identify us properly to the OS
|
||||
window.document.title = title;
|
||||
|
||||
// Apply if we can
|
||||
if (this.title) {
|
||||
this.title.text(title);
|
||||
} else {
|
||||
this.pendingTitle = title;
|
||||
}
|
||||
}
|
||||
|
||||
private updateLayout(dimension: Dimension) {
|
||||
// Store initital title sizing if we need to prevent zooming
|
||||
if (typeof this.initialSizing.titleFontSize !== 'number') {
|
||||
this.initialSizing.titleFontSize = parseInt(this.title.getComputedStyle().fontSize, 10);
|
||||
}
|
||||
|
||||
if (typeof this.initialSizing.titlebarHeight !== 'number') {
|
||||
this.initialSizing.titlebarHeight = parseInt(this.title.getComputedStyle().height, 10);
|
||||
}
|
||||
|
||||
// Only prevent zooming behavior on macOS or when the menubar is not visible
|
||||
if (isMacintosh || this.configurationService.getValue<MenuBarVisibility>('window.menuBarVisibility') === 'hidden') {
|
||||
// To prevent zooming we need to adjust the font size with the zoom factor
|
||||
const newHeight = this.initialSizing.titlebarHeight / getZoomFactor();
|
||||
this.title.style({
|
||||
fontSize: `${this.initialSizing.titleFontSize / getZoomFactor()}px`,
|
||||
'line-height': `${newHeight}px`
|
||||
});
|
||||
|
||||
// Windows/Linux specific layout
|
||||
if (isWindows || isLinux) {
|
||||
if (typeof this.initialSizing.controlsWidth !== 'number') {
|
||||
this.initialSizing.controlsWidth = parseInt(this.windowControls.getComputedStyle().width, 10);
|
||||
}
|
||||
|
||||
if (typeof this.initialSizing.appIconWidth !== 'number') {
|
||||
this.initialSizing.appIconWidth = parseInt(this.appIcon.getComputedStyle().width, 10);
|
||||
}
|
||||
|
||||
if (typeof this.initialSizing.appIconSize !== 'number') {
|
||||
this.initialSizing.appIconSize = parseInt(this.appIcon.getComputedStyle().backgroundSize, 10);
|
||||
}
|
||||
|
||||
const currentAppIconHeight = parseInt(this.appIcon.getComputedStyle().height, 10);
|
||||
const newControlsWidth = this.initialSizing.controlsWidth / getZoomFactor();
|
||||
const newAppIconWidth = this.initialSizing.appIconWidth / getZoomFactor();
|
||||
const newAppIconSize = this.initialSizing.appIconSize / getZoomFactor();
|
||||
|
||||
// Adjust app icon mimic menubar
|
||||
this.appIcon.style({
|
||||
'width': `${newAppIconWidth}px`,
|
||||
'background-size': `${newAppIconSize}px`,
|
||||
'padding-top': `${(newHeight - currentAppIconHeight) / 2.0}px`,
|
||||
'padding-bottom': `${(newHeight - currentAppIconHeight) / 2.0}px`
|
||||
});
|
||||
|
||||
// Adjust windows controls
|
||||
this.windowControls.style({
|
||||
'width': `${newControlsWidth}px`
|
||||
});
|
||||
private adjustTitleMarginToCenter(): void {
|
||||
setTimeout(() => {
|
||||
// Cannot center
|
||||
if (!isMacintosh &&
|
||||
(this.appIcon.clientWidth + this.menubar.clientWidth + 10 > (this.titleContainer.clientWidth - this.title.clientWidth) / 2 ||
|
||||
this.titleContainer.clientWidth - this.windowControls.clientWidth - 10 < (this.titleContainer.clientWidth + this.title.clientWidth) / 2)) {
|
||||
this.title.style.position = null;
|
||||
this.title.style.left = null;
|
||||
this.title.style.transform = null;
|
||||
} else {
|
||||
this.title.style.position = 'absolute';
|
||||
this.title.style.left = '50%';
|
||||
this.title.style.transform = 'translate(-50%, 0)';
|
||||
}
|
||||
} else {
|
||||
// We need to undo zoom prevention
|
||||
this.title.style({
|
||||
fontSize: null,
|
||||
'line-height': null
|
||||
});
|
||||
|
||||
this.appIcon.style({
|
||||
'width': null,
|
||||
'background-size': null,
|
||||
'padding-top': null,
|
||||
'padding-bottom': null
|
||||
});
|
||||
|
||||
this.windowControls.style({
|
||||
'width': null
|
||||
});
|
||||
}
|
||||
|
||||
if (this.menubarPart) {
|
||||
const menubarDimension = new Dimension(undefined, dimension.height);
|
||||
this.menubarPart.layout(menubarDimension);
|
||||
}
|
||||
}, 0); // delay so that we can get accurate information about widths
|
||||
}
|
||||
|
||||
layout(dimension: Dimension): Dimension[] {
|
||||
this.updateLayout(dimension);
|
||||
if (getTitleBarStyle(this.configurationService, this.environmentService) === 'custom') {
|
||||
// Only prevent zooming behavior on macOS or when the menubar is not visible
|
||||
if (isMacintosh || this.configurationService.getValue<MenuBarVisibility>('window.menuBarVisibility') === 'hidden') {
|
||||
this.title.style.zoom = `${1.0 / getZoomFactor()}`;
|
||||
if (isWindows || isLinux) {
|
||||
this.appIcon.style.zoom = `${1.0 / getZoomFactor()}`;
|
||||
this.windowControls.style.zoom = `${1.0 / getZoomFactor()}`;
|
||||
}
|
||||
} else {
|
||||
this.title.style.zoom = null;
|
||||
if (isWindows || isLinux) {
|
||||
this.appIcon.style.zoom = null;
|
||||
this.windowControls.style.zoom = null;
|
||||
}
|
||||
}
|
||||
|
||||
this.adjustTitleMarginToCenter();
|
||||
|
||||
if (this.menubarPart) {
|
||||
const menubarDimension = new Dimension(undefined, dimension.height);
|
||||
this.menubarPart.layout(menubarDimension);
|
||||
}
|
||||
}
|
||||
|
||||
return super.layout(dimension);
|
||||
}
|
||||
@@ -558,7 +542,7 @@ class ShowItemInFolderAction extends Action {
|
||||
super('showItemInFolder.action.id', label);
|
||||
}
|
||||
|
||||
run(): TPromise<void> {
|
||||
run(): Thenable<void> {
|
||||
return this.windowsService.showItemInFolder(this.path);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,85 +7,93 @@ import 'vs/css!./media/views';
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import { IDisposable, dispose, Disposable, toDisposable } from 'vs/base/common/lifecycle';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { IAction, IActionItem, ActionRunner } from 'vs/base/common/actions';
|
||||
import { IAction, IActionItem, ActionRunner, Action } from 'vs/base/common/actions';
|
||||
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
||||
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
|
||||
import { IMenuService, MenuId, MenuItemAction } from 'vs/platform/actions/common/actions';
|
||||
import { ContextAwareMenuItemActionItem, fillInActionBarActions, fillInContextMenuActions } from 'vs/platform/actions/browser/menuItemActionItem';
|
||||
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { IViewsService, ITreeViewer, ITreeItem, TreeItemCollapsibleState, ITreeViewDataProvider, TreeViewItemHandleArg, ICustomViewDescriptor, ViewsRegistry, ViewContainer } from 'vs/workbench/common/views';
|
||||
import { IViewsService, ITreeView, ITreeItem, TreeItemCollapsibleState, ITreeViewDataProvider, TreeViewItemHandleArg, ICustomViewDescriptor, ViewsRegistry, ViewContainer, ITreeItemLabel } from 'vs/workbench/common/views';
|
||||
import { IViewletViewOptions, FileIconThemableWorkbenchTree } from 'vs/workbench/browser/parts/views/viewsViewlet';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { INotificationService } from 'vs/platform/notification/common/notification';
|
||||
import { IProgressService2 } from 'vs/workbench/services/progress/common/progress';
|
||||
import { IProgressService2 } from 'vs/platform/progress/common/progress';
|
||||
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
|
||||
import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService';
|
||||
import { ICommandService } from 'vs/platform/commands/common/commands';
|
||||
import * as DOM from 'vs/base/browser/dom';
|
||||
import * as errors from 'vs/base/common/errors';
|
||||
import { isUndefinedOrNull } from 'vs/base/common/types';
|
||||
import { IDataSource, ITree, IRenderer, ContextMenuEvent } from 'vs/base/parts/tree/browser/tree';
|
||||
import { ResourceLabel } from 'vs/workbench/browser/labels';
|
||||
import { ActionBar, IActionItemProvider, ActionItem } from 'vs/base/browser/ui/actionbar/actionbar';
|
||||
import URI from 'vs/base/common/uri';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { basename } from 'vs/base/common/paths';
|
||||
import { LIGHT, FileThemeIcon, FolderThemeIcon } from 'vs/platform/theme/common/themeService';
|
||||
import { LIGHT, FileThemeIcon, FolderThemeIcon, registerThemingParticipant } from 'vs/platform/theme/common/themeService';
|
||||
import { FileKind } from 'vs/platform/files/common/files';
|
||||
import { WorkbenchTreeController } from 'vs/platform/list/browser/listService';
|
||||
import { ViewletPanel, IViewletPanelOptions } from 'vs/workbench/browser/parts/views/panelViewlet';
|
||||
import { IMouseEvent } from 'vs/base/browser/mouseEvent';
|
||||
import { localize } from 'vs/nls';
|
||||
import { timeout } from 'vs/base/common/async';
|
||||
import { CollapseAllAction } from 'vs/base/parts/tree/browser/treeDefaults';
|
||||
import { editorFindMatchHighlight, editorFindMatchHighlightBorder, textLinkForeground, textCodeBlockBackground, focusBorder } from 'vs/platform/theme/common/colorRegistry';
|
||||
import { IMarkdownString } from 'vs/base/common/htmlContent';
|
||||
import { isString } from 'vs/base/common/types';
|
||||
import { renderMarkdown, RenderOptions } from 'vs/base/browser/htmlContentRenderer';
|
||||
import { onUnexpectedError } from 'vs/base/common/errors';
|
||||
import { IOpenerService } from 'vs/platform/opener/common/opener';
|
||||
import { IMarkdownRenderResult } from 'vs/editor/contrib/markdown/markdownRenderer';
|
||||
import { ILabelService } from 'vs/platform/label/common/label';
|
||||
import { dirname } from 'vs/base/common/resources';
|
||||
|
||||
export class CustomTreeViewPanel extends ViewletPanel {
|
||||
|
||||
private menus: TitleMenus;
|
||||
private treeViewer: ITreeViewer;
|
||||
private treeView: ITreeView;
|
||||
|
||||
constructor(
|
||||
options: IViewletViewOptions,
|
||||
@INotificationService private notificationService: INotificationService,
|
||||
@IKeybindingService keybindingService: IKeybindingService,
|
||||
@IContextMenuService contextMenuService: IContextMenuService,
|
||||
@IInstantiationService private instantiationService: IInstantiationService,
|
||||
@IConfigurationService configurationService: IConfigurationService,
|
||||
@IViewsService viewsService: IViewsService,
|
||||
) {
|
||||
super({ ...(options as IViewletPanelOptions), ariaHeaderLabel: options.title }, keybindingService, contextMenuService, configurationService);
|
||||
this.treeViewer = (<ICustomViewDescriptor>ViewsRegistry.getView(options.id)).treeViewer;
|
||||
this.disposables.push(toDisposable(() => this.treeViewer.setVisibility(false)));
|
||||
this.menus = this.instantiationService.createInstance(TitleMenus, this.id);
|
||||
this.menus.onDidChangeTitle(() => this.updateActions(), this, this.disposables);
|
||||
const { treeView } = (<ICustomViewDescriptor>ViewsRegistry.getView(options.id));
|
||||
this.treeView = treeView;
|
||||
this.treeView.onDidChangeActions(() => this.updateActions(), this, this.disposables);
|
||||
this.disposables.push(toDisposable(() => this.treeView.setVisibility(false)));
|
||||
this.updateTreeVisibility();
|
||||
}
|
||||
|
||||
setVisible(visible: boolean): TPromise<void> {
|
||||
return super.setVisible(visible).then(() => this.updateTreeVisibility());
|
||||
setVisible(visible: boolean): void {
|
||||
super.setVisible(visible);
|
||||
this.updateTreeVisibility();
|
||||
}
|
||||
|
||||
focus(): void {
|
||||
super.focus();
|
||||
this.treeViewer.focus();
|
||||
this.treeView.focus();
|
||||
}
|
||||
|
||||
renderBody(container: HTMLElement): void {
|
||||
this.treeViewer.show(container);
|
||||
this.treeView.show(container);
|
||||
}
|
||||
|
||||
setExpanded(expanded: boolean): void {
|
||||
this.treeViewer.setVisibility(this.isVisible() && expanded);
|
||||
this.treeView.setVisibility(this.isVisible() && expanded);
|
||||
super.setExpanded(expanded);
|
||||
}
|
||||
|
||||
layoutBody(size: number): void {
|
||||
this.treeViewer.layout(size);
|
||||
this.treeView.layout(size);
|
||||
}
|
||||
|
||||
getActions(): IAction[] {
|
||||
return [...this.menus.getTitleActions()];
|
||||
return [...this.treeView.getPrimaryActions()];
|
||||
}
|
||||
|
||||
getSecondaryActions(): IAction[] {
|
||||
return this.menus.getTitleSecondaryActions();
|
||||
return [...this.treeView.getSecondaryActions()];
|
||||
}
|
||||
|
||||
getActionItem(action: IAction): IActionItem {
|
||||
@@ -93,11 +101,11 @@ export class CustomTreeViewPanel extends ViewletPanel {
|
||||
}
|
||||
|
||||
getOptimalWidth(): number {
|
||||
return this.treeViewer.getOptimalWidth();
|
||||
return this.treeView.getOptimalWidth();
|
||||
}
|
||||
|
||||
private updateTreeVisibility(): void {
|
||||
this.treeViewer.setVisibility(this.isVisible() && this.isExpanded());
|
||||
this.treeView.setVisibility(this.isVisible() && this.isExpanded());
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
@@ -163,26 +171,35 @@ class TitleMenus implements IDisposable {
|
||||
}
|
||||
|
||||
class Root implements ITreeItem {
|
||||
label = 'root';
|
||||
label = { label: 'root' };
|
||||
handle = '0';
|
||||
parentHandle = null;
|
||||
collapsibleState = TreeItemCollapsibleState.Expanded;
|
||||
children = void 0;
|
||||
}
|
||||
|
||||
export class CustomTreeViewer extends Disposable implements ITreeViewer {
|
||||
const noDataProviderMessage = localize('no-dataprovider', "There is no data provider registered that can provide view data.");
|
||||
|
||||
export class CustomTreeView extends Disposable implements ITreeView {
|
||||
|
||||
private isVisible: boolean = false;
|
||||
private activated: boolean = false;
|
||||
private _hasIconForParentNode = false;
|
||||
private _hasIconForLeafNode = false;
|
||||
private _showCollapseAllAction = false;
|
||||
|
||||
private focused: boolean = false;
|
||||
private domNode: HTMLElement;
|
||||
private treeContainer: HTMLElement;
|
||||
private _messageValue: string | IMarkdownString | undefined;
|
||||
private messageElement: HTMLDivElement;
|
||||
private tree: FileIconThemableWorkbenchTree;
|
||||
private root: ITreeItem;
|
||||
private elementsToRefresh: ITreeItem[] = [];
|
||||
private menus: TitleMenus;
|
||||
|
||||
private _dataProvider: ITreeViewDataProvider;
|
||||
private markdownRenderer: MarkdownRenderer;
|
||||
private markdownResult: IMarkdownRenderResult;
|
||||
|
||||
private _onDidExpandItem: Emitter<ITreeItem> = this._register(new Emitter<ITreeItem>());
|
||||
readonly onDidExpandItem: Event<ITreeItem> = this._onDidExpandItem.event;
|
||||
@@ -196,6 +213,9 @@ export class CustomTreeViewer extends Disposable implements ITreeViewer {
|
||||
private _onDidChangeVisibility: Emitter<boolean> = this._register(new Emitter<boolean>());
|
||||
readonly onDidChangeVisibility: Event<boolean> = this._onDidChangeVisibility.event;
|
||||
|
||||
private _onDidChangeActions: Emitter<void> = this._register(new Emitter<void>());
|
||||
readonly onDidChangeActions: Event<void> = this._onDidChangeActions.event;
|
||||
|
||||
constructor(
|
||||
private id: string,
|
||||
private container: ViewContainer,
|
||||
@@ -203,10 +223,13 @@ export class CustomTreeViewer extends Disposable implements ITreeViewer {
|
||||
@IWorkbenchThemeService private themeService: IWorkbenchThemeService,
|
||||
@IInstantiationService private instantiationService: IInstantiationService,
|
||||
@ICommandService private commandService: ICommandService,
|
||||
@IConfigurationService private configurationService: IConfigurationService
|
||||
@IConfigurationService private configurationService: IConfigurationService,
|
||||
@IProgressService2 private progressService: IProgressService2
|
||||
) {
|
||||
super();
|
||||
this.root = new Root();
|
||||
this.menus = this._register(instantiationService.createInstance(TitleMenus, this.id));
|
||||
this._register(this.menus.onDidChangeTitle(() => this._onDidChangeActions.fire()));
|
||||
this._register(this.themeService.onDidFileIconThemeChange(() => this.doRefresh([this.root]) /** soft refresh **/));
|
||||
this._register(this.themeService.onThemeChange(() => this.doRefresh([this.root]) /** soft refresh **/));
|
||||
this._register(this.configurationService.onDidChangeConfiguration(e => {
|
||||
@@ -214,8 +237,16 @@ export class CustomTreeViewer extends Disposable implements ITreeViewer {
|
||||
this.doRefresh([this.root]); /** soft refresh **/
|
||||
}
|
||||
}));
|
||||
this.markdownRenderer = instantiationService.createInstance(MarkdownRenderer);
|
||||
this._register(toDisposable(() => {
|
||||
if (this.markdownResult) {
|
||||
this.markdownResult.dispose();
|
||||
}
|
||||
}));
|
||||
this.create();
|
||||
}
|
||||
|
||||
private _dataProvider: ITreeViewDataProvider;
|
||||
get dataProvider(): ITreeViewDataProvider {
|
||||
return this._dataProvider;
|
||||
}
|
||||
@@ -223,9 +254,9 @@ export class CustomTreeViewer extends Disposable implements ITreeViewer {
|
||||
set dataProvider(dataProvider: ITreeViewDataProvider) {
|
||||
if (dataProvider) {
|
||||
this._dataProvider = new class implements ITreeViewDataProvider {
|
||||
getChildren(node?: ITreeItem): TPromise<ITreeItem[]> {
|
||||
getChildren(node?: ITreeItem): Promise<ITreeItem[]> {
|
||||
if (node && node.children) {
|
||||
return TPromise.as(node.children);
|
||||
return Promise.resolve(node.children);
|
||||
}
|
||||
const promise = node instanceof Root ? dataProvider.getChildren() : dataProvider.getChildren(node);
|
||||
return promise.then(children => {
|
||||
@@ -234,10 +265,22 @@ export class CustomTreeViewer extends Disposable implements ITreeViewer {
|
||||
});
|
||||
}
|
||||
};
|
||||
this.updateMessage();
|
||||
this.refresh();
|
||||
} else {
|
||||
this._dataProvider = null;
|
||||
this.updateMessage();
|
||||
}
|
||||
this.refresh();
|
||||
}
|
||||
|
||||
private _message: string | IMarkdownString | undefined;
|
||||
get message(): string | IMarkdownString | undefined {
|
||||
return this._message;
|
||||
}
|
||||
|
||||
set message(message: string | IMarkdownString | undefined) {
|
||||
this._message = message;
|
||||
this.updateMessage();
|
||||
}
|
||||
|
||||
get hasIconForParentNode(): boolean {
|
||||
@@ -252,6 +295,30 @@ export class CustomTreeViewer extends Disposable implements ITreeViewer {
|
||||
return this.isVisible;
|
||||
}
|
||||
|
||||
get showCollapseAllAction(): boolean {
|
||||
return this._showCollapseAllAction;
|
||||
}
|
||||
|
||||
set showCollapseAllAction(showCollapseAllAction: boolean) {
|
||||
if (this._showCollapseAllAction !== !!showCollapseAllAction) {
|
||||
this._showCollapseAllAction = !!showCollapseAllAction;
|
||||
this._onDidChangeActions.fire();
|
||||
}
|
||||
}
|
||||
|
||||
getPrimaryActions(): IAction[] {
|
||||
if (this.showCollapseAllAction) {
|
||||
const collapseAllAction = new Action('vs.tree.collapse', localize('collapse', "Collapse"), 'monaco-tree-action collapse-all', true, () => this.tree ? new CollapseAllAction(this.tree, true).run() : Promise.resolve());
|
||||
return [...this.menus.getTitleActions(), collapseAllAction];
|
||||
} else {
|
||||
return this.menus.getTitleActions();
|
||||
}
|
||||
}
|
||||
|
||||
getSecondaryActions(): IAction[] {
|
||||
return this.menus.getTitleSecondaryActions();
|
||||
}
|
||||
|
||||
setVisibility(isVisible: boolean): void {
|
||||
isVisible = !!isVisible;
|
||||
if (this.isVisible === isVisible) {
|
||||
@@ -286,27 +353,34 @@ export class CustomTreeViewer extends Disposable implements ITreeViewer {
|
||||
}
|
||||
|
||||
focus(): void {
|
||||
if (this.tree) {
|
||||
if (this.tree && this.root.children && this.root.children.length > 0) {
|
||||
// Make sure the current selected element is revealed
|
||||
const selectedElement = this.tree.getSelection()[0];
|
||||
if (selectedElement) {
|
||||
this.tree.reveal(selectedElement, 0.5).done(null, errors.onUnexpectedError);
|
||||
this.tree.reveal(selectedElement, 0.5);
|
||||
}
|
||||
|
||||
// Pass Focus to Viewer
|
||||
this.tree.domFocus();
|
||||
} else {
|
||||
this.domNode.focus();
|
||||
}
|
||||
}
|
||||
|
||||
show(container: HTMLElement): void {
|
||||
if (!this.tree) {
|
||||
this.createTree();
|
||||
}
|
||||
DOM.append(container, this.treeContainer);
|
||||
DOM.append(container, this.domNode);
|
||||
}
|
||||
|
||||
private create() {
|
||||
this.domNode = DOM.$('.tree-explorer-viewlet-tree-view');
|
||||
this.messageElement = DOM.append(this.domNode, DOM.$('.message'));
|
||||
this.treeContainer = DOM.append(this.domNode, DOM.$('.customview-tree'));
|
||||
const focusTracker = this._register(DOM.trackFocus(this.domNode));
|
||||
this._register(focusTracker.onDidFocus(() => this.focused = true));
|
||||
this._register(focusTracker.onDidBlur(() => this.focused = false));
|
||||
}
|
||||
|
||||
private createTree() {
|
||||
this.treeContainer = DOM.$('.tree-explorer-viewlet-tree-view');
|
||||
const actionItemProvider = (action: IAction) => action instanceof MenuItemAction ? this.instantiationService.createInstance(ContextAwareMenuItemActionItem, action) : undefined;
|
||||
const menus = this.instantiationService.createInstance(TreeMenus, this.id);
|
||||
const dataSource = this.instantiationService.createInstance(TreeDataSource, this, this.container);
|
||||
@@ -319,27 +393,72 @@ export class CustomTreeViewer extends Disposable implements ITreeViewer {
|
||||
this._register(this.tree.onDidExpandItem(e => this._onDidExpandItem.fire(e.item.getElement())));
|
||||
this._register(this.tree.onDidCollapseItem(e => this._onDidCollapseItem.fire(e.item.getElement())));
|
||||
this._register(this.tree.onDidChangeSelection(e => this._onDidChangeSelection.fire(e.selection)));
|
||||
this.tree.setInput(this.root);
|
||||
this.tree.setInput(this.root).then(() => this.updateContentAreas());
|
||||
}
|
||||
|
||||
private updateMessage(): void {
|
||||
if (this._message) {
|
||||
this.showMessage(this._message);
|
||||
} else if (!this.dataProvider) {
|
||||
this.showMessage(noDataProviderMessage);
|
||||
} else {
|
||||
this.hideMessage();
|
||||
}
|
||||
this.updateContentAreas();
|
||||
}
|
||||
|
||||
private showMessage(message: string | IMarkdownString): void {
|
||||
DOM.removeClass(this.messageElement, 'hide');
|
||||
if (this._messageValue !== message) {
|
||||
this.resetMessageElement();
|
||||
this._messageValue = message;
|
||||
if (isString(this._messageValue)) {
|
||||
this.messageElement.textContent = this._messageValue;
|
||||
} else {
|
||||
this.markdownResult = this.markdownRenderer.render(this._messageValue);
|
||||
DOM.append(this.messageElement, this.markdownResult.element);
|
||||
}
|
||||
this.layout(this._size);
|
||||
}
|
||||
}
|
||||
|
||||
private hideMessage(): void {
|
||||
this.resetMessageElement();
|
||||
DOM.addClass(this.messageElement, 'hide');
|
||||
this.layout(this._size);
|
||||
}
|
||||
|
||||
private resetMessageElement(): void {
|
||||
if (this.markdownResult) {
|
||||
this.markdownResult.dispose();
|
||||
this.markdownResult = null;
|
||||
}
|
||||
DOM.clearNode(this.messageElement);
|
||||
}
|
||||
|
||||
private _size: number;
|
||||
layout(size: number) {
|
||||
if (this.tree) {
|
||||
this.treeContainer.style.height = size + 'px';
|
||||
this.tree.layout(size);
|
||||
if (size) {
|
||||
this._size = size;
|
||||
const treeSize = size - DOM.getTotalHeight(this.messageElement);
|
||||
this.treeContainer.style.height = treeSize + 'px';
|
||||
if (this.tree) {
|
||||
this.tree.layout(treeSize);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getOptimalWidth(): number {
|
||||
if (this.tree) {
|
||||
const parentNode = this.tree.getHTMLElement();
|
||||
const childNodes = [].slice.call(parentNode.querySelectorAll('.outline-item-label > a'));
|
||||
const childNodes = ([] as Element[]).slice.call(parentNode.querySelectorAll('.outline-item-label > a'));
|
||||
return DOM.getLargestChildWidth(parentNode, childNodes);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
refresh(elements?: ITreeItem[]): TPromise<void> {
|
||||
if (this.tree) {
|
||||
refresh(elements?: ITreeItem[]): Promise<void> {
|
||||
if (this.dataProvider && this.tree) {
|
||||
elements = elements || [this.root];
|
||||
for (const element of elements) {
|
||||
element.children = null; // reset children
|
||||
@@ -350,49 +469,75 @@ export class CustomTreeViewer extends Disposable implements ITreeViewer {
|
||||
this.elementsToRefresh.push(...elements);
|
||||
}
|
||||
}
|
||||
return TPromise.as(null);
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
|
||||
reveal(item: ITreeItem, parentChain: ITreeItem[], options?: { select?: boolean, focus?: boolean }): TPromise<void> {
|
||||
if (this.tree && this.isVisible) {
|
||||
options = options ? options : { select: false, focus: false };
|
||||
const select = isUndefinedOrNull(options.select) ? false : options.select;
|
||||
const focus = isUndefinedOrNull(options.focus) ? false : options.focus;
|
||||
|
||||
const root: Root = this.tree.getInput();
|
||||
const promise = root.children ? TPromise.as(null) : this.refresh(); // Refresh if root is not populated
|
||||
return promise.then(() => {
|
||||
var result = TPromise.as(null);
|
||||
parentChain.forEach((e) => {
|
||||
result = result.then(() => this.tree.expand(e));
|
||||
});
|
||||
return result.then(() => this.tree.reveal(item))
|
||||
.then(() => {
|
||||
if (select) {
|
||||
this.tree.setSelection([item], { source: 'api' });
|
||||
}
|
||||
if (focus) {
|
||||
this.focus();
|
||||
this.tree.setFocus(item);
|
||||
}
|
||||
});
|
||||
});
|
||||
expand(itemOrItems: ITreeItem | ITreeItem[]): Thenable<void> {
|
||||
if (this.tree) {
|
||||
itemOrItems = Array.isArray(itemOrItems) ? itemOrItems : [itemOrItems];
|
||||
return this.tree.expandAll(itemOrItems);
|
||||
}
|
||||
return TPromise.as(null);
|
||||
return Promise.arguments(null);
|
||||
}
|
||||
|
||||
setSelection(items: ITreeItem[]): void {
|
||||
if (this.tree) {
|
||||
this.tree.setSelection(items, { source: 'api' });
|
||||
}
|
||||
}
|
||||
|
||||
setFocus(item: ITreeItem): void {
|
||||
if (this.tree) {
|
||||
this.focus();
|
||||
this.tree.setFocus(item);
|
||||
}
|
||||
}
|
||||
|
||||
reveal(item: ITreeItem): Thenable<void> {
|
||||
if (this.tree) {
|
||||
return this.tree.reveal(item);
|
||||
}
|
||||
return Promise.arguments(null);
|
||||
}
|
||||
|
||||
private activate() {
|
||||
if (!this.activated) {
|
||||
this.extensionService.activateByEvent(`onView:${this.id}`);
|
||||
this.createTree();
|
||||
this.progressService.withProgress({ location: this.container.id }, () => this.extensionService.activateByEvent(`onView:${this.id}`))
|
||||
.then(() => timeout(2000))
|
||||
.then(() => {
|
||||
this.updateMessage();
|
||||
});
|
||||
this.activated = true;
|
||||
}
|
||||
}
|
||||
|
||||
private doRefresh(elements: ITreeItem[]): TPromise<void> {
|
||||
private refreshing: boolean = false;
|
||||
private doRefresh(elements: ITreeItem[]): Promise<void> {
|
||||
if (this.tree) {
|
||||
return TPromise.join(elements.map(e => this.tree.refresh(e))).then(() => null);
|
||||
this.refreshing = true;
|
||||
return Promise.all(elements.map(e => this.tree.refresh(e)))
|
||||
.then(() => {
|
||||
this.refreshing = false;
|
||||
this.updateContentAreas();
|
||||
if (this.focused) {
|
||||
this.focus();
|
||||
}
|
||||
});
|
||||
}
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
|
||||
private updateContentAreas(): void {
|
||||
const isTreeEmpty = !this.root.children || this.root.children.length === 0;
|
||||
// Hide tree container only when there is a message and tree is empty and not refreshing
|
||||
if (this._messageValue && isTreeEmpty && !this.refreshing) {
|
||||
DOM.addClass(this.treeContainer, 'hide');
|
||||
this.domNode.setAttribute('tabindex', '0');
|
||||
} else {
|
||||
DOM.removeClass(this.treeContainer, 'hide');
|
||||
this.domNode.removeAttribute('tabindex');
|
||||
}
|
||||
return TPromise.as(null);
|
||||
}
|
||||
|
||||
private onSelection({ payload }: any): void {
|
||||
@@ -417,7 +562,7 @@ export class CustomTreeViewer extends Disposable implements ITreeViewer {
|
||||
class TreeDataSource implements IDataSource {
|
||||
|
||||
constructor(
|
||||
private treeView: ITreeViewer,
|
||||
private treeView: ITreeView,
|
||||
private container: ViewContainer,
|
||||
@IProgressService2 private progressService: IProgressService2
|
||||
) {
|
||||
@@ -431,19 +576,19 @@ class TreeDataSource implements IDataSource {
|
||||
return this.treeView.dataProvider && node.collapsibleState !== TreeItemCollapsibleState.None;
|
||||
}
|
||||
|
||||
getChildren(tree: ITree, node: ITreeItem): TPromise<any[]> {
|
||||
getChildren(tree: ITree, node: ITreeItem): Promise<any[]> {
|
||||
if (this.treeView.dataProvider) {
|
||||
return this.progressService.withProgress({ location: this.container }, () => this.treeView.dataProvider.getChildren(node));
|
||||
return this.progressService.withProgress({ location: this.container.id }, () => this.treeView.dataProvider.getChildren(node));
|
||||
}
|
||||
return TPromise.as([]);
|
||||
return Promise.resolve([]);
|
||||
}
|
||||
|
||||
shouldAutoexpand(tree: ITree, node: ITreeItem): boolean {
|
||||
return node.collapsibleState === TreeItemCollapsibleState.Expanded;
|
||||
}
|
||||
|
||||
getParent(tree: ITree, node: any): TPromise<any> {
|
||||
return TPromise.as(null);
|
||||
getParent(tree: ITree, node: any): Promise<any> {
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -454,6 +599,31 @@ interface ITreeExplorerTemplateData {
|
||||
aligner: Aligner;
|
||||
}
|
||||
|
||||
// todo@joh,sandy make this proper and contributable from extensions
|
||||
registerThemingParticipant((theme, collector) => {
|
||||
|
||||
const findMatchHighlightColor = theme.getColor(editorFindMatchHighlight);
|
||||
if (findMatchHighlightColor) {
|
||||
collector.addRule(`.file-icon-themable-tree .monaco-tree-row .content .monaco-highlighted-label .highlight { color: unset !important; background-color: ${findMatchHighlightColor}; }`);
|
||||
}
|
||||
const findMatchHighlightColorBorder = theme.getColor(editorFindMatchHighlightBorder);
|
||||
if (findMatchHighlightColorBorder) {
|
||||
collector.addRule(`.file-icon-themable-tree .monaco-tree-row .content .monaco-highlighted-label .highlight { color: unset !important; border: 1px dotted ${findMatchHighlightColorBorder}; box-sizing: border-box; }`);
|
||||
}
|
||||
const link = theme.getColor(textLinkForeground);
|
||||
if (link) {
|
||||
collector.addRule(`.tree-explorer-viewlet-tree-view > .message a { color: ${link}; }`);
|
||||
}
|
||||
const focustBorderColor = theme.getColor(focusBorder);
|
||||
if (focustBorderColor) {
|
||||
collector.addRule(`.tree-explorer-viewlet-tree-view > .message a:focus { outline: 1px solid ${focustBorderColor}; outline-offset: -1px; }`);
|
||||
}
|
||||
const codeBackground = theme.getColor(textCodeBlockBackground);
|
||||
if (codeBackground) {
|
||||
collector.addRule(`.tree-explorer-viewlet-tree-view > .message code { background-color: ${codeBackground}; }`);
|
||||
}
|
||||
});
|
||||
|
||||
class TreeRenderer implements IRenderer {
|
||||
|
||||
private static readonly ITEM_HEIGHT = 22;
|
||||
@@ -466,6 +636,7 @@ class TreeRenderer implements IRenderer {
|
||||
@IInstantiationService private instantiationService: IInstantiationService,
|
||||
@IWorkbenchThemeService private themeService: IWorkbenchThemeService,
|
||||
@IConfigurationService private configurationService: IConfigurationService,
|
||||
@ILabelService private labelService: ILabelService
|
||||
) {
|
||||
}
|
||||
|
||||
@@ -481,7 +652,7 @@ class TreeRenderer implements IRenderer {
|
||||
DOM.addClass(container, 'custom-view-tree-node-item');
|
||||
|
||||
const icon = DOM.append(container, DOM.$('.custom-view-tree-node-item-icon'));
|
||||
const resourceLabel = this.instantiationService.createInstance(ResourceLabel, container, {});
|
||||
const resourceLabel = this.instantiationService.createInstance(ResourceLabel, container, { supportHighlights: true, donotSupportOcticons: true });
|
||||
DOM.addClass(resourceLabel.element, 'custom-view-tree-node-item-resourceLabel');
|
||||
const actionsContainer = DOM.append(resourceLabel.element, DOM.$('.actions'));
|
||||
const actionBar = new ActionBar(actionsContainer, {
|
||||
@@ -494,23 +665,27 @@ class TreeRenderer implements IRenderer {
|
||||
|
||||
renderElement(tree: ITree, node: ITreeItem, templateId: string, templateData: ITreeExplorerTemplateData): void {
|
||||
const resource = node.resourceUri ? URI.revive(node.resourceUri) : null;
|
||||
const label = node.label ? node.label : resource ? basename(resource.path) : '';
|
||||
const treeItemLabel: ITreeItemLabel = node.label ? node.label : resource ? { label: basename(resource.path) } : void 0;
|
||||
const description = isString(node.description) ? node.description : resource && node.description === true ? this.labelService.getUriLabel(dirname(resource), { relative: true }) : void 0;
|
||||
const label = treeItemLabel ? treeItemLabel.label : void 0;
|
||||
const matches = treeItemLabel && treeItemLabel.highlights ? treeItemLabel.highlights.map(([start, end]) => ({ start, end })) : void 0;
|
||||
const icon = this.themeService.getTheme().type === LIGHT ? node.icon : node.iconDark;
|
||||
const iconUrl = icon ? URI.revive(icon) : null;
|
||||
const title = node.tooltip ? node.tooltip : resource ? void 0 : label;
|
||||
|
||||
// reset
|
||||
templateData.resourceLabel.clear();
|
||||
templateData.actionBar.clear();
|
||||
|
||||
if ((resource || node.themeIcon) && !icon) {
|
||||
if (resource || node.themeIcon) {
|
||||
const fileDecorations = this.configurationService.getValue<{ colors: boolean, badges: boolean }>('explorer.decorations');
|
||||
templateData.resourceLabel.setLabel({ name: label, resource: resource ? resource : URI.parse('_icon_resource') }, { fileKind: this.getFileKind(node), title, fileDecorations: fileDecorations, extraClasses: ['custom-view-tree-node-item-resourceLabel'] });
|
||||
templateData.resourceLabel.setLabel({ name: label, description, resource: resource ? resource : URI.parse('missing:_icon_resource') }, { fileKind: this.getFileKind(node), title, hideIcon: !!iconUrl, fileDecorations, extraClasses: ['custom-view-tree-node-item-resourceLabel'], matches });
|
||||
} else {
|
||||
templateData.resourceLabel.setLabel({ name: label }, { title, hideIcon: true, extraClasses: ['custom-view-tree-node-item-resourceLabel'] });
|
||||
templateData.resourceLabel.setLabel({ name: label, description }, { title, hideIcon: true, extraClasses: ['custom-view-tree-node-item-resourceLabel'], matches });
|
||||
}
|
||||
|
||||
templateData.icon.style.backgroundImage = icon ? `url('${icon}')` : '';
|
||||
DOM.toggleClass(templateData.icon, 'custom-view-tree-node-item-icon', !!icon);
|
||||
templateData.icon.style.backgroundImage = iconUrl ? `url('${iconUrl.toString(true)}')` : '';
|
||||
DOM.toggleClass(templateData.icon, 'custom-view-tree-node-item-icon', !!iconUrl);
|
||||
templateData.actionBar.context = (<TreeViewItemHandleArg>{ $treeViewId: this.treeViewId, $treeItemHandle: node.handle });
|
||||
templateData.actionBar.push(this.menus.getResourceActions(node), { icon: true, label: false });
|
||||
|
||||
@@ -621,9 +796,7 @@ class TreeController extends WorkbenchTreeController {
|
||||
this.contextMenuService.showContextMenu({
|
||||
getAnchor: () => anchor,
|
||||
|
||||
getActions: () => {
|
||||
return TPromise.as(actions);
|
||||
},
|
||||
getActions: () => actions,
|
||||
|
||||
getActionItem: (action) => {
|
||||
const keybinding = this._keybindingService.lookupKeybinding(action.id);
|
||||
@@ -654,7 +827,7 @@ class MultipleSelectionActionRunner extends ActionRunner {
|
||||
super();
|
||||
}
|
||||
|
||||
runAction(action: IAction, context: any): TPromise<any> {
|
||||
runAction(action: IAction, context: any): Thenable<any> {
|
||||
if (action instanceof MenuItemAction) {
|
||||
const selection = this.getSelectedResources();
|
||||
const filteredSelection = selection.filter(s => s !== context);
|
||||
@@ -706,3 +879,39 @@ class TreeMenus extends Disposable implements IDisposable {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
class MarkdownRenderer {
|
||||
|
||||
constructor(
|
||||
@IOpenerService private readonly _openerService: IOpenerService
|
||||
) {
|
||||
}
|
||||
|
||||
private getOptions(disposeables: IDisposable[]): RenderOptions {
|
||||
return {
|
||||
actionHandler: {
|
||||
callback: (content) => {
|
||||
let uri: URI | undefined;
|
||||
try {
|
||||
uri = URI.parse(content);
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
if (uri && this._openerService) {
|
||||
this._openerService.open(uri).catch(onUnexpectedError);
|
||||
}
|
||||
},
|
||||
disposeables
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
render(markdown: IMarkdownString): IMarkdownRenderResult {
|
||||
let disposeables: IDisposable[] = [];
|
||||
const element: HTMLElement = markdown ? renderMarkdown(markdown, this.getOptions(disposeables)) : document.createElement('span');
|
||||
return {
|
||||
element,
|
||||
dispose: () => dispose(disposeables)
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,10 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
.monaco-panel-view .split-view-view:first-of-type > .panel > .panel-header {
|
||||
border-top: none !important; /* less clutter: do not show any border for first views in a panel */
|
||||
}
|
||||
|
||||
.monaco-panel-view .panel > .panel-header h3.title {
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
|
||||
@@ -50,19 +50,51 @@
|
||||
display: none;
|
||||
}
|
||||
|
||||
.tree-explorer-viewlet-tree-view.file-icon-themable-tree .monaco-tree-row .content.align-icon-with-twisty::before {
|
||||
.monaco-workbench .tree-explorer-viewlet-tree-view {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.monaco-workbench .tree-explorer-viewlet-tree-view .message {
|
||||
display: flex;
|
||||
padding: 4px 12px 0px 18px;
|
||||
user-select: text
|
||||
}
|
||||
|
||||
.monaco-workbench .tree-explorer-viewlet-tree-view .message p {
|
||||
margin-top: 0px;
|
||||
margin-bottom: 0px;
|
||||
padding-bottom: 4px;
|
||||
}
|
||||
|
||||
.monaco-workbench .tree-explorer-viewlet-tree-view .message ul {
|
||||
padding-left: 24px;
|
||||
}
|
||||
|
||||
.monaco-workbench .tree-explorer-viewlet-tree-view .message.hide {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.tree-explorer-viewlet-tree-view.file-icon-themable-tree .monaco-tree-row .content:not(.align-icon-with-twisty)::before {
|
||||
.monaco-workbench .tree-explorer-viewlet-tree-view .customview-tree {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.monaco-workbench .tree-explorer-viewlet-tree-view .customview-tree.hide {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.customview-tree.file-icon-themable-tree .monaco-tree-row .content.align-icon-with-twisty::before {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.customview-tree.file-icon-themable-tree .monaco-tree-row .content:not(.align-icon-with-twisty)::before {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.tree-explorer-viewlet-tree-view .monaco-tree .monaco-tree-row {
|
||||
.customview-tree .monaco-tree .monaco-tree-row {
|
||||
padding-right: 12px;
|
||||
}
|
||||
|
||||
.tree-explorer-viewlet-tree-view .monaco-tree .monaco-tree-row .custom-view-tree-node-item {
|
||||
.customview-tree .monaco-tree .monaco-tree-row .custom-view-tree-node-item {
|
||||
display: flex;
|
||||
height: 22px;
|
||||
line-height: 22px;
|
||||
@@ -72,13 +104,13 @@
|
||||
flex-wrap: nowrap
|
||||
}
|
||||
|
||||
.tree-explorer-viewlet-tree-view .monaco-tree .monaco-tree-row .custom-view-tree-node-item .custom-view-tree-node-item-resourceLabel {
|
||||
.customview-tree .monaco-tree .monaco-tree-row .custom-view-tree-node-item .custom-view-tree-node-item-resourceLabel {
|
||||
flex: 1;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.tree-explorer-viewlet-tree-view .monaco-tree .monaco-tree-row .custom-view-tree-node-item > .custom-view-tree-node-item-icon {
|
||||
.customview-tree .monaco-tree .monaco-tree-row .custom-view-tree-node-item > .custom-view-tree-node-item-icon {
|
||||
background-size: 16px;
|
||||
background-position: left center;
|
||||
background-repeat: no-repeat;
|
||||
@@ -88,25 +120,25 @@
|
||||
-webkit-font-smoothing: antialiased;
|
||||
}
|
||||
|
||||
.tree-explorer-viewlet-tree-view .monaco-tree .monaco-tree-row .custom-view-tree-node-item > .custom-view-tree-node-item-resourceLabel .monaco-icon-label-description-container {
|
||||
.customview-tree .monaco-tree .monaco-tree-row .custom-view-tree-node-item > .custom-view-tree-node-item-resourceLabel .monaco-icon-label-description-container {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.tree-explorer-viewlet-tree-view .monaco-tree .monaco-tree-row .custom-view-tree-node-item > .custom-view-tree-node-item-resourceLabel::after {
|
||||
.customview-tree .monaco-tree .monaco-tree-row .custom-view-tree-node-item > .custom-view-tree-node-item-resourceLabel::after {
|
||||
padding-right: 0px;
|
||||
}
|
||||
|
||||
.tree-explorer-viewlet-tree-view .monaco-tree .monaco-tree-row .custom-view-tree-node-item > .custom-view-tree-node-item-resourceLabel > .actions {
|
||||
.customview-tree .monaco-tree .monaco-tree-row .custom-view-tree-node-item > .custom-view-tree-node-item-resourceLabel > .actions {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.tree-explorer-viewlet-tree-view .monaco-tree .monaco-tree-row:hover .custom-view-tree-node-item > .custom-view-tree-node-item-resourceLabel > .actions,
|
||||
.tree-explorer-viewlet-tree-view .monaco-tree .monaco-tree-row.selected .custom-view-tree-node-item > .custom-view-tree-node-item-resourceLabel > .actions,
|
||||
.tree-explorer-viewlet-tree-view .monaco-tree .monaco-tree-row.focused .custom-view-tree-node-item > .custom-view-tree-node-item-resourceLabel > .actions {
|
||||
.customview-tree .monaco-tree .monaco-tree-row:hover .custom-view-tree-node-item > .custom-view-tree-node-item-resourceLabel > .actions,
|
||||
.customview-tree .monaco-tree .monaco-tree-row.selected .custom-view-tree-node-item > .custom-view-tree-node-item-resourceLabel > .actions,
|
||||
.customview-tree .monaco-tree .monaco-tree-row.focused .custom-view-tree-node-item > .custom-view-tree-node-item-resourceLabel > .actions {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.tree-explorer-viewlet-tree-view .monaco-tree .custom-view-tree-node-item > .custom-view-tree-node-item-resourceLabel > .actions .action-label {
|
||||
.customview-tree .monaco-tree .custom-view-tree-node-item > .custom-view-tree-node-item-resourceLabel > .actions .action-label {
|
||||
width: 16px;
|
||||
height: 100%;
|
||||
background-position: 50% 50%;
|
||||
|
||||
@@ -5,11 +5,10 @@
|
||||
|
||||
import 'vs/css!./media/panelviewlet';
|
||||
import * as nls from 'vs/nls';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { Event, Emitter, filterEvent } from 'vs/base/common/event';
|
||||
import { ColorIdentifier, contrastBorder } from 'vs/platform/theme/common/colorRegistry';
|
||||
import { ColorIdentifier } from 'vs/platform/theme/common/colorRegistry';
|
||||
import { attachStyler, IColorMapping } from 'vs/platform/theme/common/styler';
|
||||
import { SIDE_BAR_DRAG_AND_DROP_BACKGROUND, SIDE_BAR_SECTION_HEADER_FOREGROUND, SIDE_BAR_SECTION_HEADER_BACKGROUND } from 'vs/workbench/common/theme';
|
||||
import { SIDE_BAR_DRAG_AND_DROP_BACKGROUND, SIDE_BAR_SECTION_HEADER_FOREGROUND, SIDE_BAR_SECTION_HEADER_BACKGROUND, SIDE_BAR_SECTION_HEADER_BORDER } from 'vs/workbench/common/theme';
|
||||
import { append, $, trackFocus, toggleClass, EventType, isAncestor, Dimension, addDisposableListener } from 'vs/base/browser/dom';
|
||||
import { IDisposable, combinedDisposable } from 'vs/base/common/lifecycle';
|
||||
import { firstIndex } from 'vs/base/common/arrays';
|
||||
@@ -28,12 +27,13 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur
|
||||
import { IPartService } from 'vs/workbench/services/part/common/partService';
|
||||
import { StandardMouseEvent } from 'vs/base/browser/mouseEvent';
|
||||
import { IView } from 'vs/workbench/common/views';
|
||||
import { IStorageService } from 'vs/platform/storage/common/storage';
|
||||
|
||||
export interface IPanelColors extends IColorMapping {
|
||||
dropBackground?: ColorIdentifier;
|
||||
headerForeground?: ColorIdentifier;
|
||||
headerBackground?: ColorIdentifier;
|
||||
headerHighContrastBorder?: ColorIdentifier;
|
||||
headerBorder?: ColorIdentifier;
|
||||
}
|
||||
|
||||
export interface IViewletPanelOptions extends IPanelOptions {
|
||||
@@ -55,7 +55,7 @@ export abstract class ViewletPanel extends Panel implements IView {
|
||||
protected _onDidChangeTitleArea = new Emitter<void>();
|
||||
readonly onDidChangeTitleArea: Event<void> = this._onDidChangeTitleArea.event;
|
||||
|
||||
private _isVisible: boolean;
|
||||
private _isVisible: boolean = true;
|
||||
readonly id: string;
|
||||
readonly title: string;
|
||||
|
||||
@@ -76,12 +76,10 @@ export abstract class ViewletPanel extends Panel implements IView {
|
||||
this.actionRunner = options.actionRunner;
|
||||
}
|
||||
|
||||
setVisible(visible: boolean): TPromise<void> {
|
||||
setVisible(visible: boolean): void {
|
||||
if (this._isVisible !== visible) {
|
||||
this._isVisible = visible;
|
||||
}
|
||||
|
||||
return TPromise.wrap(null);
|
||||
}
|
||||
|
||||
isVisible(): boolean {
|
||||
@@ -165,7 +163,8 @@ export abstract class ViewletPanel extends Panel implements IView {
|
||||
return 0;
|
||||
}
|
||||
|
||||
shutdown(): void {
|
||||
saveState(): void {
|
||||
// Subclasses to implement for saving state
|
||||
}
|
||||
}
|
||||
|
||||
@@ -199,20 +198,21 @@ export class PanelViewlet extends Viewlet {
|
||||
constructor(
|
||||
id: string,
|
||||
private options: IViewsViewletOptions,
|
||||
@IConfigurationService configurationService: IConfigurationService,
|
||||
@IPartService partService: IPartService,
|
||||
@IContextMenuService protected contextMenuService: IContextMenuService,
|
||||
@ITelemetryService telemetryService: ITelemetryService,
|
||||
@IThemeService themeService: IThemeService
|
||||
@IThemeService themeService: IThemeService,
|
||||
@IStorageService storageService: IStorageService
|
||||
) {
|
||||
super(id, partService, telemetryService, themeService);
|
||||
super(id, configurationService, partService, telemetryService, themeService, storageService);
|
||||
}
|
||||
|
||||
create(parent: HTMLElement): TPromise<void> {
|
||||
return super.create(parent).then(() => {
|
||||
this.panelview = this._register(new PanelView(parent, this.options));
|
||||
this._register(this.panelview.onDidDrop(({ from, to }) => this.movePanel(from as ViewletPanel, to as ViewletPanel)));
|
||||
this._register(addDisposableListener(parent, EventType.CONTEXT_MENU, (e: MouseEvent) => this.showContextMenu(new StandardMouseEvent(e))));
|
||||
});
|
||||
create(parent: HTMLElement): void {
|
||||
super.create(parent);
|
||||
this.panelview = this._register(new PanelView(parent, this.options));
|
||||
this._register(this.panelview.onDidDrop(({ from, to }) => this.movePanel(from as ViewletPanel, to as ViewletPanel)));
|
||||
this._register(addDisposableListener(parent, EventType.CONTEXT_MENU, (e: MouseEvent) => this.showContextMenu(new StandardMouseEvent(e))));
|
||||
}
|
||||
|
||||
private showContextMenu(event: StandardMouseEvent): void {
|
||||
@@ -229,7 +229,7 @@ export class PanelViewlet extends Viewlet {
|
||||
let anchor: { x: number, y: number } = { x: event.posx, y: event.posy };
|
||||
this.contextMenuService.showContextMenu({
|
||||
getAnchor: () => anchor,
|
||||
getActions: () => TPromise.as(this.getContextMenuActions())
|
||||
getActions: () => this.getContextMenuActions()
|
||||
});
|
||||
}
|
||||
|
||||
@@ -323,7 +323,7 @@ export class PanelViewlet extends Viewlet {
|
||||
const panelStyler = attachStyler<IPanelColors>(this.themeService, {
|
||||
headerForeground: SIDE_BAR_SECTION_HEADER_FOREGROUND,
|
||||
headerBackground: SIDE_BAR_SECTION_HEADER_BACKGROUND,
|
||||
headerHighContrastBorder: index === 0 ? null : contrastBorder,
|
||||
headerBorder: SIDE_BAR_SECTION_HEADER_BORDER,
|
||||
dropBackground: SIDE_BAR_DRAG_AND_DROP_BACKGROUND
|
||||
}, panel);
|
||||
const disposable = combinedDisposable([onDidFocus, onDidChangeTitleArea, panelStyler, onDidChange]);
|
||||
|
||||
@@ -5,19 +5,20 @@
|
||||
|
||||
import 'vs/css!./media/views';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { IViewsService, ViewsRegistry, IViewsViewlet, ViewContainer, IViewDescriptor, IViewContainersRegistry, Extensions as ViewContainerExtensions, TEST_VIEW_CONTAINER_ID, IView } from 'vs/workbench/common/views';
|
||||
import { IViewsService, ViewsRegistry, IViewsViewlet, ViewContainer, IViewDescriptor, IViewContainersRegistry, Extensions as ViewContainerExtensions, IView, IViewDescriptorCollection } from 'vs/workbench/common/views';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { ViewletRegistry, Extensions as ViewletExtensions } from 'vs/workbench/browser/viewlet';
|
||||
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
|
||||
import { ILifecycleService, LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle';
|
||||
import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet';
|
||||
import { IContextKeyService, IContextKeyChangeEvent, IReadableSet } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { IContextKeyService, IContextKeyChangeEvent, IReadableSet, IContextKey, RawContextKey, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { Event, chain, filterEvent, Emitter } from 'vs/base/common/event';
|
||||
import { sortedDiff, firstIndex, move } from 'vs/base/common/arrays';
|
||||
import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace';
|
||||
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
|
||||
import { isUndefinedOrNull } from 'vs/base/common/types';
|
||||
import { MenuId, MenuRegistry, ICommandAction } from 'vs/platform/actions/common/actions';
|
||||
import { CommandsRegistry } from 'vs/platform/commands/common/commands';
|
||||
import { localize } from 'vs/nls';
|
||||
import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
|
||||
import { values } from 'vs/base/common/map';
|
||||
|
||||
function filterViewEvent(container: ViewContainer, event: Event<IViewDescriptor[]>): Event<IViewDescriptor[]> {
|
||||
return chain(event)
|
||||
@@ -63,20 +64,24 @@ interface IViewItem {
|
||||
active: boolean;
|
||||
}
|
||||
|
||||
class ViewDescriptorCollection extends Disposable {
|
||||
class ViewDescriptorCollection extends Disposable implements IViewDescriptorCollection {
|
||||
|
||||
private contextKeys = new CounterSet<string>();
|
||||
private items: IViewItem[] = [];
|
||||
|
||||
private _onDidChange = this._register(new Emitter<void>());
|
||||
readonly onDidChange: Event<void> = this._onDidChange.event;
|
||||
private _onDidChange: Emitter<{ added: IViewDescriptor[], removed: IViewDescriptor[] }> = this._register(new Emitter<{ added: IViewDescriptor[], removed: IViewDescriptor[] }>());
|
||||
readonly onDidChangeActiveViews: Event<{ added: IViewDescriptor[], removed: IViewDescriptor[] }> = this._onDidChange.event;
|
||||
|
||||
get viewDescriptors(): IViewDescriptor[] {
|
||||
get activeViewDescriptors(): IViewDescriptor[] {
|
||||
return this.items
|
||||
.filter(i => i.active)
|
||||
.map(i => i.viewDescriptor);
|
||||
}
|
||||
|
||||
get allViewDescriptors(): IViewDescriptor[] {
|
||||
return this.items.map(i => i.viewDescriptor);
|
||||
}
|
||||
|
||||
constructor(
|
||||
container: ViewContainer,
|
||||
@IContextKeyService private contextKeyService: IContextKeyService
|
||||
@@ -95,7 +100,7 @@ class ViewDescriptorCollection extends Disposable {
|
||||
}
|
||||
|
||||
private onViewsRegistered(viewDescriptors: IViewDescriptor[]): any {
|
||||
let fireChangeEvent = false;
|
||||
const added: IViewDescriptor[] = [];
|
||||
|
||||
for (const viewDescriptor of viewDescriptors) {
|
||||
const item = {
|
||||
@@ -112,17 +117,17 @@ class ViewDescriptorCollection extends Disposable {
|
||||
}
|
||||
|
||||
if (item.active) {
|
||||
fireChangeEvent = true;
|
||||
added.push(viewDescriptor);
|
||||
}
|
||||
}
|
||||
|
||||
if (fireChangeEvent) {
|
||||
this._onDidChange.fire();
|
||||
if (added.length) {
|
||||
this._onDidChange.fire({ added, removed: [] });
|
||||
}
|
||||
}
|
||||
|
||||
private onViewsDeregistered(viewDescriptors: IViewDescriptor[]): any {
|
||||
let fireChangeEvent = false;
|
||||
const removed: IViewDescriptor[] = [];
|
||||
|
||||
for (const viewDescriptor of viewDescriptors) {
|
||||
const index = firstIndex(this.items, i => i.viewDescriptor.id === viewDescriptor.id);
|
||||
@@ -141,30 +146,35 @@ class ViewDescriptorCollection extends Disposable {
|
||||
}
|
||||
|
||||
if (item.active) {
|
||||
fireChangeEvent = true;
|
||||
removed.push(viewDescriptor);
|
||||
}
|
||||
}
|
||||
|
||||
if (fireChangeEvent) {
|
||||
this._onDidChange.fire();
|
||||
if (removed.length) {
|
||||
this._onDidChange.fire({ added: [], removed });
|
||||
}
|
||||
}
|
||||
|
||||
private onContextChanged(event: IContextKeyChangeEvent): any {
|
||||
let fireChangeEvent = false;
|
||||
const removed: IViewDescriptor[] = [];
|
||||
const added: IViewDescriptor[] = [];
|
||||
|
||||
for (const item of this.items) {
|
||||
const active = this.isViewDescriptorActive(item.viewDescriptor);
|
||||
|
||||
if (item.active !== active) {
|
||||
fireChangeEvent = true;
|
||||
if (active) {
|
||||
added.push(item.viewDescriptor);
|
||||
} else {
|
||||
removed.push(item.viewDescriptor);
|
||||
}
|
||||
}
|
||||
|
||||
item.active = active;
|
||||
}
|
||||
|
||||
if (fireChangeEvent) {
|
||||
this._onDidChange.fire();
|
||||
if (added.length || removed.length) {
|
||||
this._onDidChange.fire({ added, removed });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -208,14 +218,14 @@ export class ContributableViewsModel extends Disposable {
|
||||
|
||||
constructor(
|
||||
container: ViewContainer,
|
||||
contextKeyService: IContextKeyService,
|
||||
viewsService: IViewsService,
|
||||
protected viewStates = new Map<string, IViewState>(),
|
||||
) {
|
||||
super();
|
||||
const viewDescriptorCollection = this._register(new ViewDescriptorCollection(container, contextKeyService));
|
||||
const viewDescriptorCollection = viewsService.getViewDescriptors(container);
|
||||
|
||||
this._register(viewDescriptorCollection.onDidChange(() => this.onDidChangeViewDescriptors(viewDescriptorCollection.viewDescriptors)));
|
||||
this.onDidChangeViewDescriptors(viewDescriptorCollection.viewDescriptors);
|
||||
this._register(viewDescriptorCollection.onDidChangeActiveViews(() => this.onDidChangeViewDescriptors(viewDescriptorCollection.activeViewDescriptors)));
|
||||
this.onDidChangeViewDescriptors(viewDescriptorCollection.activeViewDescriptors);
|
||||
}
|
||||
|
||||
isVisible(id: string): boolean {
|
||||
@@ -407,14 +417,14 @@ export class PersistentContributableViewsModel extends ContributableViewsModel {
|
||||
constructor(
|
||||
container: ViewContainer,
|
||||
viewletStateStorageId: string,
|
||||
@IContextKeyService contextKeyService: IContextKeyService,
|
||||
@IViewsService viewsService: IViewsService,
|
||||
@IStorageService storageService: IStorageService,
|
||||
@IWorkspaceContextService contextService: IWorkspaceContextService
|
||||
) {
|
||||
const hiddenViewsStorageId = `${viewletStateStorageId}.hidden`;
|
||||
const viewStates = PersistentContributableViewsModel.loadViewsStates(viewletStateStorageId, hiddenViewsStorageId, storageService, contextService);
|
||||
|
||||
super(container, contextKeyService, viewStates);
|
||||
super(container, viewsService, viewStates);
|
||||
|
||||
this.viewletStateStorageId = viewletStateStorageId;
|
||||
this.hiddenViewsStorageId = hiddenViewsStorageId;
|
||||
@@ -423,17 +433,26 @@ export class PersistentContributableViewsModel extends ContributableViewsModel {
|
||||
|
||||
this._register(this.onDidAdd(viewDescriptorRefs => this.saveVisibilityStates(viewDescriptorRefs.map(r => r.viewDescriptor))));
|
||||
this._register(this.onDidRemove(viewDescriptorRefs => this.saveVisibilityStates(viewDescriptorRefs.map(r => r.viewDescriptor))));
|
||||
this._register(this.storageService.onWillSaveState(() => this.saveViewsStates()));
|
||||
}
|
||||
|
||||
saveViewsStates(): void {
|
||||
private saveViewsStates(): void {
|
||||
const storedViewsStates: { [id: string]: { collapsed: boolean, size: number, order: number } } = {};
|
||||
|
||||
let hasState = false;
|
||||
for (const viewDescriptor of this.viewDescriptors) {
|
||||
const viewState = this.viewStates.get(viewDescriptor.id);
|
||||
if (viewState) {
|
||||
storedViewsStates[viewDescriptor.id] = { collapsed: viewState.collapsed, size: viewState.size, order: viewState.order };
|
||||
hasState = true;
|
||||
}
|
||||
}
|
||||
this.storageService.store(this.viewletStateStorageId, JSON.stringify(storedViewsStates), this.contextService.getWorkbenchState() !== WorkbenchState.EMPTY ? StorageScope.WORKSPACE : StorageScope.GLOBAL);
|
||||
|
||||
if (hasState) {
|
||||
this.storageService.store(this.viewletStateStorageId, JSON.stringify(storedViewsStates), StorageScope.WORKSPACE);
|
||||
} else {
|
||||
this.storageService.remove(this.viewletStateStorageId, StorageScope.WORKSPACE);
|
||||
}
|
||||
}
|
||||
|
||||
private saveVisibilityStates(viewDescriptors: IViewDescriptor[]): void {
|
||||
@@ -441,17 +460,17 @@ export class PersistentContributableViewsModel extends ContributableViewsModel {
|
||||
for (const viewDescriptor of viewDescriptors) {
|
||||
if (viewDescriptor.canToggleVisibility) {
|
||||
const viewState = this.viewStates.get(viewDescriptor.id);
|
||||
storedViewsVisibilityStates.push({ id: viewDescriptor.id, isHidden: viewState ? !viewState.visible : void 0 });
|
||||
storedViewsVisibilityStates.set(viewDescriptor.id, { id: viewDescriptor.id, isHidden: viewState ? !viewState.visible : void 0 });
|
||||
}
|
||||
}
|
||||
this.storageService.store(this.hiddenViewsStorageId, JSON.stringify(storedViewsVisibilityStates), StorageScope.GLOBAL);
|
||||
this.storageService.store(this.hiddenViewsStorageId, JSON.stringify(values(storedViewsVisibilityStates)), StorageScope.GLOBAL);
|
||||
}
|
||||
|
||||
private static loadViewsStates(viewletStateStorageId: string, hiddenViewsStorageId: string, storageService: IStorageService, contextService: IWorkspaceContextService): Map<string, IViewState> {
|
||||
const viewStates = new Map<string, IViewState>();
|
||||
const storedViewsStates = JSON.parse(storageService.get(viewletStateStorageId, contextService.getWorkbenchState() !== WorkbenchState.EMPTY ? StorageScope.WORKSPACE : StorageScope.GLOBAL, '{}'));
|
||||
const storedViewsStates = JSON.parse(storageService.get(viewletStateStorageId, StorageScope.WORKSPACE, '{}'));
|
||||
const viewsVisibilityStates = PersistentContributableViewsModel.loadViewsVisibilityState(hiddenViewsStorageId, storageService, contextService);
|
||||
for (const { id, isHidden } of viewsVisibilityStates) {
|
||||
for (const { id, isHidden } of values(viewsVisibilityStates)) {
|
||||
const viewState = storedViewsStates[id];
|
||||
if (viewState) {
|
||||
viewStates.set(id, <IViewState>{ ...viewState, ...{ visible: !isHidden } });
|
||||
@@ -468,43 +487,62 @@ export class PersistentContributableViewsModel extends ContributableViewsModel {
|
||||
return viewStates;
|
||||
}
|
||||
|
||||
private static loadViewsVisibilityState(hiddenViewsStorageId: string, storageService: IStorageService, contextService: IWorkspaceContextService): { id: string, isHidden: boolean }[] {
|
||||
private static loadViewsVisibilityState(hiddenViewsStorageId: string, storageService: IStorageService, contextService: IWorkspaceContextService): Map<string, { id: string, isHidden: boolean }> {
|
||||
const storedVisibilityStates = <Array<string | { id: string, isHidden: boolean }>>JSON.parse(storageService.get(hiddenViewsStorageId, StorageScope.GLOBAL, '[]'));
|
||||
return <{ id: string, isHidden: boolean }[]>storedVisibilityStates.map(c => typeof c === 'string' /* migration */ ? { id: c, isHidden: true } : c);
|
||||
}
|
||||
let hasDuplicates = false;
|
||||
const storedViewsVisibilityStates = storedVisibilityStates.reduce((result, storedState) => {
|
||||
if (typeof storedState === 'string' /* migration */) {
|
||||
hasDuplicates = hasDuplicates || result.has(storedState);
|
||||
result.set(storedState, { id: storedState, isHidden: true });
|
||||
} else {
|
||||
hasDuplicates = hasDuplicates || result.has(storedState.id);
|
||||
result.set(storedState.id, storedState);
|
||||
}
|
||||
return result;
|
||||
}, new Map<string, { id: string, isHidden: boolean }>());
|
||||
|
||||
dispose(): void {
|
||||
this.saveViewsStates();
|
||||
super.dispose();
|
||||
if (hasDuplicates) {
|
||||
storageService.store(hiddenViewsStorageId, JSON.stringify(values(storedViewsVisibilityStates)), StorageScope.GLOBAL);
|
||||
}
|
||||
|
||||
return storedViewsVisibilityStates;
|
||||
}
|
||||
}
|
||||
|
||||
const SCM_VIEWLET_ID = 'workbench.view.scm';
|
||||
|
||||
export class ViewsService extends Disposable implements IViewsService {
|
||||
|
||||
_serviceBrand: any;
|
||||
|
||||
private readonly viewDescriptorCollections: Map<ViewContainer, IViewDescriptorCollection>;
|
||||
private readonly activeViewContextKeys: Map<string, IContextKey<boolean>>;
|
||||
|
||||
constructor(
|
||||
@IInstantiationService private instantiationService: IInstantiationService,
|
||||
@ILifecycleService private lifecycleService: ILifecycleService,
|
||||
@IViewletService private viewletService: IViewletService,
|
||||
@IStorageService private storageService: IStorageService
|
||||
@IContextKeyService private contextKeyService: IContextKeyService
|
||||
) {
|
||||
super();
|
||||
|
||||
this.viewDescriptorCollections = new Map<ViewContainer, IViewDescriptorCollection>();
|
||||
this.activeViewContextKeys = new Map<string, IContextKey<boolean>>();
|
||||
|
||||
this.onDidRegisterViews(ViewsRegistry.getAllViews());
|
||||
this._register(ViewsRegistry.onViewsRegistered(views => this.onDidRegisterViews(views)));
|
||||
|
||||
const viewContainersRegistry = Registry.as<IViewContainersRegistry>(ViewContainerExtensions.ViewContainersRegistry);
|
||||
viewContainersRegistry.all.forEach(viewContainer => this.onDidRegisterViewContainer(viewContainer));
|
||||
this._register(viewContainersRegistry.onDidRegister(viewContainer => this.onDidRegisterViewContainer(viewContainer)));
|
||||
this._register(Registry.as<ViewletRegistry>(ViewletExtensions.Viewlets).onDidRegister(viewlet => this.viewletService.setViewletEnablement(viewlet.id, this.storageService.getBoolean(`viewservice.${viewlet.id}.enablement`, StorageScope.GLOBAL, viewlet.id !== TEST_VIEW_CONTAINER_ID))));
|
||||
}
|
||||
|
||||
openView(id: string, focus: boolean): TPromise<IView> {
|
||||
getViewDescriptors(container: ViewContainer): IViewDescriptorCollection {
|
||||
return this.viewDescriptorCollections.get(container);
|
||||
}
|
||||
|
||||
openView(id: string, focus: boolean): Thenable<IView> {
|
||||
const viewDescriptor = ViewsRegistry.getView(id);
|
||||
if (viewDescriptor) {
|
||||
const viewletDescriptor = this.viewletService.getViewlet(viewDescriptor.container.id);
|
||||
if (viewletDescriptor) {
|
||||
return this.viewletService.openViewlet(viewletDescriptor.id)
|
||||
return this.viewletService.openViewlet(viewletDescriptor.id, focus)
|
||||
.then((viewlet: IViewsViewlet) => {
|
||||
if (viewlet && viewlet.openView) {
|
||||
return viewlet.openView(id, focus);
|
||||
@@ -513,21 +551,62 @@ export class ViewsService extends Disposable implements IViewsService {
|
||||
});
|
||||
}
|
||||
}
|
||||
return TPromise.as(null);
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
|
||||
private onDidRegisterViewContainer(viewContainer: ViewContainer): void {
|
||||
// TODO: @Joao Remove this after moving SCM Viewlet to ViewContainerViewlet - https://github.com/Microsoft/vscode/issues/49054
|
||||
if (viewContainer.id !== SCM_VIEWLET_ID) {
|
||||
const viewDescriptorCollection = this._register(this.instantiationService.createInstance(ViewDescriptorCollection, viewContainer));
|
||||
this._register(viewDescriptorCollection.onDidChange(() => this.updateViewletEnablement(viewContainer, viewDescriptorCollection)));
|
||||
this.lifecycleService.when(LifecyclePhase.Eventually).then(() => this.updateViewletEnablement(viewContainer, viewDescriptorCollection));
|
||||
const viewDescriptorCollection = this._register(new ViewDescriptorCollection(viewContainer, this.contextKeyService));
|
||||
|
||||
this.onDidChangeActiveViews({ added: viewDescriptorCollection.activeViewDescriptors, removed: [] });
|
||||
this._register(viewDescriptorCollection.onDidChangeActiveViews(changed => this.onDidChangeActiveViews(changed)));
|
||||
|
||||
this.viewDescriptorCollections.set(viewContainer, viewDescriptorCollection);
|
||||
}
|
||||
|
||||
private onDidChangeActiveViews({ added, removed }: { added: IViewDescriptor[], removed: IViewDescriptor[] }): void {
|
||||
added.forEach(viewDescriptor => this.getOrCreateActiveViewContextKey(viewDescriptor).set(true));
|
||||
removed.forEach(viewDescriptor => this.getOrCreateActiveViewContextKey(viewDescriptor).set(false));
|
||||
}
|
||||
|
||||
private onDidRegisterViews(viewDescriptors: IViewDescriptor[]): void {
|
||||
for (const viewDescriptor of viewDescriptors) {
|
||||
const viewlet = this.viewletService.getViewlet(viewDescriptor.container.id);
|
||||
const command: ICommandAction = {
|
||||
id: viewDescriptor.focusCommand ? viewDescriptor.focusCommand.id : `${viewDescriptor.id}.focus`,
|
||||
title: { original: `Focus on ${viewDescriptor.name} View`, value: localize('focus view', "Focus on {0} View", viewDescriptor.name) },
|
||||
category: viewlet ? viewlet.name : localize('view category', "View"),
|
||||
};
|
||||
const when = ContextKeyExpr.has(`${viewDescriptor.id}.active`);
|
||||
|
||||
CommandsRegistry.registerCommand(command.id, () => this.openView(viewDescriptor.id, true).then(() => null));
|
||||
|
||||
MenuRegistry.appendMenuItem(MenuId.CommandPalette, {
|
||||
command,
|
||||
when
|
||||
});
|
||||
|
||||
if (viewDescriptor.focusCommand && viewDescriptor.focusCommand.keybindings) {
|
||||
KeybindingsRegistry.registerKeybindingRule({
|
||||
id: command.id,
|
||||
when,
|
||||
weight: KeybindingWeight.WorkbenchContrib,
|
||||
primary: viewDescriptor.focusCommand.keybindings.primary,
|
||||
secondary: viewDescriptor.focusCommand.keybindings.secondary,
|
||||
linux: viewDescriptor.focusCommand.keybindings.linux,
|
||||
mac: viewDescriptor.focusCommand.keybindings.mac,
|
||||
win: viewDescriptor.focusCommand.keybindings.win
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private updateViewletEnablement(viewContainer: ViewContainer, viewDescriptorCollection: ViewDescriptorCollection): void {
|
||||
const enabled = viewDescriptorCollection.viewDescriptors.length > 0;
|
||||
this.viewletService.setViewletEnablement(viewContainer.id, enabled);
|
||||
this.storageService.store(`viewservice.${viewContainer.id}.enablement`, enabled, StorageScope.GLOBAL);
|
||||
private getOrCreateActiveViewContextKey(viewDescriptor: IViewDescriptor): IContextKey<boolean> {
|
||||
const activeContextKeyId = `${viewDescriptor.id}.active`;
|
||||
let contextKey = this.activeViewContextKeys.get(activeContextKeyId);
|
||||
if (!contextKey) {
|
||||
contextKey = new RawContextKey(activeContextKeyId, false).bindTo(this.contextKeyService);
|
||||
this.activeViewContextKeys.set(activeContextKeyId, contextKey);
|
||||
}
|
||||
return contextKey;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,10 +3,7 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
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 { Scope } from 'vs/workbench/common/memento';
|
||||
import { dispose, IDisposable, combinedDisposable, toDisposable } from 'vs/base/common/lifecycle';
|
||||
import { IAction } from 'vs/base/common/actions';
|
||||
import { Separator } from 'vs/base/browser/ui/actionbar/actionbar';
|
||||
@@ -18,7 +15,7 @@ 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 { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
|
||||
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { StandardMouseEvent } from 'vs/base/browser/mouseEvent';
|
||||
import { PanelViewlet, ViewletPanel, IViewletPanelOptions } from 'vs/workbench/browser/parts/views/panelViewlet';
|
||||
@@ -44,12 +41,11 @@ export abstract class TreeViewsViewletPanel extends ViewletPanel {
|
||||
}
|
||||
}
|
||||
|
||||
setVisible(visible: boolean): TPromise<void> {
|
||||
setVisible(visible: boolean): void {
|
||||
if (this.isVisible() !== visible) {
|
||||
return super.setVisible(visible)
|
||||
.then(() => this.updateTreeVisibility(this.tree, visible && this.isExpanded()));
|
||||
super.setVisible(visible);
|
||||
this.updateTreeVisibility(this.tree, visible && this.isExpanded());
|
||||
}
|
||||
return TPromise.wrap(null);
|
||||
}
|
||||
|
||||
focus(): void {
|
||||
@@ -89,7 +85,7 @@ export abstract class TreeViewsViewletPanel extends ViewletPanel {
|
||||
// Make sure the current selected element is revealed
|
||||
const selectedElement = this.tree.getSelection()[0];
|
||||
if (selectedElement) {
|
||||
this.tree.reveal(selectedElement, 0.5).done(null, errors.onUnexpectedError);
|
||||
this.tree.reveal(selectedElement);
|
||||
}
|
||||
|
||||
// Pass Focus to Viewer
|
||||
@@ -105,12 +101,12 @@ export abstract class TreeViewsViewletPanel extends ViewletPanel {
|
||||
}
|
||||
|
||||
export interface IViewletViewOptions extends IViewletPanelOptions {
|
||||
viewletSettings: object;
|
||||
viewletState: object;
|
||||
}
|
||||
|
||||
export abstract class ViewContainerViewlet extends PanelViewlet implements IViewsViewlet {
|
||||
|
||||
private readonly viewletSettings: Object;
|
||||
private readonly viewletState: object;
|
||||
private didLayout = false;
|
||||
private dimension: DOM.Dimension;
|
||||
private areExtensionsReady: boolean = false;
|
||||
@@ -124,6 +120,7 @@ export abstract class ViewContainerViewlet extends PanelViewlet implements IView
|
||||
id: string,
|
||||
viewletStateStorageId: string,
|
||||
showHeaderInTitleWhenSingleView: boolean,
|
||||
@IConfigurationService configurationService: IConfigurationService,
|
||||
@IPartService partService: IPartService,
|
||||
@ITelemetryService telemetryService: ITelemetryService,
|
||||
@IStorageService protected storageService: IStorageService,
|
||||
@@ -133,42 +130,41 @@ export abstract class ViewContainerViewlet extends PanelViewlet implements IView
|
||||
@IExtensionService protected extensionService: IExtensionService,
|
||||
@IWorkspaceContextService protected contextService: IWorkspaceContextService
|
||||
) {
|
||||
super(id, { showHeaderInTitleWhenSingleView, dnd: new DefaultPanelDndController() }, partService, contextMenuService, telemetryService, themeService);
|
||||
super(id, { showHeaderInTitleWhenSingleView, dnd: new DefaultPanelDndController() }, configurationService, partService, contextMenuService, telemetryService, themeService, storageService);
|
||||
|
||||
const container = Registry.as<IViewContainersRegistry>(ViewContainerExtensions.ViewContainersRegistry).get(id);
|
||||
this.viewsModel = this._register(this.instantiationService.createInstance(PersistentContributableViewsModel, container, viewletStateStorageId));
|
||||
this.viewletSettings = this.getMemento(storageService, Scope.WORKSPACE);
|
||||
this.viewletState = this.getMemento(StorageScope.WORKSPACE);
|
||||
|
||||
this.visibleViewsStorageId = `${id}.numberOfVisibleViews`;
|
||||
this.visibleViewsCountFromCache = this.storageService.getInteger(this.visibleViewsStorageId, this.contextService.getWorkbenchState() === WorkbenchState.EMPTY ? StorageScope.GLOBAL : StorageScope.WORKSPACE, 0);
|
||||
this.visibleViewsCountFromCache = this.storageService.getInteger(this.visibleViewsStorageId, StorageScope.WORKSPACE, 1);
|
||||
this._register(toDisposable(() => this.viewDisposables = dispose(this.viewDisposables)));
|
||||
}
|
||||
|
||||
create(parent: HTMLElement): TPromise<void> {
|
||||
return super.create(parent).then(() => {
|
||||
this._register(this.onDidSashChange(() => this.saveViewSizes()));
|
||||
this.viewsModel.onDidAdd(added => this.onDidAddViews(added));
|
||||
this.viewsModel.onDidRemove(removed => this.onDidRemoveViews(removed));
|
||||
const addedViews: IAddedViewDescriptorRef[] = this.viewsModel.visibleViewDescriptors.map((viewDescriptor, index) => {
|
||||
const size = this.viewsModel.getSize(viewDescriptor.id);
|
||||
const collapsed = this.viewsModel.isCollapsed(viewDescriptor.id);
|
||||
return ({ viewDescriptor, index, size, collapsed });
|
||||
});
|
||||
if (addedViews.length) {
|
||||
this.onDidAddViews(addedViews);
|
||||
}
|
||||
|
||||
// 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.whenInstalledExtensionsRegistered().then(() => {
|
||||
this.areExtensionsReady = true;
|
||||
if (this.panels.length) {
|
||||
this.updateTitleArea();
|
||||
this.updateViewHeaders();
|
||||
}
|
||||
});
|
||||
|
||||
this.focus();
|
||||
create(parent: HTMLElement): void {
|
||||
super.create(parent);
|
||||
this._register(this.onDidSashChange(() => this.saveViewSizes()));
|
||||
this.viewsModel.onDidAdd(added => this.onDidAddViews(added));
|
||||
this.viewsModel.onDidRemove(removed => this.onDidRemoveViews(removed));
|
||||
const addedViews: IAddedViewDescriptorRef[] = this.viewsModel.visibleViewDescriptors.map((viewDescriptor, index) => {
|
||||
const size = this.viewsModel.getSize(viewDescriptor.id);
|
||||
const collapsed = this.viewsModel.isCollapsed(viewDescriptor.id);
|
||||
return ({ viewDescriptor, index, size, collapsed });
|
||||
});
|
||||
if (addedViews.length) {
|
||||
this.onDidAddViews(addedViews);
|
||||
}
|
||||
|
||||
// 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.whenInstalledExtensionsRegistered().then(() => {
|
||||
this.areExtensionsReady = true;
|
||||
if (this.panels.length) {
|
||||
this.updateTitleArea();
|
||||
this.updateViewHeaders();
|
||||
}
|
||||
});
|
||||
|
||||
this.focus();
|
||||
}
|
||||
|
||||
getContextMenuActions(): IAction[] {
|
||||
@@ -191,14 +187,13 @@ export abstract class ViewContainerViewlet extends PanelViewlet implements IView
|
||||
return result;
|
||||
}
|
||||
|
||||
setVisible(visible: boolean): TPromise<void> {
|
||||
return super.setVisible(visible)
|
||||
.then(() => TPromise.join(this.panels.filter(view => view.isVisible() !== visible)
|
||||
.map((view) => view.setVisible(visible))))
|
||||
.then(() => void 0);
|
||||
setVisible(visible: boolean): void {
|
||||
super.setVisible(visible);
|
||||
this.panels.filter(view => view.isVisible() !== visible)
|
||||
.map((view) => view.setVisible(visible));
|
||||
}
|
||||
|
||||
openView(id: string, focus?: boolean): TPromise<IView> {
|
||||
openView(id: string, focus?: boolean): IView {
|
||||
if (focus) {
|
||||
this.focus();
|
||||
}
|
||||
@@ -211,7 +206,7 @@ export abstract class ViewContainerViewlet extends PanelViewlet implements IView
|
||||
if (focus) {
|
||||
view.focus();
|
||||
}
|
||||
return TPromise.as(view);
|
||||
return view;
|
||||
}
|
||||
|
||||
movePanel(from: ViewletPanel, to: ViewletPanel): void {
|
||||
@@ -241,13 +236,6 @@ export abstract class ViewContainerViewlet extends PanelViewlet implements IView
|
||||
return optimalWidth + additionalMargin;
|
||||
}
|
||||
|
||||
shutdown(): void {
|
||||
this.panels.forEach((view) => view.shutdown());
|
||||
this.storageService.store(this.visibleViewsStorageId, this.length, this.contextService.getWorkbenchState() !== WorkbenchState.EMPTY ? StorageScope.WORKSPACE : StorageScope.GLOBAL);
|
||||
this.viewsModel.saveViewsStates();
|
||||
super.shutdown();
|
||||
}
|
||||
|
||||
protected isSingleView(): boolean {
|
||||
if (!super.isSingleView()) {
|
||||
return false;
|
||||
@@ -276,7 +264,7 @@ export abstract class ViewContainerViewlet extends PanelViewlet implements IView
|
||||
title: viewDescriptor.name,
|
||||
actionRunner: this.getActionRunner(),
|
||||
expanded: !collapsed,
|
||||
viewletSettings: this.viewletSettings
|
||||
viewletState: this.viewletState
|
||||
});
|
||||
panel.render();
|
||||
panel.setVisible(true);
|
||||
@@ -330,7 +318,7 @@ export abstract class ViewContainerViewlet extends PanelViewlet implements IView
|
||||
let anchor: { x: number, y: number } = { x: event.posx, y: event.posy };
|
||||
this.contextMenuService.showContextMenu({
|
||||
getAnchor: () => anchor,
|
||||
getActions: () => TPromise.as(actions)
|
||||
getActions: () => actions
|
||||
});
|
||||
}
|
||||
|
||||
@@ -377,12 +365,20 @@ export abstract class ViewContainerViewlet extends PanelViewlet implements IView
|
||||
private computeInitialSizes(): { [id: string]: number } {
|
||||
let sizes = {};
|
||||
if (this.dimension) {
|
||||
const totalWeight = this.viewsModel.visibleViewDescriptors.reduce((totalWeight, { weight }) => totalWeight + (weight || 20), 0);
|
||||
for (const viewDescriptor of this.viewsModel.visibleViewDescriptors) {
|
||||
sizes[viewDescriptor.id] = this.dimension.height * (viewDescriptor.weight || 20) / 100;
|
||||
sizes[viewDescriptor.id] = this.dimension.height * (viewDescriptor.weight || 20) / totalWeight;
|
||||
}
|
||||
}
|
||||
return sizes;
|
||||
}
|
||||
|
||||
protected saveState(): void {
|
||||
this.panels.forEach((view) => view.saveState());
|
||||
this.storageService.store(this.visibleViewsStorageId, this.length, StorageScope.WORKSPACE);
|
||||
|
||||
super.saveState();
|
||||
}
|
||||
}
|
||||
|
||||
export class FileIconThemableWorkbenchTree extends WorkbenchTree {
|
||||
|
||||