diff --git a/src/vs/platform/editor/common/editor.ts b/src/vs/platform/editor/common/editor.ts index 44665b5be8e0bc9c2e9aeac90939db7716098330..0a816d707a1f16f8d151127f1550282a71829f2d 100644 --- a/src/vs/platform/editor/common/editor.ts +++ b/src/vs/platform/editor/common/editor.ts @@ -22,6 +22,17 @@ export interface IEditorService { openEditor(input: IResourceInput, sideBySide?: boolean): TPromise; } +export interface IEditorInputWithOptions { + editor: IEditorInput; + options?: IEditorOptions; +} + +export function isEditorInputWithOptions(obj: any): obj is IEditorInputWithOptions { + const editorInputWithOptions = obj as IEditorInputWithOptions; + + return !!editorInputWithOptions && !!editorInputWithOptions.editor; +} + export interface IEditorModel { onDispose: Event; diff --git a/src/vs/workbench/browser/dnd.ts b/src/vs/workbench/browser/dnd.ts index 1ab41bc152a94bea0d5f89665fac18c590e9e0c3..a63c01f261d981ea18097882cab1825191138fc0 100644 --- a/src/vs/workbench/browser/dnd.ts +++ b/src/vs/workbench/browser/dnd.ts @@ -12,12 +12,10 @@ import { IWindowsService, IWindowService } from 'vs/platform/windows/common/wind import URI from 'vs/base/common/uri'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import { IBackupFileService } from 'vs/workbench/services/backup/common/backup'; -import { IEditorGroupService } from 'vs/workbench/services/group/common/groupService'; import { TPromise } from 'vs/base/common/winjs.base'; import { Schemas } from 'vs/base/common/network'; import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService'; import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { Position } from 'vs/platform/editor/common/editor'; import { onUnexpectedError } from 'vs/base/common/errors'; import { DefaultEndOfLine } from 'vs/editor/common/model'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; @@ -32,8 +30,9 @@ import { isWindows } from 'vs/base/common/platform'; import { coalesce } from 'vs/base/common/arrays'; import { ServicesAccessor, IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { getCodeEditor } from 'vs/editor/browser/services/codeEditorService'; -import { IEditorIdentifier } from 'vs/workbench/common/editor'; +import { IEditorIdentifier, GroupIdentifier } from 'vs/workbench/common/editor'; import { basenameOrAuthority } from 'vs/base/common/resources'; +import { INextEditorService, IResourceEditor } from 'vs/workbench/services/editor/common/nextEditorService'; export interface IDraggedResource { resource: URI; @@ -41,10 +40,9 @@ export interface IDraggedResource { } export class DraggedEditorIdentifier { - constructor(private _identifier: IEditorIdentifier) { - } + constructor(private _identifier: IEditorIdentifier) { } - public get identifier(): IEditorIdentifier { + get identifier(): IEditorIdentifier { return this._identifier; } } @@ -155,14 +153,13 @@ export class ResourcesDropHandler { @IWorkspacesService private workspacesService: IWorkspacesService, @ITextFileService private textFileService: ITextFileService, @IBackupFileService private backupFileService: IBackupFileService, - @IEditorGroupService private groupService: IEditorGroupService, @IUntitledEditorService private untitledEditorService: IUntitledEditorService, - @IWorkbenchEditorService private editorService: IWorkbenchEditorService, + @INextEditorService private editorService: INextEditorService, @IConfigurationService private configurationService: IConfigurationService ) { } - public handleDrop(event: DragEvent, afterDrop: () => void, targetPosition: Position, targetIndex?: number): void { + handleDrop(event: DragEvent, afterDrop: (targetGroup: GroupIdentifier) => void, resolveTargetGroup: () => GroupIdentifier, targetIndex?: number): void { const untitledOrFileResources = extractResources(event).filter(r => this.fileService.canHandleResource(r.resource) || r.resource.scheme === Schemas.untitled); if (!untitledOrFileResources.length) { return; @@ -183,23 +180,21 @@ export class ResourcesDropHandler { this.windowsService.addRecentlyOpened(filesToAddToHistory.map(resource => resource.fsPath)); } + const editors: IResourceEditor[] = untitledOrFileResources.map(untitledOrFileResource => ({ + resource: untitledOrFileResource.resource, + options: { + pinned: true, + index: targetIndex, + viewState: (untitledOrFileResource as IDraggedEditor).viewState + } + })); + // Open in Editor - return this.editorService.openEditors(untitledOrFileResources.map(untitledOrFileResource => { - return { - input: { - resource: untitledOrFileResource.resource, - options: { - pinned: true, - index: targetIndex, - viewState: (untitledOrFileResource as IDraggedEditor).viewState - } - }, - position: targetPosition - }; - })).then(() => { + const targetGroup = resolveTargetGroup(); + return this.editorService.openEditors(editors, targetGroup).then(() => { // Finish with provided function - afterDrop(); + afterDrop(targetGroup); }); }); }).done(null, onUnexpectedError); @@ -232,7 +227,7 @@ export class ResourcesDropHandler { } // Return early if the resource is already dirty in target or opened already - if (this.textFileService.isDirty(droppedDirtyEditor.resource) || this.groupService.getStacksModel().isOpen(droppedDirtyEditor.resource)) { + if (this.textFileService.isDirty(droppedDirtyEditor.resource) || this.editorService.isOpen({ resource: droppedDirtyEditor.resource })) { return TPromise.as(false); } @@ -316,7 +311,7 @@ export class SimpleFileResourceDragAndDrop extends DefaultDragAndDrop { super(); } - public getDragURI(tree: ITree, obj: any): string { + getDragURI(tree: ITree, obj: any): string { const resource = this.toResource(obj); if (resource) { return resource.toString(); @@ -325,7 +320,7 @@ export class SimpleFileResourceDragAndDrop extends DefaultDragAndDrop { return void 0; } - public getDragLabel(tree: ITree, elements: any[]): string { + getDragLabel(tree: ITree, elements: any[]): string { if (elements.length > 1) { return String(elements.length); } @@ -338,7 +333,7 @@ export class SimpleFileResourceDragAndDrop extends DefaultDragAndDrop { return void 0; } - public onDragStart(tree: ITree, data: IDragAndDropData, originalEvent: DragMouseEvent): void { + onDragStart(tree: ITree, data: IDragAndDropData, originalEvent: DragMouseEvent): void { // Apply some datatransfer types to allow for dragging the element outside of the application const resources: URI[] = data.getData().map(source => this.toResource(source)); @@ -428,20 +423,20 @@ export class LocalSelectionTransfer { // protect against external instantiation } - public static getInstance(): LocalSelectionTransfer { + static getInstance(): LocalSelectionTransfer { return LocalSelectionTransfer.INSTANCE as LocalSelectionTransfer; } - public hasData(proto: T): boolean { + hasData(proto: T): boolean { return proto && proto === this.proto; } - public clearData(): void { + clearData(): void { this.proto = void 0; this.data = void 0; } - public getData(proto: T): T[] { + getData(proto: T): T[] { if (this.hasData(proto)) { return this.data; } @@ -449,7 +444,7 @@ export class LocalSelectionTransfer { return void 0; } - public setData(data: T[], proto: T): void { + setData(data: T[], proto: T): void { if (proto) { this.data = data; this.proto = proto; diff --git a/src/vs/workbench/browser/parts/editor/editorActions.ts b/src/vs/workbench/browser/parts/editor/editorActions.ts index eafe24bceb441a4be89bb4d3a729485ee4284f24..fd6b7ed170482970a3af6e52a251915966506f11 100644 --- a/src/vs/workbench/browser/parts/editor/editorActions.ts +++ b/src/vs/workbench/browser/parts/editor/editorActions.ts @@ -137,7 +137,7 @@ export class BaseSplitEditorGroupAction extends Action { } // Add group - const newGroup = this.nextEditorGroupsService.addGroup(group, this.direction); + const newGroup = this.nextEditorGroupsService.addGroup(group, this.direction, { activate: true }); // Open editor if (activeEditor) { diff --git a/src/vs/workbench/browser/parts/editor/editorGroupsControl.ts b/src/vs/workbench/browser/parts/editor/editorGroupsControl.ts index 62c1a72a59fbcf73bcb993d374debee23d47e186..9e122534143cdb0c95e2127e369cc3dfe408c882 100644 --- a/src/vs/workbench/browser/parts/editor/editorGroupsControl.ts +++ b/src/vs/workbench/browser/parts/editor/editorGroupsControl.ts @@ -1209,7 +1209,7 @@ export class EditorGroupsControl extends Themable implements IEditorGroupsContro } groupService.focusGroup(splitEditor ? splitTo : position); - }, splitEditor ? freeGroup : position); + }, () => splitEditor ? freeGroup : position); } } diff --git a/src/vs/workbench/browser/parts/editor/editorPart.ts b/src/vs/workbench/browser/parts/editor/editorPart.ts index cb17165c8471adf1419741b34c2df3a85b917f10..c8ac278715ddf3ee10e8f905960ed80a515e5fff 100644 --- a/src/vs/workbench/browser/parts/editor/editorPart.ts +++ b/src/vs/workbench/browser/parts/editor/editorPart.ts @@ -404,7 +404,120 @@ export class EditorPart extends Part implements IEditorPart, IEditorGroupService //#endregion - //#region TODO@grid openEditors() + //#region Handled or Adopted or Obsolete + + public _serviceBrand: any; + + private static readonly GROUP_LEFT = nls.localize('groupOneVertical', "Left"); + private static readonly GROUP_CENTER = nls.localize('groupTwoVertical', "Center"); + private static readonly GROUP_RIGHT = nls.localize('groupThreeVertical', "Right"); + private static readonly GROUP_TOP = nls.localize('groupOneHorizontal', "Top"); + private static readonly GROUP_MIDDLE = nls.localize('groupTwoHorizontal', "Center"); + private static readonly GROUP_BOTTOM = nls.localize('groupThreeHorizontal', "Bottom"); + + private static readonly EDITOR_PART_UI_STATE_STORAGE_KEY = 'editorpart.uiState'; + + private dimension: Dimension; + private editorGroupsControl: IEditorGroupsControl; + private memento: object; + private stacks: EditorStacksModel; + private tabOptions: IEditorTabOptions; + private forceHideTabs: boolean; + private revealIfOpen: boolean; + private ignoreOpenEditorErrors: boolean; + private textCompareEditorVisible: IContextKey; + + private readonly _onEditorsChanged: ThrottledEmitter; + private readonly _onEditorOpening: Emitter; + private readonly _onEditorGroupMoved: Emitter; + private readonly _onEditorOpenFail: Emitter; + private readonly _onGroupOrientationChanged: Emitter; + private readonly _onTabOptionsChanged: Emitter; + private readonly _onLayout: Emitter; + + // The following data structures are partitioned into array of Position as provided by Services.POSITION array + private visibleEditors: BaseEditor[]; + private instantiatedEditors: BaseEditor[][]; + private editorOpenToken: number[]; + private pendingEditorInputsToClose: EditorIdentifier[]; + private pendingEditorInputCloseTimeout: number; + + public get onEditorGroupMoved(): Event { + return this._onEditorGroupMoved.event; + } + + public get onEditorsChanged(): Event { + return this._onEditorsChanged.event; + } + + constructor( + id: string, + restoreFromStorage: boolean, + @INotificationService private notificationService: INotificationService, + @ITelemetryService private telemetryService: ITelemetryService, + @IStorageService private storageService: IStorageService, + @IPartService private partService: IPartService, + @IConfigurationService private configurationService: IConfigurationService, + @IContextKeyService contextKeyService: IContextKeyService, + @IInstantiationService private instantiationService: IInstantiationService, + @IThemeService themeService: IThemeService, + @IEnvironmentService private environmentService: IEnvironmentService + ) { + super(id, { hasTitle: false }, themeService); + + this._onEditorsChanged = new ThrottledEmitter(); + this._onEditorOpening = new Emitter(); + this._onEditorGroupMoved = new Emitter(); + this._onEditorOpenFail = new Emitter(); + this._onGroupOrientationChanged = new Emitter(); + this._onTabOptionsChanged = new Emitter(); + this._onLayout = new Emitter(); + + this.visibleEditors = []; + + this.editorOpenToken = arrays.fill(POSITIONS.length, () => 0); + + this.instantiatedEditors = arrays.fill(POSITIONS.length, () => []); + + this.pendingEditorInputsToClose = []; + this.pendingEditorInputCloseTimeout = null; + + this.stacks = this.instantiationService.createInstance(EditorStacksModel, restoreFromStorage); + + this.textCompareEditorVisible = TextCompareEditorVisibleContext.bindTo(contextKeyService); + + const config = configurationService.getValue(); + if (config && config.workbench && config.workbench.editor) { + const editorConfig = config.workbench.editor; + + this.tabOptions = { + previewEditors: editorConfig.enablePreview, + showIcons: editorConfig.showIcons, + showTabs: editorConfig.showTabs, + tabCloseButton: editorConfig.tabCloseButton, + tabSizing: editorConfig.tabSizing, + labelFormat: editorConfig.labelFormat, + iconTheme: config.workbench.iconTheme + }; + + this.revealIfOpen = editorConfig.revealIfOpen; + } else { + this.tabOptions = { + previewEditors: true, + showIcons: false, + showTabs: true, + tabCloseButton: 'right', + tabSizing: 'fit', + labelFormat: 'default', + iconTheme: 'vs-seti' + }; + + this.revealIfOpen = false; + } + + this.initStyles(); + this.registerListeners(); + } public openEditors(editors: { input: EditorInput, position?: Position, options?: EditorOptions }[]): TPromise; public openEditors(editors: { input: EditorInput, options?: EditorOptions }[], sideBySide?: boolean): TPromise; @@ -528,123 +641,6 @@ export class EditorPart extends Part implements IEditorPart, IEditorGroupService }); } - //#endregion - - //#region Handled or Adopted or Obsolete - - public _serviceBrand: any; - - private static readonly GROUP_LEFT = nls.localize('groupOneVertical', "Left"); - private static readonly GROUP_CENTER = nls.localize('groupTwoVertical', "Center"); - private static readonly GROUP_RIGHT = nls.localize('groupThreeVertical', "Right"); - private static readonly GROUP_TOP = nls.localize('groupOneHorizontal', "Top"); - private static readonly GROUP_MIDDLE = nls.localize('groupTwoHorizontal', "Center"); - private static readonly GROUP_BOTTOM = nls.localize('groupThreeHorizontal', "Bottom"); - - private static readonly EDITOR_PART_UI_STATE_STORAGE_KEY = 'editorpart.uiState'; - - private dimension: Dimension; - private editorGroupsControl: IEditorGroupsControl; - private memento: object; - private stacks: EditorStacksModel; - private tabOptions: IEditorTabOptions; - private forceHideTabs: boolean; - private revealIfOpen: boolean; - private ignoreOpenEditorErrors: boolean; - private textCompareEditorVisible: IContextKey; - - private readonly _onEditorsChanged: ThrottledEmitter; - private readonly _onEditorOpening: Emitter; - private readonly _onEditorGroupMoved: Emitter; - private readonly _onEditorOpenFail: Emitter; - private readonly _onGroupOrientationChanged: Emitter; - private readonly _onTabOptionsChanged: Emitter; - private readonly _onLayout: Emitter; - - // The following data structures are partitioned into array of Position as provided by Services.POSITION array - private visibleEditors: BaseEditor[]; - private instantiatedEditors: BaseEditor[][]; - private editorOpenToken: number[]; - private pendingEditorInputsToClose: EditorIdentifier[]; - private pendingEditorInputCloseTimeout: number; - - public get onEditorGroupMoved(): Event { - return this._onEditorGroupMoved.event; - } - - public get onEditorsChanged(): Event { - return this._onEditorsChanged.event; - } - - constructor( - id: string, - restoreFromStorage: boolean, - @INotificationService private notificationService: INotificationService, - @ITelemetryService private telemetryService: ITelemetryService, - @IStorageService private storageService: IStorageService, - @IPartService private partService: IPartService, - @IConfigurationService private configurationService: IConfigurationService, - @IContextKeyService contextKeyService: IContextKeyService, - @IInstantiationService private instantiationService: IInstantiationService, - @IThemeService themeService: IThemeService, - @IEnvironmentService private environmentService: IEnvironmentService - ) { - super(id, { hasTitle: false }, themeService); - - this._onEditorsChanged = new ThrottledEmitter(); - this._onEditorOpening = new Emitter(); - this._onEditorGroupMoved = new Emitter(); - this._onEditorOpenFail = new Emitter(); - this._onGroupOrientationChanged = new Emitter(); - this._onTabOptionsChanged = new Emitter(); - this._onLayout = new Emitter(); - - this.visibleEditors = []; - - this.editorOpenToken = arrays.fill(POSITIONS.length, () => 0); - - this.instantiatedEditors = arrays.fill(POSITIONS.length, () => []); - - this.pendingEditorInputsToClose = []; - this.pendingEditorInputCloseTimeout = null; - - this.stacks = this.instantiationService.createInstance(EditorStacksModel, restoreFromStorage); - - this.textCompareEditorVisible = TextCompareEditorVisibleContext.bindTo(contextKeyService); - - const config = configurationService.getValue(); - if (config && config.workbench && config.workbench.editor) { - const editorConfig = config.workbench.editor; - - this.tabOptions = { - previewEditors: editorConfig.enablePreview, - showIcons: editorConfig.showIcons, - showTabs: editorConfig.showTabs, - tabCloseButton: editorConfig.tabCloseButton, - tabSizing: editorConfig.tabSizing, - labelFormat: editorConfig.labelFormat, - iconTheme: config.workbench.iconTheme - }; - - this.revealIfOpen = editorConfig.revealIfOpen; - } else { - this.tabOptions = { - previewEditors: true, - showIcons: false, - showTabs: true, - tabCloseButton: 'right', - tabSizing: 'fit', - labelFormat: 'default', - iconTheme: 'vs-seti' - }; - - this.revealIfOpen = false; - } - - this.initStyles(); - this.registerListeners(); - } - public moveGroup(from: EditorGroup, to: EditorGroup): void; public moveGroup(from: Position, to: Position): void; public moveGroup(arg1: any, arg2: any): void { diff --git a/src/vs/workbench/browser/parts/editor2/editor2.ts b/src/vs/workbench/browser/parts/editor2/editor2.ts index 222a39fc7ba93ddb0834db831c7ba3a4d88706c1..e3ad42337f53613a6f90ae4aafe9c35e81fd8fd2 100644 --- a/src/vs/workbench/browser/parts/editor2/editor2.ts +++ b/src/vs/workbench/browser/parts/editor2/editor2.ts @@ -7,7 +7,7 @@ import { GroupIdentifier, IWorkbenchEditorConfiguration, IWorkbenchEditorPartConfiguration, EditorOptions, TextEditorOptions } from 'vs/workbench/common/editor'; import { EditorGroup } from 'vs/workbench/common/editor/editorStacksModel'; -import { INextEditorGroup, GroupDirection } from 'vs/workbench/services/group/common/nextEditorGroupsService'; +import { INextEditorGroup, GroupDirection, IAddGroupOptions } from 'vs/workbench/services/group/common/nextEditorGroupsService'; import { IDisposable } from 'vs/base/common/lifecycle'; import { Dimension } from 'vs/base/browser/dom'; import { Event } from 'vs/base/common/event'; @@ -73,7 +73,7 @@ export interface INextEditorGroupsAccessor { getGroup(identifier: GroupIdentifier): INextEditorGroupView; - addGroup(location: INextEditorGroupView | GroupIdentifier, direction: GroupDirection, copyGroup?: boolean): INextEditorGroup; + addGroup(location: INextEditorGroupView | GroupIdentifier, direction: GroupDirection, options?: IAddGroupOptions): INextEditorGroup; } export interface INextEditorGroupView extends IDisposable, ISerializableView, INextEditorGroup { diff --git a/src/vs/workbench/browser/parts/editor2/nextEditorDragAndDrop.ts b/src/vs/workbench/browser/parts/editor2/nextEditorDragAndDrop.ts index 6b51ce460420f93b089d809c69e1c1c4604284ba..45577fe19730966b3ac6098dc951d04e3d40c3bb 100644 --- a/src/vs/workbench/browser/parts/editor2/nextEditorDragAndDrop.ts +++ b/src/vs/workbench/browser/parts/editor2/nextEditorDragAndDrop.ts @@ -6,7 +6,7 @@ 'use strict'; import 'vs/css!./media/nextEditorDragAndDrop'; -import { LocalSelectionTransfer, DraggedEditorIdentifier, DragCounter } from 'vs/workbench/browser/dnd'; +import { LocalSelectionTransfer, DraggedEditorIdentifier, DragCounter, ResourcesDropHandler } from 'vs/workbench/browser/dnd'; import { addDisposableListener, EventType, EventHelper, isAncestor, toggleClass, addClass } from 'vs/base/browser/dom'; import { INextEditorGroupsAccessor, EDITOR_TITLE_HEIGHT, INextEditorGroupView, getActiveTextEditorOptions } from 'vs/workbench/browser/parts/editor2/editor2'; import { EDITOR_DRAG_AND_DROP_BACKGROUND, Themable } from 'vs/workbench/common/theme'; @@ -16,6 +16,7 @@ import { IEditorIdentifier, EditorInput, EditorOptions } from 'vs/workbench/comm import { isMacintosh } from 'vs/base/common/platform'; import { GroupDirection, INextEditorGroup } from 'vs/workbench/services/group/common/nextEditorGroupsService'; import { toDisposable } from 'vs/base/common/lifecycle'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; class DropOverlay extends Themable { @@ -32,7 +33,8 @@ class DropOverlay extends Themable { private accessor: INextEditorGroupsAccessor, private groupView: INextEditorGroupView, private transfer: LocalSelectionTransfer, - themeService: IThemeService + themeService: IThemeService, + private instantiationService: IInstantiationService ) { super(themeService); @@ -123,34 +125,47 @@ class DropOverlay extends Themable { } private handleDrop(event: DragEvent): void { - if (!this.transfer.hasData(DraggedEditorIdentifier.prototype)) { - return; // TODO@grid support more drops - } - - const draggedEditor = this.transfer.getData(DraggedEditorIdentifier.prototype)[0].identifier; // Determine target group - let targetGroup: INextEditorGroup; - if (typeof this.splitDirection === 'number') { - targetGroup = this.accessor.addGroup(this.groupView, this.splitDirection); - } else { - targetGroup = this.groupView; - } + const ensureTargetGroup = () => { + let targetGroup: INextEditorGroup; + if (typeof this.splitDirection === 'number') { + targetGroup = this.accessor.addGroup(this.groupView, this.splitDirection, { activate: true }); + } else { + targetGroup = this.groupView; + } - // Return if the drop is a no-op - const sourceGroup = this.accessor.getGroup(draggedEditor.group.id); - if (sourceGroup === targetGroup) { - return; - } + return targetGroup; + }; + + // Check for transfer from title control + if (this.transfer.hasData(DraggedEditorIdentifier.prototype)) { + const draggedEditor = this.transfer.getData(DraggedEditorIdentifier.prototype)[0].identifier; + const targetGroup = ensureTargetGroup(); - // Open in target group - const options = getActiveTextEditorOptions(sourceGroup, draggedEditor.editor, EditorOptions.create({ pinned: true })); - targetGroup.openEditor(draggedEditor.editor, options); + // Return if the drop is a no-op + const sourceGroup = this.accessor.getGroup(draggedEditor.group.id); + if (sourceGroup === targetGroup) { + return; + } + + // Open in target group + const options = getActiveTextEditorOptions(sourceGroup, draggedEditor.editor, EditorOptions.create({ pinned: true })); + targetGroup.openEditor(draggedEditor.editor, options); + + // Close in source group unless we copy + const copyEditor = this.shouldCopyEditor(draggedEditor, event); + if (!copyEditor) { + sourceGroup.closeEditor(draggedEditor.editor); + } + } - // Close in source group unless we copy - const copyEditor = this.shouldCopyEditor(draggedEditor, event); - if (!copyEditor) { - sourceGroup.closeEditor(draggedEditor.editor); + // Check for URI transfer + else { + const dropHandler = this.instantiationService.createInstance(ResourcesDropHandler, { allowWorkspaceOpen: true /* open workspace instead of file if dropped */ }); + dropHandler.handleDrop(event, targetGroupIdentifier => { + this.accessor.getGroup(targetGroupIdentifier).focus(); + }, () => ensureTargetGroup().id); } } @@ -272,7 +287,8 @@ export class NextEditorDragAndDrop extends Themable { constructor( private accessor: INextEditorGroupsAccessor, private container: HTMLElement, - @IThemeService themeService: IThemeService + @IThemeService themeService: IThemeService, + @IInstantiationService private instantiationService: IInstantiationService ) { super(themeService); @@ -314,7 +330,7 @@ export class NextEditorDragAndDrop extends Themable { if (!this.overlay) { const groupView = this.findGroupView(target); if (groupView) { - this._overlay = new DropOverlay(this.accessor, groupView, this.transfer, this.themeService); + this._overlay = new DropOverlay(this.accessor, groupView, this.transfer, this.themeService, this.instantiationService); } } diff --git a/src/vs/workbench/browser/parts/editor2/nextEditorGroupView.ts b/src/vs/workbench/browser/parts/editor2/nextEditorGroupView.ts index bae10aeee295b7e0b322e93fec004c6bbea049aa..10124b2adb7b3af17e537a1e3f03d75b7d35c773 100644 --- a/src/vs/workbench/browser/parts/editor2/nextEditorGroupView.ts +++ b/src/vs/workbench/browser/parts/editor2/nextEditorGroupView.ts @@ -628,6 +628,32 @@ export class NextEditorGroupView extends Themable implements INextEditorGroupVie //#endregion + //#region openEditors() + + openEditors(editors: { editor: EditorInput, options?: EditorOptions }[]): Thenable { + if (!editors.length) { + return TPromise.as(void 0); + } + + // Use the first editor as active editor + const { editor, options } = editors.shift(); + return this.openEditor(editor, options).then(() => { + const startingIndex = this.getIndexOfEditor(editor) + 1; + + // Open the other ones inactive + return TPromise.join(editors.map(({ editor, options }, index) => { + const adjustedEditorOptions = options || new EditorOptions(); + adjustedEditorOptions.inactive = true; + adjustedEditorOptions.pinned = true; + adjustedEditorOptions.index = startingIndex + index; + + return this.openEditor(editor, adjustedEditorOptions); + })).then(() => void 0); + }); + } + + //#endregion + //#region moveEditor() moveEditor(editor: EditorInput, target: INextEditorGroupView, options?: IMoveEditorOptions): void { diff --git a/src/vs/workbench/browser/parts/editor2/nextEditorPart.ts b/src/vs/workbench/browser/parts/editor2/nextEditorPart.ts index 0b8567cbd2e124b1f344b90c3217df360c5fb085..a9483583cf5929783571916749539aa3d01ace57 100644 --- a/src/vs/workbench/browser/parts/editor2/nextEditorPart.ts +++ b/src/vs/workbench/browser/parts/editor2/nextEditorPart.ts @@ -11,7 +11,7 @@ import { Part } from 'vs/workbench/browser/part'; import { Dimension, isAncestor, toggleClass, addClass, clearNode } from 'vs/base/browser/dom'; import { Event, Emitter, once } from 'vs/base/common/event'; import { contrastBorder, editorBackground } from 'vs/platform/theme/common/colorRegistry'; -import { INextEditorGroupsService, GroupDirection } from 'vs/workbench/services/group/common/nextEditorGroupsService'; +import { INextEditorGroupsService, GroupDirection, IAddGroupOptions } from 'vs/workbench/services/group/common/nextEditorGroupsService'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { Direction, SerializableGrid, Sizing, ISerializedGrid, Orientation, ISerializedNode } from 'vs/base/browser/ui/grid/grid'; import { GroupIdentifier, IWorkbenchEditorConfiguration } from 'vs/workbench/common/editor'; @@ -34,10 +34,6 @@ import { IWindowService } from 'vs/platform/windows/common/windows'; import { ILifecycleService, LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; import { NextEditorDragAndDrop } from './nextEditorDragAndDrop'; -// TODO@grid provide DND support of groups/editors: -// - editor: move/copy to existing group, move/copy to new split group (up, down, left, right) -// - group: move/copy to existing group (merges?), move/copy to new split group (up, down, left, right) - // TODO@grid enable minimized/maximized groups in one dimension interface INextEditorPartUIState { @@ -198,10 +194,16 @@ export class NextEditorPart extends Part implements INextEditorGroupsService, IN return groupView; } - addGroup(location: INextEditorGroupView | GroupIdentifier, direction: GroupDirection, copy?: boolean): INextEditorGroupView { + addGroup(location: INextEditorGroupView | GroupIdentifier, direction: GroupDirection, options?: IAddGroupOptions): INextEditorGroupView { const locationView = this.assertGroupView(location); - return this.doAddGroup(locationView, direction, copy ? locationView : void 0); + const group = this.doAddGroup(locationView, direction, options && options.copyGroup ? locationView : void 0); + + if (options && options.activate) { + this.doSetGroupActive(group); + } + + return group; } private doAddGroup(locationView: INextEditorGroupView, direction: GroupDirection, groupToCopy?: INextEditorGroupView): INextEditorGroupView { diff --git a/src/vs/workbench/browser/parts/editor2/nextTabsTitleControl.ts b/src/vs/workbench/browser/parts/editor2/nextTabsTitleControl.ts index c6e7a425690b267a17409e28af19fcf7b3524605..b6d372c6853a2a6cd52455c153cd319ba03e82ac 100644 --- a/src/vs/workbench/browser/parts/editor2/nextTabsTitleControl.ts +++ b/src/vs/workbench/browser/parts/editor2/nextTabsTitleControl.ts @@ -32,7 +32,7 @@ import { getOrSet } from 'vs/base/common/map'; 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, 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 { ResourcesDropHandler, fillResourceDataTransfers, LocalSelectionTransfer, DraggedEditorIdentifier } from 'vs/workbench/browser/dnd'; +import { ResourcesDropHandler, fillResourceDataTransfers, LocalSelectionTransfer, DraggedEditorIdentifier, DragCounter } from 'vs/workbench/browser/dnd'; import { Color } from 'vs/base/common/color'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; @@ -508,11 +508,11 @@ export class NextTabsTitleControl extends NextTitleControl { // it contains a label and a close button. HTML gives us DRAG_ENTER and DRAG_LEAVE events when hovering over // these children and this can cause flicker of the drop feedback. The workaround is to count the events and only // remove the drop feedback when the counter is 0 (see https://github.com/Microsoft/vscode/issues/14470) - let counter = 0; + const counter = new DragCounter(); // Drag over disposables.push(addDisposableListener(tab, EventType.DRAG_ENTER, (e: DragEvent) => { - counter++; + counter.increment(); // Find out if the currently dragged editor is this tab and in that // case we do not want to show any drop feedback @@ -533,8 +533,9 @@ export class NextTabsTitleControl extends NextTitleControl { // Drag leave disposables.push(addDisposableListener(tab, EventType.DRAG_LEAVE, (e: DragEvent) => { - counter--; - if (counter === 0) { + counter.decrement(); + + if (!counter.value) { removeClass(tab, 'dragged-over'); this.updateDropFeedback(tab, false, index); } @@ -542,7 +543,8 @@ export class NextTabsTitleControl extends NextTitleControl { // Drag end disposables.push(addDisposableListener(tab, EventType.DRAG_END, (e: DragEvent) => { - counter = 0; + counter.reset(); + removeClass(tab, 'dragged-over'); this.updateDropFeedback(tab, false, index); @@ -551,7 +553,8 @@ export class NextTabsTitleControl extends NextTitleControl { // Drop disposables.push(addDisposableListener(tab, EventType.DROP, (e: DragEvent) => { - counter = 0; + counter.reset(); + removeClass(tab, 'dragged-over'); this.updateDropFeedback(tab, false, index); @@ -929,7 +932,7 @@ export class NextTabsTitleControl extends NextTitleControl { // External DND else { const dropHandler = this.instantiationService.createInstance(ResourcesDropHandler, { allowWorkspaceOpen: false /* open workspace file as file if dropped */ }); - dropHandler.handleDrop(e, () => this.group.focus(), this.group.id /* TODO@grid position => group id */, targetIndex); + dropHandler.handleDrop(e, () => this.group.focus(), () => this.group.id, targetIndex); } } diff --git a/src/vs/workbench/parts/files/electron-browser/views/openEditorsView.ts b/src/vs/workbench/parts/files/electron-browser/views/openEditorsView.ts index 0c9ff7fa8f73f1a5e24cd6996916bc9130282910..71e0ed301cae4df333432810c35189f4d5f49f01 100644 --- a/src/vs/workbench/parts/files/electron-browser/views/openEditorsView.ts +++ b/src/vs/workbench/parts/files/electron-browser/views/openEditorsView.ts @@ -497,7 +497,7 @@ class EditorGroupRenderer implements IRenderer this.editorGroupService.activateGroup(positionOfTargetGroup), positionOfTargetGroup); + dropHandler.handleDrop(e, () => this.editorGroupService.activateGroup(positionOfTargetGroup), () => positionOfTargetGroup); } })); @@ -584,7 +584,7 @@ class OpenEditorRenderer implements IRenderer this.editorGroupService.activateGroup(positionOfTargetGroup), positionOfTargetGroup, index); + dropHandler.handleDrop(e, () => this.editorGroupService.activateGroup(positionOfTargetGroup), () => positionOfTargetGroup, index); } })); editorTemplate.toDispose.push(dom.addDisposableListener(container, dom.EventType.DRAG_END, () => { diff --git a/src/vs/workbench/services/editor/browser/nextEditorService.ts b/src/vs/workbench/services/editor/browser/nextEditorService.ts index e4e7b6a2c6fcc36fcb2ecfa96290eb18d9210d1a..3814c06ecb692991dea5f3a14096a557653791c2 100644 --- a/src/vs/workbench/services/editor/browser/nextEditorService.ts +++ b/src/vs/workbench/services/editor/browser/nextEditorService.ts @@ -6,7 +6,7 @@ 'use strict'; import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; -import { IEditorInput, IResourceInput, IUntitledResourceInput, IResourceDiffInput, IResourceSideBySideInput, IEditor, ITextEditorOptions, IEditorOptions } from 'vs/platform/editor/common/editor'; +import { IEditorInput, IResourceInput, IUntitledResourceInput, IResourceDiffInput, IResourceSideBySideInput, IEditor, ITextEditorOptions, IEditorOptions, IEditorInputWithOptions, isEditorInputWithOptions } from 'vs/platform/editor/common/editor'; import { GroupIdentifier, IFileEditorInput, IEditorInputFactoryRegistry, Extensions as EditorExtensions, IFileInputFactory, EditorInput, SideBySideEditorInput, EditorOptions, TextEditorOptions, IEditorOpeningEvent } from 'vs/workbench/common/editor'; import { ResourceEditorInput } from 'vs/workbench/common/editor/resourceEditorInput'; import { DataUriEditorInput } from 'vs/workbench/common/editor/dataUriEditorInput'; @@ -25,7 +25,7 @@ import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput'; import { localize } from 'vs/nls'; import { TPromise } from 'vs/base/common/winjs.base'; import { INextEditorGroupsService, INextEditorGroup, GroupDirection } from 'vs/workbench/services/group/common/nextEditorGroupsService'; -import { INextEditorService, IResourceEditor, SIDE_BY_SIDE_TYPE, SIDE_BY_SIDE, IEditorInputWithOptions } from 'vs/workbench/services/editor/common/nextEditorService'; +import { INextEditorService, IResourceEditor, SIDE_BY_SIDE_TYPE, SIDE_BY_SIDE } from 'vs/workbench/services/editor/common/nextEditorService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { Disposable, IDisposable, dispose } from 'vs/base/common/lifecycle'; import { coalesce } from 'vs/base/common/arrays'; @@ -173,7 +173,10 @@ export class NextEditorService extends Disposable implements INextEditorService // Typed Editor Support if (editor instanceof EditorInput) { - return this.doOpenEditor(editor, this.toOptions(optionsOrGroup as IEditorOptions), group); + const editorOptions = this.toOptions(optionsOrGroup as IEditorOptions); + const targetGroup = this.findTargetGroup(editor, editorOptions, group); + + return targetGroup.openEditor(editor, editorOptions).then(() => targetGroup.activeControl); } // Throw error for well known foreign resources (such as a http link) (TODO@ben remove me after this has been adopted) @@ -189,18 +192,21 @@ export class NextEditorService extends Disposable implements INextEditorService const textInput = editor; const typedInput = this.createInput(textInput); if (typedInput) { - return this.doOpenEditor(typedInput, TextEditorOptions.from(textInput), optionsOrGroup as GroupIdentifier); + const editorOptions = TextEditorOptions.from(textInput); + const targetGroup = this.findTargetGroup(typedInput, editorOptions, optionsOrGroup as GroupIdentifier); + + return targetGroup.openEditor(typedInput, editorOptions).then(() => targetGroup.activeControl); } return TPromise.wrap(null); } - private doOpenEditor(input: IEditorInput, options?: EditorOptions, group?: GroupIdentifier | SIDE_BY_SIDE_TYPE): Thenable { + private findTargetGroup(input: IEditorInput, options?: IEditorOptions, group?: GroupIdentifier | SIDE_BY_SIDE_TYPE): INextEditorGroup { let targetGroup: INextEditorGroup; // Group: Side by Side if (group === SIDE_BY_SIDE) { - targetGroup = this.nextEditorGroupsService.addGroup(this.nextEditorGroupsService.activeGroup, GroupDirection.RIGHT); + targetGroup = this.createSideBySideGroup(); } // Group: Specific Group @@ -240,7 +246,11 @@ export class NextEditorService extends Disposable implements INextEditorService targetGroup = this.nextEditorGroupsService.activeGroup; } - return targetGroup.openEditor(input, options).then(() => targetGroup.activeControl); + return targetGroup; + } + + private createSideBySideGroup(): INextEditorGroup { + return this.nextEditorGroupsService.addGroup(this.nextEditorGroupsService.activeGroup, GroupDirection.RIGHT); } private toOptions(options?: IEditorOptions | EditorOptions): EditorOptions { @@ -260,13 +270,45 @@ export class NextEditorService extends Disposable implements INextEditorService //#region openEditors() - // TODO@grid openEditors() - openEditors(editors: IEditorInputWithOptions[], group?: GroupIdentifier | SIDE_BY_SIDE_TYPE): Thenable; - openEditors(editors: IResourceEditor[], group?: GroupIdentifier | SIDE_BY_SIDE_TYPE): Thenable; - openEditors(editors: (IEditorInputWithOptions | IResourceEditor)[], group?: GroupIdentifier | SIDE_BY_SIDE_TYPE): Thenable { - const inputs = editors.map(editor => this.createInput(editor)); + openEditors(editors: IEditorInputWithOptions[], group?: GroupIdentifier | SIDE_BY_SIDE_TYPE): Thenable; + openEditors(editors: IResourceEditor[], group?: GroupIdentifier | SIDE_BY_SIDE_TYPE): Thenable; + openEditors(editors: (IEditorInputWithOptions | IResourceEditor)[], group?: GroupIdentifier | SIDE_BY_SIDE_TYPE): Thenable { + + // Convert to typed editors and options + const typedEditors: IEditorInputWithOptions[] = []; + editors.forEach(editor => { + if (isEditorInputWithOptions(editor)) { + typedEditors.push(editor); + } else { + typedEditors.push({ editor: this.createInput(editor), options: TextEditorOptions.from(editor) }); + } + }); + + // Find target groups to open + const mapGroupToEditors = new Map(); + if (group === SIDE_BY_SIDE) { + mapGroupToEditors.set(this.createSideBySideGroup(), typedEditors); + } else { + typedEditors.forEach(typedEditor => { + const targetGroup = this.findTargetGroup(typedEditor.editor, typedEditor.options, group); + + let targetGroupEditors = mapGroupToEditors.get(targetGroup); + if (!targetGroupEditors) { + targetGroupEditors = []; + mapGroupToEditors.set(targetGroup, targetGroupEditors); + } + + targetGroupEditors.push(typedEditor); + }); + } + + // Open in targets + const result: TPromise[] = []; + mapGroupToEditors.forEach((editorsWithOptions, group) => { + result.push((group.openEditors(editorsWithOptions) as TPromise).then(() => group.activeControl)); + }); - return this.openEditor(inputs[0]); + return TPromise.join(result); } //#endregion diff --git a/src/vs/workbench/services/editor/common/nextEditorService.ts b/src/vs/workbench/services/editor/common/nextEditorService.ts index 5b77bc663cb7f258ef81ba47c683b1ab8be69a2f..1488ca2a20beb3b9f1d3578f900c27213dc64eb3 100644 --- a/src/vs/workbench/services/editor/common/nextEditorService.ts +++ b/src/vs/workbench/services/editor/common/nextEditorService.ts @@ -6,7 +6,7 @@ 'use strict'; import { createDecorator, ServiceIdentifier, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; -import { IEditorInput, IResourceInput, IUntitledResourceInput, IResourceDiffInput, IResourceSideBySideInput, IEditor, IEditorOptions } from 'vs/platform/editor/common/editor'; +import { IEditorInput, IResourceInput, IUntitledResourceInput, IResourceDiffInput, IResourceSideBySideInput, IEditor, IEditorOptions, IEditorInputWithOptions } from 'vs/platform/editor/common/editor'; import { GroupIdentifier, IEditorOpeningEvent } from 'vs/workbench/common/editor'; import { Event } from 'vs/base/common/event'; import { IEditor as ICodeEditor } from 'vs/editor/common/editorCommon'; @@ -18,11 +18,6 @@ export type IResourceEditor = IResourceInput | IUntitledResourceInput | IResourc export const SIDE_BY_SIDE = -1; export type SIDE_BY_SIDE_TYPE = typeof SIDE_BY_SIDE; -export interface IEditorInputWithOptions { - editor: IEditorInput; - options?: IEditorOptions; -} - export interface INextEditorService { _serviceBrand: ServiceIdentifier; @@ -107,8 +102,8 @@ export interface INextEditorService { * active group. Use `SIDE_BY_SIDE` to open the editor in a new editor group to the side * of the currently active group. */ - openEditors(editors: IEditorInputWithOptions[], group?: GroupIdentifier | SIDE_BY_SIDE_TYPE): Thenable; - openEditors(editors: IResourceEditor[], group?: GroupIdentifier | SIDE_BY_SIDE_TYPE): Thenable; + openEditors(editors: IEditorInputWithOptions[], group?: GroupIdentifier | SIDE_BY_SIDE_TYPE): Thenable; + openEditors(editors: IResourceEditor[], group?: GroupIdentifier | SIDE_BY_SIDE_TYPE): Thenable; /** * Find out if the provided editor (or resource of an editor) is opened in any group. diff --git a/src/vs/workbench/services/group/common/nextEditorGroupsService.ts b/src/vs/workbench/services/group/common/nextEditorGroupsService.ts index 3eed7e2a389b96539eb9f0dfa33aae511930cfae..8cf81729651069ca33984ee1edc790826d6d021b 100644 --- a/src/vs/workbench/services/group/common/nextEditorGroupsService.ts +++ b/src/vs/workbench/services/group/common/nextEditorGroupsService.ts @@ -8,7 +8,7 @@ import { Event } from 'vs/base/common/event'; import { createDecorator, ServiceIdentifier, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { GroupIdentifier, IEditorOpeningEvent } from 'vs/workbench/common/editor'; -import { IEditorInput, IEditor, IEditorOptions } from 'vs/platform/editor/common/editor'; +import { IEditorInput, IEditor, IEditorOptions, IEditorInputWithOptions } from 'vs/platform/editor/common/editor'; export const INextEditorGroupsService = createDecorator('nextEditorGroupsService'); @@ -25,6 +25,11 @@ export interface IMoveEditorOptions { preserveFocus?: boolean; } +export interface IAddGroupOptions { + activate?: boolean; + copyGroup?: boolean; +} + export interface ICopyEditorOptions extends IMoveEditorOptions { } export interface INextEditorGroupsService { @@ -94,9 +99,9 @@ export interface INextEditorGroupsService { * * @param location the group from which to split to add a new group * @param direction the direction of where to split to - * @param copyGroup optionally copy the editors of the group to add from + * @param options configure the newly group with options */ - addGroup(location: INextEditorGroup | GroupIdentifier, direction: GroupDirection, copyGroup?: boolean): INextEditorGroup; + addGroup(location: INextEditorGroup | GroupIdentifier, direction: GroupDirection, options?: IAddGroupOptions): INextEditorGroup; /** * Remove a group from the editor area. @@ -193,6 +198,12 @@ export interface INextEditorGroup { */ openEditor(editor: IEditorInput, options?: IEditorOptions): Thenable; + /** + * Opens editors in this group. The returned promise is resolved when the + * editor has finished loading. + */ + openEditors(editors: IEditorInputWithOptions[]): Thenable; + /** * Find out if the provided editor is opened in the group. *