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
This commit is contained in:
Karl Burtram
2018-04-04 15:27:51 -07:00
committed by GitHub
parent 5fba3e31b4
commit dafb780987
9412 changed files with 141255 additions and 98813 deletions

View File

@@ -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();
}
}

View File

@@ -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 {

View File

@@ -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);
}

View File

@@ -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());
}
}

View File

@@ -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();
}
}
}

View File

@@ -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 } });

View File

@@ -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);
}
}

View File

@@ -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] : [];
}

View File

@@ -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);
}

View File

@@ -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);

View File

@@ -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)

View File

@@ -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}`;
}
}));

View File

@@ -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

View File

@@ -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

View File

@@ -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 */
}
}

View File

@@ -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 {

View File

@@ -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;
}

View File

@@ -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 */
}

View File

@@ -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;
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}

View File

@@ -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'
});

View 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;
}
}

View File

@@ -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;

View File

@@ -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);
}
`);
}
}
});

View File

@@ -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;
}
}
}

View File

@@ -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];
}

View File

@@ -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);
}
}

View File

@@ -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;
});
}

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View 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

View 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:#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

View 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

View File

@@ -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');
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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}; }`);
}
});

View File

@@ -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 } });
}

View File

@@ -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};
}`);
}
});

View File

@@ -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();
}
}
}

View File

@@ -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';
}
}

View File

@@ -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);
}
}

View File

@@ -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 {

View File

@@ -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(

View File

@@ -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) => {
}
`);
}
});
});

View File

@@ -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;
}

View File

@@ -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 {

View File

@@ -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;
}

View File

@@ -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)));
}
}

View File

@@ -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:
*

View 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;
}
}

View 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);
}
}

View File

@@ -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

View File

@@ -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

View 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

View File

@@ -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

View File

@@ -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

View 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

View File

@@ -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;
}

View File

@@ -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);

View File

@@ -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);
}
}

View File

@@ -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;
}
};

View File

@@ -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());
}
}