Skip to content

Commit

Permalink
debt - reuse drop handler from editor also for open editors view
Browse files Browse the repository at this point in the history
  • Loading branch information
bpasero committed Feb 8, 2018
1 parent 7678585 commit 535fc34
Show file tree
Hide file tree
Showing 5 changed files with 114 additions and 54 deletions.
71 changes: 67 additions & 4 deletions src/vs/workbench/browser/dnd.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,22 @@ 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';

export interface IDraggedResource {
resource: URI;
isExternal: boolean;
}

export class DraggedEditorIdentifier {
constructor(private _identifier: IEditorIdentifier) {
}

public get identifier(): IEditorIdentifier {
return this._identifier;
}
}

export interface IDraggedEditor extends IDraggedResource {
backupResource?: URI;
viewState?: IEditorViewState;
Expand Down Expand Up @@ -110,13 +120,23 @@ export function extractResources(e: DragEvent, externalOnly?: boolean): (IDragge
return resources;
}

export interface IResourcesDropHandlerOptions {

/**
* Wether to open the actual workspace when a workspace configuration file is dropped
* or wether to open the configuration file within the editor as normal file.
*/
allowWorkspaceOpen: boolean;
}

/**
* Shared function across some editor components to handle drag & drop of external resources. E.g. of folders and workspace files
* Shared function across some components to handle drag & drop of resources. E.g. of folders and workspace files
* to open them in the window instead of the editor or to handle dirty editors being dropped between instances of Code.
*/
export class EditorAreaDropHandler {
export class ResourcesDropHandler {

constructor(
private options: IResourcesDropHandlerOptions,
@IFileService private fileService: IFileService,
@IWindowsService private windowsService: IWindowsService,
@IWindowService private windowService: IWindowService,
Expand Down Expand Up @@ -177,8 +197,8 @@ export class EditorAreaDropHandler {
return TPromise.join(resourcesWithBackups.map(resourceWithBackup => this.handleDirtyEditorDrop(resourceWithBackup))).then(() => false);
}

// Check for workspace file being dropped
if (resources.some(r => r.isExternal)) {
// Check for workspace file being dropped if we are allowed to do so
if (this.options.allowWorkspaceOpen && resources.some(r => r.isExternal)) {
return this.handleWorkspaceFileDrop(resources);
}

Expand Down Expand Up @@ -377,4 +397,47 @@ export function fillResourceDataTransfers(accessor: ServicesAccessor, resources:
});

event.dataTransfer.setData(CodeDataTransfers.EDITORS, JSON.stringify(draggedEditors));
}

/**
* A singleton to store transfer data during drag & drop operations that are only valid within the application.
*/
export class LocalSelectionTransfer<T> {

private static readonly INSTANCE = new LocalSelectionTransfer();

private data: T[];
private proto: T;

private constructor() {
// protect against external instantiation
}

public static getInstance<T>(): LocalSelectionTransfer<T> {
return LocalSelectionTransfer.INSTANCE as LocalSelectionTransfer<T>;
}

public hasData(proto: T): boolean {
return proto && proto === this.proto;
}

public clearData(): void {
this.proto = void 0;
this.data = void 0;
}

public getData(proto: T): T[] {
if (this.hasData(proto)) {
return this.data;
}

return void 0;
}

public setData(data: T[], proto: T): void {
if (proto) {
this.data = data;
this.proto = proto;
}
}
}
18 changes: 10 additions & 8 deletions src/vs/workbench/browser/parts/editor/editorGroupsControl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import { ServiceCollection } from 'vs/platform/instantiation/common/serviceColle
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { IExtensionService } from 'vs/platform/extensions/common/extensions';
import { TabsTitleControl } from 'vs/workbench/browser/parts/editor/tabsTitleControl';
import { TitleControl, ITitleAreaControl } 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 { getCodeEditor } from 'vs/editor/browser/services/codeEditorService';
Expand All @@ -35,7 +35,7 @@ import { editorBackground, contrastBorder, activeContrastBorder } from 'vs/platf
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 { IDisposable, combinedDisposable } from 'vs/base/common/lifecycle';
import { EditorAreaDropHandler } from 'vs/workbench/browser/dnd';
import { ResourcesDropHandler, LocalSelectionTransfer, DraggedEditorIdentifier } from 'vs/workbench/browser/dnd';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';

export enum Rochade {
Expand Down Expand Up @@ -137,6 +137,8 @@ export class EditorGroupsControl extends Themable implements IEditorGroupsContro
private onStacksChangeScheduler: RunOnceScheduler;
private stacksChangedBuffer: IStacksModelChangeEvent[];

private transfer = LocalSelectionTransfer.getInstance<DraggedEditorIdentifier>();

constructor(
parent: Builder,
groupOrientation: GroupOrientation,
Expand Down Expand Up @@ -1074,8 +1076,8 @@ 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) {
if ($this.transfer.hasData(DraggedEditorIdentifier.prototype)) {
const draggedEditor = $this.transfer.getData(DraggedEditorIdentifier.prototype)[0].identifier;
const isCopy = (e.ctrlKey && !isMacintosh) || (e.altKey && isMacintosh);

// Copy editor to new location
Expand Down Expand Up @@ -1114,7 +1116,7 @@ export class EditorGroupsControl extends Themable implements IEditorGroupsContro

// Check for URI transfer
else {
const dropHandler = $this.instantiationService.createInstance(EditorAreaDropHandler);
const dropHandler = $this.instantiationService.createInstance(ResourcesDropHandler, { allowWorkspaceOpen: true });
dropHandler.handleDrop(e, () => {
if (splitEditor && splitTo !== freeGroup) {
groupService.moveGroup(freeGroup, splitTo);
Expand All @@ -1129,7 +1131,7 @@ export class EditorGroupsControl extends Themable implements IEditorGroupsContro
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 overlaySize = $this.layoutVertically ? target.clientWidth : target.clientHeight;
const splitThreshold = overlayIsSplit ? overlaySize / 5 : overlaySize / 10;
Expand Down Expand Up @@ -1234,7 +1236,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';
}

Expand Down Expand Up @@ -1275,7 +1277,7 @@ export class EditorGroupsControl extends Themable implements IEditorGroupsContro
// 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
Expand Down
18 changes: 10 additions & 8 deletions src/vs/workbench/browser/parts/editor/tabsTitleControl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ import { TAB_INACTIVE_BACKGROUND, TAB_ACTIVE_BACKGROUND, TAB_ACTIVE_FOREGROUND,
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 { EditorAreaDropHandler, fillResourceDataTransfers } from 'vs/workbench/browser/dnd';
import { ResourcesDropHandler, fillResourceDataTransfers, LocalSelectionTransfer, DraggedEditorIdentifier } from 'vs/workbench/browser/dnd';
import { Color } from 'vs/base/common/color';

interface IEditorInputLabel {
Expand All @@ -63,6 +63,7 @@ export class TabsTitleControl extends TitleControl {
private blockRevealActiveTab: boolean;
private dimension: Dimension;
private layoutScheduled: IDisposable;
private transfer = LocalSelectionTransfer.getInstance<DraggedEditorIdentifier>();

constructor(
@IContextMenuService contextMenuService: IContextMenuService,
Expand Down Expand Up @@ -166,7 +167,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
Expand Down Expand Up @@ -743,7 +744,8 @@ export class TabsTitleControl extends TitleControl {
disposables.push(DOM.addDisposableListener(tab, DOM.EventType.DRAG_START, (e: DragEvent) => {
const { group, editor } = this.getGroupPositionAndEditor(index);

this.onEditorDragStart({ editor, group });
this.transfer.setData([new DraggedEditorIdentifier({ editor, group })], DraggedEditorIdentifier.prototype);

e.dataTransfer.effectAllowed = 'copyMove';

// Apply some datatransfer types to allow for dragging the element outside of the application
Expand All @@ -770,7 +772,7 @@ 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.getGroupPositionAndEditor(index);
if (draggedEditor.editor === editor && draggedEditor.group === group) {
Expand Down Expand Up @@ -800,7 +802,7 @@ export class TabsTitleControl extends TitleControl {
DOM.removeClass(tab, 'dragged-over');
this.updateDropFeedback(tab, false, index);

this.onEditorDragEnd();
this.transfer.clearData();
}));

// Drop
Expand Down Expand Up @@ -836,7 +838,7 @@ export class TabsTitleControl extends TitleControl {
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) {

// Move editor to target position and index
Expand All @@ -849,12 +851,12 @@ 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 {
const dropHandler = this.instantiationService.createInstance(EditorAreaDropHandler);
const dropHandler = this.instantiationService.createInstance(ResourcesDropHandler, { allowWorkspaceOpen: true });
dropHandler.handleDrop(e, () => this.editorGroupService.focusGroup(targetPosition), targetPosition, targetIndex);
}
}
Expand Down
14 changes: 0 additions & 14 deletions src/vs/workbench/browser/parts/editor/titleControl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,6 @@ export interface ITitleAreaControl {

export abstract class TitleControl extends Themable implements ITitleAreaControl {

private static draggedEditor: IEditorIdentifier;

protected stacks: IEditorStacksModel;
protected context: IEditorGroup;

Expand Down Expand Up @@ -115,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)));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ import { fillInActions } from 'vs/platform/actions/browser/menuItemActionItem';
import { IMenuService, MenuId, IMenu } from 'vs/platform/actions/common/actions';
import { OpenEditorsGroupContext, DirtyEditorContext } from 'vs/workbench/parts/files/electron-browser/fileCommands';
import { ResourceContextKey } from 'vs/workbench/common/resources';
import { fillResourceDataTransfers } from 'vs/workbench/browser/dnd';
import { fillResourceDataTransfers, ResourcesDropHandler, LocalSelectionTransfer } from 'vs/workbench/browser/dnd';

const $ = dom.$;

Expand Down Expand Up @@ -448,6 +448,8 @@ class OpenEditorsDelegate implements IDelegate<OpenEditor | IEditorGroup> {
class EditorGroupRenderer implements IRenderer<IEditorGroup, IEditorGroupTemplateData> {
static readonly ID = 'editorgroup';

private transfer = LocalSelectionTransfer.getInstance<OpenEditor>();

constructor(
private keybindingService: IKeybindingService,
private instantiationService: IInstantiationService,
Expand All @@ -472,21 +474,24 @@ class EditorGroupRenderer implements IRenderer<IEditorGroup, IEditorGroupTemplat

editorGroupTemplate.toDispose = [];
editorGroupTemplate.toDispose.push(dom.addDisposableListener(container, dom.EventType.DRAG_OVER, (e: DragEvent) => {
if (OpenEditorRenderer.DRAGGED_OPEN_EDITORS) {
dom.addClass(container, 'focused');
}
dom.addClass(container, 'focused');
}));
editorGroupTemplate.toDispose.push(dom.addDisposableListener(container, dom.EventType.DRAG_LEAVE, (e: DragEvent) => {
dom.removeClass(container, 'focused');
}));
editorGroupTemplate.toDispose.push(dom.addDisposableListener(container, dom.EventType.DROP, () => {
editorGroupTemplate.toDispose.push(dom.addDisposableListener(container, dom.EventType.DROP, e => {
dom.removeClass(container, 'focused');
if (OpenEditorRenderer.DRAGGED_OPEN_EDITORS) {
const model = this.editorGroupService.getStacksModel();
const positionOfTargetGroup = model.positionOfGroup(editorGroupTemplate.editorGroup);
OpenEditorRenderer.DRAGGED_OPEN_EDITORS.forEach(oe =>

const model = this.editorGroupService.getStacksModel();
const positionOfTargetGroup = model.positionOfGroup(editorGroupTemplate.editorGroup);

if (this.transfer.hasData(OpenEditor.prototype)) {
this.transfer.getData(OpenEditor.prototype).forEach(oe =>
this.editorGroupService.moveEditor(oe.editor, model.positionOfGroup(oe.group), positionOfTargetGroup, { preserveFocus: true }));
this.editorGroupService.activateGroup(positionOfTargetGroup);
} else {
const dropHandler = this.instantiationService.createInstance(ResourcesDropHandler, { allowWorkspaceOpen: false });
dropHandler.handleDrop(e, () => this.editorGroupService.activateGroup(positionOfTargetGroup), positionOfTargetGroup);
}
}));

Expand All @@ -507,7 +512,8 @@ class EditorGroupRenderer implements IRenderer<IEditorGroup, IEditorGroupTemplat

class OpenEditorRenderer implements IRenderer<OpenEditor, IOpenEditorTemplateData> {
static readonly ID = 'openeditor';
public static DRAGGED_OPEN_EDITORS: OpenEditor[];

private transfer = LocalSelectionTransfer.getInstance<OpenEditor>();

constructor(
private getSelectedElements: () => (OpenEditor | IEditorGroup)[],
Expand Down Expand Up @@ -548,34 +554,35 @@ class OpenEditorRenderer implements IRenderer<OpenEditor, IOpenEditorTemplateDat
setTimeout(() => document.body.removeChild(dragImage), 0);

const dragged = <OpenEditor[]>this.getSelectedElements().filter(e => e instanceof OpenEditor && !!e.getResource());
OpenEditorRenderer.DRAGGED_OPEN_EDITORS = dragged;
this.transfer.setData(dragged, OpenEditor.prototype);

if (editorTemplate.openEditor && editorTemplate.openEditor.editor) {
this.instantiationService.invokeFunction(fillResourceDataTransfers, dragged.map(d => d.getResource()), e);
}
}));
editorTemplate.toDispose.push(dom.addDisposableListener(container, dom.EventType.DRAG_OVER, () => {
if (OpenEditorRenderer.DRAGGED_OPEN_EDITORS) {
dom.addClass(container, 'focused');
}
dom.addClass(container, 'focused');
}));
editorTemplate.toDispose.push(dom.addDisposableListener(container, dom.EventType.DRAG_LEAVE, () => {
dom.removeClass(container, 'focused');
}));
editorTemplate.toDispose.push(dom.addDisposableListener(container, dom.EventType.DROP, (e: DragEvent) => {
dom.removeClass(container, 'focused');
if (OpenEditorRenderer.DRAGGED_OPEN_EDITORS) {
const model = this.editorGroupService.getStacksModel();
const positionOfTargetGroup = model.positionOfGroup(editorTemplate.openEditor.group);
const index = editorTemplate.openEditor.group.indexOf(editorTemplate.openEditor.editor);
const model = this.editorGroupService.getStacksModel();
const positionOfTargetGroup = model.positionOfGroup(editorTemplate.openEditor.group);
const index = editorTemplate.openEditor.group.indexOf(editorTemplate.openEditor.editor);

OpenEditorRenderer.DRAGGED_OPEN_EDITORS.forEach(oe =>
if (this.transfer.hasData(OpenEditor.prototype)) {
this.transfer.getData(OpenEditor.prototype).forEach(oe =>
this.editorGroupService.moveEditor(oe.editor, model.positionOfGroup(oe.group), positionOfTargetGroup, { index, preserveFocus: true }));
this.editorGroupService.activateGroup(positionOfTargetGroup);
} else {
const dropHandler = this.instantiationService.createInstance(ResourcesDropHandler, { allowWorkspaceOpen: false });
dropHandler.handleDrop(e, () => this.editorGroupService.activateGroup(positionOfTargetGroup), positionOfTargetGroup, index);
}
}));
editorTemplate.toDispose.push(dom.addDisposableListener(container, dom.EventType.DRAG_END, () => {
OpenEditorRenderer.DRAGGED_OPEN_EDITORS = undefined;
this.transfer.clearData();
}));

return editorTemplate;
Expand Down

0 comments on commit 535fc34

Please sign in to comment.