Merge VS Code 1.21 source code (#1067)
* Initial VS Code 1.21 file copy with patches * A few more merges * Post npm install * Fix batch of build breaks * Fix more build breaks * Fix more build errors * Fix more build breaks * Runtime fixes 1 * Get connection dialog working with some todos * Fix a few packaging issues * Copy several node_modules to package build to fix loader issues * Fix breaks from master * A few more fixes * Make tests pass * First pass of license header updates * Second pass of license header updates * Fix restore dialog issues * Remove add additional themes menu items * fix select box issues where the list doesn't show up * formatting * Fix editor dispose issue * Copy over node modules to correct location on all platforms
@@ -29,8 +29,8 @@ import { ACTIVITY_BAR_BACKGROUND, ACTIVITY_BAR_BORDER, ACTIVITY_BAR_FOREGROUND,
|
||||
import { contrastBorder } from 'vs/platform/theme/common/colorRegistry';
|
||||
import { CompositeBar } from 'vs/workbench/browser/parts/compositebar/compositeBar';
|
||||
import { ToggleCompositePinnedAction } from 'vs/workbench/browser/parts/compositebar/compositeBarActions';
|
||||
import { ViewletDescriptor } from 'vs/workbench/browser/viewlet';
|
||||
// {{SQL CARBON EDIT}}
|
||||
import { ViewletDescriptor } from 'vs/workbench/browser/viewlet';
|
||||
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
|
||||
|
||||
export class ActivitybarPart extends Part {
|
||||
@@ -103,17 +103,24 @@ export class ActivitybarPart extends Part {
|
||||
// Deactivate viewlet action on close
|
||||
this.toUnbind.push(this.viewletService.onDidViewletClose(viewlet => this.compositeBar.deactivateComposite(viewlet.getId())));
|
||||
this.toUnbind.push(this.compositeBar.onDidContextMenu(e => this.showContextMenu(e)));
|
||||
this.toUnbind.push(this.viewletService.onDidViewletEnablementChange(({ id, enabled }) => {
|
||||
if (enabled) {
|
||||
this.compositeBar.addComposite(this.viewletService.getViewlet(id));
|
||||
} else {
|
||||
this.compositeBar.removeComposite(id);
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
public showActivity(viewletOrActionId: string, badge: IBadge, clazz?: string): IDisposable {
|
||||
public showActivity(viewletOrActionId: string, badge: IBadge, clazz?: string, priority?: number): IDisposable {
|
||||
if (this.viewletService.getViewlet(viewletOrActionId)) {
|
||||
return this.compositeBar.showActivity(viewletOrActionId, badge, clazz);
|
||||
return this.compositeBar.showActivity(viewletOrActionId, badge, clazz, priority);
|
||||
}
|
||||
|
||||
return this.showGlobalActivity(viewletOrActionId, badge);
|
||||
return this.showGlobalActivity(viewletOrActionId, badge, clazz);
|
||||
}
|
||||
|
||||
private showGlobalActivity(globalActivityId: string, badge: IBadge): IDisposable {
|
||||
private showGlobalActivity(globalActivityId: string, badge: IBadge, clazz?: string): IDisposable {
|
||||
if (!badge) {
|
||||
throw illegalArgument('badge');
|
||||
}
|
||||
@@ -123,7 +130,7 @@ export class ActivitybarPart extends Part {
|
||||
throw illegalArgument('globalActivityId');
|
||||
}
|
||||
|
||||
action.setBadge(badge);
|
||||
action.setBadge(badge, clazz);
|
||||
|
||||
return toDisposable(() => action.setBadge(undefined));
|
||||
}
|
||||
@@ -187,6 +194,7 @@ export class ActivitybarPart extends Part {
|
||||
ariaLabel: nls.localize('globalActions', "Global Actions"),
|
||||
animated: false
|
||||
});
|
||||
this.toUnbind.push(this.globalActionBar);
|
||||
|
||||
actions.forEach(a => {
|
||||
this.globalActivityIdToActions[a.id] = a;
|
||||
@@ -234,12 +242,4 @@ export class ActivitybarPart extends Part {
|
||||
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
public shutdown(): void {
|
||||
// Persist Hidden State
|
||||
this.compositeBar.store();
|
||||
|
||||
// Pass to super
|
||||
super.shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,6 @@ import 'vs/css!./media/compositepart';
|
||||
import nls = require('vs/nls');
|
||||
import { defaultGenerator } from 'vs/base/common/idGenerator';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
|
||||
import { Dimension, Builder, $ } from 'vs/base/browser/builder';
|
||||
import strings = require('vs/base/common/strings');
|
||||
@@ -18,10 +17,10 @@ import types = require('vs/base/common/types');
|
||||
import errors = require('vs/base/common/errors');
|
||||
import * as DOM from 'vs/base/browser/dom';
|
||||
import { StandardMouseEvent } from 'vs/base/browser/mouseEvent';
|
||||
import { CONTEXT as ToolBarContext, ToolBar } from 'vs/base/browser/ui/toolbar/toolbar';
|
||||
import { ToolBar } from 'vs/base/browser/ui/toolbar/toolbar';
|
||||
import { IActionItem, ActionsOrientation } from 'vs/base/browser/ui/actionbar/actionbar';
|
||||
import { ProgressBar } from 'vs/base/browser/ui/progressbar/progressbar';
|
||||
import { IActionBarRegistry, Extensions, prepareActions } from 'vs/workbench/browser/actions';
|
||||
import { prepareActions } from 'vs/workbench/browser/actions';
|
||||
import { Action, IAction, IRunEvent } from 'vs/base/common/actions';
|
||||
import { Part, IPartOptions } from 'vs/workbench/browser/part';
|
||||
import { Composite, CompositeRegistry } from 'vs/workbench/browser/composite';
|
||||
@@ -32,12 +31,12 @@ import { IStorageService, StorageScope } from 'vs/platform/storage/common/storag
|
||||
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
|
||||
import { IMessageService, Severity } from 'vs/platform/message/common/message';
|
||||
import { IProgressService } from 'vs/platform/progress/common/progress';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
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';
|
||||
|
||||
export interface ICompositeTitleLabel {
|
||||
|
||||
@@ -70,7 +69,7 @@ export abstract class CompositePart<T extends Composite> extends Part {
|
||||
protected _onDidCompositeClose = new Emitter<IComposite>();
|
||||
|
||||
constructor(
|
||||
private messageService: IMessageService,
|
||||
private notificationService: INotificationService,
|
||||
private storageService: IStorageService,
|
||||
private telemetryService: ITelemetryService,
|
||||
protected contextMenuService: IContextMenuService,
|
||||
@@ -83,7 +82,6 @@ export abstract class CompositePart<T extends Composite> extends Part {
|
||||
private defaultCompositeId: string,
|
||||
private nameForTelemetry: string,
|
||||
private compositeCSSClass: string,
|
||||
private actionContributionScope: string,
|
||||
private titleForegroundColor: string,
|
||||
id: string,
|
||||
options: IPartOptions
|
||||
@@ -285,7 +283,7 @@ export abstract class CompositePart<T extends Composite> extends Part {
|
||||
|
||||
// Check for Error
|
||||
if (e.error && !errors.isPromiseCanceledError(e.error)) {
|
||||
this.messageService.show(Severity.Error, e.error);
|
||||
this.notificationService.error(e.error);
|
||||
}
|
||||
|
||||
// Log in telemetry
|
||||
@@ -363,11 +361,6 @@ export abstract class CompositePart<T extends Composite> extends Part {
|
||||
primaryActions.push(...this.getActions());
|
||||
secondaryActions.push(...this.getSecondaryActions());
|
||||
|
||||
// From Contributions
|
||||
const actionBarRegistry = Registry.as<IActionBarRegistry>(Extensions.Actionbar);
|
||||
primaryActions.push(...actionBarRegistry.getActionBarActionsForContext(this.actionContributionScope, composite));
|
||||
secondaryActions.push(...actionBarRegistry.getSecondaryActionBarActionsForContext(this.actionContributionScope, composite));
|
||||
|
||||
// Return fn to set into toolbar
|
||||
return this.toolBar.setActions(prepareActions(primaryActions), prepareActions(secondaryActions));
|
||||
}
|
||||
@@ -484,20 +477,12 @@ export abstract class CompositePart<T extends Composite> extends Part {
|
||||
}
|
||||
|
||||
private actionItemProvider(action: Action): IActionItem {
|
||||
let actionItem: IActionItem;
|
||||
|
||||
// Check Active Composite
|
||||
if (this.activeComposite) {
|
||||
actionItem = this.activeComposite.getActionItem(action);
|
||||
return this.activeComposite.getActionItem(action);
|
||||
}
|
||||
|
||||
// Check Registry
|
||||
if (!actionItem) {
|
||||
const actionBarRegistry = Registry.as<IActionBarRegistry>(Extensions.Actionbar);
|
||||
actionItem = actionBarRegistry.getActionItemForContext(this.actionContributionScope, ToolBarContext, action);
|
||||
}
|
||||
|
||||
return actionItem;
|
||||
return undefined;
|
||||
}
|
||||
|
||||
public createContentArea(parent: Builder): Builder {
|
||||
@@ -511,7 +496,7 @@ export abstract class CompositePart<T extends Composite> extends Part {
|
||||
}
|
||||
|
||||
private onError(error: any): void {
|
||||
this.messageService.show(Severity.Error, types.isString(error) ? new Error(error) : error);
|
||||
this.notificationService.error(types.isString(error) ? new Error(error) : error);
|
||||
}
|
||||
|
||||
public getProgressIndicator(id: string): IProgressService {
|
||||
|
||||
@@ -81,8 +81,29 @@ export class CompositeBar implements ICompositeBar {
|
||||
return this._onDidContextMenu.event;
|
||||
}
|
||||
|
||||
public addComposite(compositeData: { id: string; name: string }): void {
|
||||
if (this.options.composites.filter(c => c.id === compositeData.id).length) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.options.composites.push(compositeData);
|
||||
this.pin(compositeData.id);
|
||||
}
|
||||
|
||||
public removeComposite(id: string): void {
|
||||
if (this.options.composites.filter(c => c.id === id).length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.options.composites = this.options.composites.filter(c => c.id !== id);
|
||||
this.unpin(id);
|
||||
}
|
||||
|
||||
public activateComposite(id: string): void {
|
||||
if (this.compositeIdToActions[id]) {
|
||||
if (this.compositeIdToActions[this.activeCompositeId]) {
|
||||
this.compositeIdToActions[this.activeCompositeId].deactivate();
|
||||
}
|
||||
this.compositeIdToActions[id].activate();
|
||||
}
|
||||
this.activeCompositeId = id;
|
||||
@@ -100,14 +121,27 @@ export class CompositeBar implements ICompositeBar {
|
||||
}
|
||||
}
|
||||
|
||||
public showActivity(compositeId: string, badge: IBadge, clazz?: string): IDisposable {
|
||||
public showActivity(compositeId: string, badge: IBadge, clazz?: string, priority?: number): IDisposable {
|
||||
if (!badge) {
|
||||
throw illegalArgument('badge');
|
||||
}
|
||||
|
||||
const activity = <ICompositeActivity>{ badge, clazz };
|
||||
if (typeof priority !== 'number') {
|
||||
priority = 0;
|
||||
}
|
||||
|
||||
const activity: ICompositeActivity = { badge, clazz, priority };
|
||||
const stack = this.compositeIdToActivityStack[compositeId] || (this.compositeIdToActivityStack[compositeId] = []);
|
||||
stack.unshift(activity);
|
||||
|
||||
for (let i = 0; i <= stack.length; i++) {
|
||||
if (i === stack.length) {
|
||||
stack.push(activity);
|
||||
break;
|
||||
} else if (stack[i].priority <= priority) {
|
||||
stack.splice(i, 0, activity);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
this.updateActivity(compositeId);
|
||||
|
||||
@@ -164,6 +198,7 @@ export class CompositeBar implements ICompositeBar {
|
||||
ariaLabel: nls.localize('activityBarAriaLabel', "Active View Switcher"),
|
||||
animated: false,
|
||||
});
|
||||
this.toDispose.push(this.compositeSwitcherBar);
|
||||
|
||||
// Contextmenu for composites
|
||||
this.toDispose.push(dom.addDisposableListener(parent, dom.EventType.CONTEXT_MENU, (e: MouseEvent) => {
|
||||
@@ -355,6 +390,7 @@ export class CompositeBar implements ICompositeBar {
|
||||
const visibleComposites = this.getVisibleComposites();
|
||||
|
||||
let unpinPromise: TPromise<any>;
|
||||
|
||||
// remove from pinned
|
||||
const index = this.pinnedComposites.indexOf(compositeId);
|
||||
this.pinnedComposites.splice(index, 1);
|
||||
@@ -386,6 +422,9 @@ export class CompositeBar implements ICompositeBar {
|
||||
unpinPromise.then(() => {
|
||||
this.updateCompositeSwitcher();
|
||||
});
|
||||
|
||||
// Persist
|
||||
this.savePinnedComposites();
|
||||
}
|
||||
|
||||
public isPinned(compositeId: string): boolean {
|
||||
@@ -404,6 +443,9 @@ export class CompositeBar implements ICompositeBar {
|
||||
if (update) {
|
||||
this.updateCompositeSwitcher();
|
||||
}
|
||||
|
||||
// Persist
|
||||
this.savePinnedComposites();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -433,6 +475,9 @@ export class CompositeBar implements ICompositeBar {
|
||||
setTimeout(() => {
|
||||
this.updateCompositeSwitcher();
|
||||
}, 0);
|
||||
|
||||
// Persist
|
||||
this.savePinnedComposites();
|
||||
}
|
||||
|
||||
public layout(dimension: Dimension): void {
|
||||
@@ -456,7 +501,7 @@ export class CompositeBar implements ICompositeBar {
|
||||
this.updateCompositeSwitcher();
|
||||
}
|
||||
|
||||
public store(): void {
|
||||
private savePinnedComposites(): void {
|
||||
this.storageService.store(this.options.storageId, JSON.stringify(this.pinnedComposites), StorageScope.GLOBAL);
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ import * as dom from 'vs/base/browser/dom';
|
||||
import { Builder, $ } from 'vs/base/browser/builder';
|
||||
import { BaseActionItem, IBaseActionItemOptions, Separator } from 'vs/base/browser/ui/actionbar/actionbar';
|
||||
import { ICommandService } from 'vs/platform/commands/common/commands';
|
||||
import { dispose } from 'vs/base/common/lifecycle';
|
||||
import { dispose, IDisposable, empty, toDisposable } from 'vs/base/common/lifecycle';
|
||||
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
|
||||
import { IThemeService, ITheme } from 'vs/platform/theme/common/themeService';
|
||||
import { TextBadge, NumberBadge, IBadge, IconBadge, ProgressBadge } from 'vs/workbench/services/activity/common/activity';
|
||||
@@ -26,6 +26,7 @@ import Event, { Emitter } from 'vs/base/common/event';
|
||||
export interface ICompositeActivity {
|
||||
badge: IBadge;
|
||||
clazz: string;
|
||||
priority: number;
|
||||
}
|
||||
|
||||
export interface ICompositeBar {
|
||||
@@ -52,6 +53,7 @@ export interface ICompositeBar {
|
||||
|
||||
export class ActivityAction extends Action {
|
||||
private badge: IBadge;
|
||||
private clazz: string | undefined;
|
||||
private _onDidChangeBadge = new Emitter<this>();
|
||||
|
||||
constructor(private _activity: IActivity) {
|
||||
@@ -84,8 +86,13 @@ export class ActivityAction extends Action {
|
||||
return this.badge;
|
||||
}
|
||||
|
||||
public setBadge(badge: IBadge): void {
|
||||
public getClass(): string | undefined {
|
||||
return this.clazz;
|
||||
}
|
||||
|
||||
public setBadge(badge: IBadge, clazz?: string): void {
|
||||
this.badge = badge;
|
||||
this.clazz = clazz;
|
||||
this._onDidChangeBadge.fire(this);
|
||||
}
|
||||
}
|
||||
@@ -109,6 +116,7 @@ export class ActivityActionItem extends BaseActionItem {
|
||||
protected options: IActivityActionItemOptions;
|
||||
|
||||
private $badgeContent: Builder;
|
||||
private badgeDisposable: IDisposable = empty;
|
||||
private mouseUpTimeout: number;
|
||||
|
||||
constructor(
|
||||
@@ -198,7 +206,10 @@ export class ActivityActionItem extends BaseActionItem {
|
||||
this.updateStyles();
|
||||
}
|
||||
|
||||
protected updateBadge(badge: IBadge): void {
|
||||
protected updateBadge(badge: IBadge, clazz?: string): void {
|
||||
this.badgeDisposable.dispose();
|
||||
this.badgeDisposable = empty;
|
||||
|
||||
this.$badgeContent.empty();
|
||||
this.$badge.hide();
|
||||
|
||||
@@ -233,6 +244,11 @@ export class ActivityActionItem extends BaseActionItem {
|
||||
else if (badge instanceof ProgressBadge) {
|
||||
this.$badge.show();
|
||||
}
|
||||
|
||||
if (clazz) {
|
||||
this.$badge.addClass(clazz);
|
||||
this.badgeDisposable = toDisposable(() => this.$badge.removeClass(clazz));
|
||||
}
|
||||
}
|
||||
|
||||
// Title
|
||||
@@ -258,7 +274,7 @@ export class ActivityActionItem extends BaseActionItem {
|
||||
private handleBadgeChangeEvenet(): void {
|
||||
const action = this.getAction();
|
||||
if (action instanceof ActivityAction) {
|
||||
this.updateBadge(action.getBadge());
|
||||
this.updateBadge(action.getBadge(), action.getClass());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -10,7 +10,6 @@ import Event, { Emitter } from 'vs/base/common/event';
|
||||
import URI from 'vs/base/common/uri';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { Dimension, Builder, $ } from 'vs/base/browser/builder';
|
||||
import { ResourceViewer } from 'vs/base/browser/ui/resourceviewer/resourceViewer';
|
||||
import { EditorModel, EditorInput, EditorOptions } from 'vs/workbench/common/editor';
|
||||
import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor';
|
||||
import { BinaryEditorModel } from 'vs/workbench/common/editor/binaryEditorModel';
|
||||
@@ -19,6 +18,7 @@ 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 { IWindowsService } from 'vs/platform/windows/common/windows';
|
||||
import { ResourceViewerContext, ResourceViewer } from 'vs/workbench/browser/parts/editor/resourceViewer';
|
||||
|
||||
/*
|
||||
* This class is only intended to be subclassed and not instantiated.
|
||||
@@ -29,6 +29,7 @@ export abstract class BaseBinaryResourceEditor extends BaseEditor {
|
||||
|
||||
private binaryContainer: Builder;
|
||||
private scrollbar: DomScrollableElement;
|
||||
private resourceViewerContext: ResourceViewerContext;
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
@@ -87,7 +88,7 @@ export abstract class BaseBinaryResourceEditor extends BaseEditor {
|
||||
|
||||
// Render Input
|
||||
const model = <BinaryEditorModel>resolvedModel;
|
||||
ResourceViewer.show(
|
||||
this.resourceViewerContext = ResourceViewer.show(
|
||||
{ name: model.getName(), resource: model.getResource(), size: model.getSize(), etag: model.getETag(), mime: model.getMime() },
|
||||
this.binaryContainer,
|
||||
this.scrollbar,
|
||||
@@ -132,6 +133,9 @@ export abstract class BaseBinaryResourceEditor extends BaseEditor {
|
||||
// Pass on to Binary Container
|
||||
this.binaryContainer.size(dimension.width, dimension.height);
|
||||
this.scrollbar.scanDomNode();
|
||||
if (this.resourceViewerContext) {
|
||||
this.resourceViewerContext.layout(dimension);
|
||||
}
|
||||
}
|
||||
|
||||
public focus(): void {
|
||||
@@ -146,4 +150,4 @@ export abstract class BaseBinaryResourceEditor extends BaseEditor {
|
||||
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,11 +28,11 @@ import { SyncActionDescriptor, MenuRegistry, MenuId } from 'vs/platform/actions/
|
||||
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
|
||||
import { KeyMod, KeyChord, KeyCode } from 'vs/base/common/keyCodes';
|
||||
import {
|
||||
CloseEditorsInGroupAction, CloseEditorsInOtherGroupsAction, CloseAllEditorsAction, MoveGroupLeftAction, MoveGroupRightAction, SplitEditorAction, JoinTwoGroupsAction, KeepEditorAction, CloseOtherEditorsInGroupAction, OpenToSideAction, RevertAndCloseEditorAction,
|
||||
CloseEditorsInOtherGroupsAction, CloseAllEditorsAction, MoveGroupLeftAction, MoveGroupRightAction, SplitEditorAction, JoinTwoGroupsAction, OpenToSideAction, RevertAndCloseEditorAction,
|
||||
NavigateBetweenGroupsAction, FocusActiveGroupAction, FocusFirstGroupAction, FocusSecondGroupAction, FocusThirdGroupAction, EvenGroupWidthsAction, MaximizeGroupAction, MinimizeOtherGroupsAction, FocusPreviousGroup, FocusNextGroup, ShowEditorsInGroupOneAction,
|
||||
toEditorQuickOpenEntry, CloseLeftEditorsInGroupAction, CloseRightEditorsInGroupAction, CloseUnmodifiedEditorsInGroupAction, OpenNextEditor, OpenPreviousEditor, NavigateBackwardsAction, NavigateForwardAction, NavigateLastAction, ReopenClosedEditorAction, OpenPreviousRecentlyUsedEditorInGroupAction, NAVIGATE_IN_GROUP_ONE_PREFIX,
|
||||
OpenPreviousEditorFromHistoryAction, ShowAllEditorsAction, NAVIGATE_ALL_EDITORS_GROUP_PREFIX, ClearEditorHistoryAction, ShowEditorsInGroupTwoAction, MoveEditorRightInGroupAction, OpenNextEditorInGroup, OpenPreviousEditorInGroup, OpenNextRecentlyUsedEditorAction, OpenPreviousRecentlyUsedEditorAction,
|
||||
NAVIGATE_IN_GROUP_TWO_PREFIX, ShowEditorsInGroupThreeAction, NAVIGATE_IN_GROUP_THREE_PREFIX, FocusLastEditorInStackAction, OpenNextRecentlyUsedEditorInGroupAction, MoveEditorToPreviousGroupAction, MoveEditorToNextGroupAction, MoveEditorLeftInGroupAction, ClearRecentFilesAction, OpenLastEditorInGroup
|
||||
toEditorQuickOpenEntry, CloseLeftEditorsInGroupAction, OpenNextEditor, OpenPreviousEditor, NavigateBackwardsAction, NavigateForwardAction, NavigateLastAction, ReopenClosedEditorAction, OpenPreviousRecentlyUsedEditorInGroupAction,
|
||||
OpenPreviousEditorFromHistoryAction, ShowAllEditorsAction, ClearEditorHistoryAction, ShowEditorsInGroupTwoAction, MoveEditorRightInGroupAction, OpenNextEditorInGroup, OpenPreviousEditorInGroup, OpenNextRecentlyUsedEditorAction, OpenPreviousRecentlyUsedEditorAction,
|
||||
ShowEditorsInGroupThreeAction, FocusLastEditorInStackAction, OpenNextRecentlyUsedEditorInGroupAction, MoveEditorToPreviousGroupAction, MoveEditorToNextGroupAction, MoveEditorToFirstGroupAction, MoveEditorToSecondGroupAction, MoveEditorToThirdGroupAction, MoveEditorLeftInGroupAction, ClearRecentFilesAction, OpenLastEditorInGroup
|
||||
} from 'vs/workbench/browser/parts/editor/editorActions';
|
||||
import * as editorCommands from 'vs/workbench/browser/parts/editor/editorCommands';
|
||||
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
@@ -41,6 +41,7 @@ import { KeybindingsRegistry } from 'vs/platform/keybinding/common/keybindingsRe
|
||||
import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { isMacintosh } from 'vs/base/common/platform';
|
||||
import { GroupOnePicker, GroupTwoPicker, GroupThreePicker, AllEditorsPicker } from 'vs/workbench/browser/parts/editor/editorPicker';
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
|
||||
// Register String Editor
|
||||
Registry.as<IEditorRegistry>(EditorExtensions.Editors).registerEditor(
|
||||
@@ -136,7 +137,7 @@ class UntitledEditorInputFactory implements IEditorInputFactory {
|
||||
return instantiationService.invokeFunction<UntitledEditorInput>(accessor => {
|
||||
const deserialized: ISerializedUntitledEditorInput = JSON.parse(serializedEditorInput);
|
||||
const resource = !!deserialized.resourceJSON ? URI.revive(deserialized.resourceJSON) : URI.parse(deserialized.resource);
|
||||
const filePath = resource.scheme === 'file' ? resource.fsPath : void 0;
|
||||
const filePath = resource.scheme === Schemas.file ? resource.fsPath : void 0;
|
||||
const language = deserialized.modeId;
|
||||
const encoding = deserialized.encoding;
|
||||
|
||||
@@ -213,7 +214,7 @@ Registry.as<IEditorInputFactoryRegistry>(EditorInputExtensions.EditorInputFactor
|
||||
|
||||
// Register Editor Status
|
||||
const statusBar = Registry.as<IStatusbarRegistry>(StatusExtensions.Statusbar);
|
||||
statusBar.registerStatusbarItem(new StatusbarItemDescriptor(EditorStatus, StatusbarAlignment.RIGHT, 100 /* High Priority */));
|
||||
statusBar.registerStatusbarItem(new StatusbarItemDescriptor(EditorStatus, StatusbarAlignment.RIGHT, 100 /* towards the left of the right hand side */));
|
||||
|
||||
// Register Status Actions
|
||||
const registry = Registry.as<IWorkbenchActionRegistry>(ActionExtensions.WorkbenchActions);
|
||||
@@ -270,21 +271,21 @@ Registry.as<IQuickOpenRegistry>(QuickOpenExtensions.Quickopen).registerQuickOpen
|
||||
new QuickOpenHandlerDescriptor(
|
||||
GroupOnePicker,
|
||||
GroupOnePicker.ID,
|
||||
NAVIGATE_IN_GROUP_ONE_PREFIX,
|
||||
editorCommands.NAVIGATE_IN_GROUP_ONE_PREFIX,
|
||||
editorPickerContextKey,
|
||||
[
|
||||
{
|
||||
prefix: NAVIGATE_IN_GROUP_ONE_PREFIX,
|
||||
prefix: editorCommands.NAVIGATE_IN_GROUP_ONE_PREFIX,
|
||||
needsEditor: false,
|
||||
description: nls.localize('groupOnePicker', "Show Editors in First Group")
|
||||
},
|
||||
{
|
||||
prefix: NAVIGATE_IN_GROUP_TWO_PREFIX,
|
||||
prefix: editorCommands.NAVIGATE_IN_GROUP_TWO_PREFIX,
|
||||
needsEditor: false,
|
||||
description: nls.localize('groupTwoPicker', "Show Editors in Second Group")
|
||||
},
|
||||
{
|
||||
prefix: NAVIGATE_IN_GROUP_THREE_PREFIX,
|
||||
prefix: editorCommands.NAVIGATE_IN_GROUP_THREE_PREFIX,
|
||||
needsEditor: false,
|
||||
description: nls.localize('groupThreePicker', "Show Editors in Third Group")
|
||||
}
|
||||
@@ -296,7 +297,7 @@ Registry.as<IQuickOpenRegistry>(QuickOpenExtensions.Quickopen).registerQuickOpen
|
||||
new QuickOpenHandlerDescriptor(
|
||||
GroupTwoPicker,
|
||||
GroupTwoPicker.ID,
|
||||
NAVIGATE_IN_GROUP_TWO_PREFIX,
|
||||
editorCommands.NAVIGATE_IN_GROUP_TWO_PREFIX,
|
||||
editorPickerContextKey,
|
||||
[]
|
||||
)
|
||||
@@ -306,7 +307,7 @@ Registry.as<IQuickOpenRegistry>(QuickOpenExtensions.Quickopen).registerQuickOpen
|
||||
new QuickOpenHandlerDescriptor(
|
||||
GroupThreePicker,
|
||||
GroupThreePicker.ID,
|
||||
NAVIGATE_IN_GROUP_THREE_PREFIX,
|
||||
editorCommands.NAVIGATE_IN_GROUP_THREE_PREFIX,
|
||||
editorPickerContextKey,
|
||||
[]
|
||||
)
|
||||
@@ -316,11 +317,11 @@ Registry.as<IQuickOpenRegistry>(QuickOpenExtensions.Quickopen).registerQuickOpen
|
||||
new QuickOpenHandlerDescriptor(
|
||||
AllEditorsPicker,
|
||||
AllEditorsPicker.ID,
|
||||
NAVIGATE_ALL_EDITORS_GROUP_PREFIX,
|
||||
editorCommands.NAVIGATE_ALL_EDITORS_GROUP_PREFIX,
|
||||
editorPickerContextKey,
|
||||
[
|
||||
{
|
||||
prefix: NAVIGATE_ALL_EDITORS_GROUP_PREFIX,
|
||||
prefix: editorCommands.NAVIGATE_ALL_EDITORS_GROUP_PREFIX,
|
||||
needsEditor: false,
|
||||
description: nls.localize('allEditorsPicker', "Show All Opened Editors")
|
||||
}
|
||||
@@ -343,13 +344,8 @@ registry.registerWorkbenchAction(new SyncActionDescriptor(OpenNextEditor, OpenNe
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(OpenPreviousEditor, OpenPreviousEditor.ID, OpenPreviousEditor.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.PageUp, mac: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.LeftArrow, secondary: [KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.US_OPEN_SQUARE_BRACKET] } }), 'View: Open Previous Editor', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(ReopenClosedEditorAction, ReopenClosedEditorAction.ID, ReopenClosedEditorAction.LABEL, { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_T }), 'View: Reopen Closed Editor', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(ClearRecentFilesAction, ClearRecentFilesAction.ID, ClearRecentFilesAction.LABEL), 'File: Clear Recently Opened', nls.localize('file', "File"));
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(KeepEditorAction, KeepEditorAction.ID, KeepEditorAction.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.Enter) }), 'View: Keep Editor', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(CloseAllEditorsAction, CloseAllEditorsAction.ID, CloseAllEditorsAction.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.KEY_W) }), 'View: Close All Editors', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(CloseLeftEditorsInGroupAction, CloseLeftEditorsInGroupAction.ID, CloseLeftEditorsInGroupAction.LABEL), 'View: Close Editors to the Left', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(CloseRightEditorsInGroupAction, CloseRightEditorsInGroupAction.ID, CloseRightEditorsInGroupAction.LABEL), 'View: Close Editors to the Right', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(CloseUnmodifiedEditorsInGroupAction, CloseUnmodifiedEditorsInGroupAction.ID, CloseUnmodifiedEditorsInGroupAction.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.KEY_U) }), 'View: Close Unmodified Editors in Group', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(CloseEditorsInGroupAction, CloseEditorsInGroupAction.ID, CloseEditorsInGroupAction.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.KEY_W) }), 'View: Close All Editors in Group', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(CloseOtherEditorsInGroupAction, CloseOtherEditorsInGroupAction.ID, CloseOtherEditorsInGroupAction.LABEL, { primary: null, mac: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KEY_T } }), 'View: Close Other Editors', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(CloseEditorsInOtherGroupsAction, CloseEditorsInOtherGroupsAction.ID, CloseEditorsInOtherGroupsAction.LABEL), 'View: Close Editors in Other 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(JoinTwoGroupsAction, JoinTwoGroupsAction.ID, JoinTwoGroupsAction.LABEL), 'View: Join Editors of Two Groups', category);
|
||||
@@ -368,6 +364,9 @@ registry.registerWorkbenchAction(new SyncActionDescriptor(MoveGroupLeftAction, M
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(MoveGroupRightAction, MoveGroupRightAction.ID, MoveGroupRightAction.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.RightArrow) }), 'View: Move Editor Group Right', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(MoveEditorToPreviousGroupAction, MoveEditorToPreviousGroupAction.ID, MoveEditorToPreviousGroupAction.LABEL, { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.LeftArrow, mac: { primary: KeyMod.CtrlCmd | KeyMod.WinCtrl | KeyCode.LeftArrow } }), 'View: Move Editor into Previous Group', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(MoveEditorToNextGroupAction, MoveEditorToNextGroupAction.ID, MoveEditorToNextGroupAction.LABEL, { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.RightArrow, mac: { primary: KeyMod.CtrlCmd | KeyMod.WinCtrl | KeyCode.RightArrow } }), 'View: Move Editor into Next Group', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(MoveEditorToFirstGroupAction, MoveEditorToFirstGroupAction.ID, MoveEditorToFirstGroupAction.LABEL, { primary: KeyMod.Shift | KeyMod.Alt | KeyCode.KEY_1, mac: { primary: KeyMod.CtrlCmd | KeyMod.WinCtrl | KeyCode.KEY_1 } }), 'View: Move Editor into First Group', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(MoveEditorToSecondGroupAction, MoveEditorToSecondGroupAction.ID, MoveEditorToSecondGroupAction.LABEL, { primary: KeyMod.Shift | KeyMod.Alt | KeyCode.KEY_2, mac: { primary: KeyMod.CtrlCmd | KeyMod.WinCtrl | KeyCode.KEY_2 } }), 'View: Move Editor into Second Group', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(MoveEditorToThirdGroupAction, MoveEditorToThirdGroupAction.ID, MoveEditorToThirdGroupAction.LABEL, { primary: KeyMod.Shift | KeyMod.Alt | KeyCode.KEY_3, mac: { primary: KeyMod.CtrlCmd | KeyMod.WinCtrl | KeyCode.KEY_3 } }), 'View: Move Editor into Third Group', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(FocusPreviousGroup, FocusPreviousGroup.ID, FocusPreviousGroup.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.LeftArrow) }), 'View: Focus Previous Group', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(FocusNextGroup, FocusNextGroup.ID, FocusNextGroup.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.RightArrow) }), 'View: Focus Next Group', category);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(NavigateForwardAction, NavigateForwardAction.ID, NavigateForwardAction.LABEL, { primary: null, win: { primary: KeyMod.Alt | KeyCode.RightArrow }, mac: { primary: KeyMod.WinCtrl | KeyMod.Shift | KeyCode.US_MINUS }, linux: { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.US_MINUS } }), 'Go Forward');
|
||||
@@ -410,12 +409,33 @@ editorCommands.setup();
|
||||
// Touch Bar
|
||||
if (isMacintosh) {
|
||||
MenuRegistry.appendMenuItem(MenuId.TouchBarContext, {
|
||||
command: { id: NavigateBackwardsAction.ID, title: NavigateBackwardsAction.LABEL, iconPath: URI.parse(require.toUrl('vs/workbench/browser/parts/editor/media/back-tb.png')).fsPath },
|
||||
command: { id: NavigateBackwardsAction.ID, title: NavigateBackwardsAction.LABEL, iconPath: { dark: URI.parse(require.toUrl('vs/workbench/browser/parts/editor/media/back-tb.png')).fsPath } },
|
||||
group: 'navigation'
|
||||
});
|
||||
|
||||
MenuRegistry.appendMenuItem(MenuId.TouchBarContext, {
|
||||
command: { id: NavigateForwardAction.ID, title: NavigateForwardAction.LABEL, iconPath: URI.parse(require.toUrl('vs/workbench/browser/parts/editor/media/forward-tb.png')).fsPath },
|
||||
command: { id: NavigateForwardAction.ID, title: NavigateForwardAction.LABEL, iconPath: { dark: URI.parse(require.toUrl('vs/workbench/browser/parts/editor/media/forward-tb.png')).fsPath } },
|
||||
group: 'navigation'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Editor Title Context Menu
|
||||
MenuRegistry.appendMenuItem(MenuId.EditorTitleContext, { command: { id: editorCommands.CLOSE_EDITOR_COMMAND_ID, title: nls.localize('close', "Close") }, group: '1_close', order: 10 });
|
||||
MenuRegistry.appendMenuItem(MenuId.EditorTitleContext, { command: { id: editorCommands.CLOSE_OTHER_EDITORS_IN_GROUP_COMMAND_ID, title: nls.localize('closeOthers', "Close Others") }, group: '1_close', order: 20 });
|
||||
MenuRegistry.appendMenuItem(MenuId.EditorTitleContext, { command: { id: editorCommands.CLOSE_EDITORS_TO_THE_RIGHT_COMMAND_ID, title: nls.localize('closeRight', "Close to the Right") }, group: '1_close', order: 30, when: ContextKeyExpr.has('config.workbench.editor.showTabs') });
|
||||
MenuRegistry.appendMenuItem(MenuId.EditorTitleContext, { command: { id: editorCommands.CLOSE_SAVED_EDITORS_COMMAND_ID, title: nls.localize('closeAllSaved', "Close Saved") }, group: '1_close', order: 40 });
|
||||
MenuRegistry.appendMenuItem(MenuId.EditorTitleContext, { command: { id: editorCommands.CLOSE_EDITORS_IN_GROUP_COMMAND_ID, title: nls.localize('closeAll', "Close All") }, group: '1_close', order: 50 });
|
||||
MenuRegistry.appendMenuItem(MenuId.EditorTitleContext, { command: { id: editorCommands.KEEP_EDITOR_COMMAND_ID, title: nls.localize('keepOpen', "Keep Open") }, group: '3_preview', order: 10, when: ContextKeyExpr.has('config.workbench.editor.enablePreview') });
|
||||
|
||||
// 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.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') });
|
||||
|
||||
// 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"), category } });
|
||||
MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: editorCommands.CLOSE_EDITORS_TO_THE_RIGHT_COMMAND_ID, title: nls.localize('closeRightEditors', "Close Editors to the Right"), category } });
|
||||
@@ -9,19 +9,20 @@ import nls = require('vs/nls');
|
||||
import { Action } from 'vs/base/common/actions';
|
||||
import { mixin } from 'vs/base/common/objects';
|
||||
import { getCodeEditor } from 'vs/editor/browser/services/codeEditorService';
|
||||
import { EditorInput, TextEditorOptions, EditorOptions, IEditorIdentifier, IEditorContext, ActiveEditorMoveArguments, ActiveEditorMovePositioning, EditorCommands, ConfirmResult } from 'vs/workbench/common/editor';
|
||||
import { EditorInput, TextEditorOptions, EditorOptions, IEditorIdentifier, ActiveEditorMoveArguments, ActiveEditorMovePositioning, EditorCommands, ConfirmResult, IEditorCommandsContext } from 'vs/workbench/common/editor';
|
||||
import { QuickOpenEntryGroup } from 'vs/base/parts/quickopen/browser/quickOpenModel';
|
||||
import { EditorQuickOpenEntry, EditorQuickOpenEntryGroup, IEditorQuickOpenEntry, QuickOpenAction } from 'vs/workbench/browser/quickopen';
|
||||
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { IQuickOpenService } from 'vs/platform/quickOpen/common/quickOpen';
|
||||
import { IPartService } from 'vs/workbench/services/part/common/partService';
|
||||
import { Position, IEditor, Direction, IResourceInput, IEditorInput } from 'vs/platform/editor/common/editor';
|
||||
import { Position, IEditor, Direction, IResourceInput, IEditorInput, POSITIONS } from 'vs/platform/editor/common/editor';
|
||||
import { IHistoryService } from 'vs/workbench/services/history/common/history';
|
||||
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
||||
import { IEditorGroupService, GroupArrangement } from 'vs/workbench/services/group/common/groupService';
|
||||
import { ICommandService } from 'vs/platform/commands/common/commands';
|
||||
import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
|
||||
import { IWindowsService } from 'vs/platform/windows/common/windows';
|
||||
import { CLOSE_EDITOR_COMMAND_ID, NAVIGATE_IN_GROUP_ONE_PREFIX, NAVIGATE_ALL_EDITORS_GROUP_PREFIX, NAVIGATE_IN_GROUP_THREE_PREFIX, NAVIGATE_IN_GROUP_TWO_PREFIX } from 'vs/workbench/browser/parts/editor/editorCommands';
|
||||
|
||||
export class SplitEditorAction extends Action {
|
||||
|
||||
@@ -37,10 +38,11 @@ export class SplitEditorAction extends Action {
|
||||
super(id, label, 'split-editor-action');
|
||||
}
|
||||
|
||||
public run(context?: IEditorContext): TPromise<any> {
|
||||
public run(context?: IEditorCommandsContext): TPromise<any> {
|
||||
let editorToSplit: IEditor;
|
||||
if (context) {
|
||||
editorToSplit = this.editorService.getVisibleEditors()[this.editorGroupService.getStacksModel().positionOfGroup(context.group)];
|
||||
const stacks = this.editorGroupService.getStacksModel();
|
||||
editorToSplit = this.editorService.getVisibleEditors()[stacks.positionOfGroup(stacks.getGroup(context.groupId))];
|
||||
} else {
|
||||
editorToSplit = this.editorService.getActiveEditor();
|
||||
}
|
||||
@@ -118,7 +120,7 @@ export class JoinTwoGroupsAction extends Action {
|
||||
super(id, label);
|
||||
}
|
||||
|
||||
public run(context?: IEditorContext): TPromise<any> {
|
||||
public run(context?: IEditorIdentifier): TPromise<any> {
|
||||
|
||||
const editorStacksModel = this.editorGroupService.getStacksModel();
|
||||
|
||||
@@ -531,38 +533,13 @@ export class CloseEditorAction extends Action {
|
||||
constructor(
|
||||
id: string,
|
||||
label: string,
|
||||
@IEditorGroupService private editorGroupService: IEditorGroupService,
|
||||
@IWorkbenchEditorService private editorService: IWorkbenchEditorService
|
||||
@ICommandService private commandService: ICommandService
|
||||
) {
|
||||
super(id, label, 'close-editor-action');
|
||||
}
|
||||
|
||||
public run(context?: IEditorContext): TPromise<any> {
|
||||
const position = context ? this.editorGroupService.getStacksModel().positionOfGroup(context.group) : null;
|
||||
|
||||
// Close Active Editor
|
||||
if (typeof position !== 'number') {
|
||||
const activeEditor = this.editorService.getActiveEditor();
|
||||
if (activeEditor) {
|
||||
return this.editorService.closeEditor(activeEditor.position, activeEditor.input);
|
||||
}
|
||||
}
|
||||
|
||||
let input = context ? context.editor : null;
|
||||
if (!input) {
|
||||
|
||||
// Get Top Editor at Position
|
||||
const visibleEditors = this.editorService.getVisibleEditors();
|
||||
if (visibleEditors[position]) {
|
||||
input = visibleEditors[position].input;
|
||||
}
|
||||
}
|
||||
|
||||
if (input) {
|
||||
return this.editorService.closeEditor(position, input);
|
||||
}
|
||||
|
||||
return TPromise.as(false);
|
||||
public run(context?: IEditorCommandsContext): TPromise<any> {
|
||||
return this.commandService.executeCommand(CLOSE_EDITOR_COMMAND_ID, void 0, context);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -585,9 +562,14 @@ export class RevertAndCloseEditorAction extends Action {
|
||||
const input = activeEditor.input;
|
||||
const position = activeEditor.position;
|
||||
|
||||
return activeEditor.input.revert().then(ok =>
|
||||
this.editorService.closeEditor(position, input)
|
||||
);
|
||||
// first try a normal revert where the contents of the editor are restored
|
||||
return activeEditor.input.revert().then(() => this.editorService.closeEditor(position, input), error => {
|
||||
// if that fails, since we are about to close the editor, we accept that
|
||||
// the editor cannot be reverted and instead do a soft revert that just
|
||||
// enables us to close the editor. With this, a user can always close a
|
||||
// dirty editor even when reverting fails.
|
||||
return activeEditor.input.revert({ soft: true }).then(() => this.editorService.closeEditor(position, input));
|
||||
});
|
||||
}
|
||||
|
||||
return TPromise.as(false);
|
||||
@@ -608,7 +590,7 @@ export class CloseLeftEditorsInGroupAction extends Action {
|
||||
super(id, label);
|
||||
}
|
||||
|
||||
public run(context?: IEditorContext): TPromise<any> {
|
||||
public run(context?: IEditorIdentifier): TPromise<any> {
|
||||
const editor = getTarget(this.editorService, this.groupService, context);
|
||||
if (editor) {
|
||||
return this.editorService.closeEditors(editor.position, { except: editor.input, direction: Direction.LEFT });
|
||||
@@ -618,30 +600,6 @@ export class CloseLeftEditorsInGroupAction extends Action {
|
||||
}
|
||||
}
|
||||
|
||||
export class CloseRightEditorsInGroupAction extends Action {
|
||||
|
||||
public static readonly ID = 'workbench.action.closeEditorsToTheRight';
|
||||
public static readonly LABEL = nls.localize('closeEditorsToTheRight', "Close Editors to the Right");
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
label: string,
|
||||
@IWorkbenchEditorService private editorService: IWorkbenchEditorService,
|
||||
@IEditorGroupService private groupService: IEditorGroupService
|
||||
) {
|
||||
super(id, label);
|
||||
}
|
||||
|
||||
public run(context?: IEditorContext): TPromise<any> {
|
||||
const editor = getTarget(this.editorService, this.groupService, context);
|
||||
if (editor) {
|
||||
return this.editorService.closeEditors(editor.position, { except: editor.input, direction: Direction.RIGHT });
|
||||
}
|
||||
|
||||
return TPromise.as(false);
|
||||
}
|
||||
}
|
||||
|
||||
export class CloseAllEditorsAction extends Action {
|
||||
|
||||
public static readonly ID = 'workbench.action.closeAllEditors';
|
||||
@@ -660,65 +618,33 @@ export class CloseAllEditorsAction extends Action {
|
||||
|
||||
// Just close all if there are no or one dirty editor
|
||||
if (this.textFileService.getDirty().length < 2) {
|
||||
return this.editorService.closeAllEditors();
|
||||
return this.editorService.closeEditors();
|
||||
}
|
||||
|
||||
// Otherwise ask for combined confirmation
|
||||
const confirm = this.textFileService.confirmSave();
|
||||
if (confirm === ConfirmResult.CANCEL) {
|
||||
return void 0;
|
||||
}
|
||||
|
||||
let saveOrRevertPromise: TPromise<boolean>;
|
||||
if (confirm === ConfirmResult.DONT_SAVE) {
|
||||
saveOrRevertPromise = this.textFileService.revertAll(null, { soft: true }).then(() => true);
|
||||
} else {
|
||||
saveOrRevertPromise = this.textFileService.saveAll(true).then(res => res.results.every(r => r.success));
|
||||
}
|
||||
|
||||
return saveOrRevertPromise.then(success => {
|
||||
if (success) {
|
||||
return this.editorService.closeAllEditors();
|
||||
return this.textFileService.confirmSave().then(confirm => {
|
||||
if (confirm === ConfirmResult.CANCEL) {
|
||||
return void 0;
|
||||
}
|
||||
|
||||
return void 0;
|
||||
let saveOrRevertPromise: TPromise<boolean>;
|
||||
if (confirm === ConfirmResult.DONT_SAVE) {
|
||||
saveOrRevertPromise = this.textFileService.revertAll(null, { soft: true }).then(() => true);
|
||||
} else {
|
||||
saveOrRevertPromise = this.textFileService.saveAll(true).then(res => res.results.every(r => r.success));
|
||||
}
|
||||
|
||||
return saveOrRevertPromise.then(success => {
|
||||
if (success) {
|
||||
return this.editorService.closeEditors();
|
||||
}
|
||||
|
||||
return void 0;
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export class CloseUnmodifiedEditorsInGroupAction extends Action {
|
||||
|
||||
public static readonly ID = 'workbench.action.closeUnmodifiedEditors';
|
||||
public static readonly LABEL = nls.localize('closeUnmodifiedEditors', "Close Unmodified Editors in Group");
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
label: string,
|
||||
@IEditorGroupService private editorGroupService: IEditorGroupService,
|
||||
@IWorkbenchEditorService private editorService: IWorkbenchEditorService
|
||||
) {
|
||||
super(id, label);
|
||||
}
|
||||
|
||||
public run(context?: IEditorContext): TPromise<any> {
|
||||
let position = context ? this.editorGroupService.getStacksModel().positionOfGroup(context.group) : null;
|
||||
|
||||
// If position is not passed in take the position of the active editor.
|
||||
if (typeof position !== 'number') {
|
||||
const active = this.editorService.getActiveEditor();
|
||||
if (active) {
|
||||
position = active.position;
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof position === 'number') {
|
||||
return this.editorService.closeEditors(position, { unmodifiedOnly: true });
|
||||
}
|
||||
|
||||
return TPromise.as(false);
|
||||
}
|
||||
}
|
||||
|
||||
export class CloseEditorsInOtherGroupsAction extends Action {
|
||||
|
||||
public static readonly ID = 'workbench.action.closeEditorsInOtherGroups';
|
||||
@@ -733,7 +659,7 @@ export class CloseEditorsInOtherGroupsAction extends Action {
|
||||
super(id, label);
|
||||
}
|
||||
|
||||
public run(context?: IEditorContext): TPromise<any> {
|
||||
public run(context?: IEditorIdentifier): TPromise<any> {
|
||||
let position = context ? this.editorGroupService.getStacksModel().positionOfGroup(context.group) : null;
|
||||
if (typeof position !== 'number') {
|
||||
const activeEditor = this.editorService.getActiveEditor();
|
||||
@@ -743,71 +669,7 @@ export class CloseEditorsInOtherGroupsAction extends Action {
|
||||
}
|
||||
|
||||
if (typeof position === 'number') {
|
||||
return this.editorService.closeAllEditors(position);
|
||||
}
|
||||
|
||||
return TPromise.as(false);
|
||||
}
|
||||
}
|
||||
|
||||
export class CloseOtherEditorsInGroupAction extends Action {
|
||||
|
||||
public static readonly ID = 'workbench.action.closeOtherEditors';
|
||||
public static readonly LABEL = nls.localize('closeOtherEditorsInGroup', "Close Other Editors");
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
label: string,
|
||||
@IEditorGroupService private editorGroupService: IEditorGroupService,
|
||||
@IWorkbenchEditorService private editorService: IWorkbenchEditorService
|
||||
) {
|
||||
super(id, label);
|
||||
}
|
||||
|
||||
public run(context?: IEditorContext): TPromise<any> {
|
||||
let position = context ? this.editorGroupService.getStacksModel().positionOfGroup(context.group) : null;
|
||||
let input = context ? context.editor : null;
|
||||
|
||||
// If position or input are not passed in take the position and input of the active editor.
|
||||
const active = this.editorService.getActiveEditor();
|
||||
if (active) {
|
||||
position = typeof position === 'number' ? position : active.position;
|
||||
input = input ? input : <EditorInput>active.input;
|
||||
}
|
||||
|
||||
if (typeof position === 'number' && input) {
|
||||
return this.editorService.closeEditors(position, { except: input });
|
||||
}
|
||||
|
||||
return TPromise.as(false);
|
||||
}
|
||||
}
|
||||
|
||||
export class CloseEditorsInGroupAction extends Action {
|
||||
|
||||
public static readonly ID = 'workbench.action.closeEditorsInGroup';
|
||||
public static readonly LABEL = nls.localize('closeEditorsInGroup', "Close All Editors in Group");
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
label: string,
|
||||
@IEditorGroupService private editorGroupService: IEditorGroupService,
|
||||
@IWorkbenchEditorService private editorService: IWorkbenchEditorService
|
||||
) {
|
||||
super(id, label);
|
||||
}
|
||||
|
||||
public run(context?: IEditorContext): TPromise<any> {
|
||||
let position = context ? this.editorGroupService.getStacksModel().positionOfGroup(context.group) : null;
|
||||
if (typeof position !== 'number') {
|
||||
const activeEditor = this.editorService.getActiveEditor();
|
||||
if (activeEditor) {
|
||||
position = activeEditor.position;
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof position === 'number') {
|
||||
return this.editorService.closeEditors(position);
|
||||
return this.editorService.closeEditors(POSITIONS.filter(p => p !== position));
|
||||
}
|
||||
|
||||
return TPromise.as(false);
|
||||
@@ -828,7 +690,7 @@ export class MoveGroupLeftAction extends Action {
|
||||
super(id, label);
|
||||
}
|
||||
|
||||
public run(context?: IEditorContext): TPromise<any> {
|
||||
public run(context?: IEditorIdentifier): TPromise<any> {
|
||||
let position = context ? this.editorGroupService.getStacksModel().positionOfGroup(context.group) : null;
|
||||
if (typeof position !== 'number') {
|
||||
const activeEditor = this.editorService.getActiveEditor();
|
||||
@@ -862,7 +724,7 @@ export class MoveGroupRightAction extends Action {
|
||||
super(id, label);
|
||||
}
|
||||
|
||||
public run(context?: IEditorContext): TPromise<any> {
|
||||
public run(context?: IEditorIdentifier): TPromise<any> {
|
||||
let position = context ? this.editorGroupService.getStacksModel().positionOfGroup(context.group) : null;
|
||||
if (typeof position !== 'number') {
|
||||
const activeEditor = this.editorService.getActiveEditor();
|
||||
@@ -941,31 +803,7 @@ export class MaximizeGroupAction extends Action {
|
||||
}
|
||||
}
|
||||
|
||||
export class KeepEditorAction extends Action {
|
||||
|
||||
public static readonly ID = 'workbench.action.keepEditor';
|
||||
public static readonly LABEL = nls.localize('keepEditor', "Keep Editor");
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
label: string,
|
||||
@IEditorGroupService private editorGroupService: IEditorGroupService,
|
||||
@IWorkbenchEditorService private editorService: IWorkbenchEditorService
|
||||
) {
|
||||
super(id, label);
|
||||
}
|
||||
|
||||
public run(context?: IEditorContext): TPromise<any> {
|
||||
const target = getTarget(this.editorService, this.editorGroupService, context);
|
||||
if (target) {
|
||||
this.editorGroupService.pinEditor(target.position, target.input);
|
||||
}
|
||||
|
||||
return TPromise.as(true);
|
||||
}
|
||||
}
|
||||
|
||||
function getTarget(editorService: IWorkbenchEditorService, editorGroupService: IEditorGroupService, context?: IEditorContext): { input: IEditorInput, position: Position } {
|
||||
function getTarget(editorService: IWorkbenchEditorService, editorGroupService: IEditorGroupService, context?: IEditorIdentifier): { input: IEditorInput, position: Position } {
|
||||
if (context) {
|
||||
return { input: context.editor, position: editorGroupService.getStacksModel().positionOfGroup(context.group) };
|
||||
}
|
||||
@@ -1185,8 +1023,6 @@ export class ClearRecentFilesAction extends Action {
|
||||
}
|
||||
}
|
||||
|
||||
export const NAVIGATE_IN_GROUP_ONE_PREFIX = 'edt one ';
|
||||
|
||||
export class ShowEditorsInGroupOneAction extends QuickOpenAction {
|
||||
|
||||
public static readonly ID = 'workbench.action.showEditorsInFirstGroup';
|
||||
@@ -1203,8 +1039,6 @@ export class ShowEditorsInGroupOneAction extends QuickOpenAction {
|
||||
}
|
||||
}
|
||||
|
||||
export const NAVIGATE_IN_GROUP_TWO_PREFIX = 'edt two ';
|
||||
|
||||
export class ShowEditorsInGroupTwoAction extends QuickOpenAction {
|
||||
|
||||
public static readonly ID = 'workbench.action.showEditorsInSecondGroup';
|
||||
@@ -1221,8 +1055,6 @@ export class ShowEditorsInGroupTwoAction extends QuickOpenAction {
|
||||
}
|
||||
}
|
||||
|
||||
export const NAVIGATE_IN_GROUP_THREE_PREFIX = 'edt three ';
|
||||
|
||||
export class ShowEditorsInGroupThreeAction extends QuickOpenAction {
|
||||
|
||||
public static readonly ID = 'workbench.action.showEditorsInThirdGroup';
|
||||
@@ -1239,40 +1071,6 @@ export class ShowEditorsInGroupThreeAction extends QuickOpenAction {
|
||||
}
|
||||
}
|
||||
|
||||
export class ShowEditorsInGroupAction extends Action {
|
||||
|
||||
public static readonly ID = 'workbench.action.showEditorsInGroup';
|
||||
public static readonly LABEL = nls.localize('showEditorsInGroup', "Show Editors in Group");
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
label: string,
|
||||
@IQuickOpenService private quickOpenService: IQuickOpenService,
|
||||
@IEditorGroupService private editorGroupService: IEditorGroupService
|
||||
) {
|
||||
super(id, label);
|
||||
}
|
||||
|
||||
public run(context?: IEditorContext): TPromise<any> {
|
||||
const stacks = this.editorGroupService.getStacksModel();
|
||||
const groupCount = stacks.groups.length;
|
||||
if (groupCount <= 1 || !context) {
|
||||
return this.quickOpenService.show(NAVIGATE_ALL_EDITORS_GROUP_PREFIX);
|
||||
}
|
||||
|
||||
switch (stacks.positionOfGroup(context.group)) {
|
||||
case Position.TWO:
|
||||
return this.quickOpenService.show(NAVIGATE_IN_GROUP_TWO_PREFIX);
|
||||
case Position.THREE:
|
||||
return this.quickOpenService.show(NAVIGATE_IN_GROUP_THREE_PREFIX);
|
||||
}
|
||||
|
||||
return this.quickOpenService.show(NAVIGATE_IN_GROUP_ONE_PREFIX);
|
||||
}
|
||||
}
|
||||
|
||||
export const NAVIGATE_ALL_EDITORS_GROUP_PREFIX = 'edt ';
|
||||
|
||||
export class ShowAllEditorsAction extends QuickOpenAction {
|
||||
|
||||
public static readonly ID = 'workbench.action.showAllEditors';
|
||||
@@ -1547,3 +1345,70 @@ export class MoveEditorToNextGroupAction extends Action {
|
||||
return TPromise.as(true);
|
||||
}
|
||||
}
|
||||
|
||||
export abstract class MoveEditorToSpecificGroup extends Action {
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
label: string,
|
||||
private position: Position,
|
||||
private editorGroupService: IEditorGroupService,
|
||||
private editorService: IWorkbenchEditorService
|
||||
) {
|
||||
super(id, label);
|
||||
}
|
||||
|
||||
public run(): TPromise<any> {
|
||||
const activeEditor = this.editorService.getActiveEditor();
|
||||
if (activeEditor && activeEditor.position !== this.position) {
|
||||
this.editorGroupService.moveEditor(activeEditor.input, activeEditor.position, this.position);
|
||||
}
|
||||
|
||||
return TPromise.as(true);
|
||||
}
|
||||
}
|
||||
|
||||
export class MoveEditorToFirstGroupAction extends MoveEditorToSpecificGroup {
|
||||
|
||||
public static readonly ID = 'workbench.action.moveEditorToFirstGroup';
|
||||
public static readonly LABEL = nls.localize('moveEditorToFirstGroup', "Move Editor into First Group");
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
label: string,
|
||||
@IEditorGroupService editorGroupService: IEditorGroupService,
|
||||
@IWorkbenchEditorService editorService: IWorkbenchEditorService
|
||||
) {
|
||||
super(id, label, Position.ONE, editorGroupService, editorService);
|
||||
}
|
||||
}
|
||||
|
||||
export class MoveEditorToSecondGroupAction extends MoveEditorToSpecificGroup {
|
||||
|
||||
public static readonly ID = 'workbench.action.moveEditorToSecondGroup';
|
||||
public static readonly LABEL = nls.localize('moveEditorToSecondGroup', "Move Editor into Second Group");
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
label: string,
|
||||
@IEditorGroupService editorGroupService: IEditorGroupService,
|
||||
@IWorkbenchEditorService editorService: IWorkbenchEditorService
|
||||
) {
|
||||
super(id, label, Position.TWO, editorGroupService, editorService);
|
||||
}
|
||||
}
|
||||
|
||||
export class MoveEditorToThirdGroupAction extends MoveEditorToSpecificGroup {
|
||||
|
||||
public static readonly ID = 'workbench.action.moveEditorToThirdGroup';
|
||||
public static readonly LABEL = nls.localize('moveEditorToThirdGroup', "Move Editor into Third Group");
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
label: string,
|
||||
@IEditorGroupService editorGroupService: IEditorGroupService,
|
||||
@IWorkbenchEditorService editorService: IWorkbenchEditorService
|
||||
) {
|
||||
super(id, label, Position.THREE, editorGroupService, editorService);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,22 +8,40 @@ import * as types from 'vs/base/common/types';
|
||||
import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { KeybindingsRegistry } from 'vs/platform/keybinding/common/keybindingsRegistry';
|
||||
import { IEditorGroupService } from 'vs/workbench/services/group/common/groupService';
|
||||
import { ActiveEditorMoveArguments, ActiveEditorMovePositioning, ActiveEditorMovePositioningBy, EditorCommands, TextCompareEditorVisible } from 'vs/workbench/common/editor';
|
||||
import { ActiveEditorMoveArguments, ActiveEditorMovePositioning, ActiveEditorMovePositioningBy, EditorCommands, TextCompareEditorVisible, EditorInput, IEditorIdentifier, IEditorCommandsContext } from 'vs/workbench/common/editor';
|
||||
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { IEditor, Position, POSITIONS } from 'vs/platform/editor/common/editor';
|
||||
import { IEditor, Position, POSITIONS, Direction, IEditorInput } from 'vs/platform/editor/common/editor';
|
||||
import { EditorContextKeys } from 'vs/editor/common/editorContextKeys';
|
||||
import { TextDiffEditor } from 'vs/workbench/browser/parts/editor/textDiffEditor';
|
||||
import { EditorStacksModel } from 'vs/workbench/common/editor/editorStacksModel';
|
||||
import { ICommandService } from 'vs/platform/commands/common/commands';
|
||||
import { IMessageService, Severity, CloseAction } from 'vs/platform/message/common/message';
|
||||
import { Action } from 'vs/base/common/actions';
|
||||
import { KeyMod, KeyCode } from 'vs/base/common/keyCodes';
|
||||
import { EditorStacksModel, EditorGroup } from 'vs/workbench/common/editor/editorStacksModel';
|
||||
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 { 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';
|
||||
|
||||
export const CLOSE_SAVED_EDITORS_COMMAND_ID = 'workbench.action.closeUnmodifiedEditors';
|
||||
export const CLOSE_EDITORS_IN_GROUP_COMMAND_ID = 'workbench.action.closeEditorsInGroup';
|
||||
export const CLOSE_EDITORS_TO_THE_RIGHT_COMMAND_ID = 'workbench.action.closeEditorsToTheRight';
|
||||
export const CLOSE_EDITOR_COMMAND_ID = 'workbench.action.closeActiveEditor';
|
||||
export const CLOSE_OTHER_EDITORS_IN_GROUP_COMMAND_ID = 'workbench.action.closeOtherEditors';
|
||||
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 NAVIGATE_IN_GROUP_ONE_PREFIX = 'edt one ';
|
||||
export const NAVIGATE_IN_GROUP_TWO_PREFIX = 'edt two ';
|
||||
export const NAVIGATE_IN_GROUP_THREE_PREFIX = 'edt three ';
|
||||
export const NAVIGATE_ALL_EDITORS_GROUP_PREFIX = 'edt ';
|
||||
|
||||
export function setup(): void {
|
||||
registerActiveEditorMoveCommand();
|
||||
registerDiffEditorCommands();
|
||||
registerOpenEditorAtIndexCommands();
|
||||
handleCommandDeprecations();
|
||||
registerEditorCommands();
|
||||
}
|
||||
|
||||
const isActiveEditorMoveArg = function (arg: ActiveEditorMoveArguments): boolean {
|
||||
@@ -69,9 +87,8 @@ function registerActiveEditorMoveCommand(): void {
|
||||
}
|
||||
|
||||
function moveActiveEditor(args: ActiveEditorMoveArguments = {}, accessor: ServicesAccessor): void {
|
||||
const showTabs = accessor.get(IEditorGroupService).getTabOptions().showTabs;
|
||||
args.to = args.to || ActiveEditorMovePositioning.RIGHT;
|
||||
args.by = showTabs ? args.by || ActiveEditorMovePositioningBy.TAB : ActiveEditorMovePositioningBy.GROUP;
|
||||
args.by = args.by || ActiveEditorMovePositioningBy.TAB;
|
||||
args.value = types.isUndefined(args.value) ? 1 : args.value;
|
||||
|
||||
const activeEditor = accessor.get(IWorkbenchEditorService).getActiveEditor();
|
||||
@@ -168,55 +185,30 @@ function registerDiffEditorCommands(): void {
|
||||
}
|
||||
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
id: '_workbench.printStacksModel',
|
||||
weight: KeybindingsRegistry.WEIGHT.workbenchContrib(0),
|
||||
handler(accessor: ServicesAccessor) {
|
||||
console.log(`${accessor.get(IEditorGroupService).getStacksModel().toString()}\n\n`);
|
||||
},
|
||||
when: undefined,
|
||||
primary: undefined
|
||||
});
|
||||
id: TOGGLE_DIFF_INLINE_MODE,
|
||||
weight: KeybindingsRegistry.WEIGHT.workbenchContrib(),
|
||||
when: void 0,
|
||||
primary: void 0,
|
||||
handler: (accessor, resource, context: IEditorCommandsContext) => {
|
||||
const editorService = accessor.get(IWorkbenchEditorService);
|
||||
const editorGroupService = accessor.get(IEditorGroupService);
|
||||
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
id: '_workbench.validateStacksModel',
|
||||
weight: KeybindingsRegistry.WEIGHT.workbenchContrib(0),
|
||||
handler(accessor: ServicesAccessor) {
|
||||
(<EditorStacksModel>accessor.get(IEditorGroupService).getStacksModel()).validate();
|
||||
},
|
||||
when: undefined,
|
||||
primary: undefined
|
||||
});
|
||||
}
|
||||
let editor: IEditor;
|
||||
if (context) {
|
||||
const position = positionAndInput(editorGroupService, editorService, context).position;
|
||||
editor = editorService.getVisibleEditors()[position];
|
||||
} else {
|
||||
editor = editorService.getActiveEditor();
|
||||
}
|
||||
|
||||
function handleCommandDeprecations(): void {
|
||||
const mapDeprecatedCommands = {
|
||||
'workbench.action.files.newFile': 'explorer.newFile',
|
||||
'workbench.action.files.newFolder': 'explorer.newFolder'
|
||||
};
|
||||
|
||||
Object.keys(mapDeprecatedCommands).forEach(deprecatedCommandId => {
|
||||
const newCommandId: string = mapDeprecatedCommands[deprecatedCommandId];
|
||||
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
id: deprecatedCommandId,
|
||||
weight: KeybindingsRegistry.WEIGHT.workbenchContrib(0),
|
||||
handler(accessor: ServicesAccessor) {
|
||||
const messageService = accessor.get(IMessageService);
|
||||
const commandService = accessor.get(ICommandService);
|
||||
|
||||
messageService.show(Severity.Warning, {
|
||||
message: nls.localize('commandDeprecated', "Command **{0}** has been removed. You can use **{1}** instead", deprecatedCommandId, newCommandId),
|
||||
actions: [
|
||||
new Action('openKeybindings', nls.localize('openKeybindings', "Configure Keyboard Shortcuts"), null, true, () => {
|
||||
return commandService.executeCommand('workbench.action.openGlobalKeybindings');
|
||||
}),
|
||||
CloseAction
|
||||
]
|
||||
if (editor instanceof TextDiffEditor) {
|
||||
const control = editor.getControl();
|
||||
const isInlineMode = !control.renderSideBySide;
|
||||
control.updateOptions(<IDiffEditorOptions>{
|
||||
renderSideBySide: isInlineMode
|
||||
});
|
||||
},
|
||||
when: undefined,
|
||||
primary: undefined
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -243,7 +235,7 @@ function registerOpenEditorAtIndexCommands(): void {
|
||||
const editor = group.getEditor(editorIndex);
|
||||
|
||||
if (editor) {
|
||||
return editorService.openEditor(editor);
|
||||
return editorService.openEditor(editor).then(() => void 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -268,4 +260,287 @@ function registerOpenEditorAtIndexCommands(): void {
|
||||
|
||||
return void 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function registerEditorCommands() {
|
||||
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
id: CLOSE_SAVED_EDITORS_COMMAND_ID,
|
||||
weight: KeybindingsRegistry.WEIGHT.workbenchContrib(),
|
||||
when: void 0,
|
||||
primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.KEY_U),
|
||||
handler: (accessor, resource: URI, context: IEditorCommandsContext) => {
|
||||
const editorGroupService = accessor.get(IEditorGroupService);
|
||||
const model = editorGroupService.getStacksModel();
|
||||
const editorService = accessor.get(IWorkbenchEditorService);
|
||||
const contexts = getMultiSelectedEditorContexts(context, accessor.get(IListService));
|
||||
if (contexts.length === 0 && model.activeGroup) {
|
||||
// If command is triggered from the command palette use the active group
|
||||
contexts.push({ groupId: model.activeGroup.id });
|
||||
}
|
||||
|
||||
let positionOne: { savedOnly: boolean } = void 0;
|
||||
let positionTwo: { savedOnly: boolean } = void 0;
|
||||
let positionThree: { savedOnly: boolean } = void 0;
|
||||
contexts.forEach(c => {
|
||||
switch (model.positionOfGroup(model.getGroup(c.groupId))) {
|
||||
case Position.ONE: positionOne = { savedOnly: true }; break;
|
||||
case Position.TWO: positionTwo = { savedOnly: true }; break;
|
||||
case Position.THREE: positionThree = { savedOnly: true }; break;
|
||||
}
|
||||
});
|
||||
|
||||
return editorService.closeEditors({ positionOne, positionTwo, positionThree });
|
||||
}
|
||||
});
|
||||
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
id: CLOSE_EDITORS_IN_GROUP_COMMAND_ID,
|
||||
weight: KeybindingsRegistry.WEIGHT.workbenchContrib(),
|
||||
when: void 0,
|
||||
primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.KEY_W),
|
||||
handler: (accessor, resource: URI, context: IEditorCommandsContext) => {
|
||||
const editorGroupService = accessor.get(IEditorGroupService);
|
||||
const editorService = accessor.get(IWorkbenchEditorService);
|
||||
const contexts = getMultiSelectedEditorContexts(context, accessor.get(IListService));
|
||||
const distinctGroupIds = distinct(contexts.map(c => c.groupId));
|
||||
const model = editorGroupService.getStacksModel();
|
||||
|
||||
if (distinctGroupIds.length) {
|
||||
return editorService.closeEditors(distinctGroupIds.map(gid => model.positionOfGroup(model.getGroup(gid))));
|
||||
}
|
||||
const activeEditor = editorService.getActiveEditor();
|
||||
if (activeEditor) {
|
||||
return editorService.closeEditors(activeEditor.position);
|
||||
}
|
||||
|
||||
return TPromise.as(false);
|
||||
}
|
||||
});
|
||||
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
id: CLOSE_EDITOR_COMMAND_ID,
|
||||
weight: KeybindingsRegistry.WEIGHT.workbenchContrib(),
|
||||
when: void 0,
|
||||
primary: KeyMod.CtrlCmd | KeyCode.KEY_W,
|
||||
win: { primary: KeyMod.CtrlCmd | KeyCode.F4, secondary: [KeyMod.CtrlCmd | KeyCode.KEY_W] },
|
||||
handler: (accessor, resource: URI, context: IEditorCommandsContext) => {
|
||||
const editorGroupService = accessor.get(IEditorGroupService);
|
||||
const editorService = accessor.get(IWorkbenchEditorService);
|
||||
|
||||
const contexts = getMultiSelectedEditorContexts(context, accessor.get(IListService));
|
||||
const groupIds = distinct(contexts.map(context => context.groupId));
|
||||
const model = editorGroupService.getStacksModel();
|
||||
|
||||
const editorsToClose = new Map<Position, IEditorInput[]>();
|
||||
|
||||
groupIds.forEach(groupId => {
|
||||
const group = model.getGroup(groupId);
|
||||
const position = model.positionOfGroup(group);
|
||||
if (position >= 0) {
|
||||
const inputs = contexts.map(c => {
|
||||
if (c && groupId === c.groupId && types.isNumber(c.editorIndex)) {
|
||||
return group.getEditor(c.editorIndex);
|
||||
}
|
||||
|
||||
return group.activeEditor;
|
||||
}).filter(input => !!input);
|
||||
|
||||
if (inputs.length) {
|
||||
editorsToClose.set(position, inputs);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (editorsToClose.size === 0) {
|
||||
const activeEditor = editorService.getActiveEditor();
|
||||
if (activeEditor) {
|
||||
return editorService.closeEditor(activeEditor.position, activeEditor.input);
|
||||
}
|
||||
}
|
||||
|
||||
return editorService.closeEditors({
|
||||
positionOne: editorsToClose.get(Position.ONE),
|
||||
positionTwo: editorsToClose.get(Position.TWO),
|
||||
positionThree: editorsToClose.get(Position.THREE)
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
id: CLOSE_OTHER_EDITORS_IN_GROUP_COMMAND_ID,
|
||||
weight: KeybindingsRegistry.WEIGHT.workbenchContrib(),
|
||||
when: void 0,
|
||||
primary: void 0,
|
||||
mac: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KEY_T },
|
||||
handler: (accessor, resource: URI, context: IEditorCommandsContext) => {
|
||||
const editorGroupService = accessor.get(IEditorGroupService);
|
||||
const editorService = accessor.get(IWorkbenchEditorService);
|
||||
const contexts = getMultiSelectedEditorContexts(context, accessor.get(IListService));
|
||||
const model = editorGroupService.getStacksModel();
|
||||
|
||||
if (contexts.length === 0) {
|
||||
// Cover the case when run from command palette
|
||||
const activeGroup = model.activeGroup;
|
||||
const activeEditor = editorService.getActiveEditorInput();
|
||||
if (activeGroup && activeEditor) {
|
||||
contexts.push({ groupId: activeGroup.id, editorIndex: activeGroup.indexOf(activeEditor) });
|
||||
}
|
||||
}
|
||||
|
||||
const groupIds = distinct(contexts.map(context => context.groupId));
|
||||
const editorsToClose = new Map<Position, IEditorInput[]>();
|
||||
groupIds.forEach(groupId => {
|
||||
const group = model.getGroup(groupId);
|
||||
const inputsToSkip = contexts.map(c => {
|
||||
if (c.groupId === groupId && types.isNumber(c.editorIndex)) {
|
||||
return group.getEditor(c.editorIndex);
|
||||
}
|
||||
|
||||
return void 0;
|
||||
}).filter(input => !!input);
|
||||
|
||||
const toClose = group.getEditors().filter(input => inputsToSkip.indexOf(input) === -1);
|
||||
editorsToClose.set(model.positionOfGroup(group), toClose);
|
||||
});
|
||||
|
||||
return editorService.closeEditors({
|
||||
positionOne: editorsToClose.get(Position.ONE),
|
||||
positionTwo: editorsToClose.get(Position.TWO),
|
||||
positionThree: editorsToClose.get(Position.THREE)
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
id: CLOSE_EDITORS_TO_THE_RIGHT_COMMAND_ID,
|
||||
weight: KeybindingsRegistry.WEIGHT.workbenchContrib(),
|
||||
when: void 0,
|
||||
primary: void 0,
|
||||
handler: (accessor, resource: URI, context: IEditorCommandsContext) => {
|
||||
const editorGroupService = accessor.get(IEditorGroupService);
|
||||
const editorService = accessor.get(IWorkbenchEditorService);
|
||||
|
||||
const { position, input } = positionAndInput(editorGroupService, editorService, context);
|
||||
|
||||
if (typeof position === 'number' && input) {
|
||||
return editorService.closeEditors(position, { except: input, direction: Direction.RIGHT });
|
||||
}
|
||||
|
||||
return TPromise.as(false);
|
||||
}
|
||||
});
|
||||
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
id: KEEP_EDITOR_COMMAND_ID,
|
||||
weight: KeybindingsRegistry.WEIGHT.workbenchContrib(),
|
||||
when: void 0,
|
||||
primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.Enter),
|
||||
handler: (accessor, resource: URI, context: IEditorCommandsContext) => {
|
||||
const editorGroupService = accessor.get(IEditorGroupService);
|
||||
const editorService = accessor.get(IWorkbenchEditorService);
|
||||
|
||||
const { position, input } = positionAndInput(editorGroupService, editorService, context);
|
||||
|
||||
if (typeof position === 'number' && input) {
|
||||
return editorGroupService.pinEditor(position, input);
|
||||
}
|
||||
|
||||
return TPromise.as(false);
|
||||
}
|
||||
});
|
||||
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
id: SHOW_EDITORS_IN_GROUP,
|
||||
weight: KeybindingsRegistry.WEIGHT.workbenchContrib(),
|
||||
when: void 0,
|
||||
primary: void 0,
|
||||
handler: (accessor, resource: URI, context: IEditorCommandsContext) => {
|
||||
const editorGroupService = accessor.get(IEditorGroupService);
|
||||
const editorService = accessor.get(IWorkbenchEditorService);
|
||||
const quickOpenService = accessor.get(IQuickOpenService);
|
||||
|
||||
const stacks = editorGroupService.getStacksModel();
|
||||
const groupCount = stacks.groups.length;
|
||||
if (groupCount <= 1) {
|
||||
return quickOpenService.show(NAVIGATE_ALL_EDITORS_GROUP_PREFIX);
|
||||
}
|
||||
|
||||
const { position } = positionAndInput(editorGroupService, editorService, context);
|
||||
|
||||
switch (position) {
|
||||
case Position.TWO:
|
||||
return quickOpenService.show(NAVIGATE_IN_GROUP_TWO_PREFIX);
|
||||
case Position.THREE:
|
||||
return quickOpenService.show(NAVIGATE_IN_GROUP_THREE_PREFIX);
|
||||
}
|
||||
|
||||
return quickOpenService.show(NAVIGATE_IN_GROUP_ONE_PREFIX);
|
||||
}
|
||||
});
|
||||
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
id: '_workbench.printStacksModel',
|
||||
weight: KeybindingsRegistry.WEIGHT.workbenchContrib(0),
|
||||
handler(accessor: ServicesAccessor) {
|
||||
console.log(`${accessor.get(IEditorGroupService).getStacksModel().toString()}\n\n`);
|
||||
},
|
||||
when: void 0,
|
||||
primary: void 0
|
||||
});
|
||||
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
id: '_workbench.validateStacksModel',
|
||||
weight: KeybindingsRegistry.WEIGHT.workbenchContrib(0),
|
||||
handler(accessor: ServicesAccessor) {
|
||||
(<EditorStacksModel>accessor.get(IEditorGroupService).getStacksModel()).validate();
|
||||
},
|
||||
when: void 0,
|
||||
primary: void 0
|
||||
});
|
||||
}
|
||||
|
||||
function positionAndInput(editorGroupService: IEditorGroupService, editorService: IWorkbenchEditorService, context?: IEditorCommandsContext): { position: Position, input: IEditorInput } {
|
||||
|
||||
// Resolve from context
|
||||
const model = editorGroupService.getStacksModel();
|
||||
const group = context ? model.getGroup(context.groupId) : undefined;
|
||||
let position = group ? model.positionOfGroup(group) : undefined;
|
||||
let input = group && types.isNumber(context.editorIndex) ? group.getEditor(context.editorIndex) : undefined;
|
||||
|
||||
// If position or input are not passed in take the position and input of the active editor.
|
||||
const active = editorService.getActiveEditor();
|
||||
if (active) {
|
||||
position = typeof position === 'number' ? position : active.position;
|
||||
input = input ? input : <EditorInput>active.input;
|
||||
}
|
||||
|
||||
return { position, input };
|
||||
}
|
||||
|
||||
export function getMultiSelectedEditorContexts(editorContext: IEditorCommandsContext, listService: IListService): IEditorCommandsContext[] {
|
||||
// First check for a focused list to return the selected items from
|
||||
const list = listService.lastFocusedList;
|
||||
if (list instanceof List && list.isDOMFocused()) {
|
||||
const elementToContext = (element: IEditorIdentifier | EditorGroup) =>
|
||||
element instanceof EditorGroup ? { groupId: element.id, editorIndex: undefined } : { groupId: element.group.id, editorIndex: element.group.indexOf(element.editor) };
|
||||
const onlyEditorGroupAndEditor = (e: IEditorIdentifier | EditorGroup) => e instanceof EditorGroup || ('editor' in e && 'group' in e);
|
||||
|
||||
const focusedElements: (IEditorIdentifier | EditorGroup)[] = list.getFocusedElements().filter(onlyEditorGroupAndEditor);
|
||||
// need to take into account when editor context is { group: group }
|
||||
const focus = editorContext ? editorContext : focusedElements.length ? focusedElements.map(elementToContext)[0] : undefined;
|
||||
|
||||
if (focus) {
|
||||
const selection: (IEditorIdentifier | EditorGroup)[] = list.getSelectedElements().filter(onlyEditorGroupAndEditor);
|
||||
// Only respect selection if it contains focused element
|
||||
if (selection && selection.some(s => s instanceof EditorGroup ? s.id === focus.groupId : s.group.id === focus.groupId && s.group.indexOf(s.editor) === focus.editorIndex)) {
|
||||
return selection.map(elementToContext);
|
||||
}
|
||||
|
||||
return [focus];
|
||||
}
|
||||
}
|
||||
|
||||
// Otherwise go with passed in context
|
||||
return !!editorContext ? [editorContext] : [];
|
||||
}
|
||||
|
||||
@@ -24,22 +24,21 @@ import { IEditorGroupService, IEditorTabOptions, GroupArrangement, GroupOrientat
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
|
||||
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { IExtensionService } from 'vs/platform/extensions/common/extensions';
|
||||
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
|
||||
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
|
||||
import { TabsTitleControl } from 'vs/workbench/browser/parts/editor/tabsTitleControl';
|
||||
import { TitleControl, ITitleAreaControl, handleWorkspaceExternalDrop } from 'vs/workbench/browser/parts/editor/titleControl';
|
||||
import { ITitleAreaControl } from 'vs/workbench/browser/parts/editor/titleControl';
|
||||
import { NoTabsTitleControl } from 'vs/workbench/browser/parts/editor/noTabsTitleControl';
|
||||
import { IEditorStacksModel, IStacksModelChangeEvent, IEditorGroup, EditorOptions, TextEditorOptions, IEditorIdentifier } from 'vs/workbench/common/editor';
|
||||
import { extractResources } from 'vs/workbench/browser/editor';
|
||||
import { IWindowService, IWindowsService } from 'vs/platform/windows/common/windows';
|
||||
import { IEditorStacksModel, IStacksModelChangeEvent, IEditorGroup, EditorOptions, TextEditorOptions, IEditorIdentifier, EditorInput, PREFERENCES_EDITOR_ID, TEXT_DIFF_EDITOR_ID } from 'vs/workbench/common/editor';
|
||||
import { getCodeEditor } from 'vs/editor/browser/services/codeEditorService';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import { editorBackground, contrastBorder, activeContrastBorder } from 'vs/platform/theme/common/colorRegistry';
|
||||
import { Themable, EDITOR_GROUP_HEADER_TABS_BACKGROUND, EDITOR_GROUP_HEADER_NO_TABS_BACKGROUND, EDITOR_GROUP_BORDER, EDITOR_DRAG_AND_DROP_BACKGROUND, EDITOR_GROUP_BACKGROUND, EDITOR_GROUP_HEADER_TABS_BORDER } from 'vs/workbench/common/theme';
|
||||
import { attachProgressBarStyler } from 'vs/platform/theme/common/styler';
|
||||
import { IMessageService } from 'vs/platform/message/common/message';
|
||||
import { IFileService } from 'vs/platform/files/common/files';
|
||||
import { IWorkspacesService } from 'vs/platform/workspaces/common/workspaces';
|
||||
import { IDisposable, combinedDisposable } from 'vs/base/common/lifecycle';
|
||||
import { IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { ResourcesDropHandler, LocalSelectionTransfer, DraggedEditorIdentifier } from 'vs/workbench/browser/dnd';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { IPartService } from 'vs/workbench/services/part/common/partService';
|
||||
|
||||
export enum Rochade {
|
||||
NONE,
|
||||
@@ -93,20 +92,30 @@ export interface IEditorGroupsControl {
|
||||
dispose(): void;
|
||||
}
|
||||
|
||||
interface CenteredEditorLayoutData {
|
||||
leftMarginRatio: number;
|
||||
size: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper class to manage multiple side by side editors for the editor part.
|
||||
*/
|
||||
export class EditorGroupsControl extends Themable implements IEditorGroupsControl, IVerticalSashLayoutProvider, IHorizontalSashLayoutProvider {
|
||||
|
||||
private static readonly CENTERED_EDITOR_LAYOUT_DATA_STORAGE_KEY = 'workbench.centerededitorlayout.data';
|
||||
|
||||
private static readonly TITLE_AREA_CONTROL_KEY = '__titleAreaControl';
|
||||
private static readonly PROGRESS_BAR_CONTROL_KEY = '__progressBar';
|
||||
private static readonly INSTANTIATION_SERVICE_KEY = '__instantiationService';
|
||||
|
||||
private static readonly GOLDEN_RATIO = 0.61;
|
||||
private static readonly MIN_EDITOR_WIDTH = 170;
|
||||
private static readonly MIN_EDITOR_HEIGHT = 70;
|
||||
|
||||
private static readonly EDITOR_TITLE_HEIGHT = 35;
|
||||
|
||||
private static readonly CENTERED_EDITOR_MIN_MARGIN = 10;
|
||||
|
||||
private static readonly SNAP_TO_MINIMIZED_THRESHOLD_WIDTH = 50;
|
||||
private static readonly SNAP_TO_MINIMIZED_THRESHOLD_HEIGHT = 20;
|
||||
|
||||
@@ -131,6 +140,22 @@ export class EditorGroupsControl extends Themable implements IEditorGroupsContro
|
||||
private sashTwo: Sash;
|
||||
private startSiloThreeSize: number;
|
||||
|
||||
// if the centered editor layout is activated, the editor inside of silo ONE is centered
|
||||
// the silo will then contain:
|
||||
// [left margin]|[editor]|[right margin]
|
||||
// - The size of the editor is defined by centeredEditorSize
|
||||
// - The position is defined by the ratio centeredEditorLeftMarginRatio = left-margin/(left-margin + editor + right-margin).
|
||||
// - The two sashes can be used to control the size and position of the editor inside of the silo.
|
||||
// - In order to seperate the two sashes from the sashes that control the size of bordering widgets
|
||||
// CENTERED_EDITOR_MIN_MARGIN is forced as a minimum size for the two margins.
|
||||
private centeredEditorActive: boolean;
|
||||
private centeredEditorSashLeft: Sash;
|
||||
private centeredEditorSashRight: Sash;
|
||||
private centeredEditorPreferedSize: number;
|
||||
private centeredEditorLeftMarginRatio: number;
|
||||
private centeredEditorDragStartPosition: number;
|
||||
private centeredEditorDragStartSize: number;
|
||||
|
||||
private visibleEditors: BaseEditor[];
|
||||
|
||||
private lastActiveEditor: BaseEditor;
|
||||
@@ -143,20 +168,20 @@ export class EditorGroupsControl extends Themable implements IEditorGroupsContro
|
||||
private onStacksChangeScheduler: RunOnceScheduler;
|
||||
private stacksChangedBuffer: IStacksModelChangeEvent[];
|
||||
|
||||
private transfer = LocalSelectionTransfer.getInstance<DraggedEditorIdentifier>();
|
||||
|
||||
constructor(
|
||||
parent: Builder,
|
||||
groupOrientation: GroupOrientation,
|
||||
@IWorkbenchEditorService private editorService: IWorkbenchEditorService,
|
||||
@IEditorGroupService private editorGroupService: IEditorGroupService,
|
||||
@IPartService private partService: IPartService,
|
||||
@IStorageService private storageServise: IStorageService,
|
||||
@IContextKeyService private contextKeyService: IContextKeyService,
|
||||
@IExtensionService private extensionService: IExtensionService,
|
||||
@IInstantiationService private instantiationService: IInstantiationService,
|
||||
@IWindowService private windowService: IWindowService,
|
||||
@IWindowsService private windowsService: IWindowsService,
|
||||
@IThemeService themeService: IThemeService,
|
||||
@IFileService private fileService: IFileService,
|
||||
@IMessageService private messageService: IMessageService,
|
||||
@IWorkspacesService private workspacesService: IWorkspacesService
|
||||
@ITelemetryService private telemetryService: ITelemetryService
|
||||
) {
|
||||
super(themeService);
|
||||
|
||||
@@ -431,13 +456,9 @@ export class EditorGroupsControl extends Themable implements IEditorGroupsContro
|
||||
this.visibleEditorFocusTrackerDisposable[position].dispose();
|
||||
}
|
||||
|
||||
// Track focus on editor container
|
||||
const focusTracker = DOM.trackFocus(editor.getContainer().getHTMLElement());
|
||||
const listenerDispose = focusTracker.onDidFocus(() => {
|
||||
this.visibleEditorFocusTrackerDisposable[position] = editor.onDidFocus(() => {
|
||||
this.onFocusGained(editor);
|
||||
});
|
||||
|
||||
this.visibleEditorFocusTrackerDisposable[position] = combinedDisposable([focusTracker, listenerDispose]);
|
||||
}
|
||||
|
||||
private onFocusGained(editor: BaseEditor): void {
|
||||
@@ -979,41 +1000,74 @@ export class EditorGroupsControl extends Themable implements IEditorGroupsContro
|
||||
|
||||
// For each position
|
||||
POSITIONS.forEach(position => {
|
||||
const silo = this.silos[position];
|
||||
|
||||
// Containers (they contain everything and can move between silos)
|
||||
const container = $(silo).div({ 'class': 'container' });
|
||||
|
||||
// InstantiationServices
|
||||
const instantiationService = this.instantiationService.createChild(new ServiceCollection(
|
||||
[IContextKeyService, this.contextKeyService.createScoped(container.getHTMLElement())]
|
||||
));
|
||||
container.setProperty(EditorGroupsControl.INSTANTIATION_SERVICE_KEY, instantiationService); // associate with container
|
||||
|
||||
// Title containers
|
||||
const titleContainer = $(container).div({ 'class': 'title' });
|
||||
if (this.tabOptions.showTabs) {
|
||||
titleContainer.addClass('tabs');
|
||||
}
|
||||
if (this.tabOptions.showIcons) {
|
||||
titleContainer.addClass('show-file-icons');
|
||||
}
|
||||
this.hookTitleDragListener(titleContainer);
|
||||
|
||||
// Title Control
|
||||
this.createTitleControl(this.stacks.groupAt(position), silo, titleContainer, instantiationService);
|
||||
|
||||
// Progress Bar
|
||||
const progressBar = new ProgressBar($(container));
|
||||
this.toUnbind.push(attachProgressBarStyler(progressBar, this.themeService));
|
||||
progressBar.getContainer().hide();
|
||||
container.setProperty(EditorGroupsControl.PROGRESS_BAR_CONTROL_KEY, progressBar); // associate with container
|
||||
this.createSilo(position);
|
||||
});
|
||||
|
||||
// Update Styles
|
||||
this.updateStyles();
|
||||
}
|
||||
|
||||
private createSilo(position: Position): void {
|
||||
const silo = this.silos[position];
|
||||
|
||||
// Containers (they contain everything and can move between silos)
|
||||
const container = $(silo).div({ 'class': 'container' });
|
||||
|
||||
// InstantiationServices
|
||||
const instantiationService = this.instantiationService.createChild(new ServiceCollection(
|
||||
[IContextKeyService, this.contextKeyService.createScoped(container.getHTMLElement())]
|
||||
));
|
||||
container.setProperty(EditorGroupsControl.INSTANTIATION_SERVICE_KEY, instantiationService); // associate with container
|
||||
|
||||
// Title containers
|
||||
const titleContainer = $(container).div({ 'class': 'title' });
|
||||
if (this.tabOptions.showTabs) {
|
||||
titleContainer.addClass('tabs');
|
||||
}
|
||||
if (this.tabOptions.showIcons) {
|
||||
titleContainer.addClass('show-file-icons');
|
||||
}
|
||||
this.hookTitleDragListener(titleContainer);
|
||||
|
||||
// Title Control
|
||||
this.createTitleControl(this.stacks.groupAt(position), silo, titleContainer, instantiationService);
|
||||
|
||||
// Progress Bar
|
||||
const progressBar = new ProgressBar($(container));
|
||||
this.toUnbind.push(attachProgressBarStyler(progressBar, this.themeService));
|
||||
progressBar.getContainer().hide();
|
||||
container.setProperty(EditorGroupsControl.PROGRESS_BAR_CONTROL_KEY, progressBar); // associate with container
|
||||
|
||||
// Sash for first position to support centered editor layout
|
||||
if (position === Position.ONE) {
|
||||
|
||||
// Center Layout stuff
|
||||
const registerSashListeners = (sash: Sash) => {
|
||||
this.toUnbind.push(sash.onDidStart(() => this.onCenterSashDragStart()));
|
||||
this.toUnbind.push(sash.onDidChange((e: ISashEvent) => this.onCenterSashDrag(sash, e)));
|
||||
this.toUnbind.push(sash.onDidEnd(() => this.storeCenteredLayoutData()));
|
||||
this.toUnbind.push(sash.onDidReset(() => this.resetCenteredEditor()));
|
||||
};
|
||||
this.centeredEditorSashLeft = new Sash(container.getHTMLElement(), this, { baseSize: 5, orientation: Orientation.VERTICAL });
|
||||
this.centeredEditorSashRight = new Sash(container.getHTMLElement(), this, { baseSize: 5, orientation: Orientation.VERTICAL });
|
||||
registerSashListeners(this.centeredEditorSashLeft);
|
||||
registerSashListeners(this.centeredEditorSashRight);
|
||||
this.centeredEditorSashLeft.hide();
|
||||
this.centeredEditorSashRight.hide();
|
||||
|
||||
this.centeredEditorActive = false;
|
||||
this.centeredEditorLeftMarginRatio = 0.5;
|
||||
|
||||
// Restore centered layout position and size
|
||||
const centeredLayoutDataString = this.storageServise.get(EditorGroupsControl.CENTERED_EDITOR_LAYOUT_DATA_STORAGE_KEY, StorageScope.GLOBAL);
|
||||
if (centeredLayoutDataString) {
|
||||
const centeredLayout = <CenteredEditorLayoutData>JSON.parse(centeredLayoutDataString);
|
||||
this.centeredEditorLeftMarginRatio = centeredLayout.leftMarginRatio;
|
||||
this.centeredEditorPreferedSize = centeredLayout.size;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected updateStyles(): void {
|
||||
super.updateStyles();
|
||||
|
||||
@@ -1073,8 +1127,17 @@ export class EditorGroupsControl extends Themable implements IEditorGroupsContro
|
||||
return options;
|
||||
}
|
||||
|
||||
const isCopyDrag = (draggedEditor: IEditorIdentifier, e: DragEvent) => {
|
||||
if (draggedEditor && draggedEditor.editor instanceof EditorInput) {
|
||||
if (!draggedEditor.editor.supportsSplitEditor()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return (e.ctrlKey && !isMacintosh) || (e.altKey && isMacintosh);
|
||||
};
|
||||
|
||||
function onDrop(e: DragEvent, position: Position, splitTo?: Position): void {
|
||||
$this.updateFromDropping(node, false);
|
||||
$this.updateFromDragAndDrop(node, false);
|
||||
cleanUp();
|
||||
|
||||
const editorService = $this.editorService;
|
||||
@@ -1084,9 +1147,9 @@ export class EditorGroupsControl extends Themable implements IEditorGroupsContro
|
||||
const freeGroup = (stacks.groups.length === 1) ? Position.TWO : Position.THREE;
|
||||
|
||||
// Check for transfer from title control
|
||||
const draggedEditor = TitleControl.getDraggedEditor();
|
||||
if (draggedEditor) {
|
||||
const isCopy = (e.ctrlKey && !isMacintosh) || (e.altKey && isMacintosh);
|
||||
if ($this.transfer.hasData(DraggedEditorIdentifier.prototype)) {
|
||||
const draggedEditor = $this.transfer.getData(DraggedEditorIdentifier.prototype)[0].identifier;
|
||||
const isCopy = isCopyDrag(draggedEditor, e);
|
||||
|
||||
// Copy editor to new location
|
||||
if (isCopy) {
|
||||
@@ -1124,44 +1187,22 @@ export class EditorGroupsControl extends Themable implements IEditorGroupsContro
|
||||
|
||||
// Check for URI transfer
|
||||
else {
|
||||
const droppedResources = extractResources(e).filter(r => r.resource.scheme === 'file' || r.resource.scheme === 'untitled');
|
||||
if (droppedResources.length) {
|
||||
handleWorkspaceExternalDrop(droppedResources, $this.fileService, $this.messageService, $this.windowsService, $this.windowService, $this.workspacesService).then(handled => {
|
||||
if (handled) {
|
||||
return;
|
||||
}
|
||||
const dropHandler = $this.instantiationService.createInstance(ResourcesDropHandler, { allowWorkspaceOpen: true /* open workspace instead of file if dropped */ });
|
||||
dropHandler.handleDrop(e, () => {
|
||||
if (splitEditor && splitTo !== freeGroup) {
|
||||
groupService.moveGroup(freeGroup, splitTo);
|
||||
}
|
||||
|
||||
// Add external ones to recently open list
|
||||
const externalResources = droppedResources.filter(d => d.isExternal).map(d => d.resource);
|
||||
if (externalResources.length) {
|
||||
$this.windowsService.addRecentlyOpened(externalResources.map(resource => resource.fsPath));
|
||||
}
|
||||
|
||||
// Open in Editor
|
||||
$this.windowService.focusWindow()
|
||||
.then(() => editorService.openEditors(droppedResources.map(d => {
|
||||
return {
|
||||
input: { resource: d.resource, options: { pinned: true } },
|
||||
position: splitEditor ? freeGroup : position
|
||||
};
|
||||
}))).then(() => {
|
||||
if (splitEditor && splitTo !== freeGroup) {
|
||||
groupService.moveGroup(freeGroup, splitTo);
|
||||
}
|
||||
|
||||
groupService.focusGroup(splitEditor ? splitTo : position);
|
||||
})
|
||||
.done(null, errors.onUnexpectedError);
|
||||
});
|
||||
}
|
||||
groupService.focusGroup(splitEditor ? splitTo : position);
|
||||
}, splitEditor ? freeGroup : position);
|
||||
}
|
||||
}
|
||||
|
||||
function positionOverlay(e: DragEvent, groups: number, position: Position): void {
|
||||
const target = <HTMLElement>e.target;
|
||||
const overlayIsSplit = typeof overlay.getProperty(splitToPropertyKey) === 'number';
|
||||
const isCopy = (e.ctrlKey && !isMacintosh) || (e.altKey && isMacintosh);
|
||||
const draggedEditor = TitleControl.getDraggedEditor();
|
||||
const draggedEditor = $this.transfer.hasData(DraggedEditorIdentifier.prototype) ? $this.transfer.getData(DraggedEditorIdentifier.prototype)[0].identifier : void 0;
|
||||
const isCopy = isCopyDrag(draggedEditor, e);
|
||||
|
||||
const overlaySize = $this.layoutVertically ? target.clientWidth : target.clientHeight;
|
||||
const splitThreshold = overlayIsSplit ? overlaySize / 5 : overlaySize / 10;
|
||||
@@ -1266,7 +1307,7 @@ export class EditorGroupsControl extends Themable implements IEditorGroupsContro
|
||||
|
||||
// update the dropEffect, otherwise it would look like a "move" operation. but only if we are
|
||||
// not dragging a tab actually because there we support both moving as well as copying
|
||||
if (!TabsTitleControl.getDraggedEditor()) {
|
||||
if (!$this.transfer.hasData(DraggedEditorIdentifier.prototype)) {
|
||||
e.dataTransfer.dropEffect = 'copy';
|
||||
}
|
||||
|
||||
@@ -1300,14 +1341,14 @@ export class EditorGroupsControl extends Themable implements IEditorGroupsContro
|
||||
DOM.EventHelper.stop(e, true);
|
||||
onDrop(e, Position.ONE);
|
||||
} else {
|
||||
this.updateFromDropping(node, false);
|
||||
this.updateFromDragAndDrop(node, false);
|
||||
}
|
||||
}));
|
||||
|
||||
// Drag enter
|
||||
let counter = 0; // see https://github.com/Microsoft/vscode/issues/14470
|
||||
this.toUnbind.push(DOM.addDisposableListener(node, DOM.EventType.DRAG_ENTER, (e: DragEvent) => {
|
||||
if (!TitleControl.getDraggedEditor()) {
|
||||
if (!$this.transfer.hasData(DraggedEditorIdentifier.prototype)) {
|
||||
// we used to check for the dragged resources here (via dnd.extractResources()) but this
|
||||
// seems to be not possible on Linux and Windows where during DRAG_ENTER the resources
|
||||
// are always undefined up until they are dropped when dragged from the tree. The workaround
|
||||
@@ -1318,7 +1359,7 @@ export class EditorGroupsControl extends Themable implements IEditorGroupsContro
|
||||
}
|
||||
|
||||
counter++;
|
||||
this.updateFromDropping(node, true);
|
||||
this.updateFromDragAndDrop(node, true);
|
||||
|
||||
const target = <HTMLElement>e.target;
|
||||
if (target) {
|
||||
@@ -1328,7 +1369,7 @@ export class EditorGroupsControl extends Themable implements IEditorGroupsContro
|
||||
createOverlay(target);
|
||||
|
||||
if (overlay) {
|
||||
this.updateFromDropping(node, false); // if we show an overlay, we can remove the drop feedback from the editor background
|
||||
this.updateFromDragAndDrop(node, false); // if we show an overlay, we can remove the drop feedback from the editor background
|
||||
}
|
||||
}
|
||||
}));
|
||||
@@ -1337,7 +1378,7 @@ export class EditorGroupsControl extends Themable implements IEditorGroupsContro
|
||||
this.toUnbind.push(DOM.addDisposableListener(node, DOM.EventType.DRAG_LEAVE, (e: DragEvent) => {
|
||||
counter--;
|
||||
if (counter === 0) {
|
||||
this.updateFromDropping(node, false);
|
||||
this.updateFromDragAndDrop(node, false);
|
||||
}
|
||||
}));
|
||||
|
||||
@@ -1345,7 +1386,7 @@ export class EditorGroupsControl extends Themable implements IEditorGroupsContro
|
||||
[node, window].forEach(container => {
|
||||
this.toUnbind.push(DOM.addDisposableListener(container, DOM.EventType.DRAG_END, (e: DragEvent) => {
|
||||
counter = 0;
|
||||
this.updateFromDropping(node, false);
|
||||
this.updateFromDragAndDrop(node, false);
|
||||
cleanUp();
|
||||
}));
|
||||
});
|
||||
@@ -1552,6 +1593,14 @@ export class EditorGroupsControl extends Themable implements IEditorGroupsContro
|
||||
|
||||
// Move to valid position if any
|
||||
if (moveTo !== null) {
|
||||
// TODO@Ben remove me after a while
|
||||
/* __GDPR__
|
||||
"editorGroupMoved" : {
|
||||
"source" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
|
||||
"to": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
|
||||
}
|
||||
*/
|
||||
this.telemetryService.publicLog('editorGroupMoved', { source: position, to: moveTo });
|
||||
this.editorGroupService.moveGroup(position, moveTo);
|
||||
}
|
||||
|
||||
@@ -1595,16 +1644,18 @@ export class EditorGroupsControl extends Themable implements IEditorGroupsContro
|
||||
}
|
||||
}
|
||||
|
||||
private updateFromDropping(element: HTMLElement, isDropping: boolean): void {
|
||||
private updateFromDragAndDrop(element: HTMLElement, isDraggedOver: boolean): void {
|
||||
const groupCount = this.stacks.groups.length;
|
||||
const background = this.getColor(isDropping ? EDITOR_DRAG_AND_DROP_BACKGROUND : groupCount > 0 ? EDITOR_GROUP_BACKGROUND : null);
|
||||
const background = this.getColor(isDraggedOver ? EDITOR_DRAG_AND_DROP_BACKGROUND : groupCount > 0 ? EDITOR_GROUP_BACKGROUND : null);
|
||||
element.style.backgroundColor = background;
|
||||
|
||||
const activeContrastBorderColor = this.getColor(activeContrastBorder);
|
||||
element.style.outlineColor = isDropping ? activeContrastBorderColor : null;
|
||||
element.style.outlineStyle = isDropping && activeContrastBorderColor ? 'dashed' : null;
|
||||
element.style.outlineWidth = isDropping && activeContrastBorderColor ? '2px' : null;
|
||||
element.style.outlineOffset = isDropping && activeContrastBorderColor ? '-2px' : null;
|
||||
element.style.outlineColor = isDraggedOver ? activeContrastBorderColor : null;
|
||||
element.style.outlineStyle = isDraggedOver && activeContrastBorderColor ? 'dashed' : null;
|
||||
element.style.outlineWidth = isDraggedOver && activeContrastBorderColor ? '2px' : null;
|
||||
element.style.outlineOffset = isDraggedOver && activeContrastBorderColor ? '-2px' : null;
|
||||
|
||||
DOM.toggleClass(element, 'dragged-over', isDraggedOver);
|
||||
}
|
||||
|
||||
private posSilo(pos: number, leftTop: string | number, rightBottom?: string | number, borderLeftTopWidth?: string | number): void {
|
||||
@@ -1878,12 +1929,68 @@ export class EditorGroupsControl extends Themable implements IEditorGroupsContro
|
||||
this.sashTwo.layout();
|
||||
}
|
||||
|
||||
private get centeredEditorAvailableSize(): number {
|
||||
return this.silosSize[Position.ONE] - EditorGroupsControl.CENTERED_EDITOR_MIN_MARGIN * 2;
|
||||
}
|
||||
|
||||
private get centeredEditorSize(): number {
|
||||
return Math.min(this.centeredEditorAvailableSize, this.centeredEditorPreferedSize);
|
||||
}
|
||||
|
||||
private get centeredEditorPosition(): number {
|
||||
return EditorGroupsControl.CENTERED_EDITOR_MIN_MARGIN + this.centeredEditorLeftMarginRatio * (this.centeredEditorAvailableSize - this.centeredEditorSize);
|
||||
}
|
||||
|
||||
private onCenterSashDragStart(): void {
|
||||
this.centeredEditorDragStartPosition = this.centeredEditorPosition;
|
||||
this.centeredEditorDragStartSize = this.centeredEditorSize;
|
||||
}
|
||||
|
||||
private onCenterSashDrag(sash: Sash, e: ISashEvent): void {
|
||||
const sashesCoupled = !e.altKey;
|
||||
const delta = sash === this.centeredEditorSashLeft ? e.startX - e.currentX : e.currentX - e.startX;
|
||||
const size = this.centeredEditorDragStartSize + (sashesCoupled ? 2 * delta : delta);
|
||||
let position = this.centeredEditorDragStartPosition;
|
||||
if (sash === this.centeredEditorSashLeft || sashesCoupled) {
|
||||
position -= delta;
|
||||
}
|
||||
|
||||
if (size > 3 * this.minSize && size < this.centeredEditorAvailableSize) {
|
||||
this.centeredEditorPreferedSize = size;
|
||||
position -= EditorGroupsControl.CENTERED_EDITOR_MIN_MARGIN;
|
||||
position = Math.min(position, this.centeredEditorAvailableSize - this.centeredEditorSize);
|
||||
position = Math.max(0, position);
|
||||
this.centeredEditorLeftMarginRatio = position / (this.centeredEditorAvailableSize - this.centeredEditorSize);
|
||||
|
||||
this.layoutContainers();
|
||||
}
|
||||
}
|
||||
|
||||
private storeCenteredLayoutData(): void {
|
||||
const data: CenteredEditorLayoutData = {
|
||||
leftMarginRatio: this.centeredEditorLeftMarginRatio,
|
||||
size: this.centeredEditorSize
|
||||
};
|
||||
this.storageServise.store(EditorGroupsControl.CENTERED_EDITOR_LAYOUT_DATA_STORAGE_KEY, JSON.stringify(data), StorageScope.GLOBAL);
|
||||
}
|
||||
|
||||
public getVerticalSashTop(sash: Sash): number {
|
||||
return 0;
|
||||
}
|
||||
|
||||
public getVerticalSashLeft(sash: Sash): number {
|
||||
return sash === this.sashOne ? this.silosSize[Position.ONE] : this.silosSize[Position.TWO] + this.silosSize[Position.ONE];
|
||||
switch (sash) {
|
||||
case this.sashOne:
|
||||
return this.silosSize[Position.ONE];
|
||||
case this.sashTwo:
|
||||
return this.silosSize[Position.TWO] + this.silosSize[Position.ONE];
|
||||
case this.centeredEditorSashLeft:
|
||||
return this.centeredEditorPosition;
|
||||
case this.centeredEditorSashRight:
|
||||
return this.centeredEditorPosition + this.centeredEditorSize;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
public getVerticalSashHeight(sash: Sash): number {
|
||||
@@ -2033,6 +2140,29 @@ export class EditorGroupsControl extends Themable implements IEditorGroupsContro
|
||||
}
|
||||
});
|
||||
|
||||
// Layout centered Editor (only in vertical layout when one group is opened)
|
||||
const id = this.visibleEditors[Position.ONE] ? this.visibleEditors[Position.ONE].getId() : undefined;
|
||||
const doCentering = this.layoutVertically && this.stacks.groups.length === 1 && this.partService.isEditorLayoutCentered() && id !== PREFERENCES_EDITOR_ID && id !== TEXT_DIFF_EDITOR_ID;
|
||||
if (doCentering && !this.centeredEditorActive) {
|
||||
this.centeredEditorSashLeft.show();
|
||||
this.centeredEditorSashRight.show();
|
||||
|
||||
// no size set yet. Calculate a default value
|
||||
if (!this.centeredEditorPreferedSize) {
|
||||
this.resetCenteredEditor(false);
|
||||
}
|
||||
} else if (!doCentering && this.centeredEditorActive) {
|
||||
this.centeredEditorSashLeft.hide();
|
||||
this.centeredEditorSashRight.hide();
|
||||
}
|
||||
this.centeredEditorActive = doCentering;
|
||||
this.silos[Position.ONE].setClass('centered', doCentering);
|
||||
|
||||
if (this.centeredEditorActive) {
|
||||
this.centeredEditorSashLeft.layout();
|
||||
this.centeredEditorSashRight.layout();
|
||||
}
|
||||
|
||||
// Layout visible editors
|
||||
POSITIONS.forEach(position => {
|
||||
this.layoutEditor(position);
|
||||
@@ -2051,10 +2181,17 @@ export class EditorGroupsControl extends Themable implements IEditorGroupsContro
|
||||
|
||||
private layoutEditor(position: Position): void {
|
||||
const editorSize = this.silosSize[position];
|
||||
if (editorSize && this.visibleEditors[position]) {
|
||||
const editor = this.visibleEditors[position];
|
||||
if (editorSize && editor) {
|
||||
let editorWidth = this.layoutVertically ? editorSize : this.dimension.width;
|
||||
let editorHeight = (this.layoutVertically ? this.dimension.height : this.silosSize[position]) - EditorGroupsControl.EDITOR_TITLE_HEIGHT;
|
||||
|
||||
let editorPosition = 0;
|
||||
if (this.centeredEditorActive) {
|
||||
editorWidth = this.centeredEditorSize;
|
||||
editorPosition = this.centeredEditorPosition;
|
||||
}
|
||||
|
||||
if (position !== Position.ONE) {
|
||||
if (this.layoutVertically) {
|
||||
editorWidth--; // accomodate for 1px left-border in containers TWO, THREE when laying out vertically
|
||||
@@ -2063,10 +2200,23 @@ export class EditorGroupsControl extends Themable implements IEditorGroupsContro
|
||||
}
|
||||
}
|
||||
|
||||
this.visibleEditors[position].layout(new Dimension(editorWidth, editorHeight));
|
||||
const editorContainer = editor.getContainer();
|
||||
editorContainer.style('margin-left', this.centeredEditorActive ? `${editorPosition}px` : null);
|
||||
editorContainer.style('width', this.centeredEditorActive ? `${editorWidth}px` : null);
|
||||
editorContainer.style('border-color', this.centeredEditorActive ? this.getColor(EDITOR_GROUP_BORDER) || this.getColor(contrastBorder) : null);
|
||||
editor.layout(new Dimension(editorWidth, editorHeight));
|
||||
}
|
||||
}
|
||||
|
||||
private resetCenteredEditor(layout: boolean = true) {
|
||||
this.centeredEditorLeftMarginRatio = 0.5;
|
||||
this.centeredEditorPreferedSize = Math.floor(this.dimension.width * EditorGroupsControl.GOLDEN_RATIO);
|
||||
if (layout) {
|
||||
this.layoutContainers();
|
||||
}
|
||||
this.storageServise.remove(EditorGroupsControl.CENTERED_EDITOR_LAYOUT_DATA_STORAGE_KEY, StorageScope.GLOBAL);
|
||||
}
|
||||
|
||||
public getInstantiationService(position: Position): IInstantiationService {
|
||||
return this.getFromContainer(position, EditorGroupsControl.INSTANTIATION_SERVICE_KEY);
|
||||
}
|
||||
|
||||
@@ -32,7 +32,6 @@ import { Position, POSITIONS, Direction, IEditor } from 'vs/platform/editor/comm
|
||||
import { IStorageService } from 'vs/platform/storage/common/storage';
|
||||
import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
|
||||
import { IMessageService, IMessageWithAction, Severity } from 'vs/platform/message/common/message';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { IProgressService } from 'vs/platform/progress/common/progress';
|
||||
import { EditorStacksModel, EditorGroup, EditorIdentifier, EditorCloseEvent } from 'vs/workbench/common/editor/editorStacksModel';
|
||||
@@ -41,12 +40,15 @@ import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/c
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import { editorBackground } from 'vs/platform/theme/common/colorRegistry';
|
||||
import { EDITOR_GROUP_BACKGROUND } from 'vs/workbench/common/theme';
|
||||
import { createCSSRule, scheduleAtNextAnimationFrame } from 'vs/base/browser/dom';
|
||||
import { createCSSRule } from 'vs/base/browser/dom';
|
||||
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
import { join } from 'vs/base/common/paths';
|
||||
import { IEditorDescriptor, IEditorRegistry, Extensions as EditorExtensions } from 'vs/workbench/browser/editor';
|
||||
import { ThrottledEmitter } from 'vs/base/common/async';
|
||||
import { isCodeEditor } from 'vs/editor/browser/editorBrowser';
|
||||
import { isUndefinedOrNull } from 'vs/base/common/types';
|
||||
import { INotificationService, Severity, INotificationActions } from 'vs/platform/notification/common/notification';
|
||||
import { isErrorWithActions } from 'vs/base/common/errors';
|
||||
|
||||
// {{SQL CARBON EDIT}}
|
||||
import { convertEditorInput } from 'sql/parts/common/customInputConverter';
|
||||
@@ -76,6 +78,10 @@ interface IEditorReplacement extends EditorIdentifier {
|
||||
options?: EditorOptions;
|
||||
}
|
||||
|
||||
export type ICloseEditorsFilter = { except?: EditorInput, direction?: Direction, savedOnly?: boolean };
|
||||
export type ICloseEditorsByFilterArgs = { positionOne?: ICloseEditorsFilter, positionTwo?: ICloseEditorsFilter, positionThree?: ICloseEditorsFilter };
|
||||
export type ICloseEditorsArgs = { positionOne?: EditorInput[], positionTwo?: EditorInput[], positionThree?: EditorInput[] };
|
||||
|
||||
/**
|
||||
* The editor part is the container for editors in the workbench. Based on the editor input being opened, it asks the registered
|
||||
* editor for the given input to show the contents. The editor part supports up to 3 side-by-side editors.
|
||||
@@ -124,7 +130,7 @@ export class EditorPart extends Part implements IEditorPart, IEditorGroupService
|
||||
constructor(
|
||||
id: string,
|
||||
restoreFromStorage: boolean,
|
||||
@IMessageService private messageService: IMessageService,
|
||||
@INotificationService private notificationService: INotificationService,
|
||||
@ITelemetryService private telemetryService: ITelemetryService,
|
||||
@IStorageService private storageService: IStorageService,
|
||||
@IPartService private partService: IPartService,
|
||||
@@ -543,24 +549,24 @@ export class EditorPart extends Part implements IEditorPart, IEditorGroupService
|
||||
});
|
||||
}
|
||||
|
||||
private doHandleSetInputError(e: Error | IMessageWithAction, group: EditorGroup, editor: BaseEditor, input: EditorInput, options: EditorOptions, monitor: ProgressMonitor): void {
|
||||
private doHandleSetInputError(error: Error, group: EditorGroup, editor: BaseEditor, input: EditorInput, options: EditorOptions, monitor: ProgressMonitor): void {
|
||||
const position = this.stacks.positionOfGroup(group);
|
||||
|
||||
// Stop loading promise if any
|
||||
monitor.cancel();
|
||||
|
||||
// Report error only if this was not us restoring previous error state
|
||||
if (this.partService.isCreated() && !errors.isPromiseCanceledError(e)) {
|
||||
const errorMessage = nls.localize('editorOpenError', "Unable to open '{0}': {1}.", input.getName(), toErrorMessage(e));
|
||||
|
||||
let error: any;
|
||||
if (e && (<IMessageWithAction>e).actions && (<IMessageWithAction>e).actions.length) {
|
||||
error = errors.create(errorMessage, { actions: (<IMessageWithAction>e).actions }); // Support error actions from thrower
|
||||
} else {
|
||||
error = errorMessage;
|
||||
if (this.partService.isCreated() && !errors.isPromiseCanceledError(error)) {
|
||||
const actions: INotificationActions = { primary: [] };
|
||||
if (isErrorWithActions(error)) {
|
||||
actions.primary = error.actions;
|
||||
}
|
||||
|
||||
this.messageService.show(Severity.Error, types.isString(error) ? new Error(error) : error);
|
||||
this.notificationService.notify({
|
||||
severity: Severity.Error,
|
||||
message: nls.localize('editorOpenError', "Unable to open '{0}': {1}.", input.getName(), toErrorMessage(error)),
|
||||
actions
|
||||
});
|
||||
}
|
||||
|
||||
this.editorGroupsControl.updateProgress(position, ProgressState.DONE);
|
||||
@@ -691,65 +697,199 @@ export class EditorPart extends Part implements IEditorPart, IEditorGroupService
|
||||
this.textCompareEditorVisible.set(this.visibleEditors.some(e => e && e.isVisible() && e.getId() === TEXT_DIFF_EDITOR_ID));
|
||||
}
|
||||
|
||||
public closeAllEditors(except?: Position): TPromise<void> {
|
||||
public closeEditors(positions?: Position[]): TPromise<void>;
|
||||
public closeEditors(position: Position, filter?: ICloseEditorsFilter): TPromise<void>;
|
||||
public closeEditors(position: Position, editors: EditorInput[]): TPromise<void>;
|
||||
public closeEditors(editors: ICloseEditorsByFilterArgs): TPromise<void>;
|
||||
public closeEditors(editors: ICloseEditorsArgs): TPromise<void>;
|
||||
public closeEditors(positionsOrEditors?: Position[] | Position | ICloseEditorsByFilterArgs | ICloseEditorsArgs, filterOrEditors?: ICloseEditorsFilter | EditorInput[]): TPromise<void> {
|
||||
|
||||
// First check for specific position to close
|
||||
if (typeof positionsOrEditors === 'number') {
|
||||
return this.doCloseEditorsAtPosition(positionsOrEditors, filterOrEditors);
|
||||
}
|
||||
|
||||
// Then check for array of positions to close
|
||||
if (Array.isArray(positionsOrEditors) || isUndefinedOrNull(positionsOrEditors)) {
|
||||
return this.doCloseAllEditorsAtPositions(positionsOrEditors as Position[]);
|
||||
}
|
||||
|
||||
// Finally, close specific editors at multiple positions
|
||||
return this.doCloseEditorsAtPositions(positionsOrEditors);
|
||||
}
|
||||
|
||||
private doCloseEditorsAtPositions(editors: ICloseEditorsByFilterArgs | ICloseEditorsArgs): TPromise<void> {
|
||||
|
||||
// Extract editors to close for veto
|
||||
const editorsToClose: EditorIdentifier[] = [];
|
||||
let groupsWithEditorsToClose = 0;
|
||||
POSITIONS.forEach(position => {
|
||||
const details = (position === Position.ONE) ? editors.positionOne : (position === Position.TWO) ? editors.positionTwo : editors.positionThree;
|
||||
if (details && this.stacks.groupAt(position)) {
|
||||
groupsWithEditorsToClose++;
|
||||
editorsToClose.push(...this.extractCloseEditorDetails(position, details).editorsToClose);
|
||||
}
|
||||
});
|
||||
|
||||
// Check for dirty and veto
|
||||
const ignoreDirtyIfOpenedInOtherGroup = (groupsWithEditorsToClose === 1);
|
||||
return this.handleDirty(editorsToClose, ignoreDirtyIfOpenedInOtherGroup).then(veto => {
|
||||
if (veto) {
|
||||
return void 0;
|
||||
}
|
||||
|
||||
// Close by positions starting from last to first to prevent issues when
|
||||
// editor groups close and thus move other editors around that are still open.
|
||||
[Position.THREE, Position.TWO, Position.ONE].forEach(position => {
|
||||
const details = (position === Position.ONE) ? editors.positionOne : (position === Position.TWO) ? editors.positionTwo : editors.positionThree;
|
||||
if (details && this.stacks.groupAt(position)) {
|
||||
const { group, editorsToClose, filter } = this.extractCloseEditorDetails(position, details);
|
||||
|
||||
// Close with filter
|
||||
if (filter) {
|
||||
this.doCloseEditorsWithFilter(group, filter);
|
||||
}
|
||||
|
||||
// Close without filter
|
||||
else {
|
||||
this.doCloseEditors(group, editorsToClose.map(e => e.editor));
|
||||
}
|
||||
|
||||
return void 0;
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private doCloseAllEditorsAtPositions(positions?: Position[]): TPromise<void> {
|
||||
let groups = this.stacks.groups.reverse(); // start from the end to prevent layout to happen through rochade
|
||||
|
||||
// Remove position to exclude if we have any
|
||||
if (typeof except === 'number') {
|
||||
groups = groups.filter(group => this.stacks.positionOfGroup(group) !== except);
|
||||
// Remove positions that are not being asked for if provided
|
||||
if (Array.isArray(positions)) {
|
||||
groups = groups.filter(group => positions.indexOf(this.stacks.positionOfGroup(group)) >= 0);
|
||||
}
|
||||
|
||||
// Check for dirty and veto
|
||||
return this.handleDirty(arrays.flatten(groups.map(group => group.getEditors(true /* in MRU order */).map(editor => { return { group, editor }; })))).then(veto => {
|
||||
const ignoreDirtyIfOpenedInOtherGroup = (groups.length === 1);
|
||||
return this.handleDirty(arrays.flatten(groups.map(group => group.getEditors(true /* in MRU order */).map(editor => ({ group, editor })))), ignoreDirtyIfOpenedInOtherGroup).then(veto => {
|
||||
if (veto) {
|
||||
return;
|
||||
}
|
||||
|
||||
groups.forEach(group => this.doCloseEditors(group));
|
||||
groups.forEach(group => this.doCloseAllEditorsInGroup(group));
|
||||
});
|
||||
}
|
||||
|
||||
public closeEditors(position: Position, filter: { except?: EditorInput, direction?: Direction, unmodifiedOnly?: boolean } = Object.create(null)): TPromise<void> {
|
||||
private doCloseAllEditorsInGroup(group: EditorGroup): void {
|
||||
|
||||
// Update stacks model: remove all non active editors first to prevent opening the next editor in group
|
||||
group.closeEditors(group.activeEditor);
|
||||
|
||||
// Now close active editor in group which will close the group
|
||||
this.doCloseActiveEditor(group);
|
||||
}
|
||||
|
||||
private doCloseEditorsAtPosition(position: Position, filterOrEditors?: ICloseEditorsFilter | EditorInput[]): TPromise<void> {
|
||||
const closeEditorsDetails = this.extractCloseEditorDetails(position, filterOrEditors);
|
||||
if (!closeEditorsDetails) {
|
||||
return TPromise.wrap(void 0);
|
||||
}
|
||||
|
||||
const { group, editorsToClose, filter } = closeEditorsDetails;
|
||||
|
||||
// Check for dirty and veto
|
||||
return this.handleDirty(editorsToClose, true /* ignore if opened in other group */).then(veto => {
|
||||
if (veto) {
|
||||
return void 0;
|
||||
}
|
||||
|
||||
// Close with filter
|
||||
if (filter) {
|
||||
this.doCloseEditorsWithFilter(group, filter);
|
||||
}
|
||||
|
||||
// Close without filter
|
||||
else {
|
||||
this.doCloseEditors(group, editorsToClose.map(e => e.editor));
|
||||
}
|
||||
|
||||
return void 0;
|
||||
});
|
||||
}
|
||||
|
||||
private extractCloseEditorDetails(position: Position, filterOrEditors?: ICloseEditorsFilter | EditorInput[]): { group: EditorGroup, editorsToClose: EditorIdentifier[], filter?: ICloseEditorsFilter } {
|
||||
const group = this.stacks.groupAt(position);
|
||||
if (!group) {
|
||||
return TPromise.wrap<void>(null);
|
||||
return void 0;
|
||||
}
|
||||
|
||||
let editorsToClose = group.getEditors(true /* in MRU order */);
|
||||
let editorsToClose: EditorInput[];
|
||||
let filter: ICloseEditorsFilter;
|
||||
|
||||
// Filter: unmodified only
|
||||
if (filter.unmodifiedOnly) {
|
||||
editorsToClose = editorsToClose.filter(e => !e.isDirty());
|
||||
// Close: Specific Editors
|
||||
if (Array.isArray(filterOrEditors)) {
|
||||
editorsToClose = filterOrEditors;
|
||||
}
|
||||
|
||||
// Filter: direction (left / right)
|
||||
if (!types.isUndefinedOrNull(filter.direction)) {
|
||||
editorsToClose = (filter.direction === Direction.LEFT) ? editorsToClose.slice(0, group.indexOf(filter.except)) : editorsToClose.slice(group.indexOf(filter.except) + 1);
|
||||
}
|
||||
|
||||
// Filter: except
|
||||
// Close: By Filter or all
|
||||
else {
|
||||
editorsToClose = editorsToClose.filter(e => !filter.except || !e.matches(filter.except));
|
||||
}
|
||||
editorsToClose = group.getEditors(true /* in MRU order */);
|
||||
filter = filterOrEditors || Object.create(null);
|
||||
|
||||
// Check for dirty and veto
|
||||
return this.handleDirty(editorsToClose.map(editor => { return { group, editor }; }), true /* ignore if opened in other group */).then(veto => {
|
||||
if (veto) {
|
||||
return;
|
||||
// Filter: saved only
|
||||
if (filter.savedOnly) {
|
||||
editorsToClose = editorsToClose.filter(e => !e.isDirty());
|
||||
}
|
||||
|
||||
this.doCloseEditors(group, filter);
|
||||
});
|
||||
// Filter: direction (left / right)
|
||||
else if (!types.isUndefinedOrNull(filter.direction)) {
|
||||
editorsToClose = (filter.direction === Direction.LEFT) ? editorsToClose.slice(0, group.indexOf(filter.except)) : editorsToClose.slice(group.indexOf(filter.except) + 1);
|
||||
}
|
||||
|
||||
// Filter: except
|
||||
else if (filter.except) {
|
||||
editorsToClose = editorsToClose.filter(e => !e.matches(filter.except));
|
||||
}
|
||||
}
|
||||
|
||||
return { group, editorsToClose: editorsToClose.map(editor => ({ editor, group })), filter };
|
||||
}
|
||||
|
||||
private doCloseEditors(group: EditorGroup, filter: { except?: EditorInput, direction?: Direction, unmodifiedOnly?: boolean } = Object.create(null)): void {
|
||||
private doCloseEditors(group: EditorGroup, editors: EditorInput[]): void {
|
||||
|
||||
// Close all editors in group
|
||||
if (editors.length === group.count) {
|
||||
this.doCloseAllEditorsInGroup(group);
|
||||
}
|
||||
|
||||
// Close specific editors in group
|
||||
else {
|
||||
|
||||
// Editors to close are not active, so we can just close them
|
||||
if (!editors.some(editor => group.activeEditor.matches(editor))) {
|
||||
editors.forEach(editor => this.doCloseInactiveEditor(group, editor));
|
||||
}
|
||||
|
||||
// Active editor is also a candidate to close, thus we make the first
|
||||
// non-candidate editor active and then close the other ones
|
||||
else {
|
||||
const firstEditorToKeep = group.getEditors(true).filter(editorInGroup => !editors.some(editor => editor.matches(editorInGroup)))[0];
|
||||
|
||||
this.openEditor(firstEditorToKeep, null, this.stacks.positionOfGroup(group)).done(() => {
|
||||
editors.forEach(editor => this.doCloseInactiveEditor(group, editor));
|
||||
}, errors.onUnexpectedError);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private doCloseEditorsWithFilter(group: EditorGroup, filter: { except?: EditorInput, direction?: Direction, savedOnly?: boolean }): void {
|
||||
|
||||
// Close all editors if there is no editor to except and
|
||||
// we either are not only closing unmodified editors or
|
||||
// we either are not only closing saved editors or
|
||||
// there are no dirty editors.
|
||||
let closeAllEditors = false;
|
||||
if (!filter.except) {
|
||||
if (!filter.unmodifiedOnly) {
|
||||
if (!filter.savedOnly) {
|
||||
closeAllEditors = true;
|
||||
} else {
|
||||
closeAllEditors = !group.getEditors().some(e => e.isDirty());
|
||||
@@ -758,18 +898,13 @@ export class EditorPart extends Part implements IEditorPart, IEditorGroupService
|
||||
|
||||
// Close all editors in group
|
||||
if (closeAllEditors) {
|
||||
|
||||
// Update stacks model: remove all non active editors first to prevent opening the next editor in group
|
||||
group.closeEditors(group.activeEditor);
|
||||
|
||||
// Now close active editor in group which will close the group
|
||||
this.doCloseActiveEditor(group);
|
||||
this.doCloseAllEditorsInGroup(group);
|
||||
}
|
||||
|
||||
// Close unmodified editors in group
|
||||
else if (filter.unmodifiedOnly) {
|
||||
// Close saved editors in group
|
||||
else if (filter.savedOnly) {
|
||||
|
||||
// We can just close all unmodified editors around the currently active dirty one
|
||||
// We can just close all saved editors around the currently active dirty one
|
||||
if (group.activeEditor.isDirty()) {
|
||||
group.getEditors().filter(editor => !editor.isDirty() && !editor.matches(filter.except)).forEach(editor => this.doCloseInactiveEditor(group, editor));
|
||||
}
|
||||
@@ -777,10 +912,10 @@ export class EditorPart extends Part implements IEditorPart, IEditorGroupService
|
||||
// Active editor is also a candidate to close, thus we make the first dirty editor
|
||||
// active and then close the other ones
|
||||
else {
|
||||
const firstDirtyEditor = group.getEditors().filter(editor => editor.isDirty())[0];
|
||||
const firstDirtyEditor = group.getEditors(true).filter(editor => editor.isDirty())[0];
|
||||
|
||||
this.openEditor(firstDirtyEditor, null, this.stacks.positionOfGroup(group)).done(() => {
|
||||
this.doCloseEditors(group, filter);
|
||||
this.doCloseEditorsWithFilter(group, filter);
|
||||
}, errors.onUnexpectedError);
|
||||
}
|
||||
}
|
||||
@@ -801,7 +936,7 @@ export class EditorPart extends Part implements IEditorPart, IEditorGroupService
|
||||
// being the expected one, otherwise we end up in an endless loop trying to open the
|
||||
// editor
|
||||
if (filter.except.matches(group.activeEditor)) {
|
||||
this.doCloseEditors(group, filter);
|
||||
this.doCloseEditorsWithFilter(group, filter);
|
||||
}
|
||||
}, errors.onUnexpectedError);
|
||||
}
|
||||
@@ -830,14 +965,30 @@ export class EditorPart extends Part implements IEditorPart, IEditorGroupService
|
||||
|
||||
// Switch to editor that we want to handle
|
||||
return this.openEditor(identifier.editor, null, this.stacks.positionOfGroup(identifier.group)).then(() => {
|
||||
return this.ensureEditorOpenedBeforePrompt().then(() => {
|
||||
const res = editor.confirmSave();
|
||||
return editor.confirmSave().then(res => {
|
||||
|
||||
// It could be that the editor saved meanwhile, so we check again
|
||||
// to see if anything needs to happen before closing for good.
|
||||
// This can happen for example if autoSave: onFocusChange is configured
|
||||
// so that the save happens when the dialog opens.
|
||||
if (!editor.isDirty()) {
|
||||
return res === ConfirmResult.CANCEL ? true : false;
|
||||
}
|
||||
|
||||
// Otherwise, handle accordingly
|
||||
switch (res) {
|
||||
case ConfirmResult.SAVE:
|
||||
return editor.save().then(ok => !ok);
|
||||
|
||||
case ConfirmResult.DONT_SAVE:
|
||||
return editor.revert().then(ok => !ok);
|
||||
// first try a normal revert where the contents of the editor are restored
|
||||
return editor.revert().then(ok => !ok, error => {
|
||||
// if that fails, since we are about to close the editor, we accept that
|
||||
// the editor cannot be reverted and instead do a soft revert that just
|
||||
// enables us to close the editor. With this, a user can always close a
|
||||
// dirty editor even when reverting fails.
|
||||
return editor.revert({ soft: true }).then(ok => !ok);
|
||||
});
|
||||
|
||||
case ConfirmResult.CANCEL:
|
||||
return true; // veto
|
||||
@@ -846,27 +997,6 @@ export class EditorPart extends Part implements IEditorPart, IEditorGroupService
|
||||
});
|
||||
}
|
||||
|
||||
private ensureEditorOpenedBeforePrompt(): TPromise<void> {
|
||||
|
||||
// Force title area update
|
||||
this.editorGroupsControl.updateTitleAreas(true /* refresh active group */);
|
||||
|
||||
// TODO@Ben our dialogs currently use the sync API, which means they block the JS
|
||||
// thread when showing. As such, any UI update will not happen unless we wait a little
|
||||
// bit. We wait for 2 request animation frames before showing the confirm. The first
|
||||
// frame is where the UI is updating and the second is good enough to bring up the dialog.
|
||||
// See also https://github.com/Microsoft/vscode/issues/39536
|
||||
return new TPromise<void>(c => {
|
||||
scheduleAtNextAnimationFrame(() => {
|
||||
// Here the UI is updating
|
||||
scheduleAtNextAnimationFrame(() => {
|
||||
// Here we can show a blocking dialog
|
||||
c(void 0);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private countEditors(editor: EditorInput): number {
|
||||
const editors = [editor];
|
||||
if (editor instanceof SideBySideEditorInput) {
|
||||
@@ -1123,7 +1253,9 @@ export class EditorPart extends Part implements IEditorPart, IEditorGroupService
|
||||
return res;
|
||||
}
|
||||
|
||||
public openEditors(editors: { input: EditorInput, position: Position, options?: EditorOptions }[]): TPromise<IEditor[]> {
|
||||
public openEditors(editors: { input: EditorInput, position?: Position, options?: EditorOptions }[]): TPromise<IEditor[]>;
|
||||
public openEditors(editors: { input: EditorInput, options?: EditorOptions }[], sideBySide?: boolean): TPromise<IEditor[]>;
|
||||
public openEditors(editors: { input: EditorInput, position?: Position, options?: EditorOptions }[], sideBySide?: boolean): TPromise<IEditor[]> {
|
||||
if (!editors.length) {
|
||||
return TPromise.as<IEditor[]>([]);
|
||||
}
|
||||
@@ -1135,7 +1267,7 @@ export class EditorPart extends Part implements IEditorPart, IEditorGroupService
|
||||
|
||||
const ratio = this.editorGroupsControl.getRatio();
|
||||
|
||||
return this.doOpenEditors(editors, activePosition, ratio);
|
||||
return this.doOpenEditors(editors, activePosition, ratio, sideBySide);
|
||||
}
|
||||
|
||||
public hasEditorsToRestore(): boolean {
|
||||
@@ -1166,7 +1298,15 @@ export class EditorPart extends Part implements IEditorPart, IEditorGroupService
|
||||
return this._onEditorsChanged.throttle(this.doOpenEditors(editors, activePosition, editorState && editorState.ratio));
|
||||
}
|
||||
|
||||
private doOpenEditors(editors: { input: EditorInput, position: Position, options?: EditorOptions }[], activePosition?: number, ratio?: number[]): TPromise<IEditor[]> {
|
||||
private doOpenEditors(editors: { input: EditorInput, position?: Position, options?: EditorOptions }[], activePosition?: number, ratio?: number[], sideBySide?: boolean): TPromise<IEditor[]> {
|
||||
|
||||
// Find position if not provided already from calling side
|
||||
editors.forEach(editor => {
|
||||
if (typeof editor.position !== 'number') {
|
||||
editor.position = this.findPosition(editor.input, editor.options, sideBySide);
|
||||
}
|
||||
});
|
||||
|
||||
const positionOneEditors = editors.filter(e => e.position === Position.ONE);
|
||||
const positionTwoEditors = editors.filter(e => e.position === Position.TWO);
|
||||
const positionThreeEditors = editors.filter(e => e.position === Position.THREE);
|
||||
@@ -1422,8 +1562,14 @@ export class EditorPart extends Part implements IEditorPart, IEditorGroupService
|
||||
return Position.ONE; // can only be ONE
|
||||
}
|
||||
|
||||
// Ignore revealIfVisible/revealIfOpened option if we got instructed explicitly to
|
||||
// * open at a specific index
|
||||
// * open to the side
|
||||
// * open in a specific group
|
||||
const skipReveal = (options && options.index) || arg1 === true /* open to side */ || typeof arg1 === 'number' /* open specific group */;
|
||||
|
||||
// Respect option to reveal an editor if it is already visible
|
||||
if (options && options.revealIfVisible) {
|
||||
if (!skipReveal && options && options.revealIfVisible) {
|
||||
const group = this.stacks.findGroup(input, true);
|
||||
if (group) {
|
||||
return this.stacks.positionOfGroup(group);
|
||||
@@ -1431,8 +1577,7 @@ export class EditorPart extends Part implements IEditorPart, IEditorGroupService
|
||||
}
|
||||
|
||||
// Respect option to reveal an editor if it is open (not necessarily visible)
|
||||
const skipRevealIfOpen = (options && options.index) || arg1 === true /* open to side */ || typeof arg1 === 'number' /* open specific group */;
|
||||
if (!skipRevealIfOpen && (this.revealIfOpen || (options && options.revealIfOpened))) {
|
||||
if (!skipReveal && (this.revealIfOpen || (options && options.revealIfOpened))) {
|
||||
const group = this.stacks.findGroup(input);
|
||||
if (group) {
|
||||
return this.stacks.positionOfGroup(group);
|
||||
|
||||
@@ -9,7 +9,7 @@ import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import nls = require('vs/nls');
|
||||
import URI from 'vs/base/common/uri';
|
||||
import errors = require('vs/base/common/errors');
|
||||
import { IIconLabelOptions } from 'vs/base/browser/ui/iconLabel/iconLabel';
|
||||
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';
|
||||
@@ -39,7 +39,7 @@ export class EditorPickerEntry extends QuickOpenEntryGroup {
|
||||
this.stacks = editorGroupService.getStacksModel();
|
||||
}
|
||||
|
||||
public getLabelOptions(): IIconLabelOptions {
|
||||
public getLabelOptions(): IIconLabelValueOptions {
|
||||
return {
|
||||
extraClasses: getIconClasses(this.modelService, this.modeService, this.getResource()),
|
||||
italic: this._group.isPreview(this.editor)
|
||||
|
||||
@@ -23,7 +23,8 @@ import { UntitledEditorInput } from 'vs/workbench/common/editor/untitledEditorIn
|
||||
import { IFileEditorInput, EncodingMode, IEncodingSupport, toResource, SideBySideEditorInput } from 'vs/workbench/common/editor';
|
||||
import { IDisposable, combinedDisposable, dispose } from 'vs/base/common/lifecycle';
|
||||
import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService';
|
||||
import { IEditorAction, EndOfLineSequence, IModel } from 'vs/editor/common/editorCommon';
|
||||
import { IEditorAction } from 'vs/editor/common/editorCommon';
|
||||
import { EndOfLineSequence, ITextModel } from 'vs/editor/common/model';
|
||||
import { IModelLanguageChangedEvent, IModelOptionsChangedEvent } from 'vs/editor/common/model/textModelEvents';
|
||||
import { TrimTrailingWhitespaceAction } from 'vs/editor/contrib/linesOperations/linesOperations';
|
||||
import { IndentUsingSpaces, IndentUsingTabs, DetectIndentation, IndentationToSpacesAction, IndentationToTabsAction } from 'vs/editor/contrib/indentation/indentation';
|
||||
@@ -49,15 +50,17 @@ import { IConfigurationChangedEvent, IEditorOptions } from 'vs/editor/common/con
|
||||
import { ITextResourceConfigurationService } from 'vs/editor/common/services/resourceConfiguration';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import { IContextViewService } from 'vs/platform/contextview/browser/contextView';
|
||||
import { attachStylerCallback } from 'vs/platform/theme/common/styler';
|
||||
import { attachStylerCallback, attachButtonStyler } from 'vs/platform/theme/common/styler';
|
||||
import { widgetShadow, editorWidgetBackground } from 'vs/platform/theme/common/colorRegistry';
|
||||
import { ConfigurationTarget } from 'vs/platform/configuration/common/configuration';
|
||||
import { deepClone } from 'vs/base/common/objects';
|
||||
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
|
||||
import { Button } from 'vs/base/browser/ui/button/button';
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
|
||||
// TODO@Sandeep layer breaker
|
||||
// tslint:disable-next-line:import-patterns
|
||||
import { IPreferencesService } from 'vs/workbench/parts/preferences/common/preferences';
|
||||
import { ConfigurationTarget } from 'vs/platform/configuration/common/configuration';
|
||||
import { deepClone } from 'vs/base/common/objects';
|
||||
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
|
||||
|
||||
// {{SQL CARBON EDIT}}
|
||||
import { QueryEditorService } from 'sql/parts/query/services/queryEditorService';
|
||||
@@ -768,7 +771,7 @@ function isWritableBaseEditor(e: IBaseEditor): boolean {
|
||||
|
||||
export class ShowLanguageExtensionsAction extends Action {
|
||||
|
||||
static ID = 'workbench.action.showLanguageExtensions';
|
||||
static readonly ID = 'workbench.action.showLanguageExtensions';
|
||||
|
||||
constructor(
|
||||
private fileExtension: string,
|
||||
@@ -816,7 +819,7 @@ export class ChangeModeAction extends Action {
|
||||
const resource = toResource(activeEditor.input, { supportSideBySide: true });
|
||||
|
||||
let hasLanguageSupport = !!resource;
|
||||
if (resource.scheme === 'untitled' && !this.untitledEditorService.hasAssociatedFilePath(resource)) {
|
||||
if (resource.scheme === Schemas.untitled && !this.untitledEditorService.hasAssociatedFilePath(resource)) {
|
||||
hasLanguageSupport = false; // no configuration for untitled resources (e.g. "Untitled-1")
|
||||
}
|
||||
|
||||
@@ -888,7 +891,7 @@ export class ChangeModeAction extends Action {
|
||||
picks.unshift(autoDetectMode);
|
||||
}
|
||||
|
||||
return this.quickOpenService.pick(picks, { placeHolder: nls.localize('pickLanguage', "Select Language Mode") }).then(pick => {
|
||||
return this.quickOpenService.pick(picks, { placeHolder: nls.localize('pickLanguage', "Select Language Mode"), matchOnDescription: true }).then(pick => {
|
||||
if (!pick) {
|
||||
return;
|
||||
}
|
||||
@@ -913,7 +916,7 @@ export class ChangeModeAction extends Action {
|
||||
// Change mode for active editor
|
||||
activeEditor = this.editorService.getActiveEditor();
|
||||
const codeOrDiffEditor = getCodeOrDiffEditor(activeEditor);
|
||||
const models: IModel[] = [];
|
||||
const models: ITextModel[] = [];
|
||||
if (codeOrDiffEditor.codeEditor) {
|
||||
const codeEditorModel = codeOrDiffEditor.codeEditor.getModel();
|
||||
if (codeEditorModel) {
|
||||
@@ -1285,19 +1288,24 @@ class ScreenReaderDetectedExplanation {
|
||||
const question = $('p.question', {}, nls.localize('screenReaderDetectedExplanation.question', "Are you using a screen reader to operate VS Code?"));
|
||||
domNode.appendChild(question);
|
||||
|
||||
const yesBtn = $('div.button', {}, nls.localize('screenReaderDetectedExplanation.answerYes', "Yes"));
|
||||
this._toDispose.push(addDisposableListener(yesBtn, 'click', () => {
|
||||
const buttonContainer = $('div.buttons');
|
||||
domNode.appendChild(buttonContainer);
|
||||
|
||||
const yesBtn = new Button(buttonContainer);
|
||||
yesBtn.label = nls.localize('screenReaderDetectedExplanation.answerYes', "Yes");
|
||||
this._toDispose.push(attachButtonStyler(yesBtn, this.themeService));
|
||||
this._toDispose.push(yesBtn.onDidClick(e => {
|
||||
this.configurationService.updateValue('editor.accessibilitySupport', 'on', ConfigurationTarget.USER);
|
||||
this.contextViewService.hideContextView();
|
||||
}));
|
||||
domNode.appendChild(yesBtn);
|
||||
|
||||
const noBtn = $('div.button', {}, nls.localize('screenReaderDetectedExplanation.answerNo', "No"));
|
||||
this._toDispose.push(addDisposableListener(noBtn, 'click', () => {
|
||||
const noBtn = new Button(buttonContainer);
|
||||
noBtn.label = nls.localize('screenReaderDetectedExplanation.answerNo', "No");
|
||||
this._toDispose.push(attachButtonStyler(noBtn, this.themeService));
|
||||
this._toDispose.push(noBtn.onDidClick(e => {
|
||||
this.configurationService.updateValue('editor.accessibilitySupport', 'off', ConfigurationTarget.USER);
|
||||
this.contextViewService.hideContextView();
|
||||
}));
|
||||
domNode.appendChild(noBtn);
|
||||
|
||||
const clear = $('div');
|
||||
clear.style.clear = 'both';
|
||||
@@ -1320,7 +1328,7 @@ class ScreenReaderDetectedExplanation {
|
||||
this._toDispose.push(attachStylerCallback(this.themeService, { widgetShadow, editorWidgetBackground }, colors => {
|
||||
domNode.style.backgroundColor = colors.editorWidgetBackground;
|
||||
if (colors.widgetShadow) {
|
||||
domNode.style.boxShadow = `0 2px 8px ${colors.widgetShadow}`;
|
||||
domNode.style.boxShadow = `0 5px 8px ${colors.widgetShadow}`;
|
||||
}
|
||||
}));
|
||||
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.icon-canvas-transparent,.icon-vs-out{fill:#f6f6f6;}.icon-canvas-transparent{opacity:0;}.icon-vs-bg{fill:#424242;}</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: 552 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:#252526;}.icon-canvas-transparent{opacity:0;}.icon-vs-bg{fill:#c5c5c5;}</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: 552 B |
@@ -7,6 +7,11 @@
|
||||
display: none; /* hide sashes while dragging editors around */
|
||||
}
|
||||
|
||||
.monaco-workbench > .editor .one-editor-silo.centered .editor-container {
|
||||
border-style: none solid;
|
||||
border-width: 0px 1px;
|
||||
}
|
||||
|
||||
#monaco-workbench-editor-move-overlay,
|
||||
#monaco-workbench-editor-drop-overlay {
|
||||
position: absolute;
|
||||
@@ -84,4 +89,4 @@
|
||||
|
||||
.monaco-workbench > .editor > .content > .one-editor-silo > .container > .editor-container {
|
||||
height: calc(100% - 35px); /* Editor is below editor title */
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,7 +35,6 @@
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
margin: .5em 0 0;
|
||||
padding: .5em;
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
@@ -56,20 +55,18 @@
|
||||
|
||||
.monaco-shell .screen-reader-detected-explanation p.question {
|
||||
font-size: 1.4em;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.monaco-shell .screen-reader-detected-explanation .button {
|
||||
color: white;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
background-color: #007ACC;
|
||||
.monaco-shell .screen-reader-detected-explanation .buttons {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.monaco-shell .screen-reader-detected-explanation .buttons a {
|
||||
font-size: 13px;
|
||||
padding-left: 12px;
|
||||
padding-right: 12px;
|
||||
border: 4px solid #007ACC;
|
||||
border-radius: 4px;
|
||||
float: left;
|
||||
margin-right: 5px;
|
||||
max-width: fit-content;
|
||||
}
|
||||
|
||||
.monaco-shell.vs .screen-reader-detected-explanation .cancel {
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
.monaco-resource-viewer:focus {
|
||||
outline: none !important;
|
||||
}
|
||||
|
||||
.monaco-resource-viewer {
|
||||
padding: 5px 0 0 10px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.monaco-resource-viewer.image {
|
||||
padding: 0;
|
||||
background-position: 0 0, 8px 8px;
|
||||
background-size: 16px 16px;
|
||||
display: flex;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.vs .monaco-resource-viewer.image {
|
||||
background-image:
|
||||
linear-gradient(45deg, rgb(230, 230, 230) 25%, transparent 25%, transparent 75%, rgb(230, 230, 230) 75%, rgb(230, 230, 230)),
|
||||
linear-gradient(45deg, rgb(230, 230, 230) 25%, transparent 25%, transparent 75%, rgb(230, 230, 230) 75%, rgb(230, 230, 230));
|
||||
}
|
||||
|
||||
.vs-dark .monaco-resource-viewer.image {
|
||||
background-image:
|
||||
linear-gradient(45deg, rgb(20, 20, 20) 25%, transparent 25%, transparent 75%, rgb(20, 20, 20) 75%, rgb(20, 20, 20)),
|
||||
linear-gradient(45deg, rgb(20, 20, 20) 25%, transparent 25%, transparent 75%, rgb(20, 20, 20) 75%, rgb(20, 20, 20));
|
||||
}
|
||||
|
||||
.monaco-resource-viewer img.pixelated {
|
||||
image-rendering: pixelated;
|
||||
}
|
||||
|
||||
.monaco-resource-viewer img.scale-to-fit {
|
||||
max-width: calc(100% - 20px);
|
||||
max-height: calc(100% - 20px);
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.monaco-resource-viewer img {
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
.monaco-resource-viewer.zoom-in {
|
||||
cursor: zoom-in;
|
||||
}
|
||||
|
||||
.monaco-resource-viewer.zoom-out {
|
||||
cursor: zoom-out;
|
||||
}
|
||||
|
||||
.monaco-resource-viewer .open-external,
|
||||
.monaco-resource-viewer .open-external:hover {
|
||||
cursor: pointer;
|
||||
text-decoration: underline;
|
||||
}
|
||||
@@ -87,11 +87,33 @@
|
||||
margin-bottom: auto;
|
||||
}
|
||||
|
||||
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab.sizing-shrink .tab-label {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab.sizing-shrink > .tab-label::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
right: 0;
|
||||
height: 100%;
|
||||
width: 5px;
|
||||
opacity: 1;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab.sizing-fit .monaco-icon-label,
|
||||
.monaco-workbench > .part.editor > .content > .one-editor-silo > .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 */
|
||||
}
|
||||
|
||||
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab.sizing-shrink > .monaco-icon-label > .monaco-icon-label-description-container {
|
||||
text-overflow: clip;
|
||||
}
|
||||
|
||||
.hc-black .monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab.sizing-shrink > .monaco-icon-label > .monaco-icon-label-description-container {
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab .monaco-icon-label::before {
|
||||
height: 16px; /* tweak the icon size of the editor labels when icons are enabled */
|
||||
}
|
||||
|
||||
@@ -19,4 +19,22 @@
|
||||
.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_16x_nohalo.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_16x_nohalo_inversep.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;
|
||||
}
|
||||
|
||||
@@ -11,6 +11,11 @@
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.monaco-workbench > .part.editor > .content > .one-editor-silo.centered > .container > .title .title-label {
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .title-label a,
|
||||
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab .tab-label a {
|
||||
text-decoration: none;
|
||||
@@ -104,4 +109,4 @@
|
||||
.vs-dark .monaco-workbench .show-group-editors-action,
|
||||
.hc-black .monaco-workbench .show-group-editors-action {
|
||||
background: url('stackview-inverse.svg') center center no-repeat;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
import 'vs/css!./media/notabstitle';
|
||||
import errors = require('vs/base/common/errors');
|
||||
import { IEditorGroup, toResource } from 'vs/workbench/common/editor';
|
||||
import { toResource } from 'vs/workbench/common/editor';
|
||||
import DOM = require('vs/base/browser/dom');
|
||||
import { TitleControl } from 'vs/workbench/browser/parts/editor/titleControl';
|
||||
import { ResourceLabel } from 'vs/workbench/browser/labels';
|
||||
@@ -19,12 +19,6 @@ export class NoTabsTitleControl extends TitleControl {
|
||||
private titleContainer: HTMLElement;
|
||||
private editorLabel: ResourceLabel;
|
||||
|
||||
public setContext(group: IEditorGroup): void {
|
||||
super.setContext(group);
|
||||
|
||||
this.editorActionsToolbar.context = { group };
|
||||
}
|
||||
|
||||
public create(parent: HTMLElement): void {
|
||||
super.create(parent);
|
||||
|
||||
@@ -87,14 +81,14 @@ export class NoTabsTitleControl extends TitleControl {
|
||||
|
||||
// Close editor on middle mouse click
|
||||
if (e instanceof MouseEvent && e.button === 1 /* Middle Button */) {
|
||||
this.closeEditorAction.run({ group, editor: group.activeEditor }).done(null, errors.onUnexpectedError);
|
||||
this.closeEditorAction.run({ groupId: group.id, editorIndex: group.indexOf(group.activeEditor) }).done(null, errors.onUnexpectedError);
|
||||
}
|
||||
|
||||
// Focus editor group unless:
|
||||
// - click on toolbar: should trigger actions within
|
||||
// - mouse click: do not focus group if there are more than one as it otherwise makes group DND funky
|
||||
// - touch: always focus
|
||||
else if ((this.stacks.groups.length === 1 || !(e instanceof MouseEvent)) && !DOM.isAncestor((e.target || e.srcElement) as HTMLElement, this.editorActionsToolbar.getContainer().getHTMLElement())) {
|
||||
else if ((this.stacks.groups.length === 1 || !(e instanceof MouseEvent)) && !DOM.isAncestor(((e as GestureEvent).initialTarget || e.target || e.srcElement) as HTMLElement, this.editorActionsToolbar.getContainer().getHTMLElement())) {
|
||||
this.editorGroupService.focusGroup(group);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,12 +6,12 @@
|
||||
import { IDisposable } from 'vs/base/common/lifecycle';
|
||||
import URI from 'vs/base/common/uri';
|
||||
import Event, { Emitter } from 'vs/base/common/event';
|
||||
import * as editorCommon from 'vs/editor/common/editorCommon';
|
||||
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { IRange } from 'vs/editor/common/core/range';
|
||||
import { CursorChangeReason, ICursorPositionChangedEvent } from 'vs/editor/common/controller/cursorEvents';
|
||||
import { ModelDecorationOptions } from 'vs/editor/common/model/textModelWithDecorations';
|
||||
import { ModelDecorationOptions } from 'vs/editor/common/model/textModel';
|
||||
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
|
||||
import { TrackedRangeStickiness, IModelDecorationsChangeAccessor } from 'vs/editor/common/model';
|
||||
|
||||
export interface IRangeHighlightDecoration {
|
||||
resource: URI;
|
||||
@@ -48,7 +48,7 @@ export class RangeHighlightDecorations implements IDisposable {
|
||||
|
||||
private doHighlightRange(editor: ICodeEditor, selectionRange: IRangeHighlightDecoration) {
|
||||
this.removeHighlightRange();
|
||||
editor.changeDecorations((changeAccessor: editorCommon.IModelDecorationsChangeAccessor) => {
|
||||
editor.changeDecorations((changeAccessor: IModelDecorationsChangeAccessor) => {
|
||||
this.rangeHighlightDecorationId = changeAccessor.addDecoration(selectionRange.range, this.createRangeHighlightDecoration(selectionRange.isWholeLine));
|
||||
});
|
||||
this.setEditor(editor);
|
||||
@@ -93,13 +93,13 @@ export class RangeHighlightDecorations implements IDisposable {
|
||||
}
|
||||
|
||||
private static readonly _WHOLE_LINE_RANGE_HIGHLIGHT = ModelDecorationOptions.register({
|
||||
stickiness: editorCommon.TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges,
|
||||
stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges,
|
||||
className: 'rangeHighlight',
|
||||
isWholeLine: true
|
||||
});
|
||||
|
||||
private static readonly _RANGE_HIGHLIGHT = ModelDecorationOptions.register({
|
||||
stickiness: editorCommon.TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges,
|
||||
stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges,
|
||||
className: 'rangeHighlight'
|
||||
});
|
||||
|
||||
|
||||
590
src/vs/workbench/browser/parts/editor/resourceViewer.ts
Normal file
@@ -0,0 +1,590 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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/resourceviewer';
|
||||
import nls = require('vs/nls');
|
||||
import mimes = require('vs/base/common/mime');
|
||||
import URI from 'vs/base/common/uri';
|
||||
import paths = require('vs/base/common/paths');
|
||||
import { Builder, $, Dimension } from 'vs/base/browser/builder';
|
||||
import DOM = require('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 { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
|
||||
import { } from 'vs/platform/workspace/common/workspace';
|
||||
import { IDisposable } 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 { IEditorGroupService } from 'vs/workbench/services/group/common/groupService';
|
||||
import { memoize } from 'vs/base/common/decorators';
|
||||
import * as platform from 'vs/base/common/platform';
|
||||
|
||||
interface MapExtToMediaMimes {
|
||||
[index: string]: string;
|
||||
}
|
||||
|
||||
// Known media mimes that we can handle
|
||||
const mapExtToMediaMimes: MapExtToMediaMimes = {
|
||||
'.bmp': 'image/bmp',
|
||||
'.gif': 'image/gif',
|
||||
'.jpg': 'image/jpg',
|
||||
'.jpeg': 'image/jpg',
|
||||
'.jpe': 'image/jpg',
|
||||
'.png': 'image/png',
|
||||
'.tiff': 'image/tiff',
|
||||
'.tif': 'image/tiff',
|
||||
'.ico': 'image/x-icon',
|
||||
'.tga': 'image/x-tga',
|
||||
'.psd': 'image/vnd.adobe.photoshop',
|
||||
'.webp': 'image/webp',
|
||||
'.mid': 'audio/midi',
|
||||
'.midi': 'audio/midi',
|
||||
'.mp4a': 'audio/mp4',
|
||||
'.mpga': 'audio/mpeg',
|
||||
'.mp2': 'audio/mpeg',
|
||||
'.mp2a': 'audio/mpeg',
|
||||
'.mp3': 'audio/mpeg',
|
||||
'.m2a': 'audio/mpeg',
|
||||
'.m3a': 'audio/mpeg',
|
||||
'.oga': 'audio/ogg',
|
||||
'.ogg': 'audio/ogg',
|
||||
'.spx': 'audio/ogg',
|
||||
'.aac': 'audio/x-aac',
|
||||
'.wav': 'audio/x-wav',
|
||||
'.wma': 'audio/x-ms-wma',
|
||||
'.mp4': 'video/mp4',
|
||||
'.mp4v': 'video/mp4',
|
||||
'.mpg4': 'video/mp4',
|
||||
'.mpeg': 'video/mpeg',
|
||||
'.mpg': 'video/mpeg',
|
||||
'.mpe': 'video/mpeg',
|
||||
'.m1v': 'video/mpeg',
|
||||
'.m2v': 'video/mpeg',
|
||||
'.ogv': 'video/ogg',
|
||||
'.qt': 'video/quicktime',
|
||||
'.mov': 'video/quicktime',
|
||||
'.webm': 'video/webm',
|
||||
'.mkv': 'video/x-matroska',
|
||||
'.mk3d': 'video/x-matroska',
|
||||
'.mks': 'video/x-matroska',
|
||||
'.wmv': 'video/x-ms-wmv',
|
||||
'.flv': 'video/x-flv',
|
||||
'.avi': 'video/x-msvideo',
|
||||
'.movie': 'video/x-sgi-movie'
|
||||
};
|
||||
|
||||
export interface IResourceDescriptor {
|
||||
resource: URI;
|
||||
name: string;
|
||||
size: number;
|
||||
etag: string;
|
||||
mime: string;
|
||||
}
|
||||
|
||||
class BinarySize {
|
||||
public static readonly KB = 1024;
|
||||
public static readonly MB = BinarySize.KB * BinarySize.KB;
|
||||
public static readonly GB = BinarySize.MB * BinarySize.KB;
|
||||
public static readonly TB = BinarySize.GB * BinarySize.KB;
|
||||
|
||||
public static formatSize(size: number): string {
|
||||
if (size < BinarySize.KB) {
|
||||
return nls.localize('sizeB', "{0}B", size);
|
||||
}
|
||||
|
||||
if (size < BinarySize.MB) {
|
||||
return nls.localize('sizeKB', "{0}KB", (size / BinarySize.KB).toFixed(2));
|
||||
}
|
||||
|
||||
if (size < BinarySize.GB) {
|
||||
return nls.localize('sizeMB', "{0}MB", (size / BinarySize.MB).toFixed(2));
|
||||
}
|
||||
|
||||
if (size < BinarySize.TB) {
|
||||
return nls.localize('sizeGB', "{0}GB", (size / BinarySize.GB).toFixed(2));
|
||||
}
|
||||
|
||||
return nls.localize('sizeTB', "{0}TB", (size / BinarySize.TB).toFixed(2));
|
||||
}
|
||||
}
|
||||
|
||||
export interface ResourceViewerContext {
|
||||
layout(dimension: Dimension): void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to actually render the given resource into the provided container. Will adjust scrollbar (if provided) automatically based on loading
|
||||
* progress of the binary resource.
|
||||
*/
|
||||
export class ResourceViewer {
|
||||
public static show(
|
||||
descriptor: IResourceDescriptor,
|
||||
container: Builder,
|
||||
scrollbar: DomScrollableElement,
|
||||
openExternal: (uri: URI) => void,
|
||||
metadataClb: (meta: string) => void
|
||||
): ResourceViewerContext {
|
||||
// Ensure CSS class
|
||||
$(container).setClass('monaco-resource-viewer');
|
||||
|
||||
if (ResourceViewer.isImageResource(descriptor)) {
|
||||
return ImageView.create(container, descriptor, scrollbar, openExternal, metadataClb);
|
||||
}
|
||||
|
||||
GenericBinaryFileView.create(container, metadataClb, descriptor, scrollbar);
|
||||
return null;
|
||||
}
|
||||
|
||||
private static isImageResource(descriptor: IResourceDescriptor) {
|
||||
const mime = ResourceViewer.getMime(descriptor);
|
||||
return mime.indexOf('image/') >= 0;
|
||||
}
|
||||
|
||||
private static getMime(descriptor: IResourceDescriptor): string {
|
||||
let mime = descriptor.mime;
|
||||
if (!mime && descriptor.resource.scheme !== Schemas.data) {
|
||||
const ext = paths.extname(descriptor.resource.toString());
|
||||
if (ext) {
|
||||
mime = mapExtToMediaMimes[ext.toLowerCase()];
|
||||
}
|
||||
}
|
||||
return mime || mimes.MIME_BINARY;
|
||||
}
|
||||
}
|
||||
|
||||
class ImageView {
|
||||
private static readonly MAX_IMAGE_SIZE = BinarySize.MB; // showing images inline is memory intense, so we have a limit
|
||||
private static readonly BASE64_MARKER = 'base64,';
|
||||
|
||||
public static create(
|
||||
container: Builder,
|
||||
descriptor: IResourceDescriptor,
|
||||
scrollbar: DomScrollableElement,
|
||||
openExternal: (uri: URI) => void,
|
||||
metadataClb: (meta: string) => void
|
||||
): ResourceViewerContext | null {
|
||||
if (ImageView.shouldShowImageInline(descriptor)) {
|
||||
return InlineImageView.create(container, descriptor, scrollbar, metadataClb);
|
||||
}
|
||||
|
||||
LargeImageView.create(container, descriptor, openExternal);
|
||||
return null;
|
||||
}
|
||||
|
||||
private static shouldShowImageInline(descriptor: IResourceDescriptor): boolean {
|
||||
let skipInlineImage: boolean;
|
||||
|
||||
// Data URI
|
||||
if (descriptor.resource.scheme === Schemas.data) {
|
||||
const base64MarkerIndex = descriptor.resource.path.indexOf(ImageView.BASE64_MARKER);
|
||||
const hasData = base64MarkerIndex >= 0 && descriptor.resource.path.substring(base64MarkerIndex + ImageView.BASE64_MARKER.length).length > 0;
|
||||
|
||||
skipInlineImage = !hasData || descriptor.size > ImageView.MAX_IMAGE_SIZE || descriptor.resource.path.length > ImageView.MAX_IMAGE_SIZE;
|
||||
}
|
||||
|
||||
// File URI
|
||||
else {
|
||||
skipInlineImage = typeof descriptor.size !== 'number' || descriptor.size > ImageView.MAX_IMAGE_SIZE;
|
||||
}
|
||||
|
||||
return !skipInlineImage;
|
||||
}
|
||||
}
|
||||
|
||||
class LargeImageView {
|
||||
public static create(
|
||||
container: Builder,
|
||||
descriptor: IResourceDescriptor,
|
||||
openExternal: (uri: URI) => void
|
||||
) {
|
||||
const imageContainer = $(container)
|
||||
.empty()
|
||||
.p({
|
||||
text: nls.localize('largeImageError', "The file size of the image is too large (>1MB) to display in the editor. ")
|
||||
});
|
||||
|
||||
if (descriptor.resource.scheme !== Schemas.data) {
|
||||
imageContainer.append($('a', {
|
||||
role: 'button',
|
||||
class: 'open-external',
|
||||
text: nls.localize('resourceOpenExternalButton', "Open image using external program?")
|
||||
}).on(DOM.EventType.CLICK, (e) => {
|
||||
openExternal(descriptor.resource);
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class GenericBinaryFileView {
|
||||
public static create(
|
||||
container: Builder,
|
||||
metadataClb: (meta: string) => void,
|
||||
descriptor: IResourceDescriptor,
|
||||
scrollbar: DomScrollableElement
|
||||
) {
|
||||
$(container)
|
||||
.empty()
|
||||
.span({
|
||||
text: nls.localize('nativeBinaryError', "The file will not be displayed in the editor because it is either binary, very large or uses an unsupported text encoding.")
|
||||
});
|
||||
if (metadataClb) {
|
||||
metadataClb(BinarySize.formatSize(descriptor.size));
|
||||
}
|
||||
scrollbar.scanDomNode();
|
||||
}
|
||||
}
|
||||
|
||||
type Scale = number | 'fit';
|
||||
|
||||
class ZoomStatusbarItem extends Themable implements IStatusbarItem {
|
||||
showTimeout: number;
|
||||
public static instance: ZoomStatusbarItem;
|
||||
|
||||
private statusBarItem: HTMLElement;
|
||||
|
||||
private onSelectScale?: (scale: Scale) => void;
|
||||
|
||||
constructor(
|
||||
@IContextMenuService private contextMenuService: IContextMenuService,
|
||||
@IEditorGroupService editorGroupService: IEditorGroupService,
|
||||
@IThemeService themeService: IThemeService
|
||||
) {
|
||||
super(themeService);
|
||||
ZoomStatusbarItem.instance = this;
|
||||
this.toUnbind.push(editorGroupService.onEditorsChanged(() => this.onEditorsChanged()));
|
||||
}
|
||||
|
||||
private onEditorsChanged(): void {
|
||||
this.hide();
|
||||
this.onSelectScale = undefined;
|
||||
}
|
||||
|
||||
public show(scale: Scale, onSelectScale: (scale: number) => void) {
|
||||
clearTimeout(this.showTimeout);
|
||||
this.showTimeout = setTimeout(() => {
|
||||
this.onSelectScale = onSelectScale;
|
||||
this.statusBarItem.style.display = 'block';
|
||||
this.updateLabel(scale);
|
||||
}, 0);
|
||||
}
|
||||
|
||||
public hide() {
|
||||
this.statusBarItem.style.display = 'none';
|
||||
}
|
||||
|
||||
public 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.style.display = 'none';
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
private updateLabel(scale: Scale) {
|
||||
this.statusBarItem.textContent = ZoomStatusbarItem.zoomLabel(scale);
|
||||
}
|
||||
|
||||
@memoize
|
||||
private get zoomActions(): Action[] {
|
||||
const scales: Scale[] = [10, 5, 2, 1, 0.5, 0.2, 'fit'];
|
||||
return scales.map(scale =>
|
||||
new Action('zoom.' + scale, ZoomStatusbarItem.zoomLabel(scale), undefined, undefined, () => {
|
||||
if (this.onSelectScale) {
|
||||
this.onSelectScale(scale);
|
||||
}
|
||||
return null;
|
||||
}));
|
||||
}
|
||||
|
||||
private static zoomLabel(scale: Scale): string {
|
||||
return scale === 'fit'
|
||||
? nls.localize('zoom.action.fit.label', 'Whole Image')
|
||||
: `${Math.round(scale * 100)}%`;
|
||||
}
|
||||
}
|
||||
|
||||
Registry.as<IStatusbarRegistry>(Extensions.Statusbar).registerStatusbarItem(
|
||||
new StatusbarItemDescriptor(ZoomStatusbarItem, StatusbarAlignment.RIGHT, 101 /* to the left of editor status (100) */)
|
||||
);
|
||||
|
||||
interface ImageState {
|
||||
scale: Scale;
|
||||
offsetX: number;
|
||||
offsetY: number;
|
||||
}
|
||||
|
||||
class InlineImageView {
|
||||
private static readonly SCALE_PINCH_FACTOR = 0.075;
|
||||
private static readonly MAX_SCALE = 20;
|
||||
private static readonly MIN_SCALE = 0.1;
|
||||
|
||||
private static readonly zoomLevels: Scale[] = [
|
||||
0.1,
|
||||
0.2,
|
||||
0.3,
|
||||
0.4,
|
||||
0.5,
|
||||
0.6,
|
||||
0.7,
|
||||
0.8,
|
||||
0.9,
|
||||
1,
|
||||
1.5,
|
||||
2,
|
||||
3,
|
||||
5,
|
||||
7,
|
||||
10,
|
||||
15,
|
||||
20
|
||||
];
|
||||
|
||||
/**
|
||||
* Enable image-rendering: pixelated for images scaled by more than this.
|
||||
*/
|
||||
private static readonly PIXELATION_THRESHOLD = 3;
|
||||
|
||||
/**
|
||||
* Chrome is caching images very aggressively and so we use the ETag information to find out if
|
||||
* we need to bypass the cache or not. We could always bypass the cache everytime we show the image
|
||||
* however that has very bad impact on memory consumption because each time the image gets shown,
|
||||
* memory grows (see also https://github.com/electron/electron/issues/6275)
|
||||
*/
|
||||
private static IMAGE_RESOURCE_ETAG_CACHE = new LRUCache<string, { etag: string, src: string }>(100);
|
||||
|
||||
/**
|
||||
* Store the scale and position of an image so it can be restored when changing editor tabs
|
||||
*/
|
||||
private static readonly imageStateCache = new LRUCache<string, ImageState>(100);
|
||||
|
||||
public static create(
|
||||
container: Builder,
|
||||
descriptor: IResourceDescriptor,
|
||||
scrollbar: DomScrollableElement,
|
||||
metadataClb: (meta: string) => void
|
||||
) {
|
||||
const context = {
|
||||
layout(dimension: Dimension) { }
|
||||
};
|
||||
|
||||
const cacheKey = descriptor.resource.toString();
|
||||
|
||||
let ctrlPressed = false;
|
||||
let altPressed = false;
|
||||
|
||||
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;
|
||||
|
||||
function updateScale(newScale: Scale) {
|
||||
if (!img || !imgElement.parentElement) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (newScale === 'fit') {
|
||||
scale = 'fit';
|
||||
img.addClass('scale-to-fit');
|
||||
img.removeClass('pixelated');
|
||||
img.style('min-width', 'auto');
|
||||
img.style('width', 'auto');
|
||||
InlineImageView.imageStateCache.set(cacheKey, null);
|
||||
} else {
|
||||
const oldWidth = imgElement.width;
|
||||
const oldHeight = imgElement.height;
|
||||
|
||||
scale = clamp(newScale, InlineImageView.MIN_SCALE, InlineImageView.MAX_SCALE);
|
||||
if (scale >= InlineImageView.PIXELATION_THRESHOLD) {
|
||||
img.addClass('pixelated');
|
||||
} else {
|
||||
img.removeClass('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;
|
||||
|
||||
img.removeClass('scale-to-fit');
|
||||
img.style('min-width', `${(imgElement.naturalWidth * scale)}px`);
|
||||
img.style('width', `${(imgElement.naturalWidth * scale)}px`);
|
||||
|
||||
const newWidth = imgElement.width;
|
||||
const scaleFactor = (newWidth - oldWidth) / oldWidth;
|
||||
|
||||
const newScrollLeft = ((oldWidth * scaleFactor * dx) + scrollLeft);
|
||||
const newScrollTop = ((oldHeight * scaleFactor * dy) + scrollTop);
|
||||
scrollbar.setScrollPosition({
|
||||
scrollLeft: newScrollLeft,
|
||||
scrollTop: newScrollTop,
|
||||
});
|
||||
|
||||
InlineImageView.imageStateCache.set(cacheKey, { scale: scale, offsetX: newScrollLeft, offsetY: newScrollTop });
|
||||
|
||||
}
|
||||
ZoomStatusbarItem.instance.show(scale, updateScale);
|
||||
scrollbar.scanDomNode();
|
||||
}
|
||||
|
||||
function firstZoom() {
|
||||
scale = imgElement.clientWidth / imgElement.naturalWidth;
|
||||
updateScale(scale);
|
||||
}
|
||||
|
||||
$(container)
|
||||
.on(DOM.EventType.KEY_DOWN, (e: KeyboardEvent, c) => {
|
||||
if (!img) {
|
||||
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;
|
||||
}
|
||||
|
||||
ctrlPressed = e.ctrlKey;
|
||||
altPressed = e.altKey;
|
||||
|
||||
if (!(platform.isMacintosh ? altPressed : ctrlPressed)) {
|
||||
c.removeClass('zoom-out').addClass('zoom-in');
|
||||
}
|
||||
})
|
||||
.on(DOM.EventType.CLICK, (e: MouseEvent) => {
|
||||
if (!img) {
|
||||
return;
|
||||
}
|
||||
|
||||
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.MIN_SCALE);
|
||||
}
|
||||
})
|
||||
.on(DOM.EventType.WHEEL, (e: WheelEvent) => {
|
||||
if (!img) {
|
||||
return;
|
||||
}
|
||||
|
||||
const isScrollWhellKeyPressed = platform.isMacintosh ? altPressed : ctrlPressed;
|
||||
if (!isScrollWhellKeyPressed && !e.ctrlKey) { // pinching is reported as scroll wheel + ctrl
|
||||
return;
|
||||
}
|
||||
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
if (scale === 'fit') {
|
||||
firstZoom();
|
||||
}
|
||||
|
||||
let delta = e.deltaY < 0 ? 1 : -1;
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
const entry = InlineImageView.imageStateCache.get(cacheKey);
|
||||
if (entry) {
|
||||
const { scrollTop, scrollLeft } = imgElement.parentElement;
|
||||
InlineImageView.imageStateCache.set(cacheKey, { scale: entry.scale, offsetX: scrollLeft, offsetY: scrollTop });
|
||||
}
|
||||
});
|
||||
|
||||
$(container)
|
||||
.empty()
|
||||
.addClass('image', 'zoom-in')
|
||||
.img({ src: InlineImageView.imageSrc(descriptor) })
|
||||
.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,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return context;
|
||||
}
|
||||
|
||||
private static imageSrc(descriptor: IResourceDescriptor): string {
|
||||
if (descriptor.resource.scheme === Schemas.data) {
|
||||
return descriptor.resource.toString(true /* skip encoding */);
|
||||
}
|
||||
|
||||
const src = descriptor.resource.toString();
|
||||
|
||||
let cached = InlineImageView.IMAGE_RESOURCE_ETAG_CACHE.get(src);
|
||||
if (!cached) {
|
||||
cached = { etag: descriptor.etag, src };
|
||||
InlineImageView.IMAGE_RESOURCE_ETAG_CACHE.set(src, cached);
|
||||
}
|
||||
|
||||
if (cached.etag !== descriptor.etag) {
|
||||
cached.etag = descriptor.etag;
|
||||
cached.src = `${src}?${Date.now()}`; // bypass cache with this trick
|
||||
}
|
||||
|
||||
return cached.src;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ import { IEditorRegistry, Extensions as EditorExtensions } from 'vs/workbench/br
|
||||
|
||||
export class SideBySideEditor extends BaseEditor {
|
||||
|
||||
public static ID: string = 'workbench.editor.sidebysideEditor';
|
||||
public static readonly ID: string = 'workbench.editor.sidebysideEditor';
|
||||
|
||||
private dimension: Dimension;
|
||||
|
||||
|
||||
@@ -11,8 +11,7 @@ import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import errors = require('vs/base/common/errors');
|
||||
import DOM = require('vs/base/browser/dom');
|
||||
import { isMacintosh } from 'vs/base/common/platform';
|
||||
import { MIME_BINARY } from 'vs/base/common/mime';
|
||||
import { shorten, getPathLabel } from 'vs/base/common/labels';
|
||||
import { shorten } from 'vs/base/common/labels';
|
||||
import { ActionRunner, IAction } from 'vs/base/common/actions';
|
||||
import { Position, IEditorInput, Verbosity, IUntitledResourceInput } from 'vs/platform/editor/common/editor';
|
||||
import { IEditorGroup, toResource } from 'vs/workbench/common/editor';
|
||||
@@ -24,32 +23,30 @@ import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar';
|
||||
import { IWorkbenchEditorService, DelegatingWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
|
||||
import { IEditorGroupService } from 'vs/workbench/services/group/common/groupService';
|
||||
import { IMessageService } from 'vs/platform/message/common/message';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
||||
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { IMenuService } from 'vs/platform/actions/common/actions';
|
||||
import { IWindowService, IWindowsService } from 'vs/platform/windows/common/windows';
|
||||
import { TitleControl, handleWorkspaceExternalDrop } from 'vs/workbench/browser/parts/editor/titleControl';
|
||||
import { TitleControl } from 'vs/workbench/browser/parts/editor/titleControl';
|
||||
import { IQuickOpenService } from 'vs/platform/quickOpen/common/quickOpen';
|
||||
import { IDisposable, dispose, combinedDisposable } from 'vs/base/common/lifecycle';
|
||||
import { ScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement';
|
||||
import { ScrollbarVisibility } from 'vs/base/common/scrollable';
|
||||
import { extractResources } from 'vs/workbench/browser/editor';
|
||||
import { getOrSet } from 'vs/base/common/map';
|
||||
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
|
||||
import { IThemeService, registerThemingParticipant, ITheme, ICssStyleCollector } from 'vs/platform/theme/common/themeService';
|
||||
import { TAB_INACTIVE_BACKGROUND, TAB_ACTIVE_BACKGROUND, TAB_ACTIVE_FOREGROUND, TAB_INACTIVE_FOREGROUND, TAB_BORDER, EDITOR_DRAG_AND_DROP_BACKGROUND, TAB_UNFOCUSED_ACTIVE_FOREGROUND, TAB_UNFOCUSED_INACTIVE_FOREGROUND, TAB_UNFOCUSED_ACTIVE_BORDER, TAB_ACTIVE_BORDER } from 'vs/workbench/common/theme';
|
||||
import { activeContrastBorder, contrastBorder } from 'vs/platform/theme/common/colorRegistry';
|
||||
import { IFileService } from 'vs/platform/files/common/files';
|
||||
import { IWorkspacesService } from 'vs/platform/workspaces/common/workspaces';
|
||||
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, EDITOR_GROUP_BACKGROUND, WORKBENCH_BACKGROUND } from 'vs/workbench/common/theme';
|
||||
import { activeContrastBorder, contrastBorder, editorBackground } from 'vs/platform/theme/common/colorRegistry';
|
||||
import { Dimension } from 'vs/base/browser/builder';
|
||||
import { scheduleAtNextAnimationFrame } from 'vs/base/browser/dom';
|
||||
import { ResourcesDropHandler, fillResourceDataTransfers, LocalSelectionTransfer, DraggedEditorIdentifier } from 'vs/workbench/browser/dnd';
|
||||
import { Color } from 'vs/base/common/color';
|
||||
import { INotificationService } from 'vs/platform/notification/common/notification';
|
||||
|
||||
|
||||
// {{SQL CARBON EDIT}} -- Display the editor's tab color
|
||||
import { HIGH_CONTRAST } from 'vs/platform/theme/common/themeService';
|
||||
import { Color } from 'vs/base/common/color';
|
||||
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';
|
||||
@@ -77,6 +74,7 @@ export class TabsTitleControl extends TitleControl {
|
||||
private blockRevealActiveTab: boolean;
|
||||
private dimension: Dimension;
|
||||
private layoutScheduled: IDisposable;
|
||||
private transfer = LocalSelectionTransfer.getInstance<DraggedEditorIdentifier>();
|
||||
|
||||
constructor(
|
||||
@IContextMenuService contextMenuService: IContextMenuService,
|
||||
@@ -86,22 +84,17 @@ export class TabsTitleControl extends TitleControl {
|
||||
@IContextKeyService contextKeyService: IContextKeyService,
|
||||
@IKeybindingService keybindingService: IKeybindingService,
|
||||
@ITelemetryService telemetryService: ITelemetryService,
|
||||
@IMessageService messageService: IMessageService,
|
||||
@INotificationService notificationService: INotificationService,
|
||||
@IMenuService menuService: IMenuService,
|
||||
@IQuickOpenService quickOpenService: IQuickOpenService,
|
||||
@IWindowService private windowService: IWindowService,
|
||||
@IWindowsService private windowsService: IWindowsService,
|
||||
@IThemeService themeService: IThemeService,
|
||||
@IFileService private fileService: IFileService,
|
||||
@IWorkspacesService private workspacesService: IWorkspacesService,
|
||||
// {{SQL CARBON EDIT}} -- Display the editor's tab color
|
||||
@IWorkspaceConfigurationService private workspaceConfigurationService: IWorkspaceConfigurationService,
|
||||
@IConnectionManagementService private connectionService: IConnectionManagementService,
|
||||
@IQueryEditorService private queryEditorService: IQueryEditorService,
|
||||
@IObjectExplorerService private objectExplorerService: IObjectExplorerService,
|
||||
@IWorkbenchEditorService private workbenchEditorService: IWorkbenchEditorService,
|
||||
@IObjectExplorerService private objectExplorerService: IObjectExplorerService
|
||||
) {
|
||||
super(contextMenuService, instantiationService, editorService, editorGroupService, contextKeyService, keybindingService, telemetryService, messageService, menuService, quickOpenService, themeService);
|
||||
super(contextMenuService, instantiationService, editorService, editorGroupService, contextKeyService, keybindingService, telemetryService, notificationService, menuService, quickOpenService, themeService);
|
||||
|
||||
this.tabDisposeables = [];
|
||||
this.editorLabels = [];
|
||||
@@ -135,12 +128,6 @@ export class TabsTitleControl extends TitleControl {
|
||||
return this.instantiationService.createChild(new ServiceCollection([IWorkbenchEditorService, delegatingEditorService]));
|
||||
}
|
||||
|
||||
public setContext(group: IEditorGroup): void {
|
||||
super.setContext(group);
|
||||
|
||||
this.editorActionsToolbar.context = { group };
|
||||
}
|
||||
|
||||
public create(parent: HTMLElement): void {
|
||||
super.create(parent);
|
||||
|
||||
@@ -196,7 +183,7 @@ export class TabsTitleControl extends TitleControl {
|
||||
|
||||
// Drag over
|
||||
this.toUnbind.push(DOM.addDisposableListener(this.tabsContainer, DOM.EventType.DRAG_OVER, (e: DragEvent) => {
|
||||
const draggedEditor = TabsTitleControl.getDraggedEditor();
|
||||
const draggedEditor = this.transfer.hasData(DraggedEditorIdentifier.prototype) ? this.transfer.getData(DraggedEditorIdentifier.prototype)[0].identifier : void 0;
|
||||
|
||||
// update the dropEffect, otherwise it would look like a "move" operation. but only if we are
|
||||
// not dragging a tab actually because there we support both moving as well as copying
|
||||
@@ -309,7 +296,9 @@ export class TabsTitleControl extends TitleControl {
|
||||
const isGroupActive = this.stacks.isActive(group);
|
||||
if (isGroupActive) {
|
||||
DOM.addClass(this.titleContainer, 'active');
|
||||
DOM.removeClass(this.titleContainer, 'inactive');
|
||||
} else {
|
||||
DOM.addClass(this.titleContainer, 'inactive');
|
||||
DOM.removeClass(this.titleContainer, 'active');
|
||||
}
|
||||
|
||||
@@ -583,7 +572,10 @@ export class TabsTitleControl extends TitleControl {
|
||||
DOM.addClass(tabCloseContainer, 'tab-close');
|
||||
tabContainer.appendChild(tabCloseContainer);
|
||||
|
||||
const bar = new ActionBar(tabCloseContainer, { ariaLabel: nls.localize('araLabelTabActions', "Tab actions"), actionRunner: new TabActionRunner(() => this.context, index) });
|
||||
const actionRunner = new TabActionRunner(() => this.context, index);
|
||||
this.tabDisposeables.push(actionRunner);
|
||||
|
||||
const bar = new ActionBar(tabCloseContainer, { ariaLabel: nls.localize('araLabelTabActions', "Tab actions"), actionRunner });
|
||||
bar.push(this.closeEditorAction, { icon: true, label: false, keybinding: this.getKeybindingLabel(this.closeEditorAction) });
|
||||
|
||||
// Eventing
|
||||
@@ -659,7 +651,7 @@ export class TabsTitleControl extends TitleControl {
|
||||
private hookTabListeners(tab: HTMLElement, index: number): IDisposable {
|
||||
const disposables: IDisposable[] = [];
|
||||
|
||||
const handleClickOrTouch = (e: MouseEvent | GestureEvent) => {
|
||||
const handleClickOrTouch = (e: MouseEvent | GestureEvent): void => {
|
||||
tab.blur();
|
||||
|
||||
if (e instanceof MouseEvent && e.button !== 0) {
|
||||
@@ -670,8 +662,8 @@ export class TabsTitleControl extends TitleControl {
|
||||
return void 0; // only for left mouse click
|
||||
}
|
||||
|
||||
const { editor, position } = this.toTabContext(index);
|
||||
if (!this.isTabActionBar((e.target || e.srcElement) as HTMLElement)) {
|
||||
const { editor, position } = this.getGroupPositionAndEditor(index);
|
||||
if (!this.isTabActionBar(((e as GestureEvent).initialTarget || e.target || e.srcElement) as HTMLElement)) {
|
||||
setTimeout(() => this.editorService.openEditor(editor, null, position).done(null, errors.onUnexpectedError)); // timeout to keep focus in editor after mouse up
|
||||
}
|
||||
|
||||
@@ -681,7 +673,7 @@ export class TabsTitleControl extends TitleControl {
|
||||
const showContextMenu = (e: Event) => {
|
||||
DOM.EventHelper.stop(e);
|
||||
|
||||
const { group, editor } = this.toTabContext(index);
|
||||
const { group, editor } = this.getGroupPositionAndEditor(index);
|
||||
|
||||
this.onContextMenu({ group, editor }, e, tab);
|
||||
};
|
||||
@@ -703,7 +695,7 @@ export class TabsTitleControl extends TitleControl {
|
||||
tab.blur();
|
||||
|
||||
if (e.button === 1 /* Middle Button*/ && !this.isTabActionBar((e.target || e.srcElement) as HTMLElement)) {
|
||||
this.closeEditorAction.run(this.toTabContext(index)).done(null, errors.onUnexpectedError);
|
||||
this.closeEditorAction.run({ groupId: this.context.id, editorIndex: index }).done(null, errors.onUnexpectedError);
|
||||
}
|
||||
}));
|
||||
|
||||
@@ -725,7 +717,7 @@ export class TabsTitleControl extends TitleControl {
|
||||
const event = new StandardKeyboardEvent(e);
|
||||
let handled = false;
|
||||
|
||||
const { group, position, editor } = this.toTabContext(index);
|
||||
const { group, position, editor } = this.getGroupPositionAndEditor(index);
|
||||
|
||||
// Run action on Enter/Space
|
||||
if (event.equals(KeyCode.Enter) || event.equals(KeyCode.Space)) {
|
||||
@@ -768,7 +760,7 @@ export class TabsTitleControl extends TitleControl {
|
||||
disposables.push(DOM.addDisposableListener(tab, DOM.EventType.DBLCLICK, (e: MouseEvent) => {
|
||||
DOM.EventHelper.stop(e);
|
||||
|
||||
const { group, editor } = this.toTabContext(index);
|
||||
const { group, editor } = this.getGroupPositionAndEditor(index);
|
||||
|
||||
this.editorGroupService.pinEditor(group, editor);
|
||||
}));
|
||||
@@ -776,28 +768,23 @@ export class TabsTitleControl extends TitleControl {
|
||||
// Context menu
|
||||
disposables.push(DOM.addDisposableListener(tab, DOM.EventType.CONTEXT_MENU, (e: Event) => {
|
||||
DOM.EventHelper.stop(e, true);
|
||||
const { group, editor } = this.toTabContext(index);
|
||||
const { group, editor } = this.getGroupPositionAndEditor(index);
|
||||
|
||||
this.onContextMenu({ group, editor }, e, tab);
|
||||
}, true /* use capture to fix https://github.com/Microsoft/vscode/issues/19145 */));
|
||||
|
||||
// Drag start
|
||||
disposables.push(DOM.addDisposableListener(tab, DOM.EventType.DRAG_START, (e: DragEvent) => {
|
||||
const { group, editor } = this.toTabContext(index);
|
||||
const { group, editor } = this.getGroupPositionAndEditor(index);
|
||||
|
||||
this.transfer.setData([new DraggedEditorIdentifier({ editor, group })], DraggedEditorIdentifier.prototype);
|
||||
|
||||
this.onEditorDragStart({ editor, group });
|
||||
e.dataTransfer.effectAllowed = 'copyMove';
|
||||
|
||||
// Insert transfer accordingly
|
||||
// Apply some datatransfer types to allow for dragging the element outside of the application
|
||||
const resource = toResource(editor, { supportSideBySide: true });
|
||||
if (resource) {
|
||||
const resourceStr = resource.toString();
|
||||
e.dataTransfer.setData('URL', resourceStr); // enables cross window DND of tabs
|
||||
e.dataTransfer.setData('text/plain', getPathLabel(resource)); // enables dropping tab resource path into text controls
|
||||
|
||||
if (resource.scheme === 'file') {
|
||||
e.dataTransfer.setData('DownloadURL', [MIME_BINARY, editor.getName(), resourceStr].join(':')); // enables support to drag a tab as file to desktop
|
||||
}
|
||||
this.instantiationService.invokeFunction(fillResourceDataTransfers, [resource], e);
|
||||
}
|
||||
|
||||
// Fixes https://github.com/Microsoft/vscode/issues/18733
|
||||
@@ -818,9 +805,9 @@ export class TabsTitleControl extends TitleControl {
|
||||
// Find out if the currently dragged editor is this tab and in that
|
||||
// case we do not want to show any drop feedback
|
||||
let draggedEditorIsTab = false;
|
||||
const draggedEditor = TabsTitleControl.getDraggedEditor();
|
||||
const draggedEditor = this.transfer.hasData(DraggedEditorIdentifier.prototype) ? this.transfer.getData(DraggedEditorIdentifier.prototype)[0].identifier : void 0;
|
||||
if (draggedEditor) {
|
||||
const { group, editor } = this.toTabContext(index);
|
||||
const { group, editor } = this.getGroupPositionAndEditor(index);
|
||||
if (draggedEditor.editor === editor && draggedEditor.group === group) {
|
||||
draggedEditorIsTab = true;
|
||||
}
|
||||
@@ -848,7 +835,7 @@ export class TabsTitleControl extends TitleControl {
|
||||
DOM.removeClass(tab, 'dragged-over');
|
||||
this.updateDropFeedback(tab, false, index);
|
||||
|
||||
this.onEditorDragEnd();
|
||||
this.transfer.clearData();
|
||||
}));
|
||||
|
||||
// Drop
|
||||
@@ -857,7 +844,7 @@ export class TabsTitleControl extends TitleControl {
|
||||
DOM.removeClass(tab, 'dragged-over');
|
||||
this.updateDropFeedback(tab, false, index);
|
||||
|
||||
const { group, position } = this.toTabContext(index);
|
||||
const { group, position } = this.getGroupPositionAndEditor(index);
|
||||
|
||||
this.onDrop(e, group, position, index);
|
||||
}));
|
||||
@@ -869,7 +856,7 @@ export class TabsTitleControl extends TitleControl {
|
||||
return !!DOM.findParentWithClass(element, 'monaco-action-bar', 'tab');
|
||||
}
|
||||
|
||||
private toTabContext(index: number): { group: IEditorGroup, position: Position, editor: IEditorInput } {
|
||||
private getGroupPositionAndEditor(index: number): { group: IEditorGroup, position: Position, editor: IEditorInput } {
|
||||
const group = this.context;
|
||||
const position = this.stacks.positionOfGroup(group);
|
||||
const editor = group.getEditor(index);
|
||||
@@ -878,13 +865,14 @@ export class TabsTitleControl extends TitleControl {
|
||||
}
|
||||
|
||||
private onDrop(e: DragEvent, group: IEditorGroup, targetPosition: Position, targetIndex: number): void {
|
||||
DOM.EventHelper.stop(e, true);
|
||||
|
||||
this.updateDropFeedback(this.tabsContainer, false);
|
||||
DOM.removeClass(this.tabsContainer, 'scroll');
|
||||
|
||||
// Local DND
|
||||
const draggedEditor = TabsTitleControl.getDraggedEditor();
|
||||
const draggedEditor = this.transfer.hasData(DraggedEditorIdentifier.prototype) ? this.transfer.getData(DraggedEditorIdentifier.prototype)[0].identifier : void 0;
|
||||
if (draggedEditor) {
|
||||
DOM.EventHelper.stop(e, true);
|
||||
|
||||
// Move editor to target position and index
|
||||
if (this.isMoveOperation(e, draggedEditor.group, group)) {
|
||||
@@ -896,42 +884,13 @@ export class TabsTitleControl extends TitleControl {
|
||||
this.editorService.openEditor(draggedEditor.editor, { pinned: true, index: targetIndex }, targetPosition).done(null, errors.onUnexpectedError);
|
||||
}
|
||||
|
||||
this.onEditorDragEnd();
|
||||
this.transfer.clearData();
|
||||
}
|
||||
|
||||
// External DND
|
||||
else {
|
||||
this.handleExternalDrop(e, targetPosition, targetIndex);
|
||||
}
|
||||
}
|
||||
|
||||
private handleExternalDrop(e: DragEvent, targetPosition: Position, targetIndex: number): void {
|
||||
const droppedResources = extractResources(e).filter(r => r.resource.scheme === 'file' || r.resource.scheme === 'untitled');
|
||||
if (droppedResources.length) {
|
||||
DOM.EventHelper.stop(e, true);
|
||||
|
||||
handleWorkspaceExternalDrop(droppedResources, this.fileService, this.messageService, this.windowsService, this.windowService, this.workspacesService).then(handled => {
|
||||
if (handled) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Add external ones to recently open list
|
||||
const externalResources = droppedResources.filter(d => d.isExternal).map(d => d.resource);
|
||||
if (externalResources.length) {
|
||||
this.windowsService.addRecentlyOpened(externalResources.map(resource => resource.fsPath));
|
||||
}
|
||||
|
||||
// Open in Editor
|
||||
this.windowService.focusWindow()
|
||||
.then(() => this.editorService.openEditors(droppedResources.map(d => {
|
||||
return {
|
||||
input: { resource: d.resource, options: { pinned: true, index: targetIndex } },
|
||||
position: targetPosition
|
||||
};
|
||||
}))).then(() => {
|
||||
this.editorGroupService.focusGroup(targetPosition);
|
||||
}).done(null, errors.onUnexpectedError);
|
||||
});
|
||||
const dropHandler = this.instantiationService.createInstance(ResourcesDropHandler, { allowWorkspaceOpen: false /* open workspace file as file if dropped */ });
|
||||
dropHandler.handleDrop(e, () => this.editorGroupService.focusGroup(targetPosition), targetPosition, targetIndex);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -970,7 +929,10 @@ export class TabsTitleControl extends TitleControl {
|
||||
|
||||
class TabActionRunner extends ActionRunner {
|
||||
|
||||
constructor(private group: () => IEditorGroup, private index: number) {
|
||||
constructor(
|
||||
private group: () => IEditorGroup,
|
||||
private index: number
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
@@ -980,7 +942,7 @@ class TabActionRunner extends ActionRunner {
|
||||
return TPromise.as(void 0);
|
||||
}
|
||||
|
||||
return super.run(action, { group, editor: group.getEditor(this.index) });
|
||||
return super.run(action, { groupId: group.id, editorIndex: this.index });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1009,4 +971,134 @@ registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => {
|
||||
}
|
||||
`);
|
||||
}
|
||||
});
|
||||
|
||||
// Hover Background
|
||||
const tabHoverBackground = theme.getColor(TAB_HOVER_BACKGROUND);
|
||||
if (tabHoverBackground) {
|
||||
collector.addRule(`
|
||||
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title.active .tabs-container > .tab:hover {
|
||||
background: ${tabHoverBackground} !important;
|
||||
}
|
||||
`);
|
||||
}
|
||||
|
||||
const tabUnfocusedHoverBackground = theme.getColor(TAB_UNFOCUSED_HOVER_BACKGROUND);
|
||||
if (tabUnfocusedHoverBackground) {
|
||||
collector.addRule(`
|
||||
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title.inactive .tabs-container > .tab:hover {
|
||||
background: ${tabUnfocusedHoverBackground} !important;
|
||||
}
|
||||
`);
|
||||
}
|
||||
|
||||
// Hover Border
|
||||
const tabHoverBorder = theme.getColor(TAB_HOVER_BORDER);
|
||||
if (tabHoverBorder) {
|
||||
collector.addRule(`
|
||||
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title.active .tabs-container > .tab:hover {
|
||||
box-shadow: ${tabHoverBorder} 0 -1px inset !important;
|
||||
}
|
||||
`);
|
||||
}
|
||||
|
||||
const tabUnfocusedHoverBorder = theme.getColor(TAB_UNFOCUSED_HOVER_BORDER);
|
||||
if (tabUnfocusedHoverBorder) {
|
||||
collector.addRule(`
|
||||
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title.inactive .tabs-container > .tab:hover {
|
||||
box-shadow: ${tabUnfocusedHoverBorder} 0 -1px inset !important;
|
||||
}
|
||||
`);
|
||||
}
|
||||
|
||||
// Fade out styles via linear gradient (when tabs are set to shrink)
|
||||
if (theme.type !== 'hc') {
|
||||
const workbenchBackground = WORKBENCH_BACKGROUND(theme);
|
||||
const editorBackgroundColor = theme.getColor(editorBackground);
|
||||
const editorGroupBackground = theme.getColor(EDITOR_GROUP_BACKGROUND);
|
||||
const editorGroupHeaderTabsBackground = theme.getColor(EDITOR_GROUP_HEADER_TABS_BACKGROUND);
|
||||
const editorDragAndDropBackground = theme.getColor(EDITOR_DRAG_AND_DROP_BACKGROUND);
|
||||
|
||||
let adjustedTabBackground: Color;
|
||||
if (editorGroupHeaderTabsBackground && editorBackgroundColor && editorGroupBackground) {
|
||||
adjustedTabBackground = editorGroupHeaderTabsBackground.flatten(editorBackgroundColor, editorGroupBackground, editorBackgroundColor, workbenchBackground);
|
||||
}
|
||||
|
||||
let adjustedTabDragBackground: Color;
|
||||
if (editorGroupHeaderTabsBackground && editorBackgroundColor && editorDragAndDropBackground && editorBackgroundColor) {
|
||||
adjustedTabDragBackground = editorGroupHeaderTabsBackground.flatten(editorBackgroundColor, editorDragAndDropBackground, editorBackgroundColor, workbenchBackground);
|
||||
}
|
||||
|
||||
// Adjust gradient for (focused) hover background
|
||||
if (tabHoverBackground && adjustedTabBackground && adjustedTabDragBackground) {
|
||||
const adjustedColor = tabHoverBackground.flatten(adjustedTabBackground);
|
||||
const adjustedColorDrag = tabHoverBackground.flatten(adjustedTabDragBackground);
|
||||
collector.addRule(`
|
||||
.monaco-workbench > .part.editor > .content:not(.dragged-over) > .one-editor-silo > .container > .title.active .tabs-container > .tab.sizing-shrink:not(.dragged):hover > .tab-label::after {
|
||||
background: linear-gradient(to left, ${adjustedColor}, transparent);
|
||||
}
|
||||
|
||||
.monaco-workbench > .part.editor > .content.dragged-over > .one-editor-silo > .container > .title.active .tabs-container > .tab.sizing-shrink:not(.dragged):hover > .tab-label::after {
|
||||
background: linear-gradient(to left, ${adjustedColorDrag}, transparent);
|
||||
}
|
||||
`);
|
||||
}
|
||||
|
||||
// Adjust gradient for unfocused hover background
|
||||
if (tabUnfocusedHoverBackground && adjustedTabBackground && adjustedTabDragBackground) {
|
||||
const adjustedColor = tabUnfocusedHoverBackground.flatten(adjustedTabBackground);
|
||||
const adjustedColorDrag = tabUnfocusedHoverBackground.flatten(adjustedTabDragBackground);
|
||||
collector.addRule(`
|
||||
.monaco-workbench > .part.editor > .content:not(.dragged-over) > .one-editor-silo > .container > .title.inactive .tabs-container > .tab.sizing-shrink:not(.dragged):hover > .tab-label::after {
|
||||
background: linear-gradient(to left, ${adjustedColor}, transparent);
|
||||
}
|
||||
|
||||
.monaco-workbench > .part.editor > .content.dragged-over > .one-editor-silo > .container > .title.inactive .tabs-container > .tab.sizing-shrink:not(.dragged):hover > .tab-label::after {
|
||||
background: linear-gradient(to left, ${adjustedColorDrag}, transparent);
|
||||
}
|
||||
`);
|
||||
}
|
||||
|
||||
// Adjust gradient for drag and drop background
|
||||
if (editorDragAndDropBackground && adjustedTabDragBackground) {
|
||||
const adjustedColorDrag = editorDragAndDropBackground.flatten(adjustedTabDragBackground);
|
||||
collector.addRule(`
|
||||
.monaco-workbench > .part.editor > .content.dragged-over > .one-editor-silo > .container > .title.active .tabs-container > .tab.sizing-shrink.dragged-over:not(.active):not(.dragged) > .tab-label::after,
|
||||
.monaco-workbench > .part.editor > .content.dragged-over > .one-editor-silo > .container > .title.inactive .tabs-container > .tab.sizing-shrink.dragged-over:not(.dragged) > .tab-label::after {
|
||||
background: linear-gradient(to left, ${adjustedColorDrag}, transparent);
|
||||
}
|
||||
`);
|
||||
}
|
||||
|
||||
// Adjust gradient for active tab background
|
||||
const tabActiveBackground = theme.getColor(TAB_ACTIVE_BACKGROUND);
|
||||
if (tabActiveBackground && adjustedTabBackground && adjustedTabDragBackground) {
|
||||
const adjustedColor = tabActiveBackground.flatten(adjustedTabBackground);
|
||||
const adjustedColorDrag = tabActiveBackground.flatten(adjustedTabDragBackground);
|
||||
collector.addRule(`
|
||||
.monaco-workbench > .part.editor > .content:not(.dragged-over) > .one-editor-silo > .container > .title .tabs-container > .tab.sizing-shrink.active:not(.dragged) > .tab-label::after {
|
||||
background: linear-gradient(to left, ${adjustedColor}, transparent);
|
||||
}
|
||||
|
||||
.monaco-workbench > .part.editor > .content.dragged-over > .one-editor-silo > .container > .title .tabs-container > .tab.sizing-shrink.active:not(.dragged) > .tab-label::after {
|
||||
background: linear-gradient(to left, ${adjustedColorDrag}, transparent);
|
||||
}
|
||||
`);
|
||||
}
|
||||
|
||||
// Adjust gradient for inactive tab background
|
||||
const tabInactiveBackground = theme.getColor(TAB_INACTIVE_BACKGROUND);
|
||||
if (tabInactiveBackground && adjustedTabBackground && adjustedTabDragBackground) {
|
||||
const adjustedColor = tabInactiveBackground.flatten(adjustedTabBackground);
|
||||
const adjustedColorDrag = tabInactiveBackground.flatten(adjustedTabDragBackground);
|
||||
collector.addRule(`
|
||||
.monaco-workbench > .part.editor > .content:not(.dragged-over) > .one-editor-silo > .container > .title .tabs-container > .tab.sizing-shrink:not(.dragged) > .tab-label::after {
|
||||
background: linear-gradient(to left, ${adjustedColor}, transparent);
|
||||
}
|
||||
|
||||
.monaco-workbench > .part.editor > .content.dragged-over > .one-editor-silo > .container > .title .tabs-container > .tab.sizing-shrink:not(.dragged) > .tab-label::after {
|
||||
background: linear-gradient(to left, ${adjustedColorDrag}, transparent);
|
||||
}
|
||||
`);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -16,7 +16,7 @@ import types = require('vs/base/common/types');
|
||||
import { IDiffEditor } from 'vs/editor/browser/editorBrowser';
|
||||
import { IDiffEditorOptions, IEditorOptions } from 'vs/editor/common/config/editorOptions';
|
||||
import { BaseTextEditor, IEditorConfiguration } from 'vs/workbench/browser/parts/editor/textEditor';
|
||||
import { TextEditorOptions, EditorInput, EditorOptions, TEXT_DIFF_EDITOR_ID, IFileEditorInput } from 'vs/workbench/common/editor';
|
||||
import { TextEditorOptions, EditorInput, EditorOptions, TEXT_DIFF_EDITOR_ID, IEditorInputFactoryRegistry, Extensions as EditorInputExtensions } from 'vs/workbench/common/editor';
|
||||
import { ResourceEditorInput } from 'vs/workbench/common/editor/resourceEditorInput';
|
||||
import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput';
|
||||
import { DiffNavigator } from 'vs/editor/browser/widget/diffNavigator';
|
||||
@@ -32,8 +32,10 @@ import { IWorkbenchEditorService, DelegatingWorkbenchEditorService } from 'vs/wo
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import { IEditorGroupService } from 'vs/workbench/services/group/common/groupService';
|
||||
import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
|
||||
import { IEditorInput } from 'vs/platform/editor/common/editor';
|
||||
import { ScrollType } from 'vs/editor/common/editorCommon';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
|
||||
/**
|
||||
* The text editor that leverages the diff text editor for the editing experience.
|
||||
@@ -45,18 +47,27 @@ export class TextDiffEditor extends BaseTextEditor {
|
||||
private diffNavigator: DiffNavigator;
|
||||
private nextDiffAction: NavigateAction;
|
||||
private previousDiffAction: NavigateAction;
|
||||
private toggleIgnoreTrimWhitespaceAction: ToggleIgnoreTrimWhitespaceAction;
|
||||
private _configurationListener: IDisposable;
|
||||
|
||||
constructor(
|
||||
@ITelemetryService telemetryService: ITelemetryService,
|
||||
@IInstantiationService instantiationService: IInstantiationService,
|
||||
@IStorageService storageService: IStorageService,
|
||||
@ITextResourceConfigurationService configurationService: ITextResourceConfigurationService,
|
||||
@IConfigurationService private readonly _actualConfigurationService: IConfigurationService,
|
||||
@IWorkbenchEditorService private editorService: IWorkbenchEditorService,
|
||||
@IThemeService themeService: IThemeService,
|
||||
@IEditorGroupService editorGroupService: IEditorGroupService,
|
||||
@ITextFileService textFileService: ITextFileService
|
||||
) {
|
||||
super(TextDiffEditor.ID, telemetryService, instantiationService, storageService, configurationService, themeService, textFileService, editorGroupService);
|
||||
|
||||
this._configurationListener = this._actualConfigurationService.onDidChangeConfiguration((e) => {
|
||||
if (e.affectsConfiguration('diffEditor.ignoreTrimWhitespace')) {
|
||||
this.updateIgnoreTrimWhitespaceAction();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public getTitle(): string {
|
||||
@@ -72,6 +83,8 @@ export class TextDiffEditor extends BaseTextEditor {
|
||||
// Actions
|
||||
this.nextDiffAction = new NavigateAction(this, true);
|
||||
this.previousDiffAction = new NavigateAction(this, false);
|
||||
this.toggleIgnoreTrimWhitespaceAction = new ToggleIgnoreTrimWhitespaceAction(this._actualConfigurationService);
|
||||
this.updateIgnoreTrimWhitespaceAction();
|
||||
|
||||
// Support navigation within the diff editor by overriding the editor service within
|
||||
const delegatingEditorService = this.instantiationService.createInstance(DelegatingWorkbenchEditorService);
|
||||
@@ -163,6 +176,7 @@ export class TextDiffEditor extends BaseTextEditor {
|
||||
this.nextDiffAction.updateEnablement();
|
||||
this.previousDiffAction.updateEnablement();
|
||||
});
|
||||
this.updateIgnoreTrimWhitespaceAction();
|
||||
}, error => {
|
||||
|
||||
// In case we tried to open a file and the response indicates that this is not a text file, fallback to binary diff.
|
||||
@@ -176,6 +190,13 @@ export class TextDiffEditor extends BaseTextEditor {
|
||||
});
|
||||
}
|
||||
|
||||
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;
|
||||
@@ -184,12 +205,13 @@ export class TextDiffEditor extends BaseTextEditor {
|
||||
const binaryDiffInput = new DiffEditorInput(input.getName(), input.getDescription(), originalInput, modifiedInput, true);
|
||||
|
||||
// Forward binary flag to input if supported
|
||||
if (types.isFunction(((originalInput as IEditorInput) as IFileEditorInput).setForceOpenAsBinary)) {
|
||||
((originalInput as IEditorInput) as IFileEditorInput).setForceOpenAsBinary();
|
||||
const fileInputFactory = Registry.as<IEditorInputFactoryRegistry>(EditorInputExtensions.EditorInputFactories).getFileInputFactory();
|
||||
if (fileInputFactory.isFileInput(originalInput)) {
|
||||
originalInput.setForceOpenAsBinary();
|
||||
}
|
||||
|
||||
if (types.isFunction(((modifiedInput as IEditorInput) as IFileEditorInput).setForceOpenAsBinary)) {
|
||||
((modifiedInput as IEditorInput) as IFileEditorInput).setForceOpenAsBinary();
|
||||
if (fileInputFactory.isFileInput(modifiedInput)) {
|
||||
modifiedInput.setForceOpenAsBinary();
|
||||
}
|
||||
|
||||
this.editorService.openEditor(binaryDiffInput, options, this.position).done(null, onUnexpectedError);
|
||||
@@ -273,25 +295,12 @@ export class TextDiffEditor extends BaseTextEditor {
|
||||
|
||||
public getActions(): IAction[] {
|
||||
return [
|
||||
this.toggleIgnoreTrimWhitespaceAction,
|
||||
this.previousDiffAction,
|
||||
this.nextDiffAction
|
||||
];
|
||||
}
|
||||
|
||||
public getSecondaryActions(): IAction[] {
|
||||
const actions = super.getSecondaryActions();
|
||||
|
||||
// Action to toggle editor mode from inline to side by side
|
||||
const toggleEditorModeAction = new ToggleEditorModeAction(this);
|
||||
toggleEditorModeAction.order = 50; // Closer to the end
|
||||
|
||||
actions.push(...[
|
||||
toggleEditorModeAction
|
||||
]);
|
||||
|
||||
return actions;
|
||||
}
|
||||
|
||||
public getControl(): IDiffEditor {
|
||||
return super.getControl() as IDiffEditor;
|
||||
}
|
||||
@@ -303,6 +312,8 @@ export class TextDiffEditor extends BaseTextEditor {
|
||||
this.diffNavigator.dispose();
|
||||
}
|
||||
|
||||
this._configurationListener.dispose();
|
||||
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
@@ -340,33 +351,25 @@ class NavigateAction extends Action {
|
||||
}
|
||||
}
|
||||
|
||||
class ToggleEditorModeAction extends Action {
|
||||
private static readonly ID = 'toggle.diff.editorMode';
|
||||
private static readonly INLINE_LABEL = nls.localize('inlineDiffLabel', "Switch to Inline View");
|
||||
private static readonly SIDEBYSIDE_LABEL = nls.localize('sideBySideDiffLabel', "Switch to Side by Side View");
|
||||
class ToggleIgnoreTrimWhitespaceAction extends Action {
|
||||
static ID = 'workbench.action.compareEditor.toggleIgnoreTrimWhitespace';
|
||||
|
||||
constructor(private editor: TextDiffEditor) {
|
||||
super(ToggleEditorModeAction.ID);
|
||||
private _isChecked: boolean;
|
||||
|
||||
constructor(
|
||||
@IConfigurationService private readonly _configurationService: IConfigurationService
|
||||
) {
|
||||
super(ToggleIgnoreTrimWhitespaceAction.ID);
|
||||
this.label = nls.localize('toggleIgnoreTrimWhitespace.label', "Ignore Trim Whitespace");
|
||||
}
|
||||
|
||||
public get label(): string {
|
||||
return ToggleEditorModeAction.isInlineMode(this.editor) ? ToggleEditorModeAction.SIDEBYSIDE_LABEL : ToggleEditorModeAction.INLINE_LABEL;
|
||||
public updateClassName(ignoreTrimWhitespace: boolean): void {
|
||||
this._isChecked = ignoreTrimWhitespace;
|
||||
this.class = `textdiff-editor-action toggleIgnoreTrimWhitespace${this._isChecked ? ' is-checked' : ''}`;
|
||||
}
|
||||
|
||||
public run(): TPromise<boolean> {
|
||||
const inlineModeActive = ToggleEditorModeAction.isInlineMode(this.editor);
|
||||
|
||||
const control = this.editor.getControl();
|
||||
control.updateOptions(<IDiffEditorOptions>{
|
||||
renderSideBySide: inlineModeActive
|
||||
});
|
||||
|
||||
return TPromise.as(true);
|
||||
public run(): TPromise<any> {
|
||||
this._configurationService.updateValue(`diffEditor.ignoreTrimWhitespace`, !this._isChecked);
|
||||
return null;
|
||||
}
|
||||
|
||||
private static isInlineMode(editor: TextDiffEditor): boolean {
|
||||
const control = editor.getControl();
|
||||
|
||||
return control && !control.renderSideBySide;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -23,7 +23,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import { Scope } from 'vs/workbench/common/memento';
|
||||
import { getCodeEditor } from 'vs/editor/browser/services/codeEditorService';
|
||||
import { getCodeEditor, getCodeOrDiffEditor } from 'vs/editor/browser/services/codeEditorService';
|
||||
import { ITextFileService, SaveReason, AutoSaveMode } from 'vs/workbench/services/textfile/common/textfiles';
|
||||
import { ITextResourceConfigurationService } from 'vs/editor/common/services/resourceConfiguration';
|
||||
import { IEditorGroupService } from 'vs/workbench/services/group/common/groupService';
|
||||
@@ -50,11 +50,11 @@ export abstract class BaseTextEditor extends BaseEditor {
|
||||
constructor(
|
||||
id: string,
|
||||
@ITelemetryService telemetryService: ITelemetryService,
|
||||
@IInstantiationService private _instantiationService: IInstantiationService,
|
||||
@IInstantiationService private readonly _instantiationService: IInstantiationService,
|
||||
@IStorageService private storageService: IStorageService,
|
||||
@ITextResourceConfigurationService private _configurationService: ITextResourceConfigurationService,
|
||||
@ITextResourceConfigurationService private readonly _configurationService: ITextResourceConfigurationService,
|
||||
@IThemeService protected themeService: IThemeService,
|
||||
@ITextFileService private _textFileService: ITextFileService,
|
||||
@ITextFileService private readonly _textFileService: ITextFileService,
|
||||
@IEditorGroupService protected editorGroupService: IEditorGroupService
|
||||
) {
|
||||
super(id, telemetryService, themeService);
|
||||
@@ -230,48 +230,66 @@ export abstract class BaseTextEditor extends BaseEditor {
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the text editor view state under the given key.
|
||||
* Saves the text editor view state for the given resource.
|
||||
*/
|
||||
protected saveTextEditorViewState(key: string): void {
|
||||
protected saveTextEditorViewState(resource: URI): void {
|
||||
const editor = getCodeOrDiffEditor(this).codeEditor;
|
||||
if (!editor) {
|
||||
return; // not supported for diff editors
|
||||
}
|
||||
|
||||
const model = editor.getModel();
|
||||
if (!model) {
|
||||
return; // view state always needs a model
|
||||
}
|
||||
|
||||
const modelUri = model.uri;
|
||||
if (!modelUri) {
|
||||
return; // model URI is needed to make sure we save the view state correctly
|
||||
}
|
||||
|
||||
if (modelUri.toString() !== resource.toString()) {
|
||||
return; // prevent saving view state for a model that is not the expected one
|
||||
}
|
||||
|
||||
const memento = this.getMemento(this.storageService, Scope.WORKSPACE);
|
||||
|
||||
let textEditorViewStateMemento: { [key: string]: { [position: number]: IEditorViewState } } = memento[TEXT_EDITOR_VIEW_STATE_PREFERENCE_KEY];
|
||||
if (!textEditorViewStateMemento) {
|
||||
textEditorViewStateMemento = Object.create(null);
|
||||
memento[TEXT_EDITOR_VIEW_STATE_PREFERENCE_KEY] = textEditorViewStateMemento;
|
||||
}
|
||||
|
||||
const editorViewState = this.getControl().saveViewState();
|
||||
|
||||
let lastKnownViewState = textEditorViewStateMemento[key];
|
||||
let lastKnownViewState = textEditorViewStateMemento[resource.toString()];
|
||||
if (!lastKnownViewState) {
|
||||
lastKnownViewState = Object.create(null);
|
||||
textEditorViewStateMemento[key] = lastKnownViewState;
|
||||
textEditorViewStateMemento[resource.toString()] = lastKnownViewState;
|
||||
}
|
||||
|
||||
if (typeof this.position === 'number') {
|
||||
lastKnownViewState[this.position] = editorViewState;
|
||||
lastKnownViewState[this.position] = editor.saveViewState();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the text editor view state under the given key.
|
||||
* Clears the text editor view state for the given resources.
|
||||
*/
|
||||
protected clearTextEditorViewState(keys: string[]): void {
|
||||
protected clearTextEditorViewState(resources: URI[]): void {
|
||||
const memento = this.getMemento(this.storageService, Scope.WORKSPACE);
|
||||
const textEditorViewStateMemento: { [key: string]: { [position: number]: IEditorViewState } } = memento[TEXT_EDITOR_VIEW_STATE_PREFERENCE_KEY];
|
||||
if (textEditorViewStateMemento) {
|
||||
keys.forEach(key => delete textEditorViewStateMemento[key]);
|
||||
resources.forEach(resource => delete textEditorViewStateMemento[resource.toString()]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the text editor view state for the given key and returns it.
|
||||
* Loads the text editor view state for the given resource and returns it.
|
||||
*/
|
||||
protected loadTextEditorViewState(key: string): IEditorViewState {
|
||||
protected loadTextEditorViewState(resource: URI): IEditorViewState {
|
||||
const memento = this.getMemento(this.storageService, Scope.WORKSPACE);
|
||||
const textEditorViewStateMemento: { [key: string]: { [position: number]: IEditorViewState } } = memento[TEXT_EDITOR_VIEW_STATE_PREFERENCE_KEY];
|
||||
if (textEditorViewStateMemento) {
|
||||
const viewState = textEditorViewStateMemento[key];
|
||||
const viewState = textEditorViewStateMemento[resource.toString()];
|
||||
if (viewState) {
|
||||
return viewState[this.position];
|
||||
}
|
||||
|
||||
@@ -28,11 +28,10 @@ import { ScrollType } from 'vs/editor/common/editorCommon';
|
||||
* An editor implementation that is capable of showing the contents of resource inputs. Uses
|
||||
* the TextEditor widget to show the contents.
|
||||
*/
|
||||
export class TextResourceEditor extends BaseTextEditor {
|
||||
|
||||
public static readonly ID = 'workbench.editors.textResourceEditor';
|
||||
export class AbstractTextResourceEditor extends BaseTextEditor {
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
@ITelemetryService telemetryService: ITelemetryService,
|
||||
@IInstantiationService instantiationService: IInstantiationService,
|
||||
@IStorageService storageService: IStorageService,
|
||||
@@ -41,7 +40,7 @@ export class TextResourceEditor extends BaseTextEditor {
|
||||
@IEditorGroupService editorGroupService: IEditorGroupService,
|
||||
@ITextFileService textFileService: ITextFileService
|
||||
) {
|
||||
super(TextResourceEditor.ID, telemetryService, instantiationService, storageService, configurationService, themeService, textFileService, editorGroupService);
|
||||
super(id, telemetryService, instantiationService, storageService, configurationService, themeService, textFileService, editorGroupService);
|
||||
}
|
||||
|
||||
public getTitle(): string {
|
||||
@@ -108,7 +107,7 @@ export class TextResourceEditor extends BaseTextEditor {
|
||||
|
||||
protected restoreViewState(input: EditorInput) {
|
||||
if (input instanceof UntitledEditorInput || input instanceof ResourceEditorInput) {
|
||||
const viewState = this.loadTextEditorViewState(input.getResource().toString());
|
||||
const viewState = this.loadTextEditorViewState(input.getResource());
|
||||
if (viewState) {
|
||||
this.getControl().restoreViewState(viewState);
|
||||
}
|
||||
@@ -178,21 +177,38 @@ export class TextResourceEditor extends BaseTextEditor {
|
||||
return; // only enabled for untitled and resource inputs
|
||||
}
|
||||
|
||||
const key = input.getResource().toString();
|
||||
const resource = input.getResource();
|
||||
|
||||
// Clear view state if input is disposed
|
||||
if (input.isDisposed()) {
|
||||
super.clearTextEditorViewState([key]);
|
||||
super.clearTextEditorViewState([resource]);
|
||||
}
|
||||
|
||||
// Otherwise save it
|
||||
else {
|
||||
super.saveTextEditorViewState(key);
|
||||
super.saveTextEditorViewState(resource);
|
||||
|
||||
// Make sure to clean up when the input gets disposed
|
||||
once(input.onDispose)(() => {
|
||||
super.clearTextEditorViewState([key]);
|
||||
super.clearTextEditorViewState([resource]);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class TextResourceEditor extends AbstractTextResourceEditor {
|
||||
|
||||
public static readonly ID = 'workbench.editors.textResourceEditor';
|
||||
|
||||
constructor(
|
||||
@ITelemetryService telemetryService: ITelemetryService,
|
||||
@IInstantiationService instantiationService: IInstantiationService,
|
||||
@IStorageService storageService: IStorageService,
|
||||
@ITextResourceConfigurationService configurationService: ITextResourceConfigurationService,
|
||||
@IThemeService themeService: IThemeService,
|
||||
@IEditorGroupService editorGroupService: IEditorGroupService,
|
||||
@ITextFileService textFileService: ITextFileService
|
||||
) {
|
||||
super(TextResourceEditor.ID, telemetryService, instantiationService, storageService, configurationService, themeService, editorGroupService, textFileService);
|
||||
}
|
||||
}
|
||||
@@ -7,8 +7,7 @@
|
||||
|
||||
import 'vs/css!./media/titlecontrol';
|
||||
import nls = require('vs/nls');
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { Scope, IActionBarRegistry, Extensions, prepareActions } from 'vs/workbench/browser/actions';
|
||||
import { prepareActions } from 'vs/workbench/browser/actions';
|
||||
import { IAction, Action, IRunEvent } from 'vs/base/common/actions';
|
||||
import errors = require('vs/base/common/errors');
|
||||
import DOM = require('vs/base/browser/dom');
|
||||
@@ -16,13 +15,12 @@ import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor';
|
||||
import { RunOnceScheduler } from 'vs/base/common/async';
|
||||
import arrays = require('vs/base/common/arrays');
|
||||
import { IEditorStacksModel, IEditorGroup, IEditorIdentifier, EditorInput, IStacksModelChangeEvent, toResource } from 'vs/workbench/common/editor';
|
||||
import { IActionItem, ActionsOrientation, Separator } from 'vs/base/browser/ui/actionbar/actionbar';
|
||||
import { IEditorStacksModel, IEditorGroup, IEditorIdentifier, EditorInput, IStacksModelChangeEvent, toResource, IEditorCommandsContext } from 'vs/workbench/common/editor';
|
||||
import { IActionItem, ActionsOrientation } from 'vs/base/browser/ui/actionbar/actionbar';
|
||||
import { ToolBar } from 'vs/base/browser/ui/toolbar/toolbar';
|
||||
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
|
||||
import { IEditorGroupService } from 'vs/workbench/services/group/common/groupService';
|
||||
import { IMessageService, Severity } from 'vs/platform/message/common/message';
|
||||
import { StandardMouseEvent } from 'vs/base/browser/mouseEvent';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
@@ -30,21 +28,16 @@ import { IQuickOpenService } from 'vs/platform/quickOpen/common/quickOpen';
|
||||
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
||||
import { ResolvedKeybinding } from 'vs/base/common/keyCodes';
|
||||
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { CloseEditorsInGroupAction, SplitEditorAction, CloseEditorAction, KeepEditorAction, CloseOtherEditorsInGroupAction, CloseRightEditorsInGroupAction, ShowEditorsInGroupAction, CloseUnmodifiedEditorsInGroupAction } from 'vs/workbench/browser/parts/editor/editorActions';
|
||||
import { SplitEditorAction, CloseEditorAction } from 'vs/workbench/browser/parts/editor/editorActions';
|
||||
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
|
||||
import { createActionItem, fillInActions } from 'vs/platform/actions/browser/menuItemActionItem';
|
||||
import { IMenuService, MenuId, IMenu, ExecuteCommandAction } from 'vs/platform/actions/common/actions';
|
||||
import { ResourceContextKey } from 'vs/workbench/common/resources';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import { Themable } from 'vs/workbench/common/theme';
|
||||
import { IDraggedResource } from 'vs/workbench/browser/editor';
|
||||
import { WORKSPACE_EXTENSION, IWorkspacesService } from 'vs/platform/workspaces/common/workspaces';
|
||||
import { extname } from 'vs/base/common/paths';
|
||||
import { IFileService } from 'vs/platform/files/common/files';
|
||||
import { IWindowsService, IWindowService } from 'vs/platform/windows/common/windows';
|
||||
import URI from 'vs/base/common/uri';
|
||||
import { isDiffEditor, isCodeEditor } from 'vs/editor/browser/editorBrowser';
|
||||
import { Dimension } from 'vs/base/browser/builder';
|
||||
import { INotificationService } from 'vs/platform/notification/common/notification';
|
||||
|
||||
export interface IToolbarActions {
|
||||
primary: IAction[];
|
||||
@@ -67,21 +60,13 @@ export interface ITitleAreaControl {
|
||||
|
||||
export abstract class TitleControl extends Themable implements ITitleAreaControl {
|
||||
|
||||
private static draggedEditor: IEditorIdentifier;
|
||||
|
||||
protected stacks: IEditorStacksModel;
|
||||
protected context: IEditorGroup;
|
||||
|
||||
protected dragged: boolean;
|
||||
|
||||
protected closeEditorAction: CloseEditorAction;
|
||||
protected pinEditorAction: KeepEditorAction;
|
||||
protected closeOtherEditorsAction: CloseOtherEditorsInGroupAction;
|
||||
protected closeRightEditorsAction: CloseRightEditorsInGroupAction;
|
||||
protected closeUnmodifiedEditorsInGroupAction: CloseUnmodifiedEditorsInGroupAction;
|
||||
protected closeEditorsInGroupAction: CloseEditorsInGroupAction;
|
||||
protected splitEditorAction: SplitEditorAction;
|
||||
protected showEditorsInGroupAction: ShowEditorsInGroupAction;
|
||||
|
||||
private parent: HTMLElement;
|
||||
|
||||
@@ -106,7 +91,7 @@ export abstract class TitleControl extends Themable implements ITitleAreaControl
|
||||
@IContextKeyService protected contextKeyService: IContextKeyService,
|
||||
@IKeybindingService protected keybindingService: IKeybindingService,
|
||||
@ITelemetryService protected telemetryService: ITelemetryService,
|
||||
@IMessageService protected messageService: IMessageService,
|
||||
@INotificationService private notificationService: INotificationService,
|
||||
@IMenuService protected menuService: IMenuService,
|
||||
@IQuickOpenService protected quickOpenService: IQuickOpenService,
|
||||
@IThemeService protected themeService: IThemeService
|
||||
@@ -128,22 +113,10 @@ export abstract class TitleControl extends Themable implements ITitleAreaControl
|
||||
this.registerListeners();
|
||||
}
|
||||
|
||||
public static getDraggedEditor(): IEditorIdentifier {
|
||||
return TitleControl.draggedEditor;
|
||||
}
|
||||
|
||||
public setDragged(dragged: boolean): void {
|
||||
this.dragged = dragged;
|
||||
}
|
||||
|
||||
protected onEditorDragStart(editor: IEditorIdentifier): void {
|
||||
TitleControl.draggedEditor = editor;
|
||||
}
|
||||
|
||||
protected onEditorDragEnd(): void {
|
||||
TitleControl.draggedEditor = void 0;
|
||||
}
|
||||
|
||||
private registerListeners(): void {
|
||||
this.toUnbind.push(this.stacks.onModelChanged(e => this.onStacksChanged(e)));
|
||||
}
|
||||
@@ -183,6 +156,8 @@ export abstract class TitleControl extends Themable implements ITitleAreaControl
|
||||
|
||||
public setContext(group: IEditorGroup): void {
|
||||
this.context = group;
|
||||
|
||||
this.editorActionsToolbar.context = { groupId: group ? group.id : void 0 } as IEditorCommandsContext;
|
||||
}
|
||||
|
||||
public hasContext(): boolean {
|
||||
@@ -233,12 +208,6 @@ export abstract class TitleControl extends Themable implements ITitleAreaControl
|
||||
|
||||
protected initActions(services: IInstantiationService): void {
|
||||
this.closeEditorAction = services.createInstance(CloseEditorAction, CloseEditorAction.ID, nls.localize('close', "Close"));
|
||||
this.closeOtherEditorsAction = services.createInstance(CloseOtherEditorsInGroupAction, CloseOtherEditorsInGroupAction.ID, nls.localize('closeOthers', "Close Others"));
|
||||
this.closeRightEditorsAction = services.createInstance(CloseRightEditorsInGroupAction, CloseRightEditorsInGroupAction.ID, nls.localize('closeRight', "Close to the Right"));
|
||||
this.closeEditorsInGroupAction = services.createInstance(CloseEditorsInGroupAction, CloseEditorsInGroupAction.ID, nls.localize('closeAll', "Close All"));
|
||||
this.closeUnmodifiedEditorsInGroupAction = services.createInstance(CloseUnmodifiedEditorsInGroupAction, CloseUnmodifiedEditorsInGroupAction.ID, nls.localize('closeAllUnmodified', "Close Unmodified"));
|
||||
this.pinEditorAction = services.createInstance(KeepEditorAction, KeepEditorAction.ID, nls.localize('keepOpen', "Keep Open"));
|
||||
this.showEditorsInGroupAction = services.createInstance(ShowEditorsInGroupAction, ShowEditorsInGroupAction.ID, nls.localize('showOpenedEditors', "Show Opened Editors"));
|
||||
this.splitEditorAction = services.createInstance(SplitEditorAction, SplitEditorAction.ID, SplitEditorAction.LABEL);
|
||||
}
|
||||
|
||||
@@ -255,7 +224,7 @@ export abstract class TitleControl extends Themable implements ITitleAreaControl
|
||||
|
||||
// Check for Error
|
||||
if (e.error && !errors.isPromiseCanceledError(e.error)) {
|
||||
this.messageService.show(Severity.Error, e.error);
|
||||
this.notificationService.error(e.error);
|
||||
}
|
||||
|
||||
// Log in telemetry
|
||||
@@ -287,15 +256,9 @@ export abstract class TitleControl extends Themable implements ITitleAreaControl
|
||||
actionItem = editor.getActionItem(action);
|
||||
}
|
||||
|
||||
// Check Registry
|
||||
if (!actionItem) {
|
||||
const actionBarRegistry = Registry.as<IActionBarRegistry>(Extensions.Actionbar);
|
||||
actionItem = actionBarRegistry.getActionItemForContext(Scope.EDITOR, { input: editor && editor.input, editor, position }, action);
|
||||
}
|
||||
|
||||
// Check extensions
|
||||
if (!actionItem) {
|
||||
actionItem = createActionItem(action, this.keybindingService, this.messageService);
|
||||
actionItem = createActionItem(action, this.keybindingService, this.notificationService, this.contextMenuService);
|
||||
}
|
||||
|
||||
return actionItem;
|
||||
@@ -335,7 +298,7 @@ export abstract class TitleControl extends Themable implements ITitleAreaControl
|
||||
const titleBarMenu = this.menuService.createMenu(MenuId.EditorTitle, scopedContextKeyService);
|
||||
this.disposeOnEditorActions.push(titleBarMenu, titleBarMenu.onDidChange(_ => this.update()));
|
||||
|
||||
fillInActions(titleBarMenu, { arg: this.resourceContext.get() }, { primary, secondary });
|
||||
fillInActions(titleBarMenu, { arg: this.resourceContext.get(), shouldForwardArgs: true }, { primary, secondary }, this.contextMenuService);
|
||||
}
|
||||
|
||||
return { primary, secondary };
|
||||
@@ -353,26 +316,21 @@ export abstract class TitleControl extends Themable implements ITitleAreaControl
|
||||
// Update Editor Actions Toolbar
|
||||
let primaryEditorActions: IAction[] = [];
|
||||
let secondaryEditorActions: IAction[] = [];
|
||||
|
||||
const editorActions = this.getEditorActions({ group, editor });
|
||||
|
||||
// Primary actions only for the active group
|
||||
if (isActive) {
|
||||
const editorActions = this.getEditorActions({ group, editor });
|
||||
primaryEditorActions = prepareActions(editorActions.primary);
|
||||
if (isActive && editor instanceof EditorInput && editor.supportsSplitEditor()) {
|
||||
if (editor instanceof EditorInput && editor.supportsSplitEditor()) {
|
||||
this.updateSplitActionEnablement();
|
||||
primaryEditorActions.push(this.splitEditorAction);
|
||||
}
|
||||
secondaryEditorActions = prepareActions(editorActions.secondary);
|
||||
}
|
||||
|
||||
secondaryEditorActions = prepareActions(editorActions.secondary);
|
||||
|
||||
const tabOptions = this.editorGroupService.getTabOptions();
|
||||
if (tabOptions.showTabs) {
|
||||
if (secondaryEditorActions.length > 0) {
|
||||
secondaryEditorActions.push(new Separator());
|
||||
}
|
||||
secondaryEditorActions.push(this.showEditorsInGroupAction);
|
||||
secondaryEditorActions.push(new Separator());
|
||||
secondaryEditorActions.push(this.closeUnmodifiedEditorsInGroupAction);
|
||||
secondaryEditorActions.push(this.closeEditorsInGroupAction);
|
||||
}
|
||||
|
||||
const primaryEditorActionIds = primaryEditorActions.map(a => a.id);
|
||||
if (!tabOptions.showTabs) {
|
||||
@@ -418,10 +376,15 @@ export abstract class TitleControl extends Themable implements ITitleAreaControl
|
||||
anchor = { x: event.posx, y: event.posy };
|
||||
}
|
||||
|
||||
// Fill in contributed actions
|
||||
const actions: IAction[] = [];
|
||||
fillInActions(this.contextMenu, { shouldForwardArgs: true, arg: this.resourceContext.get() }, actions, this.contextMenuService);
|
||||
|
||||
// Show it
|
||||
this.contextMenuService.showContextMenu({
|
||||
getAnchor: () => anchor,
|
||||
getActions: () => TPromise.as(this.getContextMenuActions(identifier)),
|
||||
getActionsContext: () => identifier,
|
||||
getActions: () => TPromise.as(actions),
|
||||
getActionsContext: () => ({ groupId: identifier.group.id, editorIndex: identifier.group.indexOf(identifier.editor) } as IEditorCommandsContext),
|
||||
getKeyBinding: (action) => this.getKeybinding(action),
|
||||
onHide: (cancel) => {
|
||||
|
||||
@@ -447,51 +410,13 @@ export abstract class TitleControl extends Themable implements ITitleAreaControl
|
||||
return keybinding ? keybinding.getLabel() : void 0;
|
||||
}
|
||||
|
||||
protected getContextMenuActions(identifier: IEditorIdentifier): IAction[] {
|
||||
const { editor, group } = identifier;
|
||||
|
||||
// Enablement
|
||||
this.closeOtherEditorsAction.enabled = group.count > 1;
|
||||
this.pinEditorAction.enabled = !group.isPinned(editor);
|
||||
this.closeRightEditorsAction.enabled = group.indexOf(editor) !== group.count - 1;
|
||||
|
||||
// Actions: For all editors
|
||||
const actions: IAction[] = [
|
||||
this.closeEditorAction,
|
||||
this.closeOtherEditorsAction
|
||||
];
|
||||
const tabOptions = this.editorGroupService.getTabOptions();
|
||||
|
||||
if (tabOptions.showTabs) {
|
||||
actions.push(this.closeRightEditorsAction);
|
||||
}
|
||||
|
||||
actions.push(this.closeUnmodifiedEditorsInGroupAction);
|
||||
actions.push(this.closeEditorsInGroupAction);
|
||||
|
||||
if (tabOptions.previewEditors) {
|
||||
actions.push(new Separator(), this.pinEditorAction);
|
||||
}
|
||||
|
||||
// Fill in contributed actions
|
||||
fillInActions(this.contextMenu, { arg: this.resourceContext.get() }, actions);
|
||||
|
||||
return actions;
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
super.dispose();
|
||||
|
||||
// Actions
|
||||
[
|
||||
this.splitEditorAction,
|
||||
this.showEditorsInGroupAction,
|
||||
this.closeEditorAction,
|
||||
this.closeRightEditorsAction,
|
||||
this.closeUnmodifiedEditorsInGroupAction,
|
||||
this.closeOtherEditorsAction,
|
||||
this.closeEditorsInGroupAction,
|
||||
this.pinEditorAction
|
||||
this.closeEditorAction
|
||||
].forEach((action) => {
|
||||
action.dispose();
|
||||
});
|
||||
@@ -500,74 +425,3 @@ export abstract class TitleControl extends Themable implements ITitleAreaControl
|
||||
this.editorActionsToolbar.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Shared function across some editor components to handle drag & drop of folders and workspace files
|
||||
* to open them in the window instead of the editor.
|
||||
*/
|
||||
export function handleWorkspaceExternalDrop(
|
||||
resources: IDraggedResource[],
|
||||
fileService: IFileService,
|
||||
messageService: IMessageService,
|
||||
windowsService: IWindowsService,
|
||||
windowService: IWindowService,
|
||||
workspacesService: IWorkspacesService
|
||||
): TPromise<boolean /* handled */> {
|
||||
|
||||
// Return early if there are no external resources
|
||||
const externalResources = resources.filter(d => d.isExternal).map(d => d.resource);
|
||||
if (!externalResources.length) {
|
||||
return TPromise.as(false);
|
||||
}
|
||||
|
||||
const externalWorkspaceResources: { workspaces: URI[], folders: URI[] } = {
|
||||
workspaces: [],
|
||||
folders: []
|
||||
};
|
||||
|
||||
return TPromise.join(externalResources.map(resource => {
|
||||
|
||||
// Check for Workspace
|
||||
if (extname(resource.fsPath) === `.${WORKSPACE_EXTENSION}`) {
|
||||
externalWorkspaceResources.workspaces.push(resource);
|
||||
|
||||
return void 0;
|
||||
}
|
||||
|
||||
// Check for Folder
|
||||
return fileService.resolveFile(resource).then(stat => {
|
||||
if (stat.isDirectory) {
|
||||
externalWorkspaceResources.folders.push(stat.resource);
|
||||
}
|
||||
}, error => void 0);
|
||||
})).then(_ => {
|
||||
const { workspaces, folders } = externalWorkspaceResources;
|
||||
|
||||
// Return early if no external resource is a folder or workspace
|
||||
if (workspaces.length === 0 && folders.length === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Pass focus to window
|
||||
windowService.focusWindow();
|
||||
|
||||
let workspacesToOpen: TPromise<string[]>;
|
||||
|
||||
// Open in separate windows if we drop workspaces or just one folder
|
||||
if (workspaces.length > 0 || folders.length === 1) {
|
||||
workspacesToOpen = TPromise.as([...workspaces, ...folders].map(resources => resources.fsPath));
|
||||
}
|
||||
|
||||
// Multiple folders: Create new workspace with folders and open
|
||||
else if (folders.length > 1) {
|
||||
workspacesToOpen = workspacesService.createWorkspace(folders.map(folder => ({ uri: folder }))).then(workspace => [workspace.configPath]);
|
||||
}
|
||||
|
||||
// Open
|
||||
workspacesToOpen.then(workspaces => {
|
||||
windowsService.openWindow(workspaces, { forceReuseWindow: true });
|
||||
});
|
||||
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="3 3 16 16" enable-background="new 3 3 16 16"><polygon fill="#e8e8e8" points="12.597,11.042 15.4,13.845 13.844,15.4 11.042,12.598 8.239,15.4 6.683,13.845 9.485,11.042 6.683,8.239 8.238,6.683 11.042,9.486 13.845,6.683 15.4,8.239"/></svg>
|
||||
|
After Width: | Height: | Size: 307 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="3 3 16 16" enable-background="new 3 3 16 16"><polygon fill="#424242" points="12.597,11.042 15.4,13.845 13.844,15.4 11.042,12.598 8.239,15.4 6.683,13.845 9.485,11.042 6.683,8.239 8.238,6.683 11.042,9.486 13.845,6.683 15.4,8.239"/></svg>
|
||||
|
After Width: | Height: | Size: 307 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" enable-background="new 0 0 16 16" height="16" width="16"><path fill="#1E1E1E" d="M16 12h-2v2h-2v2H0V5h2V3h2V1h12v11z"/><g fill="#C5C5C5"><path d="M3 5h9v8h1V4H3zM5 2v1h9v8h1V2zM1 6v9h10V6H1zm8 7H7.5L6 11.5 4.5 13H3l2.3-2.3L3 8.5h1.5L6 10l1.5-1.5H9l-2.3 2.3L9 13z"/></g></svg>
|
||||
|
After Width: | Height: | Size: 335 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" enable-background="new 0 0 16 16" height="16" width="16"><path fill="#F6F6F6" d="M16 12h-2v2h-2v2H0V5h2V3h2V1h12v11z"/><g fill="#424242"><path d="M3 5h9v8h1V4H3zM5 2v1h9v8h1V2zM1 6v9h10V6H1zm8 7H7.5L6 11.5 4.5 13H3l2.3-2.3L3 8.5h1.5L6 10l1.5-1.5H9l-2.3 2.3L9 13z"/></g></svg>
|
||||
|
After Width: | Height: | Size: 335 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="#C5C5C5"><path d="M12.714 9.603c-.07.207-.15.407-.246.601l1.017 2.139c-.335.424-.718.807-1.142 1.143l-2.14-1.018c-.193.097-.394.176-.601.247l-.795 2.235c-.265.03-.534.05-.807.05-.272 0-.541-.02-.806-.05l-.795-2.235c-.207-.071-.408-.15-.602-.247l-2.14 1.017c-.424-.336-.807-.719-1.143-1.143l1.017-2.139c-.094-.193-.175-.393-.245-.6l-2.236-.796c-.03-.265-.05-.534-.05-.807s.02-.542.05-.807l2.236-.795c.07-.207.15-.407.246-.601l-1.016-2.139c.336-.423.719-.807 1.143-1.142l2.14 1.017c.193-.096.394-.176.602-.247l.793-2.236c.265-.03.534-.05.806-.05.273 0 .542.02.808.05l.795 2.236c.207.07.407.15.601.246l2.14-1.017c.424.335.807.719 1.142 1.142l-1.017 2.139c.096.194.176.394.246.601l2.236.795c.029.266.049.535.049.808s-.02.542-.05.807l-2.236.796zm-4.714-4.603c-1.657 0-3 1.343-3 3s1.343 3 3 3 3-1.343 3-3-1.343-3-3-3z"/><circle cx="8" cy="8" r="1.5"/></g></svg>
|
||||
|
After Width: | Height: | Size: 927 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><g fill="#424242"><path d="M12.714 9.603c-.07.207-.15.407-.246.601l1.017 2.139c-.335.424-.718.807-1.142 1.143l-2.14-1.018c-.193.097-.394.176-.601.247l-.795 2.235c-.265.03-.534.05-.807.05-.272 0-.541-.02-.806-.05l-.795-2.235c-.207-.071-.408-.15-.602-.247l-2.14 1.017c-.424-.336-.807-.719-1.143-1.143l1.017-2.139c-.094-.193-.175-.393-.245-.6l-2.236-.796c-.03-.265-.05-.534-.05-.807s.02-.542.05-.807l2.236-.795c.07-.207.15-.407.246-.601l-1.016-2.139c.336-.423.719-.807 1.143-1.142l2.14 1.017c.193-.096.394-.176.602-.247l.793-2.236c.265-.03.534-.05.806-.05.273 0 .542.02.808.05l.795 2.236c.207.07.407.15.601.246l2.14-1.017c.424.335.807.719 1.142 1.142l-1.017 2.139c.096.194.176.394.246.601l2.236.795c.029.266.049.535.049.808s-.02.542-.05.807l-2.236.796zm-4.714-4.603c-1.657 0-3 1.343-3 3s1.343 3 3 3 3-1.343 3-3-1.343-3-3-3z"/><circle cx="8" cy="8" r="1.5"/></g></svg>
|
||||
|
After Width: | Height: | Size: 927 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.icon-canvas-transparent,.icon-vs-out{fill:#2d2d30;}.icon-canvas-transparent{opacity:0;}.icon-vs-bg{fill:#c5c5c5;}</style></defs><title>CollapseChevronDown_md_16x</title><path class="icon-canvas-transparent" d="M16,0V16H0V0Z"/><path class="icon-vs-out" d="M15.444,6.061,8,13.5.556,6.061,3.03,3.586,8,8.556l4.97-4.97Z" style="display: none;"/><path class="icon-vs-bg" d="M14.03,6.061,8,12.091,1.97,6.061,3.03,5,8,9.97,12.97,5Z"/></svg>
|
||||
|
After Width: | Height: | Size: 507 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.icon-canvas-transparent,.icon-vs-out{fill:#f6f6f6;}.icon-canvas-transparent{opacity:0;}.icon-vs-bg{fill:#424242;}</style></defs><title>CollapseChevronDown_md_16x</title><path class="icon-canvas-transparent" d="M16,0V16H0V0Z"/><path class="icon-vs-out" d="M15.444,6.061,8,13.5.556,6.061,3.03,3.586,8,8.556l4.97-4.97Z" style="display: none;"/><path class="icon-vs-bg" d="M14.03,6.061,8,12.091,1.97,6.061,3.03,5,8,9.97,12.97,5Z"/></svg>
|
||||
|
After Width: | Height: | Size: 507 B |
@@ -0,0 +1,26 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 22.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 16 16" style="enable-background:new 0 0 16 16;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#CACACC;}
|
||||
.st1{fill:#E51400;}
|
||||
.st2{fill:#FFFFFF;}
|
||||
.st3{fill:#F6F6F6;fill-opacity:0;}
|
||||
.st4{fill:#1A1A1A;}
|
||||
</style>
|
||||
<path id="outline_2_" class="st0" d="M-169.7-71.2c0,1.4-1.2,2.6-2.6,2.6c-1.4,0-2.6-1.2-2.6-2.6s1.2-2.6,2.6-2.6
|
||||
C-170.9-73.8-169.7-72.6-169.7-71.2z"/>
|
||||
<path id="iconBg_2_" class="st1" d="M-172.3-73.5c-1.3,0-2.3,1-2.3,2.3s1,2.3,2.3,2.3s2.3-1,2.3-2.3S-171.1-73.5-172.3-73.5z
|
||||
M-170.9-70.2l-0.5,0.5l-1-1l-1,1l-0.5-0.5l1-1l-1-1l0.5-0.5l1,1l1-1l0.5,0.5l-1,1L-170.9-70.2z"/>
|
||||
<g id="iconFg_2_">
|
||||
<path class="st2" d="M-171.9-71.2l1,1l-0.5,0.5l-1-1l-1,1l-0.5-0.5l1-1l-1-1l0.5-0.5l1,1l1-1l0.5,0.5L-171.9-71.2z"/>
|
||||
</g>
|
||||
<path id="canvas_1_" class="st3" d="M16,16H0V0h16V16z"/>
|
||||
<path id="outline_1_" class="st4" d="M16,8c0,4.4-3.6,8-8,8s-8-3.6-8-8s3.6-8,8-8S16,3.6,16,8z"/>
|
||||
<path id="iconBg_1_" class="st1" d="M8,1C4.1,1,1,4.1,1,8c0,3.9,3.1,7,7,7s7-3.1,7-7S11.9,1,8,1z M12.4,11L11,12.4l-3-3l-3,3L3.6,11
|
||||
l3-3l-3-3L5,3.6l3,3l3-3L12.4,5l-3,3L12.4,11z"/>
|
||||
<g id="iconFg_1_">
|
||||
<path class="st2" d="M9.4,8l3,3L11,12.4l-3-3l-3,3L3.6,11l3-3l-3-3L5,3.6l3,3l3-3L12.4,5L9.4,8z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.4 KiB |
25
src/vs/workbench/browser/parts/notifications/media/error.svg
Normal file
@@ -0,0 +1,25 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 22.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 16 16" style="enable-background:new 0 0 16 16;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#CACACC;}
|
||||
.st1{fill:#E51400;}
|
||||
.st2{fill:#FFFFFF;}
|
||||
.st3{fill:#F6F6F6;fill-opacity:0;}
|
||||
</style>
|
||||
<path id="outline" class="st0" d="M16,8c0,4.4-3.6,8-8,8s-8-3.6-8-8s3.6-8,8-8S16,3.6,16,8z"/>
|
||||
<path id="iconBg" class="st1" d="M8,1C4.1,1,1,4.1,1,8c0,3.9,3.1,7,7,7s7-3.1,7-7S11.9,1,8,1z M12.4,11L11,12.4l-3-3l-3,3L3.6,11
|
||||
l3-3l-3-3L5,3.6l3,3l3-3L12.4,5l-3,3L12.4,11z"/>
|
||||
<g id="iconFg">
|
||||
<path class="st2" d="M9.4,8l3,3L11,12.4l-3-3l-3,3L3.6,11l3-3l-3-3L5,3.6l3,3l3-3L12.4,5L9.4,8z"/>
|
||||
</g>
|
||||
<path id="canvas_4_" class="st3" d="M16,16H0V0h16V16z"/>
|
||||
<path id="outline_2_" class="st0" d="M-192.7-71.2c0,1.4-1.2,2.6-2.6,2.6s-2.6-1.2-2.6-2.6s1.2-2.6,2.6-2.6S-192.7-72.6-192.7-71.2z
|
||||
"/>
|
||||
<path id="iconBg_2_" class="st1" d="M-195.4-73.5c-1.3,0-2.3,1-2.3,2.3s1,2.3,2.3,2.3c1.3,0,2.3-1,2.3-2.3S-194.1-73.5-195.4-73.5z
|
||||
M-193.9-70.2l-0.5,0.5l-1-1l-1,1l-0.5-0.5l1-1l-1-1l0.5-0.5l1,1l1-1l0.5,0.5l-1,1L-193.9-70.2z"/>
|
||||
<g id="iconFg_2_">
|
||||
<path class="st2" d="M-194.9-71.2l1,1l-0.5,0.5l-1-1l-1,1l-0.5-0.5l1-1l-1-1l0.5-0.5l1,1l1-1l0.5,0.5L-194.9-71.2z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.4 KiB |
@@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 22.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 16 16" style="enable-background:new 0 0 16 16;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#F6F6F6;fill-opacity:0;}
|
||||
.st1{fill:#1A1A1A;}
|
||||
.st2{fill:#1BA1E2;}
|
||||
.st3{fill:#FFFFFF;}
|
||||
</style>
|
||||
<path id="canvas_1_" class="st0" d="M16,16H0V0h16V16z"/>
|
||||
<path id="outline_1_" class="st1" d="M0,8c0-4.4,3.6-8,8-8s8,3.6,8,8s-3.6,8-8,8S0,12.4,0,8z"/>
|
||||
<path id="iconBg_1_" class="st2" d="M8,1C4.1,1,1,4.1,1,8s3.1,7,7,7s7-3.1,7-7S11.9,1,8,1z M9,13H7V6h2V13z M9,5H7V3h2V5z"/>
|
||||
<g id="iconFg_1_">
|
||||
<path class="st3" d="M7,6h2v7H7V6z M7,5h2V3H7V5z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 834 B |
17
src/vs/workbench/browser/parts/notifications/media/info.svg
Normal file
@@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 22.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 16 16" style="enable-background:new 0 0 16 16;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#F6F6F6;fill-opacity:0;}
|
||||
.st1{fill:#CACACC;}
|
||||
.st2{fill:#1BA1E2;}
|
||||
.st3{fill:#FFFFFF;}
|
||||
</style>
|
||||
<path id="canvas" class="st0" d="M16,16H0V0h16V16z"/>
|
||||
<path id="outline" class="st1" d="M0,8c0-4.4,3.6-8,8-8s8,3.6,8,8s-3.6,8-8,8S0,12.4,0,8z"/>
|
||||
<path id="iconBg" class="st2" d="M8,1C4.1,1,1,4.1,1,8s3.1,7,7,7s7-3.1,7-7S11.8,1,8,1z M9,13H7V6h2V13z M9,5H7V3h2V5z"/>
|
||||
<g id="iconFg">
|
||||
<path class="st3" d="M7,6h2v7H7V6z M7,5h2V3H7V5z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 822 B |
@@ -0,0 +1,69 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
.monaco-workbench .notifications-list-container .notification-list-item .notification-list-item-toolbar-container .action-label,
|
||||
.monaco-workbench > .notifications-center > .notifications-center-header > .notifications-center-header-toolbar .action-label {
|
||||
display: block;
|
||||
width: 16px;
|
||||
height: 22px;
|
||||
margin-right: 4px;
|
||||
margin-left: 4px;
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
|
||||
.vs .monaco-workbench .notifications-list-container .notification-list-item .notification-list-item-toolbar-container .clear-notification-action {
|
||||
background-image: url('close.svg');
|
||||
}
|
||||
|
||||
.vs-dark .monaco-workbench .notifications-list-container .notification-list-item .notification-list-item-toolbar-container .clear-notification-action,
|
||||
.hc-black .monaco-workbench .notifications-list-container .notification-list-item .notification-list-item-toolbar-container .clear-notification-action {
|
||||
background-image: url('close-inverse.svg');
|
||||
}
|
||||
|
||||
.vs .monaco-workbench .notifications-list-container .notification-list-item .notification-list-item-toolbar-container .expand-notification-action {
|
||||
background-image: url('down.svg');
|
||||
}
|
||||
|
||||
.vs-dark .monaco-workbench .notifications-list-container .notification-list-item .notification-list-item-toolbar-container .expand-notification-action,
|
||||
.hc-black .monaco-workbench .notifications-list-container .notification-list-item .notification-list-item-toolbar-container .expand-notification-action {
|
||||
background-image: url('down-inverse.svg');
|
||||
}
|
||||
|
||||
.vs .monaco-workbench .notifications-list-container .notification-list-item .notification-list-item-toolbar-container .collapse-notification-action {
|
||||
background-image: url('up.svg');
|
||||
}
|
||||
|
||||
.vs-dark .monaco-workbench .notifications-list-container .notification-list-item .notification-list-item-toolbar-container .collapse-notification-action,
|
||||
.hc-black .monaco-workbench .notifications-list-container .notification-list-item .notification-list-item-toolbar-container .collapse-notification-action {
|
||||
background-image: url('up-inverse.svg');
|
||||
}
|
||||
|
||||
.vs .monaco-workbench .notifications-list-container .notification-list-item .notification-list-item-toolbar-container .configure-notification-action {
|
||||
background-image: url('configure.svg');
|
||||
}
|
||||
|
||||
.vs-dark .monaco-workbench .notifications-list-container .notification-list-item .notification-list-item-toolbar-container .configure-notification-action,
|
||||
.hc-black .monaco-workbench .notifications-list-container .notification-list-item .notification-list-item-toolbar-container .configure-notification-action {
|
||||
background-image: url('configure-inverse.svg');
|
||||
}
|
||||
|
||||
.vs-dark .monaco-workbench > .notifications-center > .notifications-center-header .clear-all-notifications-action,
|
||||
.hc-black .monaco-workbench > .notifications-center > .notifications-center-header .clear-all-notifications-action {
|
||||
background-image: url('closeall-inverse.svg');
|
||||
}
|
||||
|
||||
.vs .monaco-workbench > .notifications-center > .notifications-center-header .clear-all-notifications-action {
|
||||
background-image: url('closeall.svg');
|
||||
}
|
||||
|
||||
.vs-dark .monaco-workbench > .notifications-center > .notifications-center-header .hide-all-notifications-action,
|
||||
.hc-black .monaco-workbench > .notifications-center > .notifications-center-header .hide-all-notifications-action {
|
||||
background-image: url('down-inverse.svg');
|
||||
}
|
||||
|
||||
.vs .monaco-workbench > .notifications-center > .notifications-center-header .hide-all-notifications-action {
|
||||
background-image: url('down.svg');
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
.monaco-workbench > .notifications-center {
|
||||
position: absolute;
|
||||
z-index: 1000;
|
||||
right: 8px;
|
||||
bottom: 31px;
|
||||
display: none;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.monaco-workbench.nostatusbar > .notifications-center {
|
||||
bottom: 8px;
|
||||
}
|
||||
|
||||
.monaco-workbench > .notifications-center.visible {
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* Header */
|
||||
|
||||
.monaco-workbench > .notifications-center > .notifications-center-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding-left: 8px;
|
||||
padding-right: 5px;
|
||||
height: 35px;
|
||||
}
|
||||
|
||||
.monaco-workbench > .notifications-center > .notifications-center-header > .notifications-center-header-title {
|
||||
text-transform: uppercase;
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.monaco-workbench > .notifications-center > .notifications-center-header > .notifications-center-header-toolbar {
|
||||
flex: 1;
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
/** Notification: Container */
|
||||
|
||||
.monaco-workbench .notifications-list-container .notification-list-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-direction: column-reverse; /* the details row appears first in order for better keyboard access to notification buttons */
|
||||
padding: 10px 5px;
|
||||
height: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.monaco-workbench .notifications-list-container .notification-offset-helper {
|
||||
opacity: 0;
|
||||
position: absolute;
|
||||
line-height: 22px;
|
||||
margin: 10px 0; /* 10px top and bottom */
|
||||
word-wrap: break-word; /* never overflow long words, but break to next line */
|
||||
}
|
||||
|
||||
/** Notification: Main Row */
|
||||
|
||||
.monaco-workbench .notifications-list-container .notification-list-item > .notification-list-item-main-row {
|
||||
display: flex;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
/** Notification: Icon */
|
||||
|
||||
.monaco-workbench .notifications-list-container .notification-list-item .notification-list-item-icon {
|
||||
flex: 0 0 16px;
|
||||
height: 22px;
|
||||
margin-right: 4px;
|
||||
margin-left: 4px;
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
|
||||
.vs .monaco-workbench .notifications-list-container .notification-list-item .notification-list-item-icon.icon-info {
|
||||
background-image: url('info.svg');
|
||||
}
|
||||
|
||||
.vs .monaco-workbench .notifications-list-container .notification-list-item .notification-list-item-icon.icon-warning {
|
||||
background-image: url('warning.svg');
|
||||
}
|
||||
|
||||
.vs .monaco-workbench .notifications-list-container .notification-list-item .notification-list-item-icon.icon-error {
|
||||
background-image: url('error.svg');
|
||||
}
|
||||
|
||||
.vs-dark .monaco-workbench .notifications-list-container .notification-list-item .notification-list-item-icon.icon-info,
|
||||
.hc-black .monaco-workbench .notifications-list-container .notification-list-item .notification-list-item-icon.icon-info {
|
||||
background-image: url('info-inverse.svg');
|
||||
}
|
||||
|
||||
.vs-dark .monaco-workbench .notifications-list-container .notification-list-item .notification-list-item-icon.icon-warning,
|
||||
.hc-black .monaco-workbench .notifications-list-container .notification-list-item .notification-list-item-icon.icon-warning {
|
||||
background-image: url('warning-inverse.svg');
|
||||
}
|
||||
|
||||
.vs-dark .monaco-workbench .notifications-list-container .notification-list-item .notification-list-item-icon.icon-error,
|
||||
.hc-black .monaco-workbench .notifications-list-container .notification-list-item .notification-list-item-icon.icon-error {
|
||||
background-image: url('error-inverse.svg');
|
||||
}
|
||||
|
||||
/** Notification: Message */
|
||||
|
||||
.monaco-workbench .notifications-list-container .notification-list-item .notification-list-item-message {
|
||||
line-height: 22px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
flex: 1; /* let the message always grow */
|
||||
user-select: text;
|
||||
}
|
||||
|
||||
.monaco-workbench .notifications-list-container .notification-list-item .notification-list-item-message a:focus {
|
||||
outline-width: 1px;
|
||||
outline-style: solid;
|
||||
}
|
||||
|
||||
.monaco-workbench .notifications-list-container .notification-list-item.expanded .notification-list-item-message {
|
||||
white-space: normal;
|
||||
word-wrap: break-word; /* never overflow long words, but break to next line */
|
||||
}
|
||||
|
||||
/** Notification: Toolbar Container */
|
||||
|
||||
.monaco-workbench .notifications-list-container .notification-list-item .notification-list-item-toolbar-container {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.monaco-workbench .notifications-list-container .notification-list-item:hover .notification-list-item-toolbar-container,
|
||||
.monaco-workbench .notifications-list-container .monaco-list-row.focused .notification-list-item .notification-list-item-toolbar-container,
|
||||
.monaco-workbench .notifications-list-container .notification-list-item.expanded .notification-list-item-toolbar-container {
|
||||
display: block;
|
||||
}
|
||||
|
||||
/** Notification: Details Row */
|
||||
|
||||
.monaco-workbench .notifications-list-container .notification-list-item > .notification-list-item-details-row {
|
||||
display: none;
|
||||
align-items: center;
|
||||
padding: 0 5px;
|
||||
overflow: hidden; /* details row should never overflow */
|
||||
}
|
||||
|
||||
.monaco-workbench .notifications-list-container .notification-list-item.expanded > .notification-list-item-details-row {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
/** Notification: Source */
|
||||
|
||||
.monaco-workbench .notifications-list-container .notification-list-item .notification-list-item-source {
|
||||
opacity: 0.7;
|
||||
flex: 1;
|
||||
font-size: 12px;
|
||||
overflow: hidden; /* always give away space to buttons container */
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
/** Notification: Buttons */
|
||||
|
||||
.monaco-workbench .notifications-list-container .notification-list-item .notification-list-item-buttons-container {
|
||||
display: flex;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.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;
|
||||
font-size: 12px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
/** Notification: Progress */
|
||||
|
||||
.monaco-workbench .notifications-list-container .progress-bit {
|
||||
height: 2px;
|
||||
bottom: 0;
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
.monaco-workbench > .notifications-toasts {
|
||||
position: absolute;
|
||||
z-index: 1000;
|
||||
right: 3px;
|
||||
bottom: 26px;
|
||||
display: none;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.monaco-workbench.nostatusbar > .notifications-toasts {
|
||||
bottom: 3px;
|
||||
}
|
||||
|
||||
.monaco-workbench > .notifications-toasts.visible {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.monaco-workbench > .notifications-toasts .notification-toast-container {
|
||||
overflow: hidden; /* this ensures that the notification toast does not shine through */
|
||||
}
|
||||
|
||||
.monaco-workbench > .notifications-toasts .notification-toast-container > .notification-toast {
|
||||
margin: 5px; /* enables separation and drop shadows around toasts */
|
||||
transform: translateY(100%); /* move the notification 50px to the bottom (to prevent bleed through) */
|
||||
opacity: 0; /* fade the toast in */
|
||||
transition: transform 300ms ease-out, opacity 300ms ease-out;
|
||||
will-change: transform, opacity; /* force a separate layer for the toast to speed things up */
|
||||
}
|
||||
|
||||
.monaco-workbench > .notifications-toasts .notification-toast-container > .notification-toast.notification-fade-in {
|
||||
opacity: 1;
|
||||
transform: none;
|
||||
}
|
||||
|
||||
.monaco-workbench > .notifications-toasts .notification-toast-container > .notification-toast.notification-fade-in-done {
|
||||
opacity: 1;
|
||||
transform: none;
|
||||
transition: none;
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.icon-canvas-transparent,.icon-vs-out{fill:#2d2d30;}.icon-canvas-transparent{opacity:0;}.icon-vs-bg{fill:#c5c5c5;}</style></defs><title>CollapseChevronUp_md_16x</title><path class="icon-canvas-transparent" d="M16,0V16H0V0Z"/><path class="icon-vs-out" d="M15.444,9.939,12.97,12.414,8,7.444l-4.97,4.97L.556,9.939,8,2.5Z" style="display: none;"/><path class="icon-vs-bg" d="M14.03,9.939,12.97,11,8,6.03,3.03,11,1.97,9.939,8,3.909Z"/></svg>
|
||||
|
After Width: | Height: | Size: 509 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.icon-canvas-transparent,.icon-vs-out{fill:#f6f6f6;}.icon-canvas-transparent{opacity:0;}.icon-vs-bg{fill:#424242;}</style></defs><title>CollapseChevronUp_md_16x</title><path class="icon-canvas-transparent" d="M16,0V16H0V0Z"/><path class="icon-vs-out" d="M15.444,9.939,12.97,12.414,8,7.444l-4.97,4.97L.556,9.939,8,2.5Z" style="display: none;"/><path class="icon-vs-bg" d="M14.03,9.939,12.97,11,8,6.03,3.03,11,1.97,9.939,8,3.909Z"/></svg>
|
||||
|
After Width: | Height: | Size: 509 B |
@@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 22.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 16 16" style="enable-background:new 0 0 16 16;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#F6F6F6;fill-opacity:0;}
|
||||
.st1{fill:#1A1A1A;}
|
||||
.st2{fill:#FFCC00;}
|
||||
</style>
|
||||
<title>StatusWarning_16x</title>
|
||||
<path class="st0" d="M16,0v16H0V0H16z"/>
|
||||
<path class="st1" d="M16,14l-2,2H2l-2-2L7,0h2L16,14z"/>
|
||||
<path class="st2" d="M8.4,1H7.6L1.2,13.8L2.5,15h11l1.3-1.2L8.4,1z M9,13H7v-2h2V13z M9,10H7V5h2V10z"/>
|
||||
<path d="M7,11h2v2H7V11z M7,5v5h2V5H7z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 737 B |
@@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 22.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 16 16" style="enable-background:new 0 0 16 16;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#F6F6F6;fill-opacity:0;}
|
||||
.st1{fill:#CACACC;}
|
||||
.st2{fill:#FFCC00;}
|
||||
</style>
|
||||
<title>StatusWarning_16x</title>
|
||||
<path class="st0" d="M16,0v16H0V0H16z"/>
|
||||
<path class="st1" d="M16,14l-2,2H2l-2-2L7,0h2L16,14z"/>
|
||||
<path class="st2" d="M8.4,1H7.6L1.2,13.8L2.5,15h11l1.3-1.2L8.4,1z M9,13H7v-2h2V13z M9,10H7V5h2V10z"/>
|
||||
<path d="M7,11h2v2H7V11z M7,5v5h2V5H7z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 737 B |
@@ -0,0 +1,181 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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/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';
|
||||
import { ICommandService } from 'vs/platform/commands/common/commands';
|
||||
import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService';
|
||||
|
||||
export class ClearNotificationAction extends Action {
|
||||
|
||||
public static readonly ID = CLEAR_NOTIFICATION;
|
||||
public static readonly LABEL = localize('clearNotification', "Clear Notification");
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
label: string,
|
||||
@ICommandService private commandService: ICommandService
|
||||
) {
|
||||
super(id, label, 'clear-notification-action');
|
||||
}
|
||||
|
||||
public run(notification: INotificationViewItem): TPromise<any> {
|
||||
this.commandService.executeCommand(CLEAR_NOTIFICATION, notification);
|
||||
|
||||
return TPromise.as(void 0);
|
||||
}
|
||||
}
|
||||
|
||||
export class ClearAllNotificationsAction extends Action {
|
||||
|
||||
public static readonly ID = CLEAR_ALL_NOTIFICATIONS;
|
||||
public static readonly LABEL = localize('clearNotifications', "Clear All Notifications");
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
label: string,
|
||||
@ICommandService private commandService: ICommandService
|
||||
) {
|
||||
super(id, label, 'clear-all-notifications-action');
|
||||
}
|
||||
|
||||
public run(notification: INotificationViewItem): TPromise<any> {
|
||||
this.commandService.executeCommand(CLEAR_ALL_NOTIFICATIONS);
|
||||
|
||||
return TPromise.as(void 0);
|
||||
}
|
||||
}
|
||||
|
||||
export class HideNotificationsCenterAction extends Action {
|
||||
|
||||
public static readonly ID = HIDE_NOTIFICATIONS_CENTER;
|
||||
public static readonly LABEL = localize('hideNotificationsCenter', "Hide Notifications");
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
label: string,
|
||||
@ICommandService private commandService: ICommandService
|
||||
) {
|
||||
super(id, label, 'hide-all-notifications-action');
|
||||
}
|
||||
|
||||
public run(notification: INotificationViewItem): TPromise<any> {
|
||||
this.commandService.executeCommand(HIDE_NOTIFICATIONS_CENTER);
|
||||
|
||||
return TPromise.as(void 0);
|
||||
}
|
||||
}
|
||||
|
||||
export class ExpandNotificationAction extends Action {
|
||||
|
||||
public static readonly ID = EXPAND_NOTIFICATION;
|
||||
public static readonly LABEL = localize('expandNotification', "Expand Notification");
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
label: string,
|
||||
@ICommandService private commandService: ICommandService
|
||||
) {
|
||||
super(id, label, 'expand-notification-action');
|
||||
}
|
||||
|
||||
public run(notification: INotificationViewItem): TPromise<any> {
|
||||
this.commandService.executeCommand(EXPAND_NOTIFICATION, notification);
|
||||
|
||||
return TPromise.as(void 0);
|
||||
}
|
||||
}
|
||||
|
||||
export class CollapseNotificationAction extends Action {
|
||||
|
||||
public static readonly ID = COLLAPSE_NOTIFICATION;
|
||||
public static readonly LABEL = localize('collapseNotification', "Collapse Notification");
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
label: string,
|
||||
@ICommandService private commandService: ICommandService
|
||||
) {
|
||||
super(id, label, 'collapse-notification-action');
|
||||
}
|
||||
|
||||
public run(notification: INotificationViewItem): TPromise<any> {
|
||||
this.commandService.executeCommand(COLLAPSE_NOTIFICATION, notification);
|
||||
|
||||
return TPromise.as(void 0);
|
||||
}
|
||||
}
|
||||
|
||||
export class ConfigureNotificationAction extends Action {
|
||||
|
||||
public static readonly ID = 'workbench.action.configureNotification';
|
||||
public static readonly LABEL = localize('configureNotification', "Configure Notification");
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
label: string,
|
||||
private _configurationActions: IAction[]
|
||||
) {
|
||||
super(id, label, 'configure-notification-action');
|
||||
}
|
||||
|
||||
public get configurationActions(): IAction[] {
|
||||
return this._configurationActions;
|
||||
}
|
||||
}
|
||||
|
||||
export class CopyNotificationMessageAction extends Action {
|
||||
|
||||
public static readonly ID = 'workbench.action.copyNotificationMessage';
|
||||
public static readonly LABEL = localize('copyNotification', "Copy Text");
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
label: string,
|
||||
@IClipboardService private clipboardService: IClipboardService
|
||||
) {
|
||||
super(id, label);
|
||||
}
|
||||
|
||||
public run(notification: INotificationViewItem): TPromise<any> {
|
||||
this.clipboardService.writeText(notification.message.raw);
|
||||
|
||||
return TPromise.as(void 0);
|
||||
}
|
||||
}
|
||||
|
||||
export class NotificationActionRunner extends ActionRunner {
|
||||
|
||||
constructor(
|
||||
@ITelemetryService private telemetryService: ITelemetryService,
|
||||
@INotificationService private notificationService: INotificationService
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
protected runAction(action: IAction, context: INotificationViewItem): TPromise<any> {
|
||||
|
||||
/* __GDPR__
|
||||
"workbenchActionExecuted" : {
|
||||
"id" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
|
||||
"from": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
|
||||
}
|
||||
*/
|
||||
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));
|
||||
|
||||
return TPromise.as(void 0);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 { alert } from 'vs/base/browser/ui/aria/aria';
|
||||
import { localize } from 'vs/nls';
|
||||
import { INotificationViewItem, INotificationsModel, NotificationChangeType, INotificationChangeEvent } from 'vs/workbench/common/notifications';
|
||||
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
|
||||
import { toErrorMessage } from 'vs/base/common/errorMessage';
|
||||
import { Severity } from 'vs/platform/notification/common/notification';
|
||||
|
||||
export class NotificationsAlerts {
|
||||
private toDispose: IDisposable[];
|
||||
|
||||
constructor(private model: INotificationsModel) {
|
||||
this.toDispose = [];
|
||||
|
||||
// Alert initial notifications if any
|
||||
model.notifications.forEach(n => this.ariaAlert(n));
|
||||
|
||||
this.registerListeners();
|
||||
}
|
||||
|
||||
private registerListeners(): void {
|
||||
this.toDispose.push(this.model.onDidNotificationChange(e => this.onDidNotificationChange(e)));
|
||||
}
|
||||
|
||||
private onDidNotificationChange(e: INotificationChangeEvent): void {
|
||||
if (e.kind === NotificationChangeType.ADD) {
|
||||
|
||||
// ARIA alert for screen readers
|
||||
this.ariaAlert(e.item);
|
||||
|
||||
// Always log errors to console with full details
|
||||
if (e.item.severity === Severity.Error) {
|
||||
console.error(toErrorMessage(e.item.message.value, true));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private ariaAlert(notifiation: INotificationViewItem): void {
|
||||
let alertText: string;
|
||||
if (notifiation.severity === Severity.Error) {
|
||||
alertText = localize('alertErrorMessage', "Error: {0}", notifiation.message.value);
|
||||
} else if (notifiation.severity === Severity.Warning) {
|
||||
alertText = localize('alertWarningMessage', "Warning: {0}", notifiation.message.value);
|
||||
} else {
|
||||
alertText = localize('alertInfoMessage', "Info: {0}", notifiation.message.value);
|
||||
}
|
||||
|
||||
alert(alertText);
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
this.toDispose = dispose(this.toDispose);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,295 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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/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';
|
||||
import { IThemeService, registerThemingParticipant, ITheme, ICssStyleCollector } from 'vs/platform/theme/common/themeService';
|
||||
import { INotificationsModel, INotificationChangeEvent, NotificationChangeType } from 'vs/workbench/common/notifications';
|
||||
import { Dimension } from 'vs/base/browser/builder';
|
||||
import { IPartService, Parts } from 'vs/workbench/services/part/common/partService';
|
||||
import Event, { Emitter } from 'vs/base/common/event';
|
||||
import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { NotificationsCenterVisibleContext } from 'vs/workbench/browser/parts/notifications/notificationsCommands';
|
||||
import { NotificationsList } from 'vs/workbench/browser/parts/notifications/notificationsList';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { addClass, removeClass, isAncestor } from 'vs/base/browser/dom';
|
||||
import { widgetShadow } from 'vs/platform/theme/common/colorRegistry';
|
||||
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { localize } from 'vs/nls';
|
||||
import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar';
|
||||
import { ClearAllNotificationsAction, HideNotificationsCenterAction, NotificationActionRunner } from 'vs/workbench/browser/parts/notifications/notificationsActions';
|
||||
import { IAction } from 'vs/base/common/actions';
|
||||
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
||||
|
||||
export class NotificationsCenter extends Themable {
|
||||
|
||||
private static MAX_DIMENSIONS = new Dimension(450, 400);
|
||||
|
||||
private notificationsCenterContainer: HTMLElement;
|
||||
private notificationsCenterHeader: HTMLElement;
|
||||
private notificationsList: NotificationsList;
|
||||
private _isVisible: boolean;
|
||||
private workbenchDimensions: Dimension;
|
||||
private _onDidChangeVisibility: Emitter<void>;
|
||||
private notificationsCenterVisibleContextKey: IContextKey<boolean>;
|
||||
|
||||
constructor(
|
||||
private container: HTMLElement,
|
||||
private model: INotificationsModel,
|
||||
@IThemeService themeService: IThemeService,
|
||||
@IInstantiationService private instantiationService: IInstantiationService,
|
||||
@IPartService private partService: IPartService,
|
||||
@IContextKeyService contextKeyService: IContextKeyService,
|
||||
@IWorkbenchEditorService private editorService: IWorkbenchEditorService,
|
||||
@IKeybindingService private keybindingService: IKeybindingService
|
||||
) {
|
||||
super(themeService);
|
||||
|
||||
this._onDidChangeVisibility = new Emitter<void>();
|
||||
this.toUnbind.push(this._onDidChangeVisibility);
|
||||
|
||||
this.notificationsCenterVisibleContextKey = NotificationsCenterVisibleContext.bindTo(contextKeyService);
|
||||
|
||||
this.registerListeners();
|
||||
}
|
||||
|
||||
private registerListeners(): void {
|
||||
this.toUnbind.push(this.model.onDidNotificationChange(e => this.onDidNotificationChange(e)));
|
||||
}
|
||||
|
||||
public get onDidChangeVisibility(): Event<void> {
|
||||
return this._onDidChangeVisibility.event;
|
||||
}
|
||||
|
||||
public get isVisible(): boolean {
|
||||
return this._isVisible;
|
||||
}
|
||||
|
||||
public show(): void {
|
||||
if (this.model.notifications.length === 0) {
|
||||
return; // currently not supporting to show empty (https://github.com/Microsoft/vscode/issues/44509)
|
||||
}
|
||||
|
||||
if (this._isVisible) {
|
||||
this.notificationsList.show(true /* focus */);
|
||||
|
||||
return; // already visible
|
||||
}
|
||||
|
||||
// Lazily create if showing for the first time
|
||||
if (!this.notificationsCenterContainer) {
|
||||
this.create();
|
||||
}
|
||||
|
||||
// Make visible
|
||||
this._isVisible = true;
|
||||
addClass(this.notificationsCenterContainer, 'visible');
|
||||
this.notificationsList.show();
|
||||
|
||||
// Layout
|
||||
this.layout(this.workbenchDimensions);
|
||||
|
||||
// Show all notifications that are present now
|
||||
this.notificationsList.updateNotificationsList(0, 0, this.model.notifications);
|
||||
|
||||
// Focus first
|
||||
this.notificationsList.focusFirst();
|
||||
|
||||
// Theming
|
||||
this.updateStyles();
|
||||
|
||||
// Context Key
|
||||
this.notificationsCenterVisibleContextKey.set(true);
|
||||
|
||||
// Event
|
||||
this._onDidChangeVisibility.fire();
|
||||
}
|
||||
|
||||
private create(): void {
|
||||
|
||||
// Container
|
||||
this.notificationsCenterContainer = document.createElement('div');
|
||||
addClass(this.notificationsCenterContainer, 'notifications-center');
|
||||
|
||||
// Header
|
||||
this.notificationsCenterHeader = document.createElement('div');
|
||||
addClass(this.notificationsCenterHeader, 'notifications-center-header');
|
||||
this.notificationsCenterContainer.appendChild(this.notificationsCenterHeader);
|
||||
|
||||
// Header Title
|
||||
const title = document.createElement('span');
|
||||
addClass(title, 'notifications-center-header-title');
|
||||
title.innerText = localize('notifications', "Notifications");
|
||||
this.notificationsCenterHeader.appendChild(title);
|
||||
|
||||
// Header Toolbar
|
||||
const toolbarContainer = document.createElement('div');
|
||||
addClass(toolbarContainer, 'notifications-center-header-toolbar');
|
||||
this.notificationsCenterHeader.appendChild(toolbarContainer);
|
||||
|
||||
const actionRunner = this.instantiationService.createInstance(NotificationActionRunner);
|
||||
this.toUnbind.push(actionRunner);
|
||||
|
||||
const notificationsToolBar = new ActionBar(toolbarContainer, {
|
||||
ariaLabel: localize('notificationsToolbar', "Notification Center Actions"),
|
||||
actionRunner
|
||||
});
|
||||
this.toUnbind.push(notificationsToolBar);
|
||||
|
||||
const hideAllAction = this.instantiationService.createInstance(HideNotificationsCenterAction, HideNotificationsCenterAction.ID, HideNotificationsCenterAction.LABEL);
|
||||
this.toUnbind.push(hideAllAction);
|
||||
notificationsToolBar.push(hideAllAction, { icon: true, label: false, keybinding: this.getKeybindingLabel(hideAllAction) });
|
||||
|
||||
const clearAllAction = this.instantiationService.createInstance(ClearAllNotificationsAction, ClearAllNotificationsAction.ID, ClearAllNotificationsAction.LABEL);
|
||||
this.toUnbind.push(clearAllAction);
|
||||
notificationsToolBar.push(clearAllAction, { icon: true, label: false, keybinding: this.getKeybindingLabel(clearAllAction) });
|
||||
|
||||
// Notifications List
|
||||
this.notificationsList = this.instantiationService.createInstance(NotificationsList, this.notificationsCenterContainer, {
|
||||
ariaLabel: localize('notificationsList', "Notifications List")
|
||||
});
|
||||
|
||||
this.container.appendChild(this.notificationsCenterContainer);
|
||||
}
|
||||
|
||||
private getKeybindingLabel(action: IAction): string {
|
||||
const keybinding = this.keybindingService.lookupKeybinding(action.id);
|
||||
|
||||
return keybinding ? keybinding.getLabel() : void 0;
|
||||
}
|
||||
|
||||
private onDidNotificationChange(e: INotificationChangeEvent): void {
|
||||
if (!this._isVisible) {
|
||||
return; // only if visible
|
||||
}
|
||||
|
||||
let focusEditor = false;
|
||||
|
||||
// Update notifications list based on event
|
||||
switch (e.kind) {
|
||||
case NotificationChangeType.ADD:
|
||||
this.notificationsList.updateNotificationsList(e.index, 0, [e.item]);
|
||||
break;
|
||||
case NotificationChangeType.CHANGE:
|
||||
this.notificationsList.updateNotificationsList(e.index, 1, [e.item]);
|
||||
break;
|
||||
case NotificationChangeType.REMOVE:
|
||||
focusEditor = isAncestor(document.activeElement, this.notificationsCenterContainer);
|
||||
this.notificationsList.updateNotificationsList(e.index, 1);
|
||||
break;
|
||||
}
|
||||
|
||||
// Hide if no more notifications to show
|
||||
if (this.model.notifications.length === 0) {
|
||||
this.hide();
|
||||
|
||||
// Restore focus to editor if we had focus
|
||||
if (focusEditor) {
|
||||
this.focusEditor();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private focusEditor(): void {
|
||||
const editor = this.editorService.getActiveEditor();
|
||||
if (editor) {
|
||||
editor.focus();
|
||||
}
|
||||
}
|
||||
|
||||
public hide(): void {
|
||||
if (!this._isVisible || !this.notificationsCenterContainer) {
|
||||
return; // already hidden
|
||||
}
|
||||
|
||||
const focusEditor = isAncestor(document.activeElement, this.notificationsCenterContainer);
|
||||
|
||||
// Hide
|
||||
this._isVisible = false;
|
||||
removeClass(this.notificationsCenterContainer, 'visible');
|
||||
this.notificationsList.hide();
|
||||
|
||||
// Context Key
|
||||
this.notificationsCenterVisibleContextKey.set(false);
|
||||
|
||||
// Event
|
||||
this._onDidChangeVisibility.fire();
|
||||
|
||||
if (focusEditor) {
|
||||
this.focusEditor();
|
||||
}
|
||||
}
|
||||
|
||||
protected updateStyles(): void {
|
||||
if (this.notificationsCenterContainer) {
|
||||
const widgetShadowColor = this.getColor(widgetShadow);
|
||||
this.notificationsCenterContainer.style.boxShadow = widgetShadowColor ? `0 0px 8px ${widgetShadowColor}` : null;
|
||||
|
||||
const borderColor = this.getColor(NOTIFICATIONS_CENTER_BORDER);
|
||||
this.notificationsCenterContainer.style.border = borderColor ? `1px solid ${borderColor}` : null;
|
||||
|
||||
const headerForeground = this.getColor(NOTIFICATIONS_CENTER_HEADER_FOREGROUND);
|
||||
this.notificationsCenterHeader.style.color = headerForeground ? headerForeground.toString() : null;
|
||||
|
||||
const headerBackground = this.getColor(NOTIFICATIONS_CENTER_HEADER_BACKGROUND);
|
||||
this.notificationsCenterHeader.style.background = headerBackground ? headerBackground.toString() : null;
|
||||
}
|
||||
}
|
||||
|
||||
public layout(dimension: Dimension): void {
|
||||
this.workbenchDimensions = dimension;
|
||||
|
||||
if (this._isVisible && this.notificationsCenterContainer) {
|
||||
let maxWidth = NotificationsCenter.MAX_DIMENSIONS.width;
|
||||
let maxHeight = NotificationsCenter.MAX_DIMENSIONS.height;
|
||||
|
||||
let availableWidth = maxWidth;
|
||||
let availableHeight = maxHeight;
|
||||
|
||||
if (this.workbenchDimensions) {
|
||||
|
||||
// Make sure notifications are not exceding available width
|
||||
availableWidth = this.workbenchDimensions.width;
|
||||
availableWidth -= (2 * 8); // adjust for paddings left and right
|
||||
|
||||
// Make sure notifications are not exceeding available height
|
||||
availableHeight = this.workbenchDimensions.height - 35 /* header */;
|
||||
if (this.partService.isVisible(Parts.STATUSBAR_PART)) {
|
||||
availableHeight -= 22; // adjust for status bar
|
||||
}
|
||||
|
||||
if (this.partService.isVisible(Parts.TITLEBAR_PART)) {
|
||||
availableHeight -= 22; // adjust for title bar
|
||||
}
|
||||
|
||||
availableHeight -= (2 * 12); // adjust for paddings top and bottom
|
||||
}
|
||||
|
||||
// Apply to list
|
||||
this.notificationsList.layout(Math.min(maxWidth, availableWidth), Math.min(maxHeight, availableHeight));
|
||||
}
|
||||
}
|
||||
|
||||
public clearAll(): void {
|
||||
|
||||
// Hide notifications center first
|
||||
this.hide();
|
||||
|
||||
// Dispose all
|
||||
while (this.model.notifications.length) {
|
||||
this.model.notifications[0].dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => {
|
||||
const notificationBorderColor = theme.getColor(NOTIFICATIONS_BORDER);
|
||||
if (notificationBorderColor) {
|
||||
collector.addRule(`.monaco-workbench > .notifications-center .notifications-list-container .monaco-list-row[data-last-element="false"] > .notification-list-item { border-bottom: 1px solid ${notificationBorderColor}; }`);
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,232 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 { CommandsRegistry } from 'vs/platform/commands/common/commands';
|
||||
import { RawContextKey, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { KeybindingsRegistry } from 'vs/platform/keybinding/common/keybindingsRegistry';
|
||||
import { KeyCode, KeyMod } from 'vs/base/common/keyCodes';
|
||||
import { INotificationViewItem, isNotificationViewItem } from 'vs/workbench/common/notifications';
|
||||
import { MenuRegistry, MenuId } from 'vs/platform/actions/common/actions';
|
||||
import { localize } from 'vs/nls';
|
||||
import { IListService, WorkbenchList } from 'vs/platform/list/browser/listService';
|
||||
|
||||
// Center
|
||||
export const SHOW_NOTIFICATIONS_CENTER = 'notifications.showList';
|
||||
export const HIDE_NOTIFICATIONS_CENTER = 'notifications.hideList';
|
||||
export const TOGGLE_NOTIFICATIONS_CENTER = 'notifications.toggleList';
|
||||
|
||||
// Toasts
|
||||
export const HIDE_NOTIFICATION_TOAST = 'notifications.hideToasts';
|
||||
export const FOCUS_NOTIFICATION_TOAST = 'notifications.focusToasts';
|
||||
export const FOCUS_NEXT_NOTIFICATION_TOAST = 'notifications.focusNextToast';
|
||||
export const FOCUS_PREVIOUS_NOTIFICATION_TOAST = 'notifications.focusPreviousToast';
|
||||
export const FOCUS_FIRST_NOTIFICATION_TOAST = 'notifications.focusFirstToast';
|
||||
export const FOCUS_LAST_NOTIFICATION_TOAST = 'notifications.focusLastToast';
|
||||
|
||||
// Notification
|
||||
export const COLLAPSE_NOTIFICATION = 'notification.collapse';
|
||||
export const EXPAND_NOTIFICATION = 'notification.expand';
|
||||
export const TOGGLE_NOTIFICATION = 'notification.toggle';
|
||||
export const CLEAR_NOTIFICATION = 'notification.clear';
|
||||
export const CLEAR_ALL_NOTIFICATIONS = 'notifications.clearAll';
|
||||
|
||||
const notificationFocusedId = 'notificationFocus';
|
||||
export const NotificationFocusedContext = new RawContextKey<boolean>(notificationFocusedId, true);
|
||||
|
||||
const notificationsCenterVisibleId = 'notificationCenterVisible';
|
||||
export const NotificationsCenterVisibleContext = new RawContextKey<boolean>(notificationsCenterVisibleId, false);
|
||||
|
||||
const notificationsToastsVisibleId = 'notificationToastsVisible';
|
||||
export const NotificationsToastsVisibleContext = new RawContextKey<boolean>(notificationsToastsVisibleId, false);
|
||||
|
||||
export interface INotificationsCenterController {
|
||||
readonly isVisible: boolean;
|
||||
|
||||
show(): void;
|
||||
hide(): void;
|
||||
|
||||
clearAll(): void;
|
||||
}
|
||||
|
||||
export interface INotificationsToastController {
|
||||
focus(): void;
|
||||
focusNext(): void;
|
||||
focusPrevious(): void;
|
||||
focusFirst(): void;
|
||||
focusLast(): void;
|
||||
|
||||
hide(): void;
|
||||
}
|
||||
|
||||
export function registerNotificationCommands(center: INotificationsCenterController, toasts: INotificationsToastController): void {
|
||||
|
||||
function getNotificationFromContext(listService: IListService, context?: any): INotificationViewItem {
|
||||
if (isNotificationViewItem(context)) {
|
||||
return context;
|
||||
}
|
||||
|
||||
const list = listService.lastFocusedList;
|
||||
if (list instanceof WorkbenchList) {
|
||||
const focusedElement = list.getFocusedElements()[0];
|
||||
if (isNotificationViewItem(focusedElement)) {
|
||||
return focusedElement;
|
||||
}
|
||||
}
|
||||
|
||||
return void 0;
|
||||
}
|
||||
|
||||
// Show Notifications Cneter
|
||||
CommandsRegistry.registerCommand(SHOW_NOTIFICATIONS_CENTER, () => {
|
||||
center.show();
|
||||
});
|
||||
|
||||
// Hide Notifications Center
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
id: HIDE_NOTIFICATIONS_CENTER,
|
||||
weight: KeybindingsRegistry.WEIGHT.workbenchContrib(50),
|
||||
when: NotificationsCenterVisibleContext,
|
||||
primary: KeyCode.Escape,
|
||||
handler: accessor => center.hide()
|
||||
});
|
||||
|
||||
// Toggle Notifications Center
|
||||
CommandsRegistry.registerCommand(TOGGLE_NOTIFICATIONS_CENTER, accessor => {
|
||||
if (center.isVisible) {
|
||||
center.hide();
|
||||
} else {
|
||||
center.show();
|
||||
}
|
||||
});
|
||||
|
||||
// Clear Notification
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
id: CLEAR_NOTIFICATION,
|
||||
weight: KeybindingsRegistry.WEIGHT.workbenchContrib(),
|
||||
when: NotificationFocusedContext,
|
||||
primary: KeyCode.Delete,
|
||||
mac: {
|
||||
primary: KeyMod.CtrlCmd | KeyCode.Backspace
|
||||
},
|
||||
handler: (accessor, args?: any) => {
|
||||
const notification = getNotificationFromContext(accessor.get(IListService), args);
|
||||
if (notification) {
|
||||
notification.dispose();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Expand Notification
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
id: EXPAND_NOTIFICATION,
|
||||
weight: KeybindingsRegistry.WEIGHT.workbenchContrib(),
|
||||
when: NotificationFocusedContext,
|
||||
primary: KeyCode.RightArrow,
|
||||
handler: (accessor, args?: any) => {
|
||||
const notification = getNotificationFromContext(accessor.get(IListService), args);
|
||||
if (notification) {
|
||||
notification.expand();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Collapse Notification
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
id: COLLAPSE_NOTIFICATION,
|
||||
weight: KeybindingsRegistry.WEIGHT.workbenchContrib(),
|
||||
when: NotificationFocusedContext,
|
||||
primary: KeyCode.LeftArrow,
|
||||
handler: (accessor, args?: any) => {
|
||||
const notification = getNotificationFromContext(accessor.get(IListService), args);
|
||||
if (notification) {
|
||||
notification.collapse();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Toggle Notification
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
id: TOGGLE_NOTIFICATION,
|
||||
weight: KeybindingsRegistry.WEIGHT.workbenchContrib(),
|
||||
when: NotificationFocusedContext,
|
||||
primary: KeyCode.Space,
|
||||
secondary: [KeyCode.Enter],
|
||||
handler: accessor => {
|
||||
const notification = getNotificationFromContext(accessor.get(IListService));
|
||||
if (notification) {
|
||||
notification.toggle();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Hide Toasts
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
id: HIDE_NOTIFICATION_TOAST,
|
||||
weight: KeybindingsRegistry.WEIGHT.workbenchContrib(50),
|
||||
when: NotificationsToastsVisibleContext,
|
||||
primary: KeyCode.Escape,
|
||||
handler: accessor => toasts.hide()
|
||||
});
|
||||
|
||||
// Focus Toasts
|
||||
CommandsRegistry.registerCommand(FOCUS_NOTIFICATION_TOAST, () => toasts.focus());
|
||||
|
||||
// Focus Next Toast
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
id: FOCUS_NEXT_NOTIFICATION_TOAST,
|
||||
weight: KeybindingsRegistry.WEIGHT.workbenchContrib(),
|
||||
when: ContextKeyExpr.and(NotificationFocusedContext, NotificationsToastsVisibleContext),
|
||||
primary: KeyCode.DownArrow,
|
||||
handler: (accessor) => {
|
||||
toasts.focusNext();
|
||||
}
|
||||
});
|
||||
|
||||
// Focus Previous Toast
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
id: FOCUS_PREVIOUS_NOTIFICATION_TOAST,
|
||||
weight: KeybindingsRegistry.WEIGHT.workbenchContrib(),
|
||||
when: ContextKeyExpr.and(NotificationFocusedContext, NotificationsToastsVisibleContext),
|
||||
primary: KeyCode.UpArrow,
|
||||
handler: (accessor) => {
|
||||
toasts.focusPrevious();
|
||||
}
|
||||
});
|
||||
|
||||
// Focus First Toast
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
id: FOCUS_FIRST_NOTIFICATION_TOAST,
|
||||
weight: KeybindingsRegistry.WEIGHT.workbenchContrib(),
|
||||
when: ContextKeyExpr.and(NotificationFocusedContext, NotificationsToastsVisibleContext),
|
||||
primary: KeyCode.PageUp,
|
||||
secondary: [KeyCode.Home],
|
||||
handler: (accessor) => {
|
||||
toasts.focusFirst();
|
||||
}
|
||||
});
|
||||
|
||||
// Focus Last Toast
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
id: FOCUS_LAST_NOTIFICATION_TOAST,
|
||||
weight: KeybindingsRegistry.WEIGHT.workbenchContrib(),
|
||||
when: ContextKeyExpr.and(NotificationFocusedContext, NotificationsToastsVisibleContext),
|
||||
primary: KeyCode.PageDown,
|
||||
secondary: [KeyCode.End],
|
||||
handler: (accessor) => {
|
||||
toasts.focusLast();
|
||||
}
|
||||
});
|
||||
|
||||
/// Clear All Notifications
|
||||
CommandsRegistry.registerCommand(CLEAR_ALL_NOTIFICATIONS, () => center.clearAll());
|
||||
|
||||
// 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 } });
|
||||
}
|
||||
@@ -0,0 +1,257 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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/notificationsList';
|
||||
import { addClass, isAncestor, trackFocus } from 'vs/base/browser/dom';
|
||||
import { WorkbenchList } from 'vs/platform/list/browser/listService';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IListOptions } from 'vs/base/browser/ui/list/listWidget';
|
||||
import { Themable, NOTIFICATIONS_LINKS, NOTIFICATIONS_BACKGROUND, NOTIFICATIONS_FOREGROUND } from 'vs/workbench/common/theme';
|
||||
import { IThemeService, registerThemingParticipant, ITheme, ICssStyleCollector } from 'vs/platform/theme/common/themeService';
|
||||
import { contrastBorder, focusBorder } from 'vs/platform/theme/common/colorRegistry';
|
||||
import { INotificationViewItem } from 'vs/workbench/common/notifications';
|
||||
import { NotificationsListDelegate, NotificationRenderer } from 'vs/workbench/browser/parts/notifications/notificationsViewer';
|
||||
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;
|
||||
private list: WorkbenchList<INotificationViewItem>;
|
||||
private viewModel: INotificationViewItem[];
|
||||
private isVisible: boolean;
|
||||
|
||||
constructor(
|
||||
private container: HTMLElement,
|
||||
private options: IListOptions<INotificationViewItem>,
|
||||
@IInstantiationService private instantiationService: IInstantiationService,
|
||||
@IThemeService themeService: IThemeService,
|
||||
@IContextMenuService private contextMenuService: IContextMenuService
|
||||
) {
|
||||
super(themeService);
|
||||
|
||||
this.viewModel = [];
|
||||
}
|
||||
|
||||
public show(focus?: boolean): void {
|
||||
if (this.isVisible) {
|
||||
if (focus) {
|
||||
this.list.domFocus();
|
||||
}
|
||||
|
||||
return; // already visible
|
||||
}
|
||||
|
||||
// Lazily create if showing for the first time
|
||||
if (!this.list) {
|
||||
this.createNotificationsList();
|
||||
}
|
||||
|
||||
// Make visible
|
||||
this.isVisible = true;
|
||||
|
||||
// Focus
|
||||
if (focus) {
|
||||
this.list.domFocus();
|
||||
}
|
||||
}
|
||||
|
||||
private createNotificationsList(): void {
|
||||
|
||||
// List Container
|
||||
this.listContainer = document.createElement('div');
|
||||
addClass(this.listContainer, 'notifications-list-container');
|
||||
|
||||
const actionRunner = this.instantiationService.createInstance(NotificationActionRunner);
|
||||
this.toUnbind.push(actionRunner);
|
||||
|
||||
// Notification Renderer
|
||||
const renderer = this.instantiationService.createInstance(NotificationRenderer, actionRunner);
|
||||
|
||||
// List
|
||||
this.list = this.instantiationService.createInstance(
|
||||
WorkbenchList,
|
||||
this.listContainer,
|
||||
new NotificationsListDelegate(this.listContainer),
|
||||
[renderer],
|
||||
this.options
|
||||
);
|
||||
this.toUnbind.push(this.list);
|
||||
|
||||
// Context menu to copy message
|
||||
const copyAction = this.instantiationService.createInstance(CopyNotificationMessageAction, CopyNotificationMessageAction.ID, CopyNotificationMessageAction.LABEL);
|
||||
this.toUnbind.push(copyAction);
|
||||
this.toUnbind.push(this.list.onContextMenu(e => {
|
||||
this.contextMenuService.showContextMenu({
|
||||
getAnchor: () => e.anchor,
|
||||
getActions: () => TPromise.as([copyAction]),
|
||||
getActionsContext: () => e.element,
|
||||
actionRunner
|
||||
});
|
||||
}));
|
||||
|
||||
// Toggle on double click
|
||||
this.toUnbind.push(this.list.onMouseDblClick(event => (event.element as INotificationViewItem).toggle()));
|
||||
|
||||
// Clear focus when DOM focus moves out
|
||||
// Use document.hasFocus() to not clear the focus when the entire window lost focus
|
||||
// This ensures that when the focus comes back, the notifciation is still focused
|
||||
const listFocusTracker = trackFocus(this.list.getHTMLElement());
|
||||
listFocusTracker.onDidBlur(() => {
|
||||
if (document.hasFocus()) {
|
||||
this.list.setFocus([]);
|
||||
}
|
||||
});
|
||||
this.toUnbind.push(listFocusTracker);
|
||||
|
||||
// Context key
|
||||
NotificationFocusedContext.bindTo(this.list.contextKeyService);
|
||||
|
||||
// Only allow for focus in notifications, as the
|
||||
// selection is too strong over the contents of
|
||||
// the notification
|
||||
this.toUnbind.push(this.list.onSelectionChange(e => {
|
||||
if (e.indexes.length > 0) {
|
||||
this.list.setSelection([]);
|
||||
}
|
||||
}));
|
||||
|
||||
this.container.appendChild(this.listContainer);
|
||||
|
||||
this.updateStyles();
|
||||
}
|
||||
|
||||
public updateNotificationsList(start: number, deleteCount: number, items: INotificationViewItem[] = []) {
|
||||
const listHasDOMFocus = isAncestor(document.activeElement, this.listContainer);
|
||||
|
||||
// Remember focus and relative top of that item
|
||||
const focusedIndex = this.list.getFocus()[0];
|
||||
const focusedItem = this.viewModel[focusedIndex];
|
||||
|
||||
let focusRelativeTop: number;
|
||||
if (typeof focusedIndex === 'number') {
|
||||
focusRelativeTop = this.list.getRelativeTop(focusedIndex);
|
||||
}
|
||||
|
||||
// Update view model
|
||||
this.viewModel.splice(start, deleteCount, ...items);
|
||||
|
||||
// Update list
|
||||
this.list.splice(start, deleteCount, items);
|
||||
this.list.layout();
|
||||
|
||||
// Hide if no more notifications to show
|
||||
if (this.viewModel.length === 0) {
|
||||
this.hide();
|
||||
}
|
||||
|
||||
// Otherwise restore focus if we had
|
||||
else if (typeof focusedIndex === 'number') {
|
||||
let indexToFocus = 0;
|
||||
if (focusedItem) {
|
||||
let indexToFocusCandidate = this.viewModel.indexOf(focusedItem);
|
||||
if (indexToFocusCandidate === -1) {
|
||||
indexToFocusCandidate = focusedIndex - 1; // item could have been removed
|
||||
}
|
||||
|
||||
if (indexToFocusCandidate < this.viewModel.length && indexToFocusCandidate >= 0) {
|
||||
indexToFocus = indexToFocusCandidate;
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof focusRelativeTop === 'number') {
|
||||
this.list.reveal(indexToFocus, focusRelativeTop);
|
||||
}
|
||||
|
||||
this.list.setFocus([indexToFocus]);
|
||||
}
|
||||
|
||||
// Restore DOM focus if we had focus before
|
||||
if (listHasDOMFocus) {
|
||||
this.list.domFocus();
|
||||
}
|
||||
}
|
||||
|
||||
public hide(): void {
|
||||
if (!this.isVisible || !this.list) {
|
||||
return; // already hidden
|
||||
}
|
||||
|
||||
// Hide
|
||||
this.isVisible = false;
|
||||
|
||||
// Clear list
|
||||
this.list.splice(0, this.viewModel.length);
|
||||
|
||||
// Clear view model
|
||||
this.viewModel = [];
|
||||
}
|
||||
|
||||
public focusFirst(): void {
|
||||
if (!this.isVisible || !this.list) {
|
||||
return; // hidden
|
||||
}
|
||||
|
||||
this.list.focusFirst();
|
||||
this.list.domFocus();
|
||||
}
|
||||
|
||||
public hasFocus(): boolean {
|
||||
if (!this.isVisible || !this.list) {
|
||||
return false; // hidden
|
||||
}
|
||||
|
||||
return isAncestor(document.activeElement, this.listContainer);
|
||||
}
|
||||
|
||||
protected updateStyles(): void {
|
||||
if (this.listContainer) {
|
||||
const foreground = this.getColor(NOTIFICATIONS_FOREGROUND);
|
||||
this.listContainer.style.color = foreground ? foreground.toString() : null;
|
||||
|
||||
const background = this.getColor(NOTIFICATIONS_BACKGROUND);
|
||||
this.listContainer.style.background = background ? background.toString() : null;
|
||||
|
||||
const outlineColor = this.getColor(contrastBorder);
|
||||
this.listContainer.style.outlineColor = outlineColor ? outlineColor.toString() : null;
|
||||
}
|
||||
}
|
||||
|
||||
public layout(width: number, maxHeight?: number): void {
|
||||
if (this.list) {
|
||||
this.listContainer.style.width = `${width}px`;
|
||||
|
||||
if (typeof maxHeight === 'number') {
|
||||
this.list.getHTMLElement().style.maxHeight = `${maxHeight}px`;
|
||||
}
|
||||
|
||||
this.list.layout();
|
||||
}
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
this.hide();
|
||||
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => {
|
||||
const linkColor = theme.getColor(NOTIFICATIONS_LINKS);
|
||||
if (linkColor) {
|
||||
collector.addRule(`.monaco-workbench .notifications-list-container .notification-list-item .notification-list-item-message a { color: ${linkColor}; }`);
|
||||
}
|
||||
|
||||
const focusOutline = theme.getColor(focusBorder);
|
||||
if (focusOutline) {
|
||||
collector.addRule(`
|
||||
.monaco-workbench .notifications-list-container .notification-list-item .notification-list-item-message a:focus {
|
||||
outline-color: ${focusOutline};
|
||||
}`);
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,111 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 { INotificationsModel, INotificationChangeEvent, NotificationChangeType, INotificationViewItem } from 'vs/workbench/common/notifications';
|
||||
import { IStatusbarService, StatusbarAlignment } from 'vs/platform/statusbar/common/statusbar';
|
||||
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
|
||||
import { HIDE_NOTIFICATIONS_CENTER, SHOW_NOTIFICATIONS_CENTER } from 'vs/workbench/browser/parts/notifications/notificationsCommands';
|
||||
import { localize } from 'vs/nls';
|
||||
|
||||
export class NotificationsStatus {
|
||||
private statusItem: IDisposable;
|
||||
private toDispose: IDisposable[];
|
||||
private isNotificationsCenterVisible: boolean;
|
||||
private _counter: Set<INotificationViewItem>;
|
||||
|
||||
constructor(
|
||||
private model: INotificationsModel,
|
||||
@IStatusbarService private statusbarService: IStatusbarService
|
||||
) {
|
||||
this.toDispose = [];
|
||||
this._counter = new Set<INotificationViewItem>();
|
||||
|
||||
this.updateNotificationsStatusItem();
|
||||
|
||||
this.registerListeners();
|
||||
}
|
||||
|
||||
private get count(): number {
|
||||
return this._counter.size;
|
||||
}
|
||||
|
||||
public update(isCenterVisible: boolean): void {
|
||||
if (this.isNotificationsCenterVisible !== isCenterVisible) {
|
||||
this.isNotificationsCenterVisible = isCenterVisible;
|
||||
|
||||
// Showing the notification center resets the counter to 0
|
||||
this._counter.clear();
|
||||
this.updateNotificationsStatusItem();
|
||||
}
|
||||
}
|
||||
|
||||
private registerListeners(): void {
|
||||
this.toDispose.push(this.model.onDidNotificationChange(e => this.onDidNotificationChange(e)));
|
||||
}
|
||||
|
||||
private onDidNotificationChange(e: INotificationChangeEvent): void {
|
||||
if (this.isNotificationsCenterVisible) {
|
||||
return; // no change if notification center is visible
|
||||
}
|
||||
|
||||
// Notification got Added
|
||||
if (e.kind === NotificationChangeType.ADD) {
|
||||
this._counter.add(e.item);
|
||||
}
|
||||
|
||||
// Notification got Removed
|
||||
else if (e.kind === NotificationChangeType.REMOVE) {
|
||||
this._counter.delete(e.item);
|
||||
}
|
||||
|
||||
this.updateNotificationsStatusItem();
|
||||
}
|
||||
|
||||
private updateNotificationsStatusItem(): void {
|
||||
|
||||
// Dispose old first
|
||||
if (this.statusItem) {
|
||||
this.statusItem.dispose();
|
||||
}
|
||||
|
||||
// Create new
|
||||
this.statusItem = this.statusbarService.addEntry({
|
||||
text: this.count === 0 ? '$(bell)' : `$(bell) ${this.count}`,
|
||||
command: this.isNotificationsCenterVisible ? HIDE_NOTIFICATIONS_CENTER : this.model.notifications.length > 0 ? SHOW_NOTIFICATIONS_CENTER : void 0,
|
||||
tooltip: this.getTooltip(),
|
||||
showBeak: this.isNotificationsCenterVisible
|
||||
}, StatusbarAlignment.RIGHT, -1000 /* towards the far end of the right hand side */);
|
||||
}
|
||||
|
||||
private getTooltip(): string {
|
||||
if (this.isNotificationsCenterVisible) {
|
||||
return localize('hideNotifications', "Hide Notifications");
|
||||
}
|
||||
|
||||
if (this.model.notifications.length === 0) {
|
||||
return localize('zeroNotifications', "No Notifications");
|
||||
}
|
||||
|
||||
if (this.count === 0) {
|
||||
return localize('noNotifications', "No New Notifications");
|
||||
}
|
||||
|
||||
if (this.count === 1) {
|
||||
return localize('oneNotification', "1 New Notification");
|
||||
}
|
||||
|
||||
return localize('notifications', "{0} New Notifications", this.count);
|
||||
}
|
||||
|
||||
public dispose() {
|
||||
this.toDispose = dispose(this.toDispose);
|
||||
|
||||
if (this.statusItem) {
|
||||
this.statusItem.dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,475 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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/notificationsToasts';
|
||||
import { INotificationsModel, NotificationChangeType, INotificationChangeEvent, INotificationViewItem, NotificationViewItemLabelKind } from 'vs/workbench/common/notifications';
|
||||
import { IDisposable, dispose, toDisposable } from 'vs/base/common/lifecycle';
|
||||
import { addClass, removeClass, isAncestor, addDisposableListener, EventType } from 'vs/base/browser/dom';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { NotificationsList } from 'vs/workbench/browser/parts/notifications/notificationsList';
|
||||
import { Dimension } from 'vs/base/browser/builder';
|
||||
import { once } from 'vs/base/common/event';
|
||||
import { IPartService, Parts } from 'vs/workbench/services/part/common/partService';
|
||||
import { Themable, NOTIFICATIONS_TOAST_BORDER } from 'vs/workbench/common/theme';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import { widgetShadow } from 'vs/platform/theme/common/colorRegistry';
|
||||
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { NotificationsToastsVisibleContext } from 'vs/workbench/browser/parts/notifications/notificationsCommands';
|
||||
import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey';
|
||||
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';
|
||||
|
||||
interface INotificationToast {
|
||||
item: INotificationViewItem;
|
||||
list: NotificationsList;
|
||||
container: HTMLElement;
|
||||
toast: HTMLElement;
|
||||
disposeables: IDisposable[];
|
||||
}
|
||||
|
||||
enum ToastVisibility {
|
||||
HIDDEN_OR_VISIBLE,
|
||||
HIDDEN,
|
||||
VISIBLE
|
||||
}
|
||||
|
||||
export class NotificationsToasts extends Themable {
|
||||
|
||||
private static MAX_WIDTH = 450;
|
||||
private static MAX_NOTIFICATIONS = 3;
|
||||
|
||||
private static PURGE_TIMEOUT: { [severity: number]: number } = (() => {
|
||||
const intervals = Object.create(null);
|
||||
intervals[Severity.Info] = 10000;
|
||||
intervals[Severity.Warning] = 12000;
|
||||
intervals[Severity.Error] = 15000;
|
||||
|
||||
return intervals;
|
||||
})();
|
||||
|
||||
private notificationsToastsContainer: HTMLElement;
|
||||
private workbenchDimensions: Dimension;
|
||||
private isNotificationsCenterVisible: boolean;
|
||||
private mapNotificationToToast: Map<INotificationViewItem, INotificationToast>;
|
||||
private notificationsToastsVisibleContextKey: IContextKey<boolean>;
|
||||
|
||||
constructor(
|
||||
private container: HTMLElement,
|
||||
private model: INotificationsModel,
|
||||
@IInstantiationService private instantiationService: IInstantiationService,
|
||||
@IPartService private partService: IPartService,
|
||||
@IThemeService themeService: IThemeService,
|
||||
@IWorkbenchEditorService private editorService: IWorkbenchEditorService,
|
||||
@IContextKeyService contextKeyService: IContextKeyService,
|
||||
@ILifecycleService private lifecycleService: ILifecycleService
|
||||
) {
|
||||
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.registerListeners();
|
||||
}
|
||||
|
||||
private registerListeners(): void {
|
||||
this.toUnbind.push(this.model.onDidNotificationChange(e => this.onDidNotificationChange(e)));
|
||||
}
|
||||
|
||||
private onDidNotificationChange(e: INotificationChangeEvent): void {
|
||||
switch (e.kind) {
|
||||
case NotificationChangeType.ADD:
|
||||
return this.addToast(e.item);
|
||||
case NotificationChangeType.REMOVE:
|
||||
return this.removeToast(e.item);
|
||||
}
|
||||
}
|
||||
|
||||
private addToast(item: INotificationViewItem): void {
|
||||
if (this.isNotificationsCenterVisible) {
|
||||
return; // do not show toasts while notification center is visibles
|
||||
}
|
||||
|
||||
// Lazily create toasts containers
|
||||
if (!this.notificationsToastsContainer) {
|
||||
this.notificationsToastsContainer = document.createElement('div');
|
||||
addClass(this.notificationsToastsContainer, 'notifications-toasts');
|
||||
|
||||
this.container.appendChild(this.notificationsToastsContainer);
|
||||
}
|
||||
|
||||
// Make Visible
|
||||
addClass(this.notificationsToastsContainer, 'visible');
|
||||
|
||||
const itemDisposeables: IDisposable[] = [];
|
||||
|
||||
// Container
|
||||
const notificationToastContainer = document.createElement('div');
|
||||
addClass(notificationToastContainer, 'notification-toast-container');
|
||||
|
||||
const firstToast = this.notificationsToastsContainer.firstChild;
|
||||
if (firstToast) {
|
||||
this.notificationsToastsContainer.insertBefore(notificationToastContainer, firstToast); // always first
|
||||
} else {
|
||||
this.notificationsToastsContainer.appendChild(notificationToastContainer);
|
||||
}
|
||||
|
||||
itemDisposeables.push(toDisposable(() => this.notificationsToastsContainer.removeChild(notificationToastContainer)));
|
||||
|
||||
// Toast
|
||||
const notificationToast = document.createElement('div');
|
||||
addClass(notificationToast, 'notification-toast');
|
||||
notificationToastContainer.appendChild(notificationToast);
|
||||
|
||||
// Create toast with item and show
|
||||
const notificationList = this.instantiationService.createInstance(NotificationsList, notificationToast, {
|
||||
ariaLabel: localize('notificationsToast', "Notification Toast"),
|
||||
verticalScrollMode: ScrollbarVisibility.Hidden
|
||||
});
|
||||
itemDisposeables.push(notificationList);
|
||||
this.mapNotificationToToast.set(item, { item, list: notificationList, container: notificationToastContainer, toast: notificationToast, disposeables: itemDisposeables });
|
||||
|
||||
// Make visible
|
||||
notificationList.show();
|
||||
|
||||
// Layout lists
|
||||
const maxDimensions = this.computeMaxDimensions();
|
||||
this.layoutLists(maxDimensions.width);
|
||||
|
||||
// Show notification
|
||||
notificationList.updateNotificationsList(0, 0, [item]);
|
||||
|
||||
// Layout container: only after we show the notification to ensure that
|
||||
// the height computation takes the content of it into account!
|
||||
this.layoutContainer(maxDimensions.height);
|
||||
|
||||
// Update when item height changes due to expansion
|
||||
itemDisposeables.push(item.onDidExpansionChange(() => {
|
||||
notificationList.updateNotificationsList(0, 1, [item]);
|
||||
}));
|
||||
|
||||
// Update when item height potentially changes due to label changes
|
||||
itemDisposeables.push(item.onDidLabelChange(e => {
|
||||
if (e.kind === NotificationViewItemLabelKind.ACTIONS || e.kind === NotificationViewItemLabelKind.MESSAGE) {
|
||||
notificationList.updateNotificationsList(0, 1, [item]);
|
||||
}
|
||||
}));
|
||||
|
||||
// Remove when item gets disposed
|
||||
once(item.onDidDispose)(() => {
|
||||
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(() => {
|
||||
if (!notificationList.hasFocus() && !item.expanded && !isMouseOverToast) {
|
||||
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)));
|
||||
}
|
||||
|
||||
// Theming
|
||||
this.updateStyles();
|
||||
|
||||
// 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 {
|
||||
addClass(notificationToast, 'notification-fade-in-done');
|
||||
}
|
||||
}
|
||||
|
||||
private removeToast(item: INotificationViewItem): void {
|
||||
const notificationToast = this.mapNotificationToToast.get(item);
|
||||
let focusEditor = false;
|
||||
if (notificationToast) {
|
||||
const toastHasDOMFocus = isAncestor(document.activeElement, notificationToast.container);
|
||||
if (toastHasDOMFocus) {
|
||||
focusEditor = !(this.focusNext() || this.focusPrevious()); // focus next if any, otherwise focus editor
|
||||
}
|
||||
|
||||
// Listeners
|
||||
dispose(notificationToast.disposeables);
|
||||
|
||||
// Remove from Map
|
||||
this.mapNotificationToToast.delete(item);
|
||||
}
|
||||
|
||||
// Layout if we still have toasts
|
||||
if (this.mapNotificationToToast.size > 0) {
|
||||
this.layout(this.workbenchDimensions);
|
||||
}
|
||||
|
||||
// Otherwise hide if no more toasts to show
|
||||
else {
|
||||
this.doHide();
|
||||
|
||||
// Move focus to editor as needed
|
||||
if (focusEditor) {
|
||||
this.focusEditor();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private focusEditor(): void {
|
||||
const editor = this.editorService.getActiveEditor();
|
||||
if (editor) {
|
||||
editor.focus();
|
||||
}
|
||||
}
|
||||
|
||||
private removeToasts(): void {
|
||||
this.mapNotificationToToast.forEach(toast => dispose(toast.disposeables));
|
||||
this.mapNotificationToToast.clear();
|
||||
|
||||
this.doHide();
|
||||
}
|
||||
|
||||
private doHide(): void {
|
||||
if (this.notificationsToastsContainer) {
|
||||
removeClass(this.notificationsToastsContainer, 'visible');
|
||||
}
|
||||
|
||||
// Context Key
|
||||
this.notificationsToastsVisibleContextKey.set(false);
|
||||
}
|
||||
|
||||
public hide(): void {
|
||||
const focusEditor = isAncestor(document.activeElement, this.notificationsToastsContainer);
|
||||
|
||||
this.removeToasts();
|
||||
|
||||
if (focusEditor) {
|
||||
this.focusEditor();
|
||||
}
|
||||
}
|
||||
|
||||
public focus(): boolean {
|
||||
const toasts = this.getToasts(ToastVisibility.VISIBLE);
|
||||
if (toasts.length > 0) {
|
||||
toasts[0].list.focusFirst();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public focusNext(): boolean {
|
||||
const toasts = this.getToasts(ToastVisibility.VISIBLE);
|
||||
for (let i = 0; i < toasts.length; i++) {
|
||||
const toast = toasts[i];
|
||||
if (toast.list.hasFocus()) {
|
||||
const nextToast = toasts[i + 1];
|
||||
if (nextToast) {
|
||||
nextToast.list.focusFirst();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public focusPrevious(): boolean {
|
||||
const toasts = this.getToasts(ToastVisibility.VISIBLE);
|
||||
for (let i = 0; i < toasts.length; i++) {
|
||||
const toast = toasts[i];
|
||||
if (toast.list.hasFocus()) {
|
||||
const previousToast = toasts[i - 1];
|
||||
if (previousToast) {
|
||||
previousToast.list.focusFirst();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public focusFirst(): boolean {
|
||||
const toast = this.getToasts(ToastVisibility.VISIBLE)[0];
|
||||
if (toast) {
|
||||
toast.list.focusFirst();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public focusLast(): boolean {
|
||||
const toasts = this.getToasts(ToastVisibility.VISIBLE);
|
||||
if (toasts.length > 0) {
|
||||
toasts[toasts.length - 1].list.focusFirst();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public update(isCenterVisible: boolean): void {
|
||||
if (this.isNotificationsCenterVisible !== isCenterVisible) {
|
||||
this.isNotificationsCenterVisible = isCenterVisible;
|
||||
|
||||
// Hide all toasts when the notificationcenter gets visible
|
||||
if (this.isNotificationsCenterVisible) {
|
||||
this.removeToasts();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected updateStyles(): void {
|
||||
this.mapNotificationToToast.forEach(t => {
|
||||
const widgetShadowColor = this.getColor(widgetShadow);
|
||||
t.toast.style.boxShadow = widgetShadowColor ? `0 0px 8px ${widgetShadowColor}` : null;
|
||||
|
||||
const borderColor = this.getColor(NOTIFICATIONS_TOAST_BORDER);
|
||||
t.toast.style.border = borderColor ? `1px solid ${borderColor}` : null;
|
||||
});
|
||||
}
|
||||
|
||||
private getToasts(state: ToastVisibility): INotificationToast[] {
|
||||
const notificationToasts: INotificationToast[] = [];
|
||||
|
||||
this.mapNotificationToToast.forEach(toast => {
|
||||
switch (state) {
|
||||
case ToastVisibility.HIDDEN_OR_VISIBLE:
|
||||
notificationToasts.push(toast);
|
||||
break;
|
||||
case ToastVisibility.HIDDEN:
|
||||
if (!this.isVisible(toast)) {
|
||||
notificationToasts.push(toast);
|
||||
}
|
||||
break;
|
||||
case ToastVisibility.VISIBLE:
|
||||
if (this.isVisible(toast)) {
|
||||
notificationToasts.push(toast);
|
||||
}
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
return notificationToasts.reverse(); // from newest to oldest
|
||||
}
|
||||
|
||||
public layout(dimension: Dimension): void {
|
||||
this.workbenchDimensions = dimension;
|
||||
|
||||
const maxDimensions = this.computeMaxDimensions();
|
||||
|
||||
// Hide toasts that exceed height
|
||||
if (maxDimensions.height) {
|
||||
this.layoutContainer(maxDimensions.height);
|
||||
}
|
||||
|
||||
// Layout all lists of toasts
|
||||
this.layoutLists(maxDimensions.width);
|
||||
}
|
||||
|
||||
private computeMaxDimensions(): Dimension {
|
||||
let maxWidth = NotificationsToasts.MAX_WIDTH;
|
||||
|
||||
let availableWidth = maxWidth;
|
||||
let availableHeight: number;
|
||||
|
||||
if (this.workbenchDimensions) {
|
||||
|
||||
// Make sure notifications are not exceding available width
|
||||
availableWidth = this.workbenchDimensions.width;
|
||||
availableWidth -= (2 * 8); // adjust for paddings left and right
|
||||
|
||||
// Make sure notifications are not exceeding available height
|
||||
availableHeight = this.workbenchDimensions.height;
|
||||
if (this.partService.isVisible(Parts.STATUSBAR_PART)) {
|
||||
availableHeight -= 22; // adjust for status bar
|
||||
}
|
||||
|
||||
if (this.partService.isVisible(Parts.TITLEBAR_PART)) {
|
||||
availableHeight -= 22; // adjust for title bar
|
||||
}
|
||||
|
||||
availableHeight -= (2 * 12); // adjust for paddings top and bottom
|
||||
}
|
||||
|
||||
return new Dimension(Math.min(maxWidth, availableWidth), availableHeight);
|
||||
}
|
||||
|
||||
private layoutLists(width: number): void {
|
||||
this.mapNotificationToToast.forEach(toast => toast.list.layout(width));
|
||||
}
|
||||
|
||||
private layoutContainer(heightToGive: number): void {
|
||||
let visibleToasts = 0;
|
||||
this.getToasts(ToastVisibility.HIDDEN_OR_VISIBLE).forEach(toast => {
|
||||
|
||||
// In order to measure the client height, the element cannot have display: none
|
||||
toast.container.style.opacity = '0';
|
||||
this.setVisibility(toast, true);
|
||||
|
||||
heightToGive -= toast.container.offsetHeight;
|
||||
|
||||
let makeVisible = false;
|
||||
if (visibleToasts === NotificationsToasts.MAX_NOTIFICATIONS) {
|
||||
makeVisible = false; // never show more than MAX_NOTIFICATIONS
|
||||
} else if (heightToGive >= 0) {
|
||||
makeVisible = true; // hide toast if available height is too little
|
||||
}
|
||||
|
||||
// Hide or show toast based on context
|
||||
this.setVisibility(toast, makeVisible);
|
||||
toast.container.style.opacity = null;
|
||||
|
||||
if (makeVisible) {
|
||||
visibleToasts++;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private setVisibility(toast: INotificationToast, visible: boolean): void {
|
||||
toast.container.style.display = visible ? 'block' : 'none';
|
||||
}
|
||||
|
||||
private isVisible(toast: INotificationToast): boolean {
|
||||
return toast.container.style.display === 'block';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,505 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 { IDelegate, IRenderer } from 'vs/base/browser/ui/list/list';
|
||||
import { clearNode, addClass, removeClass, toggleClass, addDisposableListener } 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 { localize } from 'vs/nls';
|
||||
import { ButtonGroup } from 'vs/base/browser/ui/button/button';
|
||||
import { attachButtonStyler, attachProgressBarStyler } from 'vs/platform/theme/common/styler';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar';
|
||||
import { IAction, IActionRunner } from 'vs/base/common/actions';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
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 { 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 IDelegate<INotificationViewItem> {
|
||||
|
||||
private static readonly ROW_HEIGHT = 42;
|
||||
private static readonly LINE_HEIGHT = 22;
|
||||
|
||||
private offsetHelper: HTMLElement;
|
||||
|
||||
constructor(container: HTMLElement) {
|
||||
this.offsetHelper = this.createOffsetHelper(container);
|
||||
}
|
||||
|
||||
private createOffsetHelper(container: HTMLElement): HTMLElement {
|
||||
const offsetHelper = document.createElement('div');
|
||||
addClass(offsetHelper, 'notification-offset-helper');
|
||||
|
||||
container.appendChild(offsetHelper);
|
||||
|
||||
return offsetHelper;
|
||||
}
|
||||
|
||||
public getHeight(notification: INotificationViewItem): number {
|
||||
|
||||
// First row: message and actions
|
||||
let expandedHeight = NotificationsListDelegate.ROW_HEIGHT;
|
||||
|
||||
if (!notification.expanded) {
|
||||
return expandedHeight; // return early if there are no more rows to show
|
||||
}
|
||||
|
||||
// Dynamic height: if message overflows
|
||||
const preferredMessageHeight = this.computePreferredHeight(notification);
|
||||
const messageOverflows = NotificationsListDelegate.LINE_HEIGHT < preferredMessageHeight;
|
||||
if (messageOverflows) {
|
||||
const overflow = preferredMessageHeight - NotificationsListDelegate.LINE_HEIGHT;
|
||||
expandedHeight += overflow;
|
||||
}
|
||||
|
||||
// Last row: source and buttons if we have any
|
||||
if (notification.source || notification.actions.primary.length > 0) {
|
||||
expandedHeight += NotificationsListDelegate.ROW_HEIGHT;
|
||||
}
|
||||
|
||||
// If the expanded height is same as collapsed, unset the expanded state
|
||||
// but skip events because there is no change that has visual impact
|
||||
if (expandedHeight === NotificationsListDelegate.ROW_HEIGHT) {
|
||||
notification.collapse(true /* skip events, no change in height */);
|
||||
}
|
||||
|
||||
return expandedHeight;
|
||||
}
|
||||
|
||||
private computePreferredHeight(notification: INotificationViewItem): number {
|
||||
|
||||
// Prepare offset helper depending on toolbar actions count
|
||||
let actions = 1; // close
|
||||
if (notification.canCollapse) {
|
||||
actions++; // expand/collapse
|
||||
}
|
||||
if (notification.actions.secondary.length > 0) {
|
||||
actions++; // secondary actions
|
||||
}
|
||||
this.offsetHelper.style.width = `calc(100% - ${10 /* padding */ + 24 /* severity icon */ + (actions * 24) /* 24px per action */}px)`;
|
||||
|
||||
// Render message into offset helper
|
||||
const renderedMessage = NotificationMessageRenderer.render(notification.message);
|
||||
this.offsetHelper.appendChild(renderedMessage);
|
||||
|
||||
// Compute height
|
||||
const preferredHeight = Math.max(this.offsetHelper.offsetHeight, this.offsetHelper.scrollHeight);
|
||||
|
||||
// Always clear offset helper after use
|
||||
clearNode(this.offsetHelper);
|
||||
|
||||
return preferredHeight;
|
||||
}
|
||||
|
||||
public getTemplateId(element: INotificationViewItem): string {
|
||||
if (element instanceof NotificationViewItem) {
|
||||
return NotificationRenderer.TEMPLATE_ID;
|
||||
}
|
||||
|
||||
return void 0;
|
||||
}
|
||||
}
|
||||
|
||||
export interface INotificationTemplateData {
|
||||
container: HTMLElement;
|
||||
toDispose: IDisposable[];
|
||||
|
||||
mainRow: HTMLElement;
|
||||
icon: HTMLElement;
|
||||
message: HTMLElement;
|
||||
toolbar: ActionBar;
|
||||
|
||||
detailsRow: HTMLElement;
|
||||
source: HTMLElement;
|
||||
buttonsContainer: HTMLElement;
|
||||
progress: ProgressBar;
|
||||
|
||||
renderer: NotificationTemplateRenderer;
|
||||
}
|
||||
|
||||
interface IMessageActionHandler {
|
||||
callback: (href: string) => void;
|
||||
disposeables: IDisposable[];
|
||||
}
|
||||
|
||||
class NotificationMessageRenderer {
|
||||
|
||||
public static render(message: INotificationMessage, actionHandler?: IMessageActionHandler): HTMLElement {
|
||||
const messageContainer = document.createElement('span');
|
||||
|
||||
// Message has no links
|
||||
if (message.links.length === 0) {
|
||||
messageContainer.textContent = message.value;
|
||||
}
|
||||
|
||||
// Message has links
|
||||
else {
|
||||
let index = 0;
|
||||
for (let i = 0; i < message.links.length; i++) {
|
||||
const link = message.links[i];
|
||||
|
||||
const textBefore = message.value.substring(index, link.offset);
|
||||
if (textBefore) {
|
||||
messageContainer.appendChild(document.createTextNode(textBefore));
|
||||
}
|
||||
|
||||
const anchor = document.createElement('a');
|
||||
anchor.textContent = link.name;
|
||||
anchor.title = link.href;
|
||||
anchor.href = link.href;
|
||||
|
||||
if (actionHandler) {
|
||||
actionHandler.disposeables.push(addDisposableListener(anchor, 'click', () => actionHandler.callback(link.href)));
|
||||
}
|
||||
|
||||
messageContainer.appendChild(anchor);
|
||||
|
||||
index = link.offset + link.length;
|
||||
}
|
||||
|
||||
// Add text after links if any
|
||||
const textAfter = message.value.substring(index);
|
||||
if (textAfter) {
|
||||
messageContainer.appendChild(document.createTextNode(textAfter));
|
||||
}
|
||||
}
|
||||
|
||||
return messageContainer;
|
||||
}
|
||||
}
|
||||
|
||||
export class NotificationRenderer implements IRenderer<INotificationViewItem, INotificationTemplateData> {
|
||||
|
||||
public static readonly TEMPLATE_ID = 'notification';
|
||||
|
||||
constructor(
|
||||
private actionRunner: IActionRunner,
|
||||
@IThemeService private themeService: IThemeService,
|
||||
@IContextMenuService private contextMenuService: IContextMenuService,
|
||||
@IInstantiationService private instantiationService: IInstantiationService
|
||||
) {
|
||||
}
|
||||
|
||||
public get templateId() {
|
||||
return NotificationRenderer.TEMPLATE_ID;
|
||||
}
|
||||
|
||||
public renderTemplate(container: HTMLElement): INotificationTemplateData {
|
||||
const data: INotificationTemplateData = Object.create(null);
|
||||
data.toDispose = [];
|
||||
|
||||
// Container
|
||||
data.container = document.createElement('div');
|
||||
addClass(data.container, 'notification-list-item');
|
||||
|
||||
// Main Row
|
||||
data.mainRow = document.createElement('div');
|
||||
addClass(data.mainRow, 'notification-list-item-main-row');
|
||||
|
||||
// Icon
|
||||
data.icon = document.createElement('div');
|
||||
addClass(data.icon, 'notification-list-item-icon');
|
||||
|
||||
// Message
|
||||
data.message = document.createElement('div');
|
||||
addClass(data.message, 'notification-list-item-message');
|
||||
|
||||
// Toolbar
|
||||
const toolbarContainer = document.createElement('div');
|
||||
addClass(toolbarContainer, 'notification-list-item-toolbar-container');
|
||||
data.toolbar = new ActionBar(
|
||||
toolbarContainer,
|
||||
{
|
||||
ariaLabel: localize('notificationActions', "Notification Actions"),
|
||||
actionItemProvider: action => {
|
||||
if (action instanceof ConfigureNotificationAction) {
|
||||
const item = new DropdownMenuActionItem(action, action.configurationActions, this.contextMenuService, null, this.actionRunner, null, action.class);
|
||||
data.toDispose.push(item);
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
actionRunner: this.actionRunner
|
||||
}
|
||||
);
|
||||
data.toDispose.push(data.toolbar);
|
||||
|
||||
// Details Row
|
||||
data.detailsRow = document.createElement('div');
|
||||
addClass(data.detailsRow, 'notification-list-item-details-row');
|
||||
|
||||
// Source
|
||||
data.source = document.createElement('div');
|
||||
addClass(data.source, 'notification-list-item-source');
|
||||
|
||||
// Buttons Container
|
||||
data.buttonsContainer = document.createElement('div');
|
||||
addClass(data.buttonsContainer, 'notification-list-item-buttons-container');
|
||||
|
||||
container.appendChild(data.container);
|
||||
|
||||
// the details row appears first in order for better keyboard access to notification buttons
|
||||
data.container.appendChild(data.detailsRow);
|
||||
data.detailsRow.appendChild(data.source);
|
||||
data.detailsRow.appendChild(data.buttonsContainer);
|
||||
|
||||
// main row
|
||||
data.container.appendChild(data.mainRow);
|
||||
data.mainRow.appendChild(data.icon);
|
||||
data.mainRow.appendChild(data.message);
|
||||
data.mainRow.appendChild(toolbarContainer);
|
||||
|
||||
// Progress: below the rows to span the entire width of the item
|
||||
data.progress = new ProgressBar(container);
|
||||
data.toDispose.push(attachProgressBarStyler(data.progress, this.themeService));
|
||||
data.toDispose.push(data.progress);
|
||||
|
||||
// Renderer
|
||||
data.renderer = this.instantiationService.createInstance(NotificationTemplateRenderer, data, this.actionRunner);
|
||||
data.toDispose.push(data.renderer);
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
public renderElement(notification: INotificationViewItem, index: number, data: INotificationTemplateData): void {
|
||||
data.renderer.setInput(notification);
|
||||
}
|
||||
|
||||
public disposeTemplate(templateData: INotificationTemplateData): void {
|
||||
templateData.toDispose = dispose(templateData.toDispose);
|
||||
}
|
||||
}
|
||||
|
||||
export class NotificationTemplateRenderer {
|
||||
|
||||
private static closeNotificationAction: ClearNotificationAction;
|
||||
private static expandNotificationAction: ExpandNotificationAction;
|
||||
private static collapseNotificationAction: CollapseNotificationAction;
|
||||
|
||||
private static readonly SEVERITIES: ('info' | 'warning' | 'error')[] = ['info', 'warning', 'error'];
|
||||
|
||||
private inputDisposeables: IDisposable[];
|
||||
|
||||
constructor(
|
||||
private template: INotificationTemplateData,
|
||||
private actionRunner: IActionRunner,
|
||||
@IOpenerService private openerService: IOpenerService,
|
||||
@IInstantiationService private instantiationService: IInstantiationService,
|
||||
@IThemeService private themeService: IThemeService,
|
||||
@IKeybindingService private keybindingService: IKeybindingService
|
||||
) {
|
||||
this.inputDisposeables = [];
|
||||
|
||||
if (!NotificationTemplateRenderer.closeNotificationAction) {
|
||||
NotificationTemplateRenderer.closeNotificationAction = instantiationService.createInstance(ClearNotificationAction, ClearNotificationAction.ID, ClearNotificationAction.LABEL);
|
||||
NotificationTemplateRenderer.expandNotificationAction = instantiationService.createInstance(ExpandNotificationAction, ExpandNotificationAction.ID, ExpandNotificationAction.LABEL);
|
||||
NotificationTemplateRenderer.collapseNotificationAction = instantiationService.createInstance(CollapseNotificationAction, CollapseNotificationAction.ID, CollapseNotificationAction.LABEL);
|
||||
}
|
||||
}
|
||||
|
||||
public setInput(notification: INotificationViewItem): void {
|
||||
this.inputDisposeables = dispose(this.inputDisposeables);
|
||||
|
||||
this.render(notification);
|
||||
}
|
||||
|
||||
private render(notification: INotificationViewItem): void {
|
||||
|
||||
// Container
|
||||
toggleClass(this.template.container, 'expanded', notification.expanded);
|
||||
|
||||
// Severity Icon
|
||||
this.renderSeverity(notification);
|
||||
|
||||
// Message
|
||||
const messageOverflows = this.renderMessage(notification);
|
||||
|
||||
// Secondary Actions
|
||||
this.renderSecondaryActions(notification, messageOverflows);
|
||||
|
||||
// Source
|
||||
this.renderSource(notification);
|
||||
|
||||
// Buttons
|
||||
this.renderButtons(notification);
|
||||
|
||||
// Progress
|
||||
this.renderProgress(notification);
|
||||
|
||||
// Label Change Events
|
||||
this.inputDisposeables.push(notification.onDidLabelChange(event => {
|
||||
switch (event.kind) {
|
||||
case NotificationViewItemLabelKind.SEVERITY:
|
||||
this.renderSeverity(notification);
|
||||
break;
|
||||
case NotificationViewItemLabelKind.PROGRESS:
|
||||
this.renderProgress(notification);
|
||||
break;
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
private renderSeverity(notification: INotificationViewItem): void {
|
||||
NotificationTemplateRenderer.SEVERITIES.forEach(severity => {
|
||||
const domAction = notification.severity === this.toSeverity(severity) ? addClass : removeClass;
|
||||
domAction(this.template.icon, `icon-${severity}`);
|
||||
});
|
||||
}
|
||||
|
||||
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),
|
||||
disposeables: this.inputDisposeables
|
||||
}));
|
||||
|
||||
const messageOverflows = notification.canCollapse && !notification.expanded && this.template.message.scrollWidth > this.template.message.clientWidth;
|
||||
if (messageOverflows) {
|
||||
this.template.message.title = this.template.message.textContent;
|
||||
} else {
|
||||
this.template.message.removeAttribute('title');
|
||||
}
|
||||
|
||||
const links = this.template.message.querySelectorAll('a');
|
||||
for (let i = 0; i < links.length; i++) {
|
||||
links.item(i).tabIndex = -1; // prevent keyboard navigation to links to allow for better keyboard support within a message
|
||||
}
|
||||
|
||||
return messageOverflows;
|
||||
}
|
||||
|
||||
private renderSecondaryActions(notification: INotificationViewItem, messageOverflows: boolean): void {
|
||||
const actions: IAction[] = [];
|
||||
|
||||
// Secondary Actions
|
||||
if (notification.actions.secondary.length > 0) {
|
||||
const configureNotificationAction = this.instantiationService.createInstance(ConfigureNotificationAction, ConfigureNotificationAction.ID, ConfigureNotificationAction.LABEL, notification.actions.secondary);
|
||||
actions.push(configureNotificationAction);
|
||||
this.inputDisposeables.push(configureNotificationAction);
|
||||
}
|
||||
|
||||
// Expand / Collapse
|
||||
let showExpandCollapseAction = false;
|
||||
if (notification.canCollapse) {
|
||||
if (notification.expanded) {
|
||||
showExpandCollapseAction = true; // allow to collapse an expanded message
|
||||
} else if (notification.source) {
|
||||
showExpandCollapseAction = true; // allow to expand to details row
|
||||
} else if (messageOverflows) {
|
||||
showExpandCollapseAction = true; // allow to expand if message overflows
|
||||
}
|
||||
}
|
||||
|
||||
if (showExpandCollapseAction) {
|
||||
actions.push(notification.expanded ? NotificationTemplateRenderer.collapseNotificationAction : NotificationTemplateRenderer.expandNotificationAction);
|
||||
}
|
||||
|
||||
// Close
|
||||
actions.push(NotificationTemplateRenderer.closeNotificationAction);
|
||||
|
||||
this.template.toolbar.clear();
|
||||
this.template.toolbar.context = notification;
|
||||
actions.forEach(action => this.template.toolbar.push(action, { icon: true, label: false, keybinding: this.getKeybindingLabel(action) }));
|
||||
}
|
||||
|
||||
private renderSource(notification): void {
|
||||
if (notification.expanded && notification.source) {
|
||||
this.template.source.innerText = localize('notificationSource', "Source: {0}", notification.source);
|
||||
this.template.source.title = notification.source;
|
||||
} else {
|
||||
this.template.source.innerText = '';
|
||||
this.template.source.removeAttribute('title');
|
||||
}
|
||||
}
|
||||
|
||||
private renderButtons(notification: INotificationViewItem): void {
|
||||
clearNode(this.template.buttonsContainer);
|
||||
|
||||
if (notification.expanded) {
|
||||
const buttonGroup = new ButtonGroup(this.template.buttonsContainer, notification.actions.primary.length, { title: true /* assign titles to buttons in case they overflow */ });
|
||||
buttonGroup.buttons.forEach((button, index) => {
|
||||
const action = notification.actions.primary[index];
|
||||
button.label = action.label;
|
||||
|
||||
this.inputDisposeables.push(button.onDidClick(() => {
|
||||
|
||||
// Run action
|
||||
this.actionRunner.run(action, notification);
|
||||
|
||||
// Hide notification
|
||||
notification.dispose();
|
||||
}));
|
||||
|
||||
this.inputDisposeables.push(attachButtonStyler(button, this.themeService));
|
||||
});
|
||||
|
||||
this.inputDisposeables.push(buttonGroup);
|
||||
}
|
||||
}
|
||||
|
||||
private renderProgress(notification: INotificationViewItem): void {
|
||||
|
||||
// Return early if the item has no progress
|
||||
if (!notification.hasProgress()) {
|
||||
this.template.progress.stop().getContainer().hide();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Infinite
|
||||
const state = notification.progress.state;
|
||||
if (state.infinite) {
|
||||
this.template.progress.infinite().getContainer().show();
|
||||
}
|
||||
|
||||
// Total / Worked
|
||||
else if (state.total || state.worked) {
|
||||
if (state.total) {
|
||||
this.template.progress.total(state.total);
|
||||
}
|
||||
|
||||
if (state.worked) {
|
||||
this.template.progress.worked(state.worked).getContainer().show();
|
||||
}
|
||||
}
|
||||
|
||||
// Done
|
||||
else {
|
||||
this.template.progress.done().getContainer().hide();
|
||||
}
|
||||
}
|
||||
|
||||
private toSeverity(severity: 'info' | 'warning' | 'error'): Severity {
|
||||
switch (severity) {
|
||||
case 'info':
|
||||
return Severity.Info;
|
||||
case 'warning':
|
||||
return Severity.Warning;
|
||||
case 'error':
|
||||
return Severity.Error;
|
||||
}
|
||||
}
|
||||
|
||||
private getKeybindingLabel(action: IAction): string {
|
||||
const keybinding = this.keybindingService.lookupKeybinding(action.id);
|
||||
|
||||
return keybinding ? keybinding.getLabel() : void 0;
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
this.inputDisposeables = dispose(this.inputDisposeables);
|
||||
}
|
||||
}
|
||||
@@ -50,7 +50,8 @@
|
||||
}
|
||||
|
||||
.monaco-workbench > .part.panel > .title > .panel-switcher-container > .monaco-action-bar {
|
||||
line-height: 32px;
|
||||
line-height: 30px;
|
||||
height: 35px;
|
||||
}
|
||||
|
||||
.monaco-workbench > .part.panel > .title > .panel-switcher-container > .monaco-action-bar .action-item:first-child {
|
||||
@@ -62,7 +63,8 @@
|
||||
padding-left: 16px;
|
||||
padding-right: 16px;
|
||||
font-size: 11px;
|
||||
padding-bottom: 4px; /* puts the bottom border down */
|
||||
padding-bottom: 3px; /* puts the bottom border down */
|
||||
padding-top: 2px;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
@@ -72,7 +74,6 @@
|
||||
|
||||
.monaco-workbench > .part.panel > .title > .panel-switcher-container > .monaco-action-bar .action-item.checked .action-label {
|
||||
border-bottom: 1px solid;
|
||||
height: 82%;
|
||||
}
|
||||
|
||||
.monaco-workbench > .part.panel > .title > .panel-switcher-container > .monaco-action-bar .badge .badge-content {
|
||||
|
||||
@@ -18,7 +18,7 @@ import { ActivityAction } from 'vs/workbench/browser/parts/compositebar/composit
|
||||
import { IActivity } from 'vs/workbench/common/activity';
|
||||
|
||||
export class ClosePanelAction extends Action {
|
||||
static ID = 'workbench.action.closePanel';
|
||||
static readonly ID = 'workbench.action.closePanel';
|
||||
static LABEL = nls.localize('closePanel', "Close Panel");
|
||||
|
||||
constructor(
|
||||
@@ -35,7 +35,7 @@ export class ClosePanelAction extends Action {
|
||||
}
|
||||
|
||||
export class TogglePanelAction extends Action {
|
||||
static ID = 'workbench.action.togglePanel';
|
||||
static readonly ID = 'workbench.action.togglePanel';
|
||||
static LABEL = nls.localize('togglePanel', "Toggle Panel");
|
||||
|
||||
constructor(
|
||||
|
||||
@@ -10,7 +10,6 @@ import Event from 'vs/base/common/event';
|
||||
import { Builder, Dimension } from 'vs/base/browser/builder';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { ActionsOrientation } from 'vs/base/browser/ui/actionbar/actionbar';
|
||||
import { Scope } from 'vs/workbench/browser/actions';
|
||||
import { IPanel } from 'vs/workbench/common/panel';
|
||||
import { CompositePart, ICompositeTitleLabel } from 'vs/workbench/browser/parts/compositePart';
|
||||
import { Panel, PanelRegistry, Extensions as PanelExtensions } from 'vs/workbench/browser/panel';
|
||||
@@ -18,7 +17,6 @@ import { IPanelService, IPanelIdentifier } from 'vs/workbench/services/panel/com
|
||||
import { IPartService, Parts, Position } from 'vs/workbench/services/part/common/partService';
|
||||
import { IStorageService } from 'vs/platform/storage/common/storage';
|
||||
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
|
||||
import { IMessageService } from 'vs/platform/message/common/message';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
@@ -31,6 +29,7 @@ import { ToggleCompositePinnedAction } from 'vs/workbench/browser/parts/composit
|
||||
import { StandardMouseEvent } from 'vs/base/browser/mouseEvent';
|
||||
import { dispose, IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { IBadge } from 'vs/workbench/services/activity/common/activity';
|
||||
import { INotificationService } from 'vs/platform/notification/common/notification';
|
||||
|
||||
export class PanelPart extends CompositePart<Panel> implements IPanelService {
|
||||
|
||||
@@ -47,17 +46,17 @@ export class PanelPart extends CompositePart<Panel> implements IPanelService {
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
@IMessageService messageService: IMessageService,
|
||||
@INotificationService notificationService: INotificationService,
|
||||
@IStorageService storageService: IStorageService,
|
||||
@ITelemetryService telemetryService: ITelemetryService,
|
||||
@IContextMenuService contextMenuService: IContextMenuService,
|
||||
@IPartService partService: IPartService,
|
||||
@IKeybindingService keybindingService: IKeybindingService,
|
||||
@IInstantiationService instantiationService: IInstantiationService,
|
||||
@IThemeService themeService: IThemeService,
|
||||
@IThemeService themeService: IThemeService
|
||||
) {
|
||||
super(
|
||||
messageService,
|
||||
notificationService,
|
||||
storageService,
|
||||
telemetryService,
|
||||
contextMenuService,
|
||||
@@ -70,7 +69,6 @@ export class PanelPart extends CompositePart<Panel> implements IPanelService {
|
||||
Registry.as<PanelRegistry>(PanelExtensions.Panels).getDefaultPanelId(),
|
||||
'panel',
|
||||
'panel',
|
||||
Scope.PANEL,
|
||||
null,
|
||||
id,
|
||||
{ hasTitle: true }
|
||||
@@ -108,10 +106,10 @@ export class PanelPart extends CompositePart<Panel> implements IPanelService {
|
||||
// Need to relayout composite bar since different panels have different action bar width
|
||||
this.layoutCompositeBar();
|
||||
}));
|
||||
this.toUnbind.push(this.compositeBar.onDidContextMenu(e => this.showContextMenu(e)));
|
||||
|
||||
// Deactivate panel action on close
|
||||
this.toUnbind.push(this.onDidPanelClose(panel => this.compositeBar.deactivateComposite(panel.getId())));
|
||||
this.toUnbind.push(this.compositeBar.onDidContextMenu(e => this.showContextMenu(e)));
|
||||
}
|
||||
|
||||
public get onDidPanelOpen(): Event<IPanel> {
|
||||
@@ -157,7 +155,7 @@ export class PanelPart extends CompositePart<Panel> implements IPanelService {
|
||||
}
|
||||
|
||||
private getPanel(panelId: string): IPanelIdentifier {
|
||||
return Registry.as<PanelRegistry>(PanelExtensions.Panels).getPanels().filter(p => p.id === panelId).pop();
|
||||
return this.getPanels().filter(p => p.id === panelId).pop();
|
||||
}
|
||||
|
||||
private showContextMenu(e: MouseEvent): void {
|
||||
@@ -173,9 +171,22 @@ export class PanelPart extends CompositePart<Panel> implements IPanelService {
|
||||
|
||||
public getPanels(): IPanelIdentifier[] {
|
||||
return Registry.as<PanelRegistry>(PanelExtensions.Panels).getPanels()
|
||||
.filter(p => p.enabled)
|
||||
.sort((v1, v2) => v1.order - v2.order);
|
||||
}
|
||||
|
||||
public 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) {
|
||||
descriptor.enabled = enabled;
|
||||
if (enabled) {
|
||||
this.compositeBar.addComposite(descriptor);
|
||||
} else {
|
||||
this.compositeBar.removeComposite(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected getActions(): IAction[] {
|
||||
return [
|
||||
this.instantiationService.createInstance(ToggleMaximizedPanelAction, ToggleMaximizedPanelAction.ID, ToggleMaximizedPanelAction.LABEL),
|
||||
@@ -252,14 +263,6 @@ export class PanelPart extends CompositePart<Panel> implements IPanelService {
|
||||
|
||||
return this.toolbarWidth.get(activePanel.getId());
|
||||
}
|
||||
|
||||
public shutdown(): void {
|
||||
// Persist Hidden State
|
||||
this.compositeBar.store();
|
||||
|
||||
// Pass to super
|
||||
super.shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => {
|
||||
@@ -331,7 +334,7 @@ registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => {
|
||||
outline-style: solid;
|
||||
border-bottom: none;
|
||||
padding-bottom: 0;
|
||||
outline-offset: 3px;
|
||||
outline-offset: 1px;
|
||||
}
|
||||
|
||||
.monaco-workbench > .part.panel > .title > .panel-switcher-container > .monaco-action-bar .action-item:not(.checked) .action-label:hover {
|
||||
@@ -339,4 +342,4 @@ registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => {
|
||||
}
|
||||
`);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -11,14 +11,13 @@ import nls = require('vs/nls');
|
||||
import * as browser from 'vs/base/browser/browser';
|
||||
import { Dimension, withElementById } from 'vs/base/browser/builder';
|
||||
import strings = require('vs/base/common/strings');
|
||||
import filters = require('vs/base/common/filters');
|
||||
import DOM = require('vs/base/browser/dom');
|
||||
import URI from 'vs/base/common/uri';
|
||||
import * as resources from 'vs/base/common/resources';
|
||||
import { defaultGenerator } from 'vs/base/common/idGenerator';
|
||||
import types = require('vs/base/common/types');
|
||||
import { Action, IAction } from 'vs/base/common/actions';
|
||||
import { IIconLabelOptions } from 'vs/base/browser/ui/iconLabel/iconLabel';
|
||||
import { IIconLabelValueOptions } from 'vs/base/browser/ui/iconLabel/iconLabel';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { Mode, IEntryRunContext, IAutoFocus, IQuickNavigateConfiguration, IModel } from 'vs/base/parts/quickopen/common/quickOpen';
|
||||
import { QuickOpenEntry, QuickOpenModel, QuickOpenEntryGroup, compareEntries, QuickOpenItemAccessorClass } from 'vs/base/parts/quickopen/browser/quickOpenModel';
|
||||
@@ -41,7 +40,6 @@ import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/edi
|
||||
import { IPickOpenEntry, IFilePickOpenEntry, IInputOptions, IQuickOpenService, IPickOptions, IShowOptions, IPickOpenItem } from 'vs/platform/quickOpen/common/quickOpen';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IMessageService, Severity } from 'vs/platform/message/common/message';
|
||||
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
|
||||
import { IContextKeyService, RawContextKey, IContextKey } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { IHistoryService } from 'vs/workbench/services/history/common/history';
|
||||
@@ -54,7 +52,12 @@ import { BaseActionItem } from 'vs/base/browser/ui/actionbar/actionbar';
|
||||
import { FileKind, IFileService } from 'vs/platform/files/common/files';
|
||||
import { scoreItem, ScorerCache, compareItemsByScore, prepareQuery } from 'vs/base/parts/quickopen/common/quickOpenScorer';
|
||||
import { getBaseLabel } from 'vs/base/common/labels';
|
||||
import { WorkbenchTree, IListService } from 'vs/platform/list/browser/listService';
|
||||
import { WorkbenchTree } from 'vs/platform/list/browser/listService';
|
||||
import { matchesFuzzyOcticonAware, parseOcticons, IParsedOcticons } from 'vs/base/common/octicon';
|
||||
import { IMatch } from 'vs/base/common/filters';
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
import Severity from 'vs/base/common/severity';
|
||||
import { INotificationService } from 'vs/platform/notification/common/notification';
|
||||
|
||||
const HELP_PREFIX = '?';
|
||||
|
||||
@@ -102,12 +105,11 @@ export class QuickOpenController extends Component implements IQuickOpenService
|
||||
|
||||
constructor(
|
||||
@IWorkbenchEditorService private editorService: IWorkbenchEditorService,
|
||||
@IMessageService private messageService: IMessageService,
|
||||
@INotificationService private notificationService: INotificationService,
|
||||
@IContextKeyService private contextKeyService: IContextKeyService,
|
||||
@IConfigurationService private configurationService: IConfigurationService,
|
||||
@IInstantiationService private instantiationService: IInstantiationService,
|
||||
@IPartService private partService: IPartService,
|
||||
@IListService private listService: IListService,
|
||||
@IEnvironmentService private environmentService: IEnvironmentService,
|
||||
@IThemeService themeService: IThemeService
|
||||
) {
|
||||
@@ -181,7 +183,7 @@ export class QuickOpenController extends Component implements IQuickOpenService
|
||||
|
||||
// open quick pick with just one choice. we will recurse whenever
|
||||
// the validation/success message changes
|
||||
this.doPick(TPromise.as([{ label: currentPick }]), {
|
||||
this.doPick(TPromise.as([{ label: currentPick, tooltip: currentPick /* make sure message/validation can be read through the hover */ }]), {
|
||||
ignoreFocusLost: options.ignoreFocusLost,
|
||||
autoFocus: { autoFocusFirstEntry: true },
|
||||
password: options.password,
|
||||
@@ -311,7 +313,7 @@ export class QuickOpenController extends Component implements IQuickOpenService
|
||||
}, {
|
||||
inputPlaceHolder: options.placeHolder || '',
|
||||
keyboardSupport: false,
|
||||
treeCreator: (container, config, opts) => new WorkbenchTree(container, config, opts, this.contextKeyService, this.listService, this.themeService)
|
||||
treeCreator: (container, config, opts) => this.instantiationService.createInstance(WorkbenchTree, container, config, opts)
|
||||
}
|
||||
);
|
||||
this.toUnbind.push(attachQuickOpenStyler(this.pickOpenWidget, this.themeService, { background: SIDE_BAR_BACKGROUND, foreground: SIDE_BAR_FOREGROUND }));
|
||||
@@ -432,12 +434,10 @@ export class QuickOpenController extends Component implements IQuickOpenService
|
||||
});
|
||||
}
|
||||
|
||||
// Filter by value
|
||||
// Filter by value (since we support octicons, use octicon aware fuzzy matching)
|
||||
else {
|
||||
entries.forEach(entry => {
|
||||
const labelHighlights = filters.matchesFuzzy(value, entry.getLabel());
|
||||
const descriptionHighlights = options.matchOnDescription && filters.matchesFuzzy(value, entry.getDescription());
|
||||
const detailHighlights = options.matchOnDetail && entry.getDetail() && filters.matchesFuzzy(value, entry.getDetail());
|
||||
const { labelHighlights, descriptionHighlights, detailHighlights } = entry.matchesFuzzy(value, options);
|
||||
|
||||
if (entry.shouldAlwaysShow() || labelHighlights || descriptionHighlights || detailHighlights) {
|
||||
entry.setHighlights(labelHighlights, descriptionHighlights, detailHighlights);
|
||||
@@ -569,7 +569,7 @@ export class QuickOpenController extends Component implements IQuickOpenService
|
||||
}, {
|
||||
inputPlaceHolder: this.hasHandler(HELP_PREFIX) ? nls.localize('quickOpenInput', "Type '?' to get help on the actions you can take from here") : '',
|
||||
keyboardSupport: false,
|
||||
treeCreator: (container, config, opts) => new WorkbenchTree(container, config, opts, this.contextKeyService, this.listService, this.themeService)
|
||||
treeCreator: (container, config, opts) => this.instantiationService.createInstance(WorkbenchTree, container, config, opts)
|
||||
}
|
||||
);
|
||||
this.toUnbind.push(attachQuickOpenStyler(this.quickOpenWidget, this.themeService, { background: SIDE_BAR_BACKGROUND, foreground: SIDE_BAR_FOREGROUND }));
|
||||
@@ -799,7 +799,7 @@ export class QuickOpenController extends Component implements IQuickOpenService
|
||||
}, (error: any) => {
|
||||
resultPromiseDone = true;
|
||||
errors.onUnexpectedError(error);
|
||||
this.messageService.show(Severity.Error, types.isString(error) ? new Error(error) : error);
|
||||
this.notificationService.error(types.isString(error) ? new Error(error) : error);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -857,7 +857,7 @@ export class QuickOpenController extends Component implements IQuickOpenService
|
||||
const result = handlerResults[i];
|
||||
const resource = result.getResource();
|
||||
|
||||
if (!result.isFile() || !resource || !mapEntryToResource[resource.toString()]) {
|
||||
if (!result.mergeWithEditorHistory() || !resource || !mapEntryToResource[resource.toString()]) {
|
||||
additionalHandlerResults.push(result);
|
||||
}
|
||||
}
|
||||
@@ -1025,6 +1025,8 @@ class PickOpenEntry extends PlaceholderQuickOpenEntry implements IPickOpenItem {
|
||||
private _shouldRunWithContext: IEntryRunContext;
|
||||
private description: string;
|
||||
private detail: string;
|
||||
private tooltip: string;
|
||||
private descriptionTooltip: string;
|
||||
private hasSeparator: boolean;
|
||||
private separatorLabel: string;
|
||||
private alwaysShow: boolean;
|
||||
@@ -1033,6 +1035,9 @@ class PickOpenEntry extends PlaceholderQuickOpenEntry implements IPickOpenItem {
|
||||
private _action: IAction;
|
||||
private removed: boolean;
|
||||
private payload: any;
|
||||
private labelOcticons: IParsedOcticons;
|
||||
private descriptionOcticons: IParsedOcticons;
|
||||
private detailOcticons: IParsedOcticons;
|
||||
|
||||
constructor(
|
||||
item: IPickOpenEntry,
|
||||
@@ -1046,6 +1051,9 @@ class PickOpenEntry extends PlaceholderQuickOpenEntry implements IPickOpenItem {
|
||||
|
||||
this.description = item.description;
|
||||
this.detail = item.detail;
|
||||
this.tooltip = item.tooltip;
|
||||
this.descriptionOcticons = item.description ? parseOcticons(item.description) : void 0;
|
||||
this.descriptionTooltip = this.descriptionOcticons ? this.descriptionOcticons.text : void 0;
|
||||
this.hasSeparator = item.separator && item.separator.border;
|
||||
this.separatorLabel = item.separator && item.separator.label;
|
||||
this.alwaysShow = item.alwaysShow;
|
||||
@@ -1057,6 +1065,23 @@ class PickOpenEntry extends PlaceholderQuickOpenEntry implements IPickOpenItem {
|
||||
this.fileKind = fileItem.fileKind;
|
||||
}
|
||||
|
||||
public matchesFuzzy(query: string, options: IInternalPickOptions): { labelHighlights: IMatch[], descriptionHighlights: IMatch[], detailHighlights: IMatch[] } {
|
||||
if (!this.labelOcticons) {
|
||||
this.labelOcticons = parseOcticons(this.getLabel()); // parse on demand
|
||||
}
|
||||
|
||||
const detail = this.getDetail();
|
||||
if (detail && options.matchOnDetail && !this.detailOcticons) {
|
||||
this.detailOcticons = parseOcticons(detail); // parse on demand
|
||||
}
|
||||
|
||||
return {
|
||||
labelHighlights: matchesFuzzyOcticonAware(query, this.labelOcticons),
|
||||
descriptionHighlights: options.matchOnDescription && this.descriptionOcticons ? matchesFuzzyOcticonAware(query, this.descriptionOcticons) : void 0,
|
||||
detailHighlights: options.matchOnDetail && this.detailOcticons ? matchesFuzzyOcticonAware(query, this.detailOcticons) : void 0
|
||||
};
|
||||
}
|
||||
|
||||
public getPayload(): any {
|
||||
return this.payload;
|
||||
}
|
||||
@@ -1080,7 +1105,7 @@ class PickOpenEntry extends PlaceholderQuickOpenEntry implements IPickOpenItem {
|
||||
return this._index;
|
||||
}
|
||||
|
||||
public getLabelOptions(): IIconLabelOptions {
|
||||
public getLabelOptions(): IIconLabelValueOptions {
|
||||
return {
|
||||
extraClasses: this.resource ? getIconClasses(this.modelService, this.modeService, this.resource, this.fileKind) : []
|
||||
};
|
||||
@@ -1098,6 +1123,14 @@ class PickOpenEntry extends PlaceholderQuickOpenEntry implements IPickOpenItem {
|
||||
return this.detail;
|
||||
}
|
||||
|
||||
public getTooltip(): string {
|
||||
return this.tooltip;
|
||||
}
|
||||
|
||||
public getDescriptionTooltip(): string {
|
||||
return this.descriptionTooltip;
|
||||
}
|
||||
|
||||
public showBorder(): boolean {
|
||||
return this.hasSeparator;
|
||||
}
|
||||
@@ -1277,7 +1310,7 @@ export class EditorHistoryEntry extends EditorQuickOpenEntry {
|
||||
return this.label;
|
||||
}
|
||||
|
||||
public getLabelOptions(): IIconLabelOptions {
|
||||
public getLabelOptions(): IIconLabelValueOptions {
|
||||
return {
|
||||
extraClasses: getIconClasses(this.modelService, this.modeService, this.resource)
|
||||
};
|
||||
@@ -1301,7 +1334,7 @@ export class EditorHistoryEntry extends EditorQuickOpenEntry {
|
||||
|
||||
public run(mode: Mode, context: IEntryRunContext): boolean {
|
||||
if (mode === Mode.OPEN) {
|
||||
const sideBySide = !context.quickNavigateConfiguration && context.keymods.ctrlCmd;
|
||||
const sideBySide = !context.quickNavigateConfiguration && (context.keymods.alt || context.keymods.ctrlCmd);
|
||||
const pinned = !this.configurationService.getValue<IWorkbenchEditorConfiguration>().workbench.editor.enablePreviewFromQuickOpen || context.keymods.alt;
|
||||
|
||||
if (this.input instanceof EditorInput) {
|
||||
@@ -1322,7 +1355,7 @@ function resourceForEditorHistory(input: EditorInput, fileService: IFileService)
|
||||
|
||||
// For the editor history we only prefer resources that are either untitled or
|
||||
// can be handled by the file service which indicates they are editable resources.
|
||||
if (resource && (fileService.canHandleResource(resource) || resource.scheme === 'untitled')) {
|
||||
if (resource && (fileService.canHandleResource(resource) || resource.scheme === Schemas.untitled)) {
|
||||
return resource;
|
||||
}
|
||||
|
||||
|
||||
@@ -7,19 +7,16 @@ import 'vs/css!./media/sidebarpart';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import nls = require('vs/nls');
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { Action, IAction } from 'vs/base/common/actions';
|
||||
import { Action } from 'vs/base/common/actions';
|
||||
import { CompositePart } from 'vs/workbench/browser/parts/compositePart';
|
||||
import { Viewlet, ViewletRegistry, Extensions as ViewletExtensions } from 'vs/workbench/browser/viewlet';
|
||||
import { IWorkbenchActionRegistry, Extensions as ActionExtensions } from 'vs/workbench/common/actions';
|
||||
import { SyncActionDescriptor } from 'vs/platform/actions/common/actions';
|
||||
import { Separator } from 'vs/base/browser/ui/actionbar/actionbar';
|
||||
import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet';
|
||||
import { IPartService, Parts, Position as SideBarPosition } from 'vs/workbench/services/part/common/partService';
|
||||
import { IViewlet } from 'vs/workbench/common/viewlet';
|
||||
import { Scope } from 'vs/workbench/browser/actions';
|
||||
import { IStorageService } from 'vs/platform/storage/common/storage';
|
||||
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
|
||||
import { IMessageService } from 'vs/platform/message/common/message';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
||||
import { KeyMod, KeyCode } from 'vs/base/common/keyCodes';
|
||||
@@ -28,8 +25,8 @@ import Event 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 { ToggleSidebarVisibilityAction } from 'vs/workbench/browser/actions/toggleSidebarVisibility';
|
||||
import { Dimension } from 'vs/base/browser/builder';
|
||||
import { INotificationService } from 'vs/platform/notification/common/notification';
|
||||
|
||||
export class SidebarPart extends CompositePart<Viewlet> {
|
||||
|
||||
@@ -41,7 +38,7 @@ export class SidebarPart extends CompositePart<Viewlet> {
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
@IMessageService messageService: IMessageService,
|
||||
@INotificationService notificationService: INotificationService,
|
||||
@IStorageService storageService: IStorageService,
|
||||
@ITelemetryService telemetryService: ITelemetryService,
|
||||
@IContextMenuService contextMenuService: IContextMenuService,
|
||||
@@ -51,7 +48,7 @@ export class SidebarPart extends CompositePart<Viewlet> {
|
||||
@IThemeService themeService: IThemeService
|
||||
) {
|
||||
super(
|
||||
messageService,
|
||||
notificationService,
|
||||
storageService,
|
||||
telemetryService,
|
||||
contextMenuService,
|
||||
@@ -64,7 +61,6 @@ export class SidebarPart extends CompositePart<Viewlet> {
|
||||
Registry.as<ViewletRegistry>(ViewletExtensions.Viewlets).getDefaultViewletId(),
|
||||
'sideBar',
|
||||
'viewlet',
|
||||
Scope.VIEWLET,
|
||||
SIDE_BAR_TITLE_FOREGROUND,
|
||||
id,
|
||||
{ hasTitle: true, borderWidth: () => (this.getColor(SIDE_BAR_BORDER) || this.getColor(contrastBorder)) ? 1 : 0 }
|
||||
@@ -136,25 +132,6 @@ export class SidebarPart extends CompositePart<Viewlet> {
|
||||
|
||||
return super.layout(dimension);
|
||||
}
|
||||
|
||||
protected getTitleAreaContextMenuActions(): IAction[] {
|
||||
const contextMenuActions = super.getTitleAreaContextMenuActions();
|
||||
if (contextMenuActions.length) {
|
||||
contextMenuActions.push(new Separator());
|
||||
}
|
||||
contextMenuActions.push(this.createHideSideBarAction());
|
||||
return contextMenuActions;
|
||||
}
|
||||
|
||||
private createHideSideBarAction(): IAction {
|
||||
return <IAction>{
|
||||
id: ToggleSidebarVisibilityAction.ID,
|
||||
label: nls.localize('compositePart.hideSideBarLabel', "Hide Side Bar"),
|
||||
enabled: true,
|
||||
run: () => this.partService.setSideBarHidden(true)
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class FocusSideBarAction extends Action {
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
height: 22px;
|
||||
font-size: 12px;
|
||||
padding: 0 10px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.monaco-workbench > .part.statusbar > .statusbar-item {
|
||||
@@ -19,6 +18,21 @@
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.monaco-workbench > .part.statusbar > .statusbar-item.has-beak {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.monaco-workbench > .part.statusbar > .statusbar-item.has-beak:before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 11px;
|
||||
top: -5px;
|
||||
border-bottom-width: 5px;
|
||||
border-bottom-style: solid;
|
||||
border-left: 5px solid transparent;
|
||||
border-right: 5px solid transparent;
|
||||
}
|
||||
|
||||
.monaco-workbench > .part.statusbar > .statusbar-item.left > :first-child {
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
'use strict';
|
||||
|
||||
import 'vs/css!./media/statusbarpart';
|
||||
import dom = require('vs/base/browser/dom');
|
||||
import nls = require('vs/nls');
|
||||
import { toErrorMessage } from 'vs/base/common/errorMessage';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
@@ -20,7 +19,6 @@ import { Part } from 'vs/workbench/browser/part';
|
||||
import { StatusbarAlignment, IStatusbarRegistry, Extensions, IStatusbarItem } from 'vs/workbench/browser/parts/statusbar/statusbar';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { IMessageService, Severity } from 'vs/platform/message/common/message';
|
||||
import { IStatusbarService, IStatusbarEntry } from 'vs/platform/statusbar/common/statusbar';
|
||||
import { getCodeEditor } from 'vs/editor/browser/services/codeEditorService';
|
||||
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
|
||||
@@ -31,6 +29,8 @@ 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 { INotificationService } from 'vs/platform/notification/common/notification';
|
||||
|
||||
export class StatusbarPart extends Part implements IStatusbarService {
|
||||
|
||||
@@ -42,6 +42,8 @@ export class StatusbarPart extends Part implements IStatusbarService {
|
||||
private statusItemsContainer: Builder;
|
||||
private statusMsgDispose: IDisposable;
|
||||
|
||||
private styleElement: HTMLStyleElement;
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
@IInstantiationService private instantiationService: IInstantiationService,
|
||||
@@ -60,7 +62,7 @@ export class StatusbarPart extends Part implements IStatusbarService {
|
||||
public addEntry(entry: IStatusbarEntry, alignment: StatusbarAlignment, priority: number = 0): IDisposable {
|
||||
|
||||
// Render entry in status bar
|
||||
const el = this.doCreateStatusItem(alignment, priority);
|
||||
const el = this.doCreateStatusItem(alignment, priority, entry.showBeak ? 'has-beak' : void 0);
|
||||
const item = this.instantiationService.createInstance(StatusBarEntryItem, entry);
|
||||
const toDispose = item.render(el);
|
||||
|
||||
@@ -140,23 +142,36 @@ export class StatusbarPart extends Part implements IStatusbarService {
|
||||
|
||||
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('background-color', this.getColor(this.contextService.getWorkbenchState() !== WorkbenchState.EMPTY ? STATUS_BAR_BACKGROUND : STATUS_BAR_NO_FOLDER_BACKGROUND));
|
||||
|
||||
// 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);
|
||||
|
||||
// Notification Beak
|
||||
if (!this.styleElement) {
|
||||
this.styleElement = createStyleSheet(container.getHTMLElement());
|
||||
}
|
||||
|
||||
this.styleElement.innerHTML = `.monaco-workbench > .part.statusbar > .statusbar-item.has-beak:before { border-bottom-color: ${backgroundColor}; }`;
|
||||
}
|
||||
|
||||
private doCreateStatusItem(alignment: StatusbarAlignment, priority: number = 0): HTMLElement {
|
||||
private doCreateStatusItem(alignment: StatusbarAlignment, priority: number = 0, extraClass?: string): HTMLElement {
|
||||
const el = document.createElement('div');
|
||||
dom.addClass(el, 'statusbar-item');
|
||||
addClass(el, 'statusbar-item');
|
||||
if (extraClass) {
|
||||
addClass(el, extraClass);
|
||||
}
|
||||
|
||||
if (alignment === StatusbarAlignment.RIGHT) {
|
||||
dom.addClass(el, 'right');
|
||||
addClass(el, 'right');
|
||||
} else {
|
||||
dom.addClass(el, 'left');
|
||||
addClass(el, 'left');
|
||||
}
|
||||
|
||||
$(el).setProperty(StatusbarPart.PRIORITY_PROP, priority);
|
||||
@@ -206,13 +221,12 @@ export class StatusbarPart extends Part implements IStatusbarService {
|
||||
|
||||
let manageExtensionAction: ManageExtensionAction;
|
||||
class StatusBarEntryItem implements IStatusbarItem {
|
||||
private entry: IStatusbarEntry;
|
||||
|
||||
constructor(
|
||||
entry: IStatusbarEntry,
|
||||
private entry: IStatusbarEntry,
|
||||
@ICommandService private commandService: ICommandService,
|
||||
@IInstantiationService private instantiationService: IInstantiationService,
|
||||
@IMessageService private messageService: IMessageService,
|
||||
@INotificationService private notificationService: INotificationService,
|
||||
@ITelemetryService private telemetryService: ITelemetryService,
|
||||
@IContextMenuService private contextMenuService: IContextMenuService,
|
||||
@IWorkbenchEditorService private editorService: IWorkbenchEditorService,
|
||||
@@ -227,7 +241,7 @@ class StatusBarEntryItem implements IStatusbarItem {
|
||||
|
||||
public render(el: HTMLElement): IDisposable {
|
||||
let toDispose: IDisposable[] = [];
|
||||
dom.addClass(el, 'statusbar-entry');
|
||||
addClass(el, 'statusbar-entry');
|
||||
|
||||
// Text Container
|
||||
let textContainer: HTMLElement;
|
||||
@@ -264,7 +278,7 @@ class StatusBarEntryItem implements IStatusbarItem {
|
||||
// Context Menu
|
||||
if (this.entry.extensionId) {
|
||||
$(textContainer).on('contextmenu', e => {
|
||||
dom.EventHelper.stop(e, true);
|
||||
EventHelper.stop(e, true);
|
||||
|
||||
this.contextMenuService.showContextMenu({
|
||||
getAnchor: () => el,
|
||||
@@ -300,7 +314,7 @@ class StatusBarEntryItem implements IStatusbarItem {
|
||||
}
|
||||
*/
|
||||
this.telemetryService.publicLog('workbenchActionExecuted', { id, from: 'status bar' });
|
||||
this.commandService.executeCommand(id, ...args).done(undefined, err => this.messageService.show(Severity.Error, toErrorMessage(err)));
|
||||
this.commandService.executeCommand(id, ...args).done(undefined, err => this.notificationService.error(toErrorMessage(err)));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ import { Builder, $, Dimension } from 'vs/base/browser/builder';
|
||||
import * as DOM from 'vs/base/browser/dom';
|
||||
import * as paths from 'vs/base/common/paths';
|
||||
import { Part } from 'vs/workbench/browser/part';
|
||||
import { ITitleService } from 'vs/workbench/services/title/common/titleService';
|
||||
import { ITitleService, ITitleProperties } from 'vs/workbench/services/title/common/titleService';
|
||||
import { getZoomFactor } from 'vs/base/browser/browser';
|
||||
import { IWindowService, IWindowsService } from 'vs/platform/windows/common/windows';
|
||||
import * as errors from 'vs/base/common/errors';
|
||||
@@ -19,7 +19,6 @@ import { IContextMenuService } from 'vs/platform/contextview/browser/contextView
|
||||
import { StandardMouseEvent } from 'vs/base/browser/mouseEvent';
|
||||
import { IAction, Action } from 'vs/base/common/actions';
|
||||
import { IConfigurationService, IConfigurationChangeEvent } from 'vs/platform/configuration/common/configuration';
|
||||
import { IIntegrityService } from 'vs/platform/integrity/common/integrity';
|
||||
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { IEditorGroupService } from 'vs/workbench/services/group/common/groupService';
|
||||
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
|
||||
@@ -31,15 +30,17 @@ import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/
|
||||
import { Verbosity } from 'vs/platform/editor/common/editor';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import { TITLE_BAR_ACTIVE_BACKGROUND, TITLE_BAR_ACTIVE_FOREGROUND, TITLE_BAR_INACTIVE_FOREGROUND, TITLE_BAR_INACTIVE_BACKGROUND, TITLE_BAR_BORDER } from 'vs/workbench/common/theme';
|
||||
import { isMacintosh } from 'vs/base/common/platform';
|
||||
import { isMacintosh, isWindows } from 'vs/base/common/platform';
|
||||
import URI from 'vs/base/common/uri';
|
||||
import { ILifecycleService, LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle';
|
||||
import { trim } from 'vs/base/common/strings';
|
||||
|
||||
export class TitlebarPart extends Part implements ITitleService {
|
||||
|
||||
public _serviceBrand: any;
|
||||
|
||||
private static readonly NLS_UNSUPPORTED = nls.localize('patchedWindowTitle', "[Unsupported]");
|
||||
private static readonly NLS_USER_IS_ADMIN = isWindows ? nls.localize('userIsAdmin', "[Administrator]") : nls.localize('userIsSudo', "[Superuser]");
|
||||
private static readonly NLS_EXTENSION_HOST = nls.localize('devExtensionWindowTitlePrefix', "[Extension Development Host]");
|
||||
private static readonly TITLE_DIRTY = '\u25cf ';
|
||||
private static readonly TITLE_SEPARATOR = isMacintosh ? ' — ' : ' - '; // macOS uses special - separator
|
||||
@@ -52,7 +53,7 @@ export class TitlebarPart extends Part implements ITitleService {
|
||||
|
||||
private isInactive: boolean;
|
||||
|
||||
private isPure: boolean;
|
||||
private properties: ITitleProperties;
|
||||
private activeEditorListeners: IDisposable[];
|
||||
|
||||
constructor(
|
||||
@@ -63,7 +64,6 @@ export class TitlebarPart extends Part implements ITitleService {
|
||||
@IWindowsService private windowsService: IWindowsService,
|
||||
@IWorkbenchEditorService private editorService: IWorkbenchEditorService,
|
||||
@IEditorGroupService private editorGroupService: IEditorGroupService,
|
||||
@IIntegrityService private integrityService: IIntegrityService,
|
||||
@IEnvironmentService private environmentService: IEnvironmentService,
|
||||
@IWorkspaceContextService private contextService: IWorkspaceContextService,
|
||||
@IThemeService themeService: IThemeService,
|
||||
@@ -71,7 +71,7 @@ export class TitlebarPart extends Part implements ITitleService {
|
||||
) {
|
||||
super(id, { hasTitle: false }, themeService);
|
||||
|
||||
this.isPure = true;
|
||||
this.properties = { isPure: true, isAdmin: false };
|
||||
this.activeEditorListeners = [];
|
||||
|
||||
this.init();
|
||||
@@ -83,14 +83,6 @@ export class TitlebarPart extends Part implements ITitleService {
|
||||
|
||||
// Initial window title when loading is done
|
||||
this.lifecycleService.when(LifecyclePhase.Running).then(() => this.setTitle(this.getWindowTitle()));
|
||||
|
||||
// Integrity for window title
|
||||
this.integrityService.isPure().then(r => {
|
||||
if (!r.isPure) {
|
||||
this.isPure = false;
|
||||
this.setTitle(this.getWindowTitle());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private registerListeners(): void {
|
||||
@@ -145,11 +137,15 @@ export class TitlebarPart extends Part implements ITitleService {
|
||||
|
||||
private getWindowTitle(): string {
|
||||
let title = this.doGetWindowTitle();
|
||||
if (!title) {
|
||||
if (!trim(title)) {
|
||||
title = this.environmentService.appNameLong;
|
||||
}
|
||||
|
||||
if (!this.isPure) {
|
||||
if (this.properties.isAdmin) {
|
||||
title = `${title} ${TitlebarPart.NLS_USER_IS_ADMIN}`;
|
||||
}
|
||||
|
||||
if (!this.properties.isPure) {
|
||||
title = `${title} ${TitlebarPart.NLS_UNSUPPORTED}`;
|
||||
}
|
||||
|
||||
@@ -161,6 +157,18 @@ export class TitlebarPart extends Part implements ITitleService {
|
||||
return title;
|
||||
}
|
||||
|
||||
public updateProperties(properties: ITitleProperties): void {
|
||||
const isAdmin = typeof properties.isAdmin === 'boolean' ? properties.isAdmin : this.properties.isAdmin;
|
||||
const isPure = typeof properties.isPure === 'boolean' ? properties.isPure : this.properties.isPure;
|
||||
|
||||
if (isAdmin !== this.properties.isAdmin || isPure !== this.properties.isPure) {
|
||||
this.properties.isAdmin = isAdmin;
|
||||
this.properties.isPure = isPure;
|
||||
|
||||
this.setTitle(this.getWindowTitle());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Possible template values:
|
||||
*
|
||||
|
||||
614
src/vs/workbench/browser/parts/views/customView.ts
Normal file
@@ -0,0 +1,614 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import 'vs/css!./media/views';
|
||||
import * as errors from 'vs/base/common/errors';
|
||||
import { IDisposable, Disposable, dispose } from 'vs/base/common/lifecycle';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import * as DOM from 'vs/base/browser/dom';
|
||||
import { $ } from 'vs/base/browser/builder';
|
||||
import { LIGHT, FileThemeIcon, FolderThemeIcon } from 'vs/platform/theme/common/themeService';
|
||||
import { ITree, IDataSource, IRenderer, ContextMenuEvent } from 'vs/base/parts/tree/browser/tree';
|
||||
import { TreeItemCollapsibleState, ITreeItem, ITreeViewer, ICustomViewsService, ITreeViewDataProvider, ViewsRegistry, IViewDescriptor, TreeViewItemHandleArg, ICustomViewDescriptor, IViewsViewlet } from 'vs/workbench/common/views';
|
||||
import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService';
|
||||
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
|
||||
import { IProgressService2, ProgressLocation } from 'vs/platform/progress/common/progress';
|
||||
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 { basename } from 'vs/base/common/paths';
|
||||
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
|
||||
import { WorkbenchTreeController } from 'vs/platform/list/browser/listService';
|
||||
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { IAction, ActionRunner } from 'vs/base/common/actions';
|
||||
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { IMenuService, MenuId, MenuItemAction } from 'vs/platform/actions/common/actions';
|
||||
import { fillInActions, ContextAwareMenuItemActionItem } from 'vs/platform/actions/browser/menuItemActionItem';
|
||||
import { FileKind } from 'vs/platform/files/common/files';
|
||||
import { ICommandService } from 'vs/platform/commands/common/commands';
|
||||
import { FileIconThemableWorkbenchTree } from 'vs/workbench/browser/parts/views/viewsViewlet';
|
||||
import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet';
|
||||
import { isUndefinedOrNull } from 'vs/base/common/types';
|
||||
|
||||
export class CustomViewsService extends Disposable implements ICustomViewsService {
|
||||
|
||||
_serviceBrand: any;
|
||||
|
||||
private viewers: Map<string, CustomTreeViewer> = new Map<string, CustomTreeViewer>();
|
||||
|
||||
constructor(
|
||||
@IInstantiationService private instantiationService: IInstantiationService,
|
||||
@IViewletService private viewletService: IViewletService
|
||||
) {
|
||||
super();
|
||||
this.createViewers(ViewsRegistry.getAllViews());
|
||||
this._register(ViewsRegistry.onViewsRegistered(viewDescriptors => this.createViewers(viewDescriptors)));
|
||||
this._register(ViewsRegistry.onViewsDeregistered(viewDescriptors => this.removeViewers(viewDescriptors)));
|
||||
}
|
||||
|
||||
getTreeViewer(id: string): ITreeViewer {
|
||||
return this.viewers.get(id);
|
||||
}
|
||||
|
||||
openView(id: string, focus: boolean): TPromise<void> {
|
||||
const viewDescriptor = ViewsRegistry.getView(id);
|
||||
if (viewDescriptor) {
|
||||
return this.viewletService.openViewlet(viewDescriptor.id)
|
||||
.then((viewlet: IViewsViewlet) => {
|
||||
if (viewlet && viewlet.openView) {
|
||||
viewlet.openView(id, focus);
|
||||
}
|
||||
});
|
||||
}
|
||||
return TPromise.as(null);
|
||||
}
|
||||
|
||||
private createViewers(viewDescriptors: IViewDescriptor[]): void {
|
||||
for (const viewDescriptor of viewDescriptors) {
|
||||
if ((<ICustomViewDescriptor>viewDescriptor).treeView) {
|
||||
this.viewers.set(viewDescriptor.id, this.instantiationService.createInstance(CustomTreeViewer, viewDescriptor.id));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private removeViewers(viewDescriptors: IViewDescriptor[]): void {
|
||||
for (const { id } of viewDescriptors) {
|
||||
const viewer = this.getTreeViewer(id);
|
||||
if (viewer) {
|
||||
viewer.dispose();
|
||||
this.viewers.delete(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class Root implements ITreeItem {
|
||||
label = 'root';
|
||||
handle = '0';
|
||||
parentHandle = null;
|
||||
collapsibleState = TreeItemCollapsibleState.Expanded;
|
||||
children = void 0;
|
||||
}
|
||||
|
||||
class CustomTreeViewer extends Disposable implements ITreeViewer {
|
||||
|
||||
private isVisible: boolean = false;
|
||||
private activated: boolean = false;
|
||||
private _hasIconForParentNode = false;
|
||||
private _hasIconForLeafNode = false;
|
||||
|
||||
private treeContainer: HTMLElement;
|
||||
private tree: FileIconThemableWorkbenchTree;
|
||||
private root: ITreeItem;
|
||||
private elementsToRefresh: ITreeItem[] = [];
|
||||
|
||||
private _dataProvider: ITreeViewDataProvider;
|
||||
private dataProviderDisposables: IDisposable[] = [];
|
||||
|
||||
constructor(
|
||||
private id: string,
|
||||
@IExtensionService private extensionService: IExtensionService,
|
||||
@IWorkbenchThemeService private themeService: IWorkbenchThemeService,
|
||||
@IInstantiationService private instantiationService: IInstantiationService,
|
||||
@ICommandService private commandService: ICommandService
|
||||
) {
|
||||
super();
|
||||
this.root = new Root();
|
||||
this._register(this.themeService.onDidFileIconThemeChange(() => this.doRefresh([this.root]) /** soft refresh **/));
|
||||
this._register(this.themeService.onThemeChange(() => this.doRefresh([this.root]) /** soft refresh **/));
|
||||
}
|
||||
|
||||
get dataProvider(): ITreeViewDataProvider {
|
||||
return this._dataProvider;
|
||||
}
|
||||
|
||||
set dataProvider(dataProvider: ITreeViewDataProvider) {
|
||||
dispose(this.dataProviderDisposables);
|
||||
if (dataProvider) {
|
||||
this._dataProvider = new class implements ITreeViewDataProvider {
|
||||
onDidChange = dataProvider.onDidChange;
|
||||
onDispose = dataProvider.onDispose;
|
||||
getChildren(node?: ITreeItem): TPromise<ITreeItem[]> {
|
||||
if (node.children) {
|
||||
return TPromise.as(node.children);
|
||||
}
|
||||
const promise = node instanceof Root ? dataProvider.getChildren() : dataProvider.getChildren(node);
|
||||
return promise.then(children => {
|
||||
node.children = children;
|
||||
return children;
|
||||
});
|
||||
}
|
||||
};
|
||||
this._register(dataProvider.onDidChange(elements => this.refresh(elements), this, this.dataProviderDisposables));
|
||||
this._register(dataProvider.onDispose(() => this.dataProvider = null, this, this.dataProviderDisposables));
|
||||
} else {
|
||||
this._dataProvider = null;
|
||||
}
|
||||
this.refresh();
|
||||
}
|
||||
|
||||
get hasIconForParentNode(): boolean {
|
||||
return this._hasIconForParentNode;
|
||||
}
|
||||
|
||||
get hasIconForLeafNode(): boolean {
|
||||
return this._hasIconForLeafNode;
|
||||
}
|
||||
|
||||
setVisibility(isVisible: boolean): void {
|
||||
if (this.isVisible === isVisible) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.isVisible = isVisible;
|
||||
if (this.isVisible) {
|
||||
this.activate();
|
||||
}
|
||||
|
||||
if (this.tree) {
|
||||
if (this.isVisible) {
|
||||
$(this.tree.getHTMLElement()).show();
|
||||
} else {
|
||||
$(this.tree.getHTMLElement()).hide(); // make sure the tree goes out of the tabindex world by hiding it
|
||||
}
|
||||
|
||||
if (this.isVisible) {
|
||||
this.tree.onVisible();
|
||||
} else {
|
||||
this.tree.onHidden();
|
||||
}
|
||||
|
||||
if (this.isVisible && this.elementsToRefresh.length) {
|
||||
this.doRefresh(this.elementsToRefresh);
|
||||
this.elementsToRefresh = [];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
focus(): void {
|
||||
if (this.tree) {
|
||||
// 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);
|
||||
}
|
||||
|
||||
// Pass Focus to Viewer
|
||||
this.tree.DOMFocus();
|
||||
}
|
||||
}
|
||||
|
||||
show(container: HTMLElement): void {
|
||||
if (!this.tree) {
|
||||
this.createTree();
|
||||
}
|
||||
DOM.append(container, this.treeContainer);
|
||||
}
|
||||
|
||||
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(Menus, this.id);
|
||||
const dataSource = this.instantiationService.createInstance(TreeDataSource, this);
|
||||
const renderer = this.instantiationService.createInstance(TreeRenderer, this.id, menus, actionItemProvider);
|
||||
const controller = this.instantiationService.createInstance(TreeController, this.id, menus);
|
||||
this.tree = this.instantiationService.createInstance(FileIconThemableWorkbenchTree, this.treeContainer, { dataSource, renderer, controller }, {});
|
||||
this.tree.contextKeyService.createKey<boolean>(this.id, true);
|
||||
this._register(this.tree);
|
||||
this._register(this.tree.onDidChangeSelection(e => this.onSelection(e)));
|
||||
this.tree.setInput(this.root);
|
||||
}
|
||||
|
||||
layout(size: number) {
|
||||
if (this.tree) {
|
||||
this.treeContainer.style.height = size + 'px';
|
||||
this.tree.layout(size);
|
||||
}
|
||||
}
|
||||
|
||||
getOptimalWidth(): number {
|
||||
if (this.tree) {
|
||||
const parentNode = this.tree.getHTMLElement();
|
||||
const childNodes = [].slice.call(parentNode.querySelectorAll('.outline-item-label > a'));
|
||||
return DOM.getLargestChildWidth(parentNode, childNodes);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
refresh(elements?: ITreeItem[]): TPromise<void> {
|
||||
if (this.tree) {
|
||||
elements = elements || [this.root];
|
||||
for (const element of elements) {
|
||||
element.children = null; // reset children
|
||||
}
|
||||
if (this.isVisible) {
|
||||
return this.doRefresh(elements);
|
||||
} else {
|
||||
this.elementsToRefresh.push(...elements);
|
||||
}
|
||||
}
|
||||
return TPromise.as(null);
|
||||
}
|
||||
|
||||
reveal(item: ITreeItem, parentChain: ITreeItem[], options?: { select?: boolean }): TPromise<void> {
|
||||
if (this.tree && this.isVisible) {
|
||||
options = options ? options : { select: true };
|
||||
const select = isUndefinedOrNull(options.select) ? true : options.select;
|
||||
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]);
|
||||
}
|
||||
});
|
||||
}
|
||||
return TPromise.as(null);
|
||||
}
|
||||
|
||||
private activate() {
|
||||
if (!this.activated) {
|
||||
this.extensionService.activateByEvent(`onView:${this.id}`);
|
||||
this.activated = true;
|
||||
}
|
||||
}
|
||||
|
||||
private doRefresh(elements: ITreeItem[]): TPromise<void> {
|
||||
if (this.tree) {
|
||||
return TPromise.join(elements.map(e => this.tree.refresh(e))).then(() => null);
|
||||
}
|
||||
return TPromise.as(null);
|
||||
}
|
||||
|
||||
private onSelection({ payload }: any): void {
|
||||
const selection: ITreeItem = this.tree.getSelection()[0];
|
||||
if (selection) {
|
||||
if (selection.command) {
|
||||
const originalEvent: KeyboardEvent | MouseEvent = payload && payload.originalEvent;
|
||||
const isMouseEvent = payload && payload.origin === 'mouse';
|
||||
const isDoubleClick = isMouseEvent && originalEvent && originalEvent.detail === 2;
|
||||
|
||||
if (!isMouseEvent || this.tree.openOnSingleClick || isDoubleClick) {
|
||||
this.commandService.executeCommand(selection.command.id, ...(selection.command.arguments || []));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class TreeDataSource implements IDataSource {
|
||||
|
||||
constructor(
|
||||
private treeView: ITreeViewer,
|
||||
@IProgressService2 private progressService: IProgressService2
|
||||
) {
|
||||
}
|
||||
|
||||
public getId(tree: ITree, node: ITreeItem): string {
|
||||
return node.handle;
|
||||
}
|
||||
|
||||
public hasChildren(tree: ITree, node: ITreeItem): boolean {
|
||||
return this.treeView.dataProvider && node.collapsibleState !== TreeItemCollapsibleState.None;
|
||||
}
|
||||
|
||||
public getChildren(tree: ITree, node: ITreeItem): TPromise<any[]> {
|
||||
if (this.treeView.dataProvider) {
|
||||
return this.progressService.withProgress({ location: ProgressLocation.Explorer }, () => this.treeView.dataProvider.getChildren(node));
|
||||
}
|
||||
return TPromise.as([]);
|
||||
}
|
||||
|
||||
public shouldAutoexpand(tree: ITree, node: ITreeItem): boolean {
|
||||
return node.collapsibleState === TreeItemCollapsibleState.Expanded;
|
||||
}
|
||||
|
||||
public getParent(tree: ITree, node: any): TPromise<any> {
|
||||
return TPromise.as(null);
|
||||
}
|
||||
}
|
||||
|
||||
interface ITreeExplorerTemplateData {
|
||||
label: HTMLElement;
|
||||
resourceLabel: ResourceLabel;
|
||||
icon: HTMLElement;
|
||||
actionBar: ActionBar;
|
||||
aligner: Aligner;
|
||||
}
|
||||
|
||||
class TreeRenderer implements IRenderer {
|
||||
|
||||
private static readonly ITEM_HEIGHT = 22;
|
||||
private static readonly TREE_TEMPLATE_ID = 'treeExplorer';
|
||||
|
||||
constructor(
|
||||
private treeViewId: string,
|
||||
private menus: Menus,
|
||||
private actionItemProvider: IActionItemProvider,
|
||||
@IInstantiationService private instantiationService: IInstantiationService,
|
||||
@IWorkbenchThemeService private themeService: IWorkbenchThemeService
|
||||
) {
|
||||
}
|
||||
|
||||
public getHeight(tree: ITree, element: any): number {
|
||||
return TreeRenderer.ITEM_HEIGHT;
|
||||
}
|
||||
|
||||
public getTemplateId(tree: ITree, element: any): string {
|
||||
return TreeRenderer.TREE_TEMPLATE_ID;
|
||||
}
|
||||
|
||||
public renderTemplate(tree: ITree, templateId: string, container: HTMLElement): ITreeExplorerTemplateData {
|
||||
DOM.addClass(container, 'custom-view-tree-node-item');
|
||||
|
||||
const icon = DOM.append(container, DOM.$('.custom-view-tree-node-item-icon'));
|
||||
const label = DOM.append(container, DOM.$('.custom-view-tree-node-item-label'));
|
||||
const resourceLabel = this.instantiationService.createInstance(ResourceLabel, container, {});
|
||||
const actionsContainer = DOM.append(container, DOM.$('.actions'));
|
||||
const actionBar = new ActionBar(actionsContainer, {
|
||||
actionItemProvider: this.actionItemProvider,
|
||||
actionRunner: new MultipleSelectionActionRunner(() => tree.getSelection())
|
||||
});
|
||||
|
||||
return { label, resourceLabel, icon, actionBar, aligner: new Aligner(container, tree, this.themeService) };
|
||||
}
|
||||
|
||||
public 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 icon = this.themeService.getTheme().type === LIGHT ? node.icon : node.iconDark;
|
||||
|
||||
// reset
|
||||
templateData.resourceLabel.clear();
|
||||
templateData.actionBar.clear();
|
||||
templateData.label.textContent = '';
|
||||
DOM.removeClass(templateData.label, 'custom-view-tree-node-item-label');
|
||||
DOM.removeClass(templateData.resourceLabel.element, 'custom-view-tree-node-item-resourceLabel');
|
||||
|
||||
if ((resource || node.themeIcon) && !icon) {
|
||||
const title = node.tooltip ? node.tooltip : resource ? void 0 : label;
|
||||
templateData.resourceLabel.setLabel({ name: label, resource: resource ? resource : URI.parse('_icon_resource') }, { fileKind: this.getFileKind(node), title });
|
||||
DOM.addClass(templateData.resourceLabel.element, 'custom-view-tree-node-item-resourceLabel');
|
||||
} else {
|
||||
templateData.label.textContent = label;
|
||||
DOM.addClass(templateData.label, 'custom-view-tree-node-item-label');
|
||||
templateData.label.title = typeof node.tooltip === 'string' ? node.tooltip : label;
|
||||
}
|
||||
|
||||
templateData.icon.style.backgroundImage = icon ? `url('${icon}')` : '';
|
||||
DOM.toggleClass(templateData.icon, 'custom-view-tree-node-item-icon', !!icon);
|
||||
templateData.actionBar.context = (<TreeViewItemHandleArg>{ $treeViewId: this.treeViewId, $treeItemHandle: node.handle });
|
||||
templateData.actionBar.push(this.menus.getResourceActions(node), { icon: true, label: false });
|
||||
|
||||
templateData.aligner.align(node);
|
||||
}
|
||||
|
||||
private getFileKind(node: ITreeItem): FileKind {
|
||||
if (node.themeIcon) {
|
||||
switch (node.themeIcon.id) {
|
||||
case FileThemeIcon.id:
|
||||
return FileKind.FILE;
|
||||
case FolderThemeIcon.id:
|
||||
return FileKind.FOLDER;
|
||||
}
|
||||
}
|
||||
return node.collapsibleState === TreeItemCollapsibleState.Collapsed || node.collapsibleState === TreeItemCollapsibleState.Expanded ? FileKind.FOLDER : FileKind.FILE;
|
||||
}
|
||||
|
||||
public disposeTemplate(tree: ITree, templateId: string, templateData: ITreeExplorerTemplateData): void {
|
||||
templateData.resourceLabel.dispose();
|
||||
templateData.actionBar.dispose();
|
||||
templateData.aligner.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
class Aligner extends Disposable {
|
||||
|
||||
private node: ITreeItem;
|
||||
|
||||
constructor(
|
||||
private container: HTMLElement,
|
||||
private tree: ITree,
|
||||
private themeService: IWorkbenchThemeService
|
||||
) {
|
||||
super();
|
||||
this._register(this.themeService.onDidFileIconThemeChange(() => this.alignByTheme()));
|
||||
}
|
||||
|
||||
align(treeItem: ITreeItem): void {
|
||||
this.node = treeItem;
|
||||
this.alignByTheme();
|
||||
}
|
||||
|
||||
private alignByTheme(): void {
|
||||
if (this.node) {
|
||||
DOM.toggleClass(this.container, 'align-with-twisty', this.hasToAlignWithTwisty());
|
||||
}
|
||||
}
|
||||
|
||||
private hasToAlignWithTwisty(): boolean {
|
||||
if (this.hasParentHasIcon()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const fileIconTheme = this.themeService.getFileIconTheme();
|
||||
if (!(fileIconTheme.hasFileIcons && !fileIconTheme.hasFolderIcons)) {
|
||||
return false;
|
||||
}
|
||||
if (this.node.collapsibleState !== TreeItemCollapsibleState.None) {
|
||||
return false;
|
||||
}
|
||||
const icon = this.themeService.getTheme().type === LIGHT ? this.node.icon : this.node.iconDark;
|
||||
const hasIcon = !!icon || !!this.node.resourceUri;
|
||||
if (!hasIcon) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const siblingsWithChildren = this.getSiblings().filter(s => s.collapsibleState !== TreeItemCollapsibleState.None);
|
||||
for (const s of siblingsWithChildren) {
|
||||
const icon = this.themeService.getTheme().type === LIGHT ? s.icon : s.iconDark;
|
||||
if (icon) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private getSiblings(): ITreeItem[] {
|
||||
const parent: ITreeItem = this.tree.getNavigator(this.node).parent() || this.tree.getInput();
|
||||
return parent.children;
|
||||
}
|
||||
|
||||
private hasParentHasIcon(): boolean {
|
||||
const parent = this.tree.getNavigator(this.node).parent() || this.tree.getInput();
|
||||
const icon = this.themeService.getTheme().type === LIGHT ? parent.icon : parent.iconDark;
|
||||
if (icon) {
|
||||
return true;
|
||||
}
|
||||
if (parent.resourceUri) {
|
||||
const fileIconTheme = this.themeService.getFileIconTheme();
|
||||
if (fileIconTheme.hasFileIcons && fileIconTheme.hasFolderIcons) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
class TreeController extends WorkbenchTreeController {
|
||||
|
||||
constructor(
|
||||
private treeViewId: string,
|
||||
private menus: Menus,
|
||||
@IContextMenuService private contextMenuService: IContextMenuService,
|
||||
@IKeybindingService private readonly _keybindingService: IKeybindingService,
|
||||
@IConfigurationService configurationService: IConfigurationService
|
||||
) {
|
||||
super({}, configurationService);
|
||||
}
|
||||
|
||||
public onContextMenu(tree: ITree, node: ITreeItem, event: ContextMenuEvent): boolean {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
tree.setFocus(node);
|
||||
const actions = this.menus.getResourceContextActions(node);
|
||||
if (!actions.length) {
|
||||
return true;
|
||||
}
|
||||
const anchor = { x: event.posx, y: event.posy };
|
||||
this.contextMenuService.showContextMenu({
|
||||
getAnchor: () => anchor,
|
||||
|
||||
getActions: () => {
|
||||
return TPromise.as(actions);
|
||||
},
|
||||
|
||||
getActionItem: (action) => {
|
||||
const keybinding = this._keybindingService.lookupKeybinding(action.id);
|
||||
if (keybinding) {
|
||||
return new ActionItem(action, action, { label: true, keybinding: keybinding.getLabel() });
|
||||
}
|
||||
return null;
|
||||
},
|
||||
|
||||
onHide: (wasCancelled?: boolean) => {
|
||||
if (wasCancelled) {
|
||||
tree.DOMFocus();
|
||||
}
|
||||
},
|
||||
|
||||
getActionsContext: () => (<TreeViewItemHandleArg>{ $treeViewId: this.treeViewId, $treeItemHandle: node.handle }),
|
||||
|
||||
actionRunner: new MultipleSelectionActionRunner(() => tree.getSelection())
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
class MultipleSelectionActionRunner extends ActionRunner {
|
||||
|
||||
constructor(private getSelectedResources: () => any[]) {
|
||||
super();
|
||||
}
|
||||
|
||||
runAction(action: IAction, context: any): TPromise<any> {
|
||||
if (action instanceof MenuItemAction) {
|
||||
const selection = this.getSelectedResources();
|
||||
const filteredSelection = selection.filter(s => s !== context);
|
||||
|
||||
if (selection.length === filteredSelection.length || selection.length === 1) {
|
||||
return action.run(context);
|
||||
}
|
||||
|
||||
return action.run(context, ...filteredSelection);
|
||||
}
|
||||
|
||||
return super.runAction(action, context);
|
||||
}
|
||||
}
|
||||
|
||||
class Menus extends Disposable implements IDisposable {
|
||||
|
||||
constructor(
|
||||
private id: string,
|
||||
@IContextKeyService private contextKeyService: IContextKeyService,
|
||||
@IMenuService private menuService: IMenuService,
|
||||
@IContextMenuService private contextMenuService: IContextMenuService
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
getResourceActions(element: ITreeItem): IAction[] {
|
||||
return this.getActions(MenuId.ViewItemContext, { key: 'viewItem', value: element.contextValue }).primary;
|
||||
}
|
||||
|
||||
getResourceContextActions(element: ITreeItem): IAction[] {
|
||||
return this.getActions(MenuId.ViewItemContext, { key: 'viewItem', value: element.contextValue }).secondary;
|
||||
}
|
||||
|
||||
private getActions(menuId: MenuId, context: { key: string, value: string }): { primary: IAction[]; secondary: IAction[]; } {
|
||||
const contextKeyService = this.contextKeyService.createScoped();
|
||||
contextKeyService.createKey('view', this.id);
|
||||
contextKeyService.createKey(context.key, context.value);
|
||||
|
||||
const menu = this.menuService.createMenu(menuId, contextKeyService);
|
||||
const primary: IAction[] = [];
|
||||
const secondary: IAction[] = [];
|
||||
const result = { primary, secondary };
|
||||
fillInActions(menu, { shouldForwardArgs: true }, result, this.contextMenuService, g => /^inline/.test(g));
|
||||
|
||||
menu.dispose();
|
||||
contextKeyService.dispose();
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
147
src/vs/workbench/browser/parts/views/customViewPanel.ts
Normal file
@@ -0,0 +1,147 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import 'vs/css!./media/views';
|
||||
import Event, { Emitter } from 'vs/base/common/event';
|
||||
import { IDisposable, dispose, empty as EmptyDisposable, toDisposable } from 'vs/base/common/lifecycle';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { IAction, IActionItem } 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 { fillInActions, ContextAwareMenuItemActionItem } from 'vs/platform/actions/browser/menuItemActionItem';
|
||||
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { ICustomViewsService, ITreeViewer } from 'vs/workbench/common/views';
|
||||
import { IViewletViewOptions, IViewOptions, ViewsViewletPanel } from 'vs/workbench/browser/parts/views/viewsViewlet';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { INotificationService } from 'vs/platform/notification/common/notification';
|
||||
|
||||
export class CustomTreeViewPanel extends ViewsViewletPanel {
|
||||
|
||||
private menus: Menus;
|
||||
private treeViewer: ITreeViewer;
|
||||
|
||||
constructor(
|
||||
options: IViewletViewOptions,
|
||||
@INotificationService private notificationService: INotificationService,
|
||||
@IKeybindingService keybindingService: IKeybindingService,
|
||||
@IContextMenuService contextMenuService: IContextMenuService,
|
||||
@IInstantiationService private instantiationService: IInstantiationService,
|
||||
@IConfigurationService configurationService: IConfigurationService,
|
||||
@ICustomViewsService customViewsService: ICustomViewsService,
|
||||
) {
|
||||
super({ ...(options as IViewOptions), ariaHeaderLabel: options.name }, keybindingService, contextMenuService, configurationService);
|
||||
this.treeViewer = customViewsService.getTreeViewer(this.id);
|
||||
this.disposables.push(toDisposable(() => this.treeViewer.setVisibility(false)));
|
||||
this.menus = this.instantiationService.createInstance(Menus, this.id);
|
||||
this.menus.onDidChangeTitle(() => this.updateActions(), this, this.disposables);
|
||||
this.updateTreeVisibility();
|
||||
}
|
||||
|
||||
setVisible(visible: boolean): TPromise<void> {
|
||||
return super.setVisible(visible).then(() => this.updateTreeVisibility());
|
||||
}
|
||||
|
||||
focus(): void {
|
||||
super.focus();
|
||||
this.treeViewer.focus();
|
||||
}
|
||||
|
||||
renderBody(container: HTMLElement): void {
|
||||
this.treeViewer.show(container);
|
||||
}
|
||||
|
||||
setExpanded(expanded: boolean): void {
|
||||
this.treeViewer.setVisibility(this.isVisible() && expanded);
|
||||
super.setExpanded(expanded);
|
||||
}
|
||||
|
||||
layoutBody(size: number): void {
|
||||
this.treeViewer.layout(size);
|
||||
}
|
||||
|
||||
getActions(): IAction[] {
|
||||
return [...this.menus.getTitleActions()];
|
||||
}
|
||||
|
||||
getSecondaryActions(): IAction[] {
|
||||
return this.menus.getTitleSecondaryActions();
|
||||
}
|
||||
|
||||
getActionItem(action: IAction): IActionItem {
|
||||
return action instanceof MenuItemAction ? new ContextAwareMenuItemActionItem(action, this.keybindingService, this.notificationService, this.contextMenuService) : undefined;
|
||||
}
|
||||
|
||||
getOptimalWidth(): number {
|
||||
return this.treeViewer.getOptimalWidth();
|
||||
}
|
||||
|
||||
private updateTreeVisibility(): void {
|
||||
this.treeViewer.setVisibility(this.isVisible() && this.isExpanded());
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
dispose(this.disposables);
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
export class Menus implements IDisposable {
|
||||
|
||||
private disposables: IDisposable[] = [];
|
||||
private titleDisposable: IDisposable = EmptyDisposable;
|
||||
private titleActions: IAction[] = [];
|
||||
private titleSecondaryActions: IAction[] = [];
|
||||
|
||||
private _onDidChangeTitle = new Emitter<void>();
|
||||
get onDidChangeTitle(): Event<void> { return this._onDidChangeTitle.event; }
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
@IContextKeyService private contextKeyService: IContextKeyService,
|
||||
@IMenuService private menuService: IMenuService,
|
||||
@IContextMenuService private contextMenuService: IContextMenuService
|
||||
) {
|
||||
if (this.titleDisposable) {
|
||||
this.titleDisposable.dispose();
|
||||
this.titleDisposable = EmptyDisposable;
|
||||
}
|
||||
|
||||
const _contextKeyService = this.contextKeyService.createScoped();
|
||||
_contextKeyService.createKey('view', id);
|
||||
|
||||
const titleMenu = this.menuService.createMenu(MenuId.ViewTitle, _contextKeyService);
|
||||
const updateActions = () => {
|
||||
this.titleActions = [];
|
||||
this.titleSecondaryActions = [];
|
||||
fillInActions(titleMenu, null, { primary: this.titleActions, secondary: this.titleSecondaryActions }, this.contextMenuService);
|
||||
this._onDidChangeTitle.fire();
|
||||
};
|
||||
|
||||
const listener = titleMenu.onDidChange(updateActions);
|
||||
updateActions();
|
||||
|
||||
this.titleDisposable = toDisposable(() => {
|
||||
listener.dispose();
|
||||
titleMenu.dispose();
|
||||
_contextKeyService.dispose();
|
||||
this.titleActions = [];
|
||||
this.titleSecondaryActions = [];
|
||||
});
|
||||
}
|
||||
|
||||
getTitleActions(): IAction[] {
|
||||
return this.titleActions;
|
||||
}
|
||||
|
||||
getTitleSecondaryActions(): IAction[] {
|
||||
return this.titleSecondaryActions;
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this.disposables = dispose(this.disposables);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path fill="#E8E8E8" d="M6 4v8l4-4-4-4zm1 2.414L8.586 8 7 9.586V6.414z"/></svg>
|
||||
|
After Width: | Height: | Size: 139 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="#fff" d="M6 4v8l4-4-4-4zm1 2.414l1.586 1.586-1.586 1.586v-3.172z"/></svg>
|
||||
|
After Width: | Height: | Size: 148 B |
1
src/vs/workbench/browser/parts/views/media/collapsed.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path fill="#646465" d="M6 4v8l4-4-4-4zm1 2.414L8.586 8 7 9.586V6.414z"/></svg>
|
||||
|
After Width: | Height: | Size: 139 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path fill="#E8E8E8" d="M11 10H5.344L11 4.414V10z"/></svg>
|
||||
|
After Width: | Height: | Size: 118 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="#fff" d="M11 10.07h-5.656l5.656-5.656v5.656z"/></svg>
|
||||
|
After Width: | Height: | Size: 128 B |
1
src/vs/workbench/browser/parts/views/media/expanded.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path fill="#646465" d="M11 10H5.344L11 4.414V10z"/></svg>
|
||||
|
After Width: | Height: | Size: 118 B |
@@ -3,13 +3,76 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
.custom-view-tree-node-item {
|
||||
/* File icon themeable tree style */
|
||||
.file-icon-themable-tree .monaco-tree-row .content {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.file-icon-themable-tree .monaco-tree-row .content::before {
|
||||
background-size: 16px;
|
||||
background-position: 50% 50%;
|
||||
background-repeat: no-repeat;
|
||||
padding-right: 6px;
|
||||
width: 16px;
|
||||
height: 22px;
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
content: ' ';
|
||||
}
|
||||
|
||||
.file-icon-themable-tree .monaco-tree-row.has-children.expanded .content::before {
|
||||
background-image: url("expanded.svg");
|
||||
}
|
||||
|
||||
.file-icon-themable-tree .monaco-tree-row.has-children .content::before {
|
||||
display: inline-block;
|
||||
background-image: url("collapsed.svg");
|
||||
}
|
||||
|
||||
.vs-dark .file-icon-themable-tree .monaco-tree-row.has-children.expanded .content::before {
|
||||
background-image: url("expanded-dark.svg");
|
||||
}
|
||||
|
||||
.vs-dark .file-icon-themable-tree .monaco-tree-row.has-children .content::before {
|
||||
background-image: url("collapsed-dark.svg");
|
||||
}
|
||||
|
||||
.hc-black .file-icon-themable-tree .monaco-tree-row.has-children.expanded .content::before {
|
||||
background-image: url("expanded-hc.svg");
|
||||
}
|
||||
|
||||
.hc-black .file-icon-themable-tree .monaco-tree-row.has-children .content::before {
|
||||
background-image: url("collapsed-hc.svg");
|
||||
}
|
||||
|
||||
.file-icon-themable-tree.align-icons-and-twisties .monaco-tree-row:not(.has-children) .content::before,
|
||||
.file-icon-themable-tree.hide-arrows .monaco-tree-row .content::before {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.tree-explorer-viewlet-tree-view.file-icon-themable-tree.align-icons-and-twisties .monaco-tree-row:not(.has-children) .content:not(.align-with-twisty)::before,
|
||||
.tree-explorer-viewlet-tree-view.file-icon-themable-tree.hide-arrows .monaco-tree-row .content::before {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.tree-explorer-viewlet-tree-view .monaco-tree .monaco-tree-row .custom-view-tree-node-item {
|
||||
display: flex;
|
||||
height: 22px;
|
||||
line-height: 22px;
|
||||
flex: 1;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
flex-wrap: nowrap
|
||||
}
|
||||
|
||||
.custom-view-tree-node-item > .custom-view-tree-node-item-icon {
|
||||
.tree-explorer-viewlet-tree-view .monaco-tree .monaco-tree-row .custom-view-tree-node-item .custom-view-tree-node-item-resourceLabel,
|
||||
.tree-explorer-viewlet-tree-view .monaco-tree .monaco-tree-row .custom-view-tree-node-item > .custom-view-tree-node-item-label {
|
||||
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 {
|
||||
background-size: 16px;
|
||||
background-position: left center;
|
||||
background-repeat: no-repeat;
|
||||
@@ -19,8 +82,20 @@
|
||||
-webkit-font-smoothing: antialiased;
|
||||
}
|
||||
|
||||
.custom-view-tree-node-item > .custom-view-tree-node-item-label {
|
||||
flex: 1;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
.tree-explorer-viewlet-tree-view .monaco-tree .monaco-tree-row .custom-view-tree-node-item > .actions {
|
||||
display: none;
|
||||
padding-right: 6px;
|
||||
}
|
||||
|
||||
.tree-explorer-viewlet-tree-view .monaco-tree .monaco-tree-row:hover .custom-view-tree-node-item > .actions,
|
||||
.tree-explorer-viewlet-tree-view .monaco-tree .monaco-tree-row.selected .custom-view-tree-node-item > .actions,
|
||||
.tree-explorer-viewlet-tree-view .monaco-tree .monaco-tree-row.focused .custom-view-tree-node-item > .actions {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.tree-explorer-viewlet-tree-view .monaco-tree .custom-view-tree-node-item > .actions .action-label {
|
||||
width: 16px;
|
||||
height: 100%;
|
||||
background-position: 50% 50%;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
@@ -6,12 +6,12 @@
|
||||
import 'vs/css!./media/panelviewlet';
|
||||
import * as nls from 'vs/nls';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import Event, { Emitter } from 'vs/base/common/event';
|
||||
import Event, { Emitter, filterEvent } from 'vs/base/common/event';
|
||||
import { ColorIdentifier, contrastBorder } from 'vs/platform/theme/common/colorRegistry';
|
||||
import { attachStyler, IColorMapping, IThemable } from 'vs/platform/theme/common/styler';
|
||||
import { SIDE_BAR_DRAG_AND_DROP_BACKGROUND, SIDE_BAR_SECTION_HEADER_FOREGROUND, SIDE_BAR_SECTION_HEADER_BACKGROUND } from 'vs/workbench/common/theme';
|
||||
import { Dimension, Builder } from 'vs/base/browser/builder';
|
||||
import { append, $, trackFocus } from 'vs/base/browser/dom';
|
||||
import { append, $, trackFocus, toggleClass, EventType, isAncestor } from 'vs/base/browser/dom';
|
||||
import { IDisposable, combinedDisposable } from 'vs/base/common/lifecycle';
|
||||
import { firstIndex } from 'vs/base/common/arrays';
|
||||
import { IAction, IActionRunner } from 'vs/base/common/actions';
|
||||
@@ -25,6 +25,9 @@ import { IContextMenuService } from 'vs/platform/contextview/browser/contextView
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import { PanelView, IPanelViewOptions, IPanelOptions, Panel } from 'vs/base/browser/ui/splitview/panelview';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { IPartService } from 'vs/workbench/services/part/common/partService';
|
||||
import { StandardMouseEvent } from 'vs/base/browser/mouseEvent';
|
||||
|
||||
export interface IPanelColors extends IColorMapping {
|
||||
dropBackground?: ColorIdentifier;
|
||||
@@ -48,17 +51,21 @@ export interface IViewletPanelOptions extends IPanelOptions {
|
||||
|
||||
export abstract class ViewletPanel extends Panel {
|
||||
|
||||
private static AlwaysShowActionsConfig = 'workbench.view.alwaysShowHeaderActions';
|
||||
|
||||
private _onDidFocus = new Emitter<void>();
|
||||
readonly onDidFocus: Event<void> = this._onDidFocus.event;
|
||||
|
||||
protected actionRunner: IActionRunner;
|
||||
protected toolbar: ToolBar;
|
||||
private headerContainer: HTMLElement;
|
||||
|
||||
constructor(
|
||||
readonly title: string,
|
||||
options: IViewletPanelOptions,
|
||||
@IKeybindingService protected keybindingService: IKeybindingService,
|
||||
@IContextMenuService protected contextMenuService: IContextMenuService
|
||||
@IContextMenuService protected contextMenuService: IContextMenuService,
|
||||
@IConfigurationService protected readonly configurationService: IConfigurationService
|
||||
) {
|
||||
super(options);
|
||||
|
||||
@@ -74,6 +81,8 @@ export abstract class ViewletPanel extends Panel {
|
||||
}
|
||||
|
||||
protected renderHeader(container: HTMLElement): void {
|
||||
this.headerContainer = container;
|
||||
|
||||
this.renderHeaderTitle(container);
|
||||
|
||||
const actions = append(container, $('.actions'));
|
||||
@@ -87,6 +96,10 @@ export abstract class ViewletPanel extends Panel {
|
||||
|
||||
this.disposables.push(this.toolbar);
|
||||
this.updateActions();
|
||||
|
||||
const onDidRelevantConfigurationChange = filterEvent(this.configurationService.onDidChangeConfiguration, e => e.affectsConfiguration(ViewletPanel.AlwaysShowActionsConfig));
|
||||
onDidRelevantConfigurationChange(this.updateActionsVisibility, this, this.disposables);
|
||||
this.updateActionsVisibility();
|
||||
}
|
||||
|
||||
protected renderHeaderTitle(container: HTMLElement): void {
|
||||
@@ -102,6 +115,11 @@ export abstract class ViewletPanel extends Panel {
|
||||
this.toolbar.context = this.getActionsContext();
|
||||
}
|
||||
|
||||
protected updateActionsVisibility(): void {
|
||||
const shouldAlwaysShowActions = this.configurationService.getValue<boolean>('workbench.view.alwaysShowHeaderActions');
|
||||
toggleClass(this.headerContainer, 'actions-always-visible', shouldAlwaysShowActions);
|
||||
}
|
||||
|
||||
getActions(): IAction[] {
|
||||
return [];
|
||||
}
|
||||
@@ -134,7 +152,7 @@ interface IViewletPanelItem {
|
||||
|
||||
export class PanelViewlet extends Viewlet {
|
||||
|
||||
protected lastFocusedPanel: ViewletPanel | undefined;
|
||||
private lastFocusedPanel: ViewletPanel | undefined;
|
||||
private panelItems: IViewletPanelItem[] = [];
|
||||
private panelview: PanelView;
|
||||
|
||||
@@ -149,10 +167,12 @@ export class PanelViewlet extends Viewlet {
|
||||
constructor(
|
||||
id: string,
|
||||
private options: IViewsViewletOptions,
|
||||
@IPartService partService: IPartService,
|
||||
@IContextMenuService protected contextMenuService: IContextMenuService,
|
||||
@ITelemetryService telemetryService: ITelemetryService,
|
||||
@IThemeService themeService: IThemeService
|
||||
) {
|
||||
super(id, telemetryService, themeService);
|
||||
super(id, partService, telemetryService, themeService);
|
||||
}
|
||||
|
||||
async create(parent: Builder): TPromise<void> {
|
||||
@@ -160,7 +180,26 @@ export class PanelViewlet extends Viewlet {
|
||||
|
||||
const container = parent.getHTMLElement();
|
||||
this.panelview = this._register(new PanelView(container, this.options));
|
||||
this.panelview.onDidDrop(({ from, to }) => this.movePanel(from as ViewletPanel, to as ViewletPanel));
|
||||
this._register(this.panelview.onDidDrop(({ from, to }) => this.movePanel(from as ViewletPanel, to as ViewletPanel)));
|
||||
this._register(parent.on(EventType.CONTEXT_MENU, (e: MouseEvent) => this.showContextMenu(new StandardMouseEvent(e))));
|
||||
}
|
||||
|
||||
private showContextMenu(event: StandardMouseEvent): void {
|
||||
for (const panelItem of this.panelItems) {
|
||||
// Do not show context menu if target is coming from inside panel views
|
||||
if (isAncestor(event.target, panelItem.panel.element)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
|
||||
let anchor: { x: number, y: number } = { x: event.posx, y: event.posy };
|
||||
this.contextMenuService.showContextMenu({
|
||||
getAnchor: () => anchor,
|
||||
getActions: () => TPromise.as(this.getContextMenuActions())
|
||||
});
|
||||
}
|
||||
|
||||
getTitle(): string {
|
||||
@@ -195,7 +234,12 @@ export class PanelViewlet extends Viewlet {
|
||||
if (this.lastFocusedPanel) {
|
||||
this.lastFocusedPanel.focus();
|
||||
} else if (this.panelItems.length > 0) {
|
||||
this.panelItems[0].panel.focus();
|
||||
for (const { panel } of this.panelItems) {
|
||||
if (panel.isExpanded()) {
|
||||
panel.focus();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -213,8 +257,13 @@ export class PanelViewlet extends Viewlet {
|
||||
addPanel(panel: ViewletPanel, size: number, index = this.panelItems.length - 1): void {
|
||||
const disposables: IDisposable[] = [];
|
||||
const onDidFocus = panel.onDidFocus(() => this.lastFocusedPanel = panel, null, disposables);
|
||||
const onDidChange = panel.onDidChange(() => {
|
||||
if (panel === this.lastFocusedPanel && !panel.isExpanded()) {
|
||||
this.lastFocusedPanel = undefined;
|
||||
}
|
||||
}, null, disposables);
|
||||
const styler = attachPanelStyler(panel, this.themeService);
|
||||
const disposable = combinedDisposable([onDidFocus, styler]);
|
||||
const disposable = combinedDisposable([onDidFocus, styler, onDidChange]);
|
||||
const panelItem: IViewletPanelItem = { panel, disposable };
|
||||
|
||||
this.panelItems.splice(index, 0, panelItem);
|
||||
|
||||
@@ -1,465 +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 'vs/css!./media/views';
|
||||
import Event, { Emitter } from 'vs/base/common/event';
|
||||
import { IDisposable, dispose, empty as EmptyDisposable, toDisposable } from 'vs/base/common/lifecycle';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import * as DOM from 'vs/base/browser/dom';
|
||||
import { Builder, $ } from 'vs/base/browser/builder';
|
||||
import { IAction, IActionItem, ActionRunner } from 'vs/base/common/actions';
|
||||
import { IMessageService } from 'vs/platform/message/common/message';
|
||||
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
||||
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
|
||||
import { ClickBehavior, DefaultController } from 'vs/base/parts/tree/browser/treeDefaults';
|
||||
import { IMenuService, MenuId, MenuItemAction } from 'vs/platform/actions/common/actions';
|
||||
import { IThemeService, LIGHT } from 'vs/platform/theme/common/themeService';
|
||||
import { createActionItem, fillInActions } from 'vs/platform/actions/browser/menuItemActionItem';
|
||||
import { IProgressService } from 'vs/platform/progress/common/progress';
|
||||
import { ITree, IDataSource, IRenderer, ContextMenuEvent } from 'vs/base/parts/tree/browser/tree';
|
||||
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { ActionItem } from 'vs/base/browser/ui/actionbar/actionbar';
|
||||
import { ViewsRegistry } from 'vs/workbench/browser/parts/views/viewsRegistry';
|
||||
import { IExtensionService } from 'vs/platform/extensions/common/extensions';
|
||||
import { TreeViewsViewletPanel, IViewletViewOptions, IViewOptions } from 'vs/workbench/browser/parts/views/viewsViewlet';
|
||||
import { ICommandService } from 'vs/platform/commands/common/commands';
|
||||
import { TreeItemCollapsibleState, ITreeItem, ITreeViewDataProvider, TreeViewItemHandleArg } from 'vs/workbench/common/views';
|
||||
import { WorkbenchTree, IListService } from 'vs/platform/list/browser/listService';
|
||||
|
||||
export class TreeView extends TreeViewsViewletPanel {
|
||||
|
||||
private menus: Menus;
|
||||
private activated: boolean = false;
|
||||
private treeInputPromise: TPromise<void>;
|
||||
|
||||
private dataProviderElementChangeListener: IDisposable;
|
||||
private elementsToRefresh: ITreeItem[] = [];
|
||||
|
||||
constructor(
|
||||
options: IViewletViewOptions,
|
||||
@IMessageService private messageService: IMessageService,
|
||||
@IKeybindingService keybindingService: IKeybindingService,
|
||||
@IContextMenuService contextMenuService: IContextMenuService,
|
||||
@IInstantiationService private instantiationService: IInstantiationService,
|
||||
@IListService private listService: IListService,
|
||||
@IThemeService private themeService: IThemeService,
|
||||
@IContextKeyService private contextKeyService: IContextKeyService,
|
||||
@IExtensionService private extensionService: IExtensionService,
|
||||
@ICommandService private commandService: ICommandService
|
||||
) {
|
||||
super({ ...(options as IViewOptions), ariaHeaderLabel: options.name }, keybindingService, contextMenuService);
|
||||
this.menus = this.instantiationService.createInstance(Menus, this.id);
|
||||
this.menus.onDidChangeTitle(() => this.updateActions(), this, this.disposables);
|
||||
this.themeService.onThemeChange(() => this.tree.refresh() /* soft refresh */, this, this.disposables);
|
||||
if (options.expanded) {
|
||||
this.activate();
|
||||
}
|
||||
}
|
||||
|
||||
public renderBody(container: HTMLElement): void {
|
||||
this.treeContainer = super.renderViewTree(container);
|
||||
DOM.addClass(this.treeContainer, 'tree-explorer-viewlet-tree-view');
|
||||
|
||||
this.tree = this.createViewer($(this.treeContainer));
|
||||
this.setInput();
|
||||
}
|
||||
|
||||
setExpanded(expanded: boolean): void {
|
||||
super.setExpanded(expanded);
|
||||
|
||||
if (expanded) {
|
||||
this.activate();
|
||||
}
|
||||
}
|
||||
|
||||
private activate() {
|
||||
if (!this.activated && this.extensionService) {
|
||||
this.extensionService.activateByEvent(`onView:${this.id}`);
|
||||
this.activated = true;
|
||||
this.setInput();
|
||||
}
|
||||
}
|
||||
|
||||
public createViewer(container: Builder): WorkbenchTree {
|
||||
const dataSource = this.instantiationService.createInstance(TreeDataSource, this.id);
|
||||
const renderer = this.instantiationService.createInstance(TreeRenderer);
|
||||
const controller = this.instantiationService.createInstance(TreeController, this.id, this.menus);
|
||||
const tree = new WorkbenchTree(
|
||||
container.getHTMLElement(),
|
||||
{ dataSource, renderer, controller },
|
||||
{ keyboardSupport: false },
|
||||
this.contextKeyService,
|
||||
this.listService,
|
||||
this.themeService
|
||||
);
|
||||
|
||||
tree.contextKeyService.createKey<boolean>(this.id, true);
|
||||
this.disposables.push(tree.onDidChangeSelection(() => this.onSelection()));
|
||||
|
||||
return tree;
|
||||
}
|
||||
|
||||
getActions(): IAction[] {
|
||||
return [...this.menus.getTitleActions()];
|
||||
}
|
||||
|
||||
getSecondaryActions(): IAction[] {
|
||||
return this.menus.getTitleSecondaryActions();
|
||||
}
|
||||
|
||||
getActionItem(action: IAction): IActionItem {
|
||||
return createActionItem(action, this.keybindingService, this.messageService);
|
||||
}
|
||||
|
||||
private setInput(): TPromise<void> {
|
||||
if (this.tree) {
|
||||
if (!this.treeInputPromise) {
|
||||
if (this.listenToDataProvider()) {
|
||||
this.treeInputPromise = this.tree.setInput(new Root());
|
||||
} else {
|
||||
this.treeInputPromise = new TPromise<void>((c, e) => {
|
||||
this.disposables.push(ViewsRegistry.onTreeViewDataProviderRegistered(id => {
|
||||
if (this.id === id) {
|
||||
if (this.listenToDataProvider()) {
|
||||
this.tree.setInput(new Root()).then(() => c(null));
|
||||
}
|
||||
}
|
||||
}));
|
||||
});
|
||||
}
|
||||
}
|
||||
return this.treeInputPromise;
|
||||
}
|
||||
return TPromise.as(null);
|
||||
}
|
||||
|
||||
private listenToDataProvider(): boolean {
|
||||
let dataProvider = ViewsRegistry.getTreeViewDataProvider(this.id);
|
||||
if (dataProvider) {
|
||||
if (this.dataProviderElementChangeListener) {
|
||||
this.dataProviderElementChangeListener.dispose();
|
||||
}
|
||||
this.dataProviderElementChangeListener = dataProvider.onDidChange(element => this.refresh(element));
|
||||
const disposable = dataProvider.onDispose(() => {
|
||||
this.dataProviderElementChangeListener.dispose();
|
||||
this.tree.setInput(new Root());
|
||||
disposable.dispose();
|
||||
});
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public getOptimalWidth(): number {
|
||||
const parentNode = this.tree.getHTMLElement();
|
||||
const childNodes = [].slice.call(parentNode.querySelectorAll('.outline-item-label > a'));
|
||||
|
||||
return DOM.getLargestChildWidth(parentNode, childNodes);
|
||||
}
|
||||
|
||||
private onSelection(): void {
|
||||
const selection: ITreeItem = this.tree.getSelection()[0];
|
||||
if (selection) {
|
||||
if (selection.command) {
|
||||
this.commandService.executeCommand(selection.command.id, ...(selection.command.arguments || []));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected updateTreeVisibility(tree: WorkbenchTree, isVisible: boolean): void {
|
||||
super.updateTreeVisibility(tree, isVisible);
|
||||
if (isVisible && this.elementsToRefresh.length) {
|
||||
this.doRefresh(this.elementsToRefresh);
|
||||
this.elementsToRefresh = [];
|
||||
}
|
||||
}
|
||||
|
||||
private refresh(elements: ITreeItem[]): void {
|
||||
if (!elements) {
|
||||
const root: ITreeItem = this.tree.getInput();
|
||||
root.children = null; // reset children
|
||||
elements = [root];
|
||||
}
|
||||
if (this.isVisible() && this.isExpanded()) {
|
||||
this.doRefresh(elements);
|
||||
} else {
|
||||
this.elementsToRefresh.push(...elements);
|
||||
}
|
||||
}
|
||||
|
||||
private doRefresh(elements: ITreeItem[]): void {
|
||||
for (const element of elements) {
|
||||
this.tree.refresh(element);
|
||||
}
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
dispose(this.disposables);
|
||||
if (this.dataProviderElementChangeListener) {
|
||||
this.dataProviderElementChangeListener.dispose();
|
||||
}
|
||||
dispose(this.disposables);
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
class Root implements ITreeItem {
|
||||
label = 'root';
|
||||
handle = '0';
|
||||
parentHandle = null;
|
||||
collapsibleState = TreeItemCollapsibleState.Expanded;
|
||||
}
|
||||
|
||||
class TreeDataSource implements IDataSource {
|
||||
|
||||
constructor(
|
||||
private id: string,
|
||||
@IProgressService private progressService: IProgressService
|
||||
) {
|
||||
}
|
||||
|
||||
public getId(tree: ITree, node: ITreeItem): string {
|
||||
return node.handle;
|
||||
}
|
||||
|
||||
public hasChildren(tree: ITree, node: ITreeItem): boolean {
|
||||
if (!this.getDataProvider()) {
|
||||
return false;
|
||||
}
|
||||
return node.collapsibleState === TreeItemCollapsibleState.Collapsed || node.collapsibleState === TreeItemCollapsibleState.Expanded;
|
||||
}
|
||||
|
||||
public getChildren(tree: ITree, node: ITreeItem): TPromise<any[]> {
|
||||
if (node.children) {
|
||||
return TPromise.as(node.children);
|
||||
}
|
||||
|
||||
const dataProvider = this.getDataProvider();
|
||||
if (dataProvider) {
|
||||
const promise = node instanceof Root ? dataProvider.getElements() : dataProvider.getChildren(node);
|
||||
this.progressService.showWhile(promise, 100);
|
||||
return promise.then(children => {
|
||||
node.children = children;
|
||||
return children;
|
||||
});
|
||||
}
|
||||
return TPromise.as(null);
|
||||
}
|
||||
|
||||
public shouldAutoexpand(tree: ITree, node: ITreeItem): boolean {
|
||||
return node.collapsibleState === TreeItemCollapsibleState.Expanded;
|
||||
}
|
||||
|
||||
public getParent(tree: ITree, node: any): TPromise<any> {
|
||||
return TPromise.as(null);
|
||||
}
|
||||
|
||||
private getDataProvider(): ITreeViewDataProvider {
|
||||
return ViewsRegistry.getTreeViewDataProvider(this.id);
|
||||
}
|
||||
}
|
||||
|
||||
interface ITreeExplorerTemplateData {
|
||||
icon: Builder;
|
||||
label: Builder;
|
||||
}
|
||||
|
||||
class TreeRenderer implements IRenderer {
|
||||
|
||||
private static readonly ITEM_HEIGHT = 22;
|
||||
private static readonly TREE_TEMPLATE_ID = 'treeExplorer';
|
||||
|
||||
constructor( @IThemeService private themeService: IThemeService) {
|
||||
}
|
||||
|
||||
public getHeight(tree: ITree, element: any): number {
|
||||
return TreeRenderer.ITEM_HEIGHT;
|
||||
}
|
||||
|
||||
public getTemplateId(tree: ITree, element: any): string {
|
||||
return TreeRenderer.TREE_TEMPLATE_ID;
|
||||
}
|
||||
|
||||
public renderTemplate(tree: ITree, templateId: string, container: HTMLElement): ITreeExplorerTemplateData {
|
||||
const el = $(container);
|
||||
const item = $('.custom-view-tree-node-item');
|
||||
item.appendTo(el);
|
||||
|
||||
const icon = $('.custom-view-tree-node-item-icon').appendTo(item);
|
||||
const label = $('.custom-view-tree-node-item-label').appendTo(item);
|
||||
const link = $('a.label').appendTo(label);
|
||||
|
||||
return { label: link, icon };
|
||||
}
|
||||
|
||||
public renderElement(tree: ITree, node: ITreeItem, templateId: string, templateData: ITreeExplorerTemplateData): void {
|
||||
templateData.label.text(node.label).title(node.label);
|
||||
|
||||
const theme = this.themeService.getTheme();
|
||||
const icon = theme.type === LIGHT ? node.icon : node.iconDark;
|
||||
|
||||
if (icon) {
|
||||
templateData.icon.getHTMLElement().style.backgroundImage = `url('${icon}')`;
|
||||
DOM.addClass(templateData.icon.getHTMLElement(), 'custom-view-tree-node-item-icon');
|
||||
} else {
|
||||
templateData.icon.getHTMLElement().style.backgroundImage = '';
|
||||
DOM.removeClass(templateData.icon.getHTMLElement(), 'custom-view-tree-node-item-icon');
|
||||
}
|
||||
}
|
||||
|
||||
public disposeTemplate(tree: ITree, templateId: string, templateData: ITreeExplorerTemplateData): void {
|
||||
}
|
||||
}
|
||||
|
||||
class TreeController extends DefaultController {
|
||||
|
||||
constructor(
|
||||
private treeViewId: string,
|
||||
private menus: Menus,
|
||||
@IContextMenuService private contextMenuService: IContextMenuService,
|
||||
@IKeybindingService private _keybindingService: IKeybindingService
|
||||
) {
|
||||
super({ clickBehavior: ClickBehavior.ON_MOUSE_UP /* do not change to not break DND */, keyboardSupport: false });
|
||||
}
|
||||
|
||||
public onContextMenu(tree: ITree, node: ITreeItem, event: ContextMenuEvent): boolean {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
tree.setFocus(node);
|
||||
const actions = this.menus.getResourceContextActions(node);
|
||||
if (!actions.length) {
|
||||
return true;
|
||||
}
|
||||
const anchor = { x: event.posx, y: event.posy };
|
||||
this.contextMenuService.showContextMenu({
|
||||
getAnchor: () => anchor,
|
||||
|
||||
getActions: () => {
|
||||
return TPromise.as(actions);
|
||||
},
|
||||
|
||||
getActionItem: (action) => {
|
||||
const keybinding = this._keybindingService.lookupKeybinding(action.id);
|
||||
if (keybinding) {
|
||||
return new ActionItem(action, action, { label: true, keybinding: keybinding.getLabel() });
|
||||
}
|
||||
return null;
|
||||
},
|
||||
|
||||
onHide: (wasCancelled?: boolean) => {
|
||||
if (wasCancelled) {
|
||||
tree.DOMFocus();
|
||||
}
|
||||
},
|
||||
|
||||
getActionsContext: () => (<TreeViewItemHandleArg>{ $treeViewId: this.treeViewId, $treeItemHandle: node.handle }),
|
||||
|
||||
actionRunner: new MultipleSelectionActionRunner(() => tree.getSelection())
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
class MultipleSelectionActionRunner extends ActionRunner {
|
||||
|
||||
constructor(private getSelectedResources: () => any[]) {
|
||||
super();
|
||||
}
|
||||
|
||||
runAction(action: IAction, context: any): TPromise<any> {
|
||||
if (action instanceof MenuItemAction) {
|
||||
const selection = this.getSelectedResources();
|
||||
const filteredSelection = selection.filter(s => s !== context);
|
||||
|
||||
if (selection.length === filteredSelection.length || selection.length === 1) {
|
||||
return action.run(context);
|
||||
}
|
||||
|
||||
return action.run(context, ...filteredSelection);
|
||||
}
|
||||
|
||||
return super.runAction(action, context);
|
||||
}
|
||||
}
|
||||
|
||||
class Menus implements IDisposable {
|
||||
|
||||
private disposables: IDisposable[] = [];
|
||||
private titleDisposable: IDisposable = EmptyDisposable;
|
||||
private titleActions: IAction[] = [];
|
||||
private titleSecondaryActions: IAction[] = [];
|
||||
|
||||
private _onDidChangeTitle = new Emitter<void>();
|
||||
get onDidChangeTitle(): Event<void> { return this._onDidChangeTitle.event; }
|
||||
|
||||
constructor(
|
||||
private id: string,
|
||||
@IContextKeyService private contextKeyService: IContextKeyService,
|
||||
@IMenuService private menuService: IMenuService
|
||||
) {
|
||||
if (this.titleDisposable) {
|
||||
this.titleDisposable.dispose();
|
||||
this.titleDisposable = EmptyDisposable;
|
||||
}
|
||||
|
||||
const _contextKeyService = this.contextKeyService.createScoped();
|
||||
_contextKeyService.createKey('view', id);
|
||||
|
||||
const titleMenu = this.menuService.createMenu(MenuId.ViewTitle, _contextKeyService);
|
||||
const updateActions = () => {
|
||||
this.titleActions = [];
|
||||
this.titleSecondaryActions = [];
|
||||
fillInActions(titleMenu, null, { primary: this.titleActions, secondary: this.titleSecondaryActions });
|
||||
this._onDidChangeTitle.fire();
|
||||
};
|
||||
|
||||
const listener = titleMenu.onDidChange(updateActions);
|
||||
updateActions();
|
||||
|
||||
this.titleDisposable = toDisposable(() => {
|
||||
listener.dispose();
|
||||
titleMenu.dispose();
|
||||
_contextKeyService.dispose();
|
||||
this.titleActions = [];
|
||||
this.titleSecondaryActions = [];
|
||||
});
|
||||
}
|
||||
|
||||
getTitleActions(): IAction[] {
|
||||
return this.titleActions;
|
||||
}
|
||||
|
||||
getTitleSecondaryActions(): IAction[] {
|
||||
return this.titleSecondaryActions;
|
||||
}
|
||||
|
||||
getResourceContextActions(element: ITreeItem): IAction[] {
|
||||
return this.getActions(MenuId.ViewItemContext, { key: 'viewItem', value: element.contextValue }).secondary;
|
||||
}
|
||||
|
||||
private getActions(menuId: MenuId, context: { key: string, value: string }): { primary: IAction[]; secondary: IAction[]; } {
|
||||
const contextKeyService = this.contextKeyService.createScoped();
|
||||
contextKeyService.createKey('view', this.id);
|
||||
contextKeyService.createKey(context.key, context.value);
|
||||
|
||||
const menu = this.menuService.createMenu(menuId, contextKeyService);
|
||||
const primary: IAction[] = [];
|
||||
const secondary: IAction[] = [];
|
||||
const result = { primary, secondary };
|
||||
fillInActions(menu, { shouldForwardArgs: true }, result);
|
||||
|
||||
menu.dispose();
|
||||
contextKeyService.dispose();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this.disposables = dispose(this.disposables);
|
||||
}
|
||||
}
|
||||
@@ -1,149 +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 Event, { Emitter } from 'vs/base/common/event';
|
||||
import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { ITreeViewDataProvider } from 'vs/workbench/common/views';
|
||||
import { localize } from 'vs/nls';
|
||||
|
||||
export class ViewLocation {
|
||||
|
||||
static readonly Explorer = new ViewLocation('explorer');
|
||||
static readonly Debug = new ViewLocation('debug');
|
||||
static readonly Extensions = new ViewLocation('extensions');
|
||||
|
||||
constructor(private _id: string) {
|
||||
}
|
||||
|
||||
get id(): string {
|
||||
return this._id;
|
||||
}
|
||||
|
||||
static getContributedViewLocation(value: string): ViewLocation {
|
||||
switch (value) {
|
||||
case ViewLocation.Explorer.id: return ViewLocation.Explorer;
|
||||
case ViewLocation.Debug.id: return ViewLocation.Debug;
|
||||
}
|
||||
return void 0;
|
||||
}
|
||||
}
|
||||
|
||||
export interface IViewDescriptor {
|
||||
|
||||
readonly id: string;
|
||||
|
||||
readonly name: string;
|
||||
|
||||
readonly location: ViewLocation;
|
||||
|
||||
// TODO do we really need this?!
|
||||
readonly ctor: any;
|
||||
|
||||
readonly when?: ContextKeyExpr;
|
||||
|
||||
readonly order?: number;
|
||||
|
||||
readonly size?: number;
|
||||
|
||||
readonly collapsed?: boolean;
|
||||
|
||||
readonly canToggleVisibility?: boolean;
|
||||
}
|
||||
|
||||
export interface IViewsRegistry {
|
||||
|
||||
readonly onViewsRegistered: Event<IViewDescriptor[]>;
|
||||
|
||||
readonly onViewsDeregistered: Event<IViewDescriptor[]>;
|
||||
|
||||
readonly onTreeViewDataProviderRegistered: Event<string>;
|
||||
|
||||
registerViews(views: IViewDescriptor[]): void;
|
||||
|
||||
deregisterViews(ids: string[], location: ViewLocation): void;
|
||||
|
||||
registerTreeViewDataProvider(id: string, factory: ITreeViewDataProvider): void;
|
||||
|
||||
deregisterTreeViewDataProviders(): void;
|
||||
|
||||
getViews(loc: ViewLocation): IViewDescriptor[];
|
||||
|
||||
getTreeViewDataProvider(id: string): ITreeViewDataProvider;
|
||||
|
||||
}
|
||||
|
||||
export const ViewsRegistry: IViewsRegistry = new class implements IViewsRegistry {
|
||||
|
||||
private _onViewsRegistered: Emitter<IViewDescriptor[]> = new Emitter<IViewDescriptor[]>();
|
||||
readonly onViewsRegistered: Event<IViewDescriptor[]> = this._onViewsRegistered.event;
|
||||
|
||||
private _onViewsDeregistered: Emitter<IViewDescriptor[]> = new Emitter<IViewDescriptor[]>();
|
||||
readonly onViewsDeregistered: Event<IViewDescriptor[]> = this._onViewsDeregistered.event;
|
||||
|
||||
private _onTreeViewDataProviderRegistered: Emitter<string> = new Emitter<string>();
|
||||
readonly onTreeViewDataProviderRegistered: Event<string> = this._onTreeViewDataProviderRegistered.event;
|
||||
|
||||
private _views: Map<ViewLocation, IViewDescriptor[]> = new Map<ViewLocation, IViewDescriptor[]>();
|
||||
private _treeViewDataPoviders: Map<string, ITreeViewDataProvider> = new Map<string, ITreeViewDataProvider>();
|
||||
|
||||
registerViews(viewDescriptors: IViewDescriptor[]): void {
|
||||
if (viewDescriptors.length) {
|
||||
for (const viewDescriptor of viewDescriptors) {
|
||||
let views = this._views.get(viewDescriptor.location);
|
||||
if (!views) {
|
||||
views = [];
|
||||
this._views.set(viewDescriptor.location, views);
|
||||
}
|
||||
if (views.some(v => v.id === viewDescriptor.id)) {
|
||||
throw new Error(localize('duplicateId', "A view with id `{0}` is already registered in the location `{1}`", viewDescriptor.id, viewDescriptor.location.id));
|
||||
}
|
||||
views.push(viewDescriptor);
|
||||
}
|
||||
this._onViewsRegistered.fire(viewDescriptors);
|
||||
}
|
||||
}
|
||||
|
||||
deregisterViews(ids: string[], location: ViewLocation): void {
|
||||
const views = this._views.get(location);
|
||||
|
||||
if (!views) {
|
||||
return;
|
||||
}
|
||||
|
||||
const viewsToDeregister = views.filter(view => ids.indexOf(view.id) !== -1);
|
||||
|
||||
if (viewsToDeregister.length) {
|
||||
this._views.set(location, views.filter(view => ids.indexOf(view.id) === -1));
|
||||
}
|
||||
|
||||
this._onViewsDeregistered.fire(viewsToDeregister);
|
||||
}
|
||||
|
||||
registerTreeViewDataProvider(id: string, factory: ITreeViewDataProvider) {
|
||||
if (!this.isDataProviderRegistered(id)) {
|
||||
// TODO: throw error
|
||||
}
|
||||
this._treeViewDataPoviders.set(id, factory);
|
||||
this._onTreeViewDataProviderRegistered.fire(id);
|
||||
}
|
||||
|
||||
deregisterTreeViewDataProviders(): void {
|
||||
this._treeViewDataPoviders.clear();
|
||||
}
|
||||
|
||||
getViews(loc: ViewLocation): IViewDescriptor[] {
|
||||
return this._views.get(loc) || [];
|
||||
}
|
||||
|
||||
getTreeViewDataProvider(id: string): ITreeViewDataProvider {
|
||||
return this._treeViewDataPoviders.get(id);
|
||||
}
|
||||
|
||||
private isDataProviderRegistered(id: string): boolean {
|
||||
let registered = false;
|
||||
this._views.forEach(views => registered = registered || views.some(view => view.id === id));
|
||||
return registered;
|
||||
}
|
||||
};
|
||||
@@ -3,7 +3,6 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as nls from 'vs/nls';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import * as errors from 'vs/base/common/errors';
|
||||
import * as DOM from 'vs/base/browser/dom';
|
||||
@@ -11,13 +10,12 @@ import { $, Dimension, Builder } from 'vs/base/browser/builder';
|
||||
import { Scope } from 'vs/workbench/common/memento';
|
||||
import { dispose, IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { IAction, IActionRunner } from 'vs/base/common/actions';
|
||||
import { IActionItem } from 'vs/base/browser/ui/actionbar/actionbar';
|
||||
import { IActionItem, Separator } from 'vs/base/browser/ui/actionbar/actionbar';
|
||||
import { firstIndex } from 'vs/base/common/arrays';
|
||||
import { DelayedDragHandler } from 'vs/base/browser/dnd';
|
||||
import { IExtensionService } from 'vs/platform/extensions/common/extensions';
|
||||
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
|
||||
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
||||
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
|
||||
import { ViewsRegistry, ViewLocation, IViewDescriptor } from 'vs/workbench/browser/parts/views/viewsRegistry';
|
||||
import { ViewsRegistry, ViewLocation, IViewDescriptor, IViewsViewlet } from 'vs/workbench/common/views';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
@@ -27,7 +25,13 @@ import { IContextKeyService, IContextKeyChangeEvent } from 'vs/platform/contextk
|
||||
import { StandardMouseEvent } from 'vs/base/browser/mouseEvent';
|
||||
import { PanelViewlet, ViewletPanel } from 'vs/workbench/browser/parts/views/panelViewlet';
|
||||
import { IPanelOptions } from 'vs/base/browser/ui/splitview/panelview';
|
||||
import { WorkbenchTree } from 'vs/platform/list/browser/listService';
|
||||
import { WorkbenchTree, IListService } from 'vs/platform/list/browser/listService';
|
||||
import { IWorkbenchThemeService, IFileIconTheme } from 'vs/workbench/services/themes/common/workbenchThemeService';
|
||||
import { ITreeConfiguration, ITreeOptions } from 'vs/base/parts/tree/browser/tree';
|
||||
import Event, { Emitter } from 'vs/base/common/event';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { IPartService } from 'vs/workbench/services/part/common/partService';
|
||||
import { localize } from 'vs/nls';
|
||||
|
||||
export interface IViewOptions extends IPanelOptions {
|
||||
id: string;
|
||||
@@ -45,9 +49,10 @@ export abstract class ViewsViewletPanel extends ViewletPanel {
|
||||
constructor(
|
||||
options: IViewOptions,
|
||||
protected keybindingService: IKeybindingService,
|
||||
protected contextMenuService: IContextMenuService
|
||||
protected contextMenuService: IContextMenuService,
|
||||
@IConfigurationService configurationService: IConfigurationService
|
||||
) {
|
||||
super(options.name, options, keybindingService, contextMenuService);
|
||||
super(options.name, options, keybindingService, contextMenuService, configurationService);
|
||||
|
||||
this.id = options.id;
|
||||
this.name = options.name;
|
||||
@@ -96,50 +101,15 @@ export abstract class ViewsViewletPanel extends ViewletPanel {
|
||||
|
||||
}
|
||||
|
||||
// TODO@isidor @sandeep remove this class
|
||||
export abstract class TreeViewsViewletPanel extends ViewsViewletPanel {
|
||||
|
||||
readonly id: string;
|
||||
readonly name: string;
|
||||
protected treeContainer: HTMLElement;
|
||||
|
||||
// TODO@sandeep why is tree here? isn't this coming only from TreeView
|
||||
protected tree: WorkbenchTree;
|
||||
protected isDisposed: boolean;
|
||||
private dragHandler: DelayedDragHandler;
|
||||
|
||||
constructor(
|
||||
options: IViewOptions,
|
||||
protected keybindingService: IKeybindingService,
|
||||
protected contextMenuService: IContextMenuService
|
||||
) {
|
||||
super(options, keybindingService, contextMenuService);
|
||||
|
||||
this.id = options.id;
|
||||
this.name = options.name;
|
||||
this._expanded = options.expanded;
|
||||
}
|
||||
|
||||
setExpanded(expanded: boolean): void {
|
||||
this.updateTreeVisibility(this.tree, expanded);
|
||||
super.setExpanded(expanded);
|
||||
}
|
||||
|
||||
protected renderHeader(container: HTMLElement): void {
|
||||
super.renderHeader(container);
|
||||
|
||||
// Expand on drag over
|
||||
this.dragHandler = new DelayedDragHandler(container, () => this.setExpanded(true));
|
||||
}
|
||||
|
||||
protected renderViewTree(container: HTMLElement): HTMLElement {
|
||||
const treeContainer = document.createElement('div');
|
||||
container.appendChild(treeContainer);
|
||||
return treeContainer;
|
||||
}
|
||||
|
||||
getViewer(): WorkbenchTree {
|
||||
return this.tree;
|
||||
if (this.isExpanded() !== expanded) {
|
||||
this.updateTreeVisibility(this.tree, expanded);
|
||||
super.setExpanded(expanded);
|
||||
}
|
||||
}
|
||||
|
||||
setVisible(visible: boolean): TPromise<void> {
|
||||
@@ -147,7 +117,6 @@ export abstract class TreeViewsViewletPanel extends ViewsViewletPanel {
|
||||
return super.setVisible(visible)
|
||||
.then(() => this.updateTreeVisibility(this.tree, visible && this.isExpanded()));
|
||||
}
|
||||
|
||||
return TPromise.wrap(null);
|
||||
}
|
||||
|
||||
@@ -156,64 +125,12 @@ export abstract class TreeViewsViewletPanel extends ViewsViewletPanel {
|
||||
this.focusTree();
|
||||
}
|
||||
|
||||
protected reveal(element: any, relativeTop?: number): TPromise<void> {
|
||||
if (!this.tree) {
|
||||
return TPromise.as(null); // return early if viewlet has not yet been created
|
||||
}
|
||||
|
||||
return this.tree.reveal(element, relativeTop);
|
||||
}
|
||||
|
||||
layoutBody(size: number): void {
|
||||
if (this.tree) {
|
||||
this.treeContainer.style.height = size + 'px';
|
||||
this.tree.layout(size);
|
||||
}
|
||||
}
|
||||
|
||||
getActions(): IAction[] {
|
||||
return [];
|
||||
}
|
||||
|
||||
getSecondaryActions(): IAction[] {
|
||||
return [];
|
||||
}
|
||||
|
||||
getActionItem(action: IAction): IActionItem {
|
||||
return null;
|
||||
}
|
||||
|
||||
getActionsContext(): any {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
getOptimalWidth(): number {
|
||||
return 0;
|
||||
}
|
||||
|
||||
create(): TPromise<void> {
|
||||
return TPromise.as(null);
|
||||
}
|
||||
|
||||
shutdown(): void {
|
||||
// Subclass to implement
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this.isDisposed = true;
|
||||
this.treeContainer = null;
|
||||
|
||||
if (this.tree) {
|
||||
this.tree.dispose();
|
||||
}
|
||||
|
||||
if (this.dragHandler) {
|
||||
this.dragHandler.dispose();
|
||||
}
|
||||
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
protected updateTreeVisibility(tree: WorkbenchTree, isVisible: boolean): void {
|
||||
if (!tree) {
|
||||
return;
|
||||
@@ -238,14 +155,21 @@ export abstract class TreeViewsViewletPanel extends ViewsViewletPanel {
|
||||
}
|
||||
|
||||
// Make sure the current selected element is revealed
|
||||
const selection = this.tree.getSelection();
|
||||
if (selection.length > 0) {
|
||||
this.reveal(selection[0], 0.5).done(null, errors.onUnexpectedError);
|
||||
const selectedElement = this.tree.getSelection()[0];
|
||||
if (selectedElement) {
|
||||
this.tree.reveal(selectedElement, 0.5).done(null, errors.onUnexpectedError);
|
||||
}
|
||||
|
||||
// Pass Focus to Viewer
|
||||
this.tree.DOMFocus();
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
if (this.tree) {
|
||||
this.tree.dispose();
|
||||
}
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
export interface IViewletViewOptions extends IViewOptions {
|
||||
@@ -259,20 +183,25 @@ export interface IViewState {
|
||||
order: number;
|
||||
}
|
||||
|
||||
export class ViewsViewlet extends PanelViewlet {
|
||||
export class ViewsViewlet extends PanelViewlet implements IViewsViewlet {
|
||||
|
||||
private viewHeaderContextMenuListeners: IDisposable[] = [];
|
||||
private viewletSettings: object;
|
||||
private readonly viewsContextKeys: Set<string> = new Set<string>();
|
||||
private viewsViewletPanels: ViewsViewletPanel[] = [];
|
||||
private didLayout = false;
|
||||
private dimension: Dimension;
|
||||
protected viewsStates: Map<string, IViewState> = new Map<string, IViewState>();
|
||||
private areExtensionsReady: boolean = false;
|
||||
|
||||
private _onDidChangeViewVisibilityState: Emitter<string> = new Emitter<string>();
|
||||
readonly onDidChangeViewVisibilityState: Event<string> = this._onDidChangeViewVisibilityState.event;
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
private location: ViewLocation,
|
||||
private showHeaderInTitleWhenSingleView: boolean,
|
||||
@IPartService partService: IPartService,
|
||||
@ITelemetryService telemetryService: ITelemetryService,
|
||||
@IStorageService protected storageService: IStorageService,
|
||||
@IInstantiationService protected instantiationService: IInstantiationService,
|
||||
@@ -281,7 +210,7 @@ export class ViewsViewlet extends PanelViewlet {
|
||||
@IContextMenuService protected contextMenuService: IContextMenuService,
|
||||
@IExtensionService protected extensionService: IExtensionService
|
||||
) {
|
||||
super(id, { showHeaderInTitleWhenSingleView, dnd: true }, telemetryService, themeService);
|
||||
super(id, { showHeaderInTitleWhenSingleView, dnd: true }, partService, contextMenuService, telemetryService, themeService);
|
||||
|
||||
this.viewletSettings = this.getMemento(storageService, Scope.WORKSPACE);
|
||||
}
|
||||
@@ -289,7 +218,7 @@ export class ViewsViewlet extends PanelViewlet {
|
||||
async create(parent: Builder): TPromise<void> {
|
||||
await super.create(parent);
|
||||
|
||||
this._register(this.onDidSashChange(() => this.updateAllViewsSizes()));
|
||||
this._register(this.onDidSashChange(() => this.snapshotViewsStates()));
|
||||
this._register(ViewsRegistry.onViewsRegistered(this.onViewsRegistered, this));
|
||||
this._register(ViewsRegistry.onViewsDeregistered(this.onViewsDeregistered, this));
|
||||
this._register(this.contextKeyService.onDidChangeContext(this.onContextChanged, this));
|
||||
@@ -305,15 +234,23 @@ export class ViewsViewlet extends PanelViewlet {
|
||||
}
|
||||
|
||||
getContextMenuActions(): IAction[] {
|
||||
return this.getViewDescriptorsFromRegistry(true)
|
||||
.filter(viewDescriptor => viewDescriptor.canToggleVisibility && this.contextKeyService.contextMatchesRules(viewDescriptor.when))
|
||||
const result: IAction[] = [];
|
||||
const viewToggleActions = this.getViewDescriptorsFromRegistry()
|
||||
.filter(viewDescriptor => this.contextKeyService.contextMatchesRules(viewDescriptor.when))
|
||||
.map(viewDescriptor => (<IAction>{
|
||||
id: `${viewDescriptor.id}.toggleVisibility`,
|
||||
label: viewDescriptor.name,
|
||||
checked: this.isCurrentlyVisible(viewDescriptor),
|
||||
enabled: true,
|
||||
enabled: viewDescriptor.canToggleVisibility,
|
||||
run: () => this.toggleViewVisibility(viewDescriptor.id)
|
||||
}));
|
||||
result.push(...viewToggleActions);
|
||||
const parentActions = super.getContextMenuActions();
|
||||
if (viewToggleActions.length && parentActions.length) {
|
||||
result.push(new Separator());
|
||||
}
|
||||
result.push(...parentActions);
|
||||
return result;
|
||||
}
|
||||
|
||||
setVisible(visible: boolean): TPromise<void> {
|
||||
@@ -323,15 +260,32 @@ export class ViewsViewlet extends PanelViewlet {
|
||||
.then(() => void 0);
|
||||
}
|
||||
|
||||
openView(id: string, focus?: boolean): TPromise<void> {
|
||||
if (focus) {
|
||||
this.focus();
|
||||
}
|
||||
const view = this.getView(id);
|
||||
if (view) {
|
||||
view.setExpanded(true);
|
||||
if (focus) {
|
||||
view.focus();
|
||||
}
|
||||
return TPromise.as(null);
|
||||
} else {
|
||||
return this.toggleViewVisibility(id, focus);
|
||||
}
|
||||
}
|
||||
|
||||
layout(dimension: Dimension): void {
|
||||
super.layout(dimension);
|
||||
|
||||
if (!this.didLayout) {
|
||||
this.dimension = dimension;
|
||||
if (this.didLayout) {
|
||||
this.snapshotViewsStates();
|
||||
} else {
|
||||
this.didLayout = true;
|
||||
this._resizePanels();
|
||||
this.resizePanels();
|
||||
}
|
||||
|
||||
this.updateAllViewsSizes();
|
||||
}
|
||||
|
||||
getOptimalWidth(): number {
|
||||
@@ -345,26 +299,25 @@ export class ViewsViewlet extends PanelViewlet {
|
||||
super.shutdown();
|
||||
}
|
||||
|
||||
toggleViewVisibility(id: string, visible?: boolean): void {
|
||||
const view = this.getView(id);
|
||||
toggleViewVisibility(id: string, focus?: boolean): TPromise<void> {
|
||||
let viewState = this.viewsStates.get(id);
|
||||
|
||||
if ((visible === true && view) || (visible === false && !view)) {
|
||||
return;
|
||||
if (!viewState) {
|
||||
return TPromise.as(null);
|
||||
}
|
||||
|
||||
if (view) {
|
||||
viewState = viewState || this.createViewState(view);
|
||||
viewState.isHidden = true;
|
||||
} else {
|
||||
viewState = viewState || { collapsed: true, size: void 0, isHidden: false, order: void 0 };
|
||||
viewState.isHidden = false;
|
||||
}
|
||||
this.viewsStates.set(id, viewState);
|
||||
this.updateViews();
|
||||
viewState.isHidden = !!this.getView(id);
|
||||
return this.updateViews()
|
||||
.then(() => {
|
||||
this._onDidChangeViewVisibilityState.fire(id);
|
||||
if (!viewState.isHidden) {
|
||||
this.openView(id, focus);
|
||||
} else if (focus) {
|
||||
this.focus();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private onViewsRegistered(views: IViewDescriptor[]): TPromise<ViewsViewletPanel[]> {
|
||||
private onViewsRegistered(views: IViewDescriptor[]): void {
|
||||
this.viewsContextKeys.clear();
|
||||
for (const viewDescriptor of this.getViewDescriptorsFromRegistry()) {
|
||||
if (viewDescriptor.when) {
|
||||
@@ -374,11 +327,11 @@ export class ViewsViewlet extends PanelViewlet {
|
||||
}
|
||||
}
|
||||
|
||||
return this.updateViews();
|
||||
this.updateViews();
|
||||
}
|
||||
|
||||
private onViewsDeregistered(views: IViewDescriptor[]): TPromise<ViewsViewletPanel[]> {
|
||||
return this.updateViews(views);
|
||||
private onViewsDeregistered(views: IViewDescriptor[]): void {
|
||||
this.updateViews(views);
|
||||
}
|
||||
|
||||
private onContextChanged(event: IContextKeyChangeEvent): void {
|
||||
@@ -414,21 +367,15 @@ export class ViewsViewlet extends PanelViewlet {
|
||||
const toCreate: ViewsViewletPanel[] = [];
|
||||
|
||||
if (toAdd.length || toRemove.length) {
|
||||
const panels = [...this.viewsViewletPanels];
|
||||
|
||||
for (const view of panels) {
|
||||
let viewState = this.viewsStates.get(view.id);
|
||||
if (!viewState || typeof viewState.size === 'undefined' || !view.isExpanded() !== viewState.collapsed) {
|
||||
this.updateViewStateSize(view);
|
||||
}
|
||||
}
|
||||
this.snapshotViewsStates();
|
||||
|
||||
if (toRemove.length) {
|
||||
for (const viewDescriptor of toRemove) {
|
||||
let view = this.getView(viewDescriptor.id);
|
||||
this.updateViewStateSize(view);
|
||||
this.removePanel(view);
|
||||
this.viewsViewletPanels.splice(this.viewsViewletPanels.indexOf(view), 1);
|
||||
view.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -445,38 +392,58 @@ export class ViewsViewlet extends PanelViewlet {
|
||||
});
|
||||
toCreate.push(view);
|
||||
|
||||
const size = (viewState && viewState.size) || viewDescriptor.size || 200;
|
||||
const size = (viewState && viewState.size) || 200;
|
||||
this.addPanel(view, size, index);
|
||||
this.viewsViewletPanels.splice(index, 0, view);
|
||||
|
||||
this.updateViewStateSize(view);
|
||||
}
|
||||
|
||||
return TPromise.join(toCreate.map(view => view.create()))
|
||||
.then(() => this.onViewsUpdated())
|
||||
.then(() => this._resizePanels())
|
||||
.then(() => toCreate);
|
||||
.then(() => {
|
||||
this.resizePanels(toCreate);
|
||||
return toCreate;
|
||||
});
|
||||
}
|
||||
|
||||
return TPromise.as([]);
|
||||
}
|
||||
|
||||
private updateAllViewsSizes(): void {
|
||||
for (const view of this.viewsViewletPanels) {
|
||||
this.updateViewStateSize(view);
|
||||
}
|
||||
}
|
||||
|
||||
private _resizePanels(): void {
|
||||
private resizePanels(panels: ViewsViewletPanel[] = this.viewsViewletPanels): void {
|
||||
if (!this.didLayout) {
|
||||
// Do not do anything if layout has not happened yet
|
||||
return;
|
||||
}
|
||||
|
||||
for (const panel of this.viewsViewletPanels) {
|
||||
let initialSizes;
|
||||
for (const panel of panels) {
|
||||
const viewState = this.viewsStates.get(panel.id);
|
||||
const size = (viewState && viewState.size) || 200;
|
||||
this.resizePanel(panel, size);
|
||||
if (viewState && viewState.size) {
|
||||
this.resizePanel(panel, viewState.size);
|
||||
} else {
|
||||
initialSizes = initialSizes ? initialSizes : this.computeInitialSizes();
|
||||
this.resizePanel(panel, initialSizes[panel.id] || 200);
|
||||
}
|
||||
}
|
||||
|
||||
this.snapshotViewsStates();
|
||||
}
|
||||
|
||||
private computeInitialSizes(): { [id: string]: number } {
|
||||
let sizes = {};
|
||||
if (this.dimension) {
|
||||
let totalWeight = 0;
|
||||
const allViewDescriptors = this.getViewDescriptorsFromRegistry();
|
||||
const viewDescriptors: IViewDescriptor[] = [];
|
||||
for (const panel of this.viewsViewletPanels) {
|
||||
const viewDescriptor = allViewDescriptors.filter(viewDescriptor => viewDescriptor.id === panel.id)[0];
|
||||
totalWeight = totalWeight + (viewDescriptor.weight || 20);
|
||||
viewDescriptors.push(viewDescriptor);
|
||||
}
|
||||
for (const viewDescriptor of viewDescriptors) {
|
||||
sizes[viewDescriptor.id] = this.dimension.height * (viewDescriptor.weight || 20) / totalWeight;
|
||||
}
|
||||
}
|
||||
return sizes;
|
||||
}
|
||||
|
||||
movePanel(from: ViewletPanel, to: ViewletPanel): void {
|
||||
@@ -529,9 +496,7 @@ export class ViewsViewlet extends PanelViewlet {
|
||||
this.viewHeaderContextMenuListeners.push(DOM.addDisposableListener(view.draggableElement, DOM.EventType.CONTEXT_MENU, e => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
if (viewDescriptor.canToggleVisibility) {
|
||||
this.onContextMenu(new StandardMouseEvent(e), view);
|
||||
}
|
||||
this.onContextMenu(new StandardMouseEvent(e), viewDescriptor);
|
||||
}));
|
||||
}
|
||||
}
|
||||
@@ -546,19 +511,26 @@ export class ViewsViewlet extends PanelViewlet {
|
||||
}
|
||||
}
|
||||
|
||||
private onContextMenu(event: StandardMouseEvent, view: ViewsViewletPanel): void {
|
||||
private onContextMenu(event: StandardMouseEvent, viewDescriptor: IViewDescriptor): void {
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
|
||||
const actions: IAction[] = [];
|
||||
actions.push(<IAction>{
|
||||
id: `${viewDescriptor.id}.removeView`,
|
||||
label: localize('hideView', "Hide"),
|
||||
enabled: viewDescriptor.canToggleVisibility,
|
||||
run: () => this.toggleViewVisibility(viewDescriptor.id)
|
||||
});
|
||||
const otherActions = this.getContextMenuActions();
|
||||
if (otherActions.length) {
|
||||
actions.push(...[new Separator(), ...otherActions]);
|
||||
}
|
||||
|
||||
let anchor: { x: number, y: number } = { x: event.posx, y: event.posy };
|
||||
this.contextMenuService.showContextMenu({
|
||||
getAnchor: () => anchor,
|
||||
getActions: () => TPromise.as([<IAction>{
|
||||
id: `${view.id}.removeView`,
|
||||
label: nls.localize('hideView', "Hide from Side Bar"),
|
||||
enabled: true,
|
||||
run: () => this.toggleViewVisibility(view.id)
|
||||
}]),
|
||||
getActions: () => TPromise.as(actions)
|
||||
});
|
||||
}
|
||||
|
||||
@@ -572,26 +544,32 @@ export class ViewsViewlet extends PanelViewlet {
|
||||
if (this.length > 1) {
|
||||
return false;
|
||||
}
|
||||
// Check in cache so that view do not jump. See #29609
|
||||
if (ViewLocation.getContributedViewLocation(this.location.id) && !this.areExtensionsReady) {
|
||||
|
||||
if (ViewLocation.getContributedViewLocation(this.location.id)) {
|
||||
let visibleViewsCount = 0;
|
||||
this.viewsStates.forEach((viewState, id) => {
|
||||
if (!viewState.isHidden) {
|
||||
visibleViewsCount++;
|
||||
}
|
||||
});
|
||||
if (this.areExtensionsReady) {
|
||||
visibleViewsCount = this.getViewDescriptorsFromRegistry().reduce((visibleViewsCount, v) => visibleViewsCount + (this.canBeVisible(v) ? 1 : 0), 0);
|
||||
} else {
|
||||
// Check in cache so that view do not jump. See #29609
|
||||
this.viewsStates.forEach((viewState, id) => {
|
||||
if (!viewState.isHidden) {
|
||||
visibleViewsCount++;
|
||||
}
|
||||
});
|
||||
}
|
||||
return visibleViewsCount === 1;
|
||||
}
|
||||
|
||||
return super.isSingleView();
|
||||
}
|
||||
|
||||
protected getViewDescriptorsFromRegistry(defaultOrder: boolean = false): IViewDescriptor[] {
|
||||
protected getViewDescriptorsFromRegistry(): IViewDescriptor[] {
|
||||
return ViewsRegistry.getViews(this.location)
|
||||
.sort((a, b) => {
|
||||
const viewStateA = this.viewsStates.get(a.id);
|
||||
const viewStateB = this.viewsStates.get(b.id);
|
||||
const orderA = !defaultOrder && viewStateA ? viewStateA.order : a.order;
|
||||
const orderB = !defaultOrder && viewStateB ? viewStateB.order : b.order;
|
||||
const orderA = viewStateA ? viewStateA.order : a.order;
|
||||
const orderB = viewStateB ? viewStateB.order : b.order;
|
||||
|
||||
if (orderB === void 0 || orderB === null) {
|
||||
return -1;
|
||||
@@ -616,35 +594,43 @@ export class ViewsViewlet extends PanelViewlet {
|
||||
return this.viewsViewletPanels.filter(view => view.id === id)[0];
|
||||
}
|
||||
|
||||
private updateViewStateSize(view: ViewsViewletPanel): void {
|
||||
const currentState = this.viewsStates.get(view.id);
|
||||
if (currentState && !this.didLayout) {
|
||||
// Do not update to new state if the layout has not happened yet
|
||||
return;
|
||||
private snapshotViewsStates(): void {
|
||||
for (const view of this.viewsViewletPanels) {
|
||||
const currentState = this.viewsStates.get(view.id);
|
||||
if (currentState && !this.didLayout) {
|
||||
// Do not update to new state if the layout has not happened yet
|
||||
return;
|
||||
}
|
||||
|
||||
const collapsed = !view.isExpanded();
|
||||
const order = this.viewsViewletPanels.indexOf(view);
|
||||
const panelSize = this.getPanelSize(view);
|
||||
if (currentState) {
|
||||
currentState.collapsed = collapsed;
|
||||
currentState.size = collapsed ? currentState.size : panelSize;
|
||||
currentState.order = order;
|
||||
} else {
|
||||
this.viewsStates.set(view.id, {
|
||||
collapsed,
|
||||
size: this.didLayout ? panelSize : void 0,
|
||||
isHidden: false,
|
||||
order,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const newViewState = this.createViewState(view);
|
||||
const stateToUpdate = currentState ? { ...currentState, collapsed: newViewState.collapsed, size: newViewState.size } : newViewState;
|
||||
this.viewsStates.set(view.id, stateToUpdate);
|
||||
}
|
||||
|
||||
protected createViewState(view: ViewsViewletPanel): IViewState {
|
||||
return {
|
||||
collapsed: !view.isExpanded(),
|
||||
size: this.getPanelSize(view),
|
||||
isHidden: false,
|
||||
order: this.viewsViewletPanels.indexOf(view)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export class PersistentViewsViewlet extends ViewsViewlet {
|
||||
|
||||
private readonly hiddenViewsStorageId: string;
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
location: ViewLocation,
|
||||
private viewletStateStorageId: string,
|
||||
private readonly viewletStateStorageId: string,
|
||||
showHeaderInTitleWhenSingleView: boolean,
|
||||
@IPartService partService: IPartService,
|
||||
@ITelemetryService telemetryService: ITelemetryService,
|
||||
@IStorageService storageService: IStorageService,
|
||||
@IInstantiationService instantiationService: IInstantiationService,
|
||||
@@ -654,7 +640,9 @@ export class PersistentViewsViewlet extends ViewsViewlet {
|
||||
@IContextMenuService contextMenuService: IContextMenuService,
|
||||
@IExtensionService extensionService: IExtensionService
|
||||
) {
|
||||
super(id, location, showHeaderInTitleWhenSingleView, telemetryService, storageService, instantiationService, themeService, contextKeyService, contextMenuService, extensionService);
|
||||
super(id, location, showHeaderInTitleWhenSingleView, partService, telemetryService, storageService, instantiationService, themeService, contextKeyService, contextMenuService, extensionService);
|
||||
this.hiddenViewsStorageId = `${this.viewletStateStorageId}.hidden`;
|
||||
this._register(this.onDidChangeViewVisibilityState(id => this.onViewVisibilityChanged(id)));
|
||||
}
|
||||
|
||||
create(parent: Builder): TPromise<void> {
|
||||
@@ -674,7 +662,12 @@ export class PersistentViewsViewlet extends ViewsViewlet {
|
||||
const view = this.getView(id);
|
||||
|
||||
if (view) {
|
||||
viewsStates[id] = this.createViewState(view);
|
||||
viewsStates[id] = {
|
||||
collapsed: !view.isExpanded(),
|
||||
size: this.getPanelSize(view),
|
||||
isHidden: false,
|
||||
order: viewState.order
|
||||
};
|
||||
} else {
|
||||
const viewDescriptor = registeredViewDescriptors.filter(v => v.id === id)[0];
|
||||
if (viewDescriptor) {
|
||||
@@ -688,6 +681,53 @@ export class PersistentViewsViewlet extends ViewsViewlet {
|
||||
|
||||
protected loadViewsStates(): void {
|
||||
const viewsStates = JSON.parse(this.storageService.get(this.viewletStateStorageId, this.contextService.getWorkbenchState() !== WorkbenchState.EMPTY ? StorageScope.WORKSPACE : StorageScope.GLOBAL, '{}'));
|
||||
Object.keys(viewsStates).forEach(id => this.viewsStates.set(id, <IViewState>viewsStates[id]));
|
||||
const hiddenViews = this.loadHiddenViews();
|
||||
Object.keys(viewsStates).forEach(id => this.viewsStates.set(id, <IViewState>{ ...viewsStates[id], ...{ isHidden: hiddenViews.indexOf(id) !== -1 } }));
|
||||
}
|
||||
|
||||
private onViewVisibilityChanged(id: string) {
|
||||
const hiddenViews = this.loadHiddenViews();
|
||||
const index = hiddenViews.indexOf(id);
|
||||
if (this.getView(id) && index !== -1) {
|
||||
hiddenViews.splice(index, 1);
|
||||
} else if (index === -1) {
|
||||
hiddenViews.push(id);
|
||||
}
|
||||
this.storeHiddenViews(hiddenViews);
|
||||
}
|
||||
|
||||
private storeHiddenViews(hiddenViews: string[]): void {
|
||||
this.storageService.store(this.hiddenViewsStorageId, JSON.stringify(hiddenViews), StorageScope.GLOBAL);
|
||||
}
|
||||
|
||||
private loadHiddenViews(): string[] {
|
||||
return JSON.parse(this.storageService.get(this.hiddenViewsStorageId, StorageScope.GLOBAL, '[]'));
|
||||
}
|
||||
}
|
||||
|
||||
export class FileIconThemableWorkbenchTree extends WorkbenchTree {
|
||||
|
||||
constructor(
|
||||
container: HTMLElement,
|
||||
configuration: ITreeConfiguration,
|
||||
options: ITreeOptions,
|
||||
@IContextKeyService contextKeyService: IContextKeyService,
|
||||
@IListService listService: IListService,
|
||||
@IThemeService themeService: IWorkbenchThemeService,
|
||||
@IConfigurationService configurationService: IConfigurationService,
|
||||
@IInstantiationService instantiationService: IInstantiationService
|
||||
) {
|
||||
super(container, configuration, { ...options, ...{ showTwistie: false, twistiePixels: 12 } }, contextKeyService, listService, themeService, instantiationService, configurationService);
|
||||
|
||||
DOM.addClass(container, 'file-icon-themable-tree');
|
||||
DOM.addClass(container, 'show-file-icons');
|
||||
|
||||
const onFileIconThemeChange = (fileIconTheme: IFileIconTheme) => {
|
||||
DOM.toggleClass(container, 'align-icons-and-twisties', fileIconTheme.hasFileIcons && !fileIconTheme.hasFolderIcons);
|
||||
DOM.toggleClass(container, 'hide-arrows', fileIconTheme.hidesExplorerArrows === true);
|
||||
};
|
||||
|
||||
this.disposables.push(themeService.onDidFileIconThemeChange(onFileIconThemeChange));
|
||||
onFileIconThemeChange(themeService.getFileIconTheme());
|
||||
}
|
||||
}
|
||||
|
||||