Skip to content

Commit

Permalink
Partially implement terminal drag and drop
Browse files Browse the repository at this point in the history
Part of #121500
  • Loading branch information
Tyriar committed May 19, 2021
1 parent b705868 commit 45e5c50
Show file tree
Hide file tree
Showing 5 changed files with 131 additions and 13 deletions.
9 changes: 9 additions & 0 deletions src/vs/workbench/contrib/terminal/browser/media/terminal.css
Original file line number Diff line number Diff line change
Expand Up @@ -289,3 +289,12 @@
margin-left: 4px;
color: inherit;
}

.monaco-workbench .pane-body.integrated-terminal .drop-target::after {
background: rgba(128, 128, 255, .5);
content: '';
display: block;
position: absolute;
width: 100%;
height: 100%;
}
15 changes: 15 additions & 0 deletions src/vs/workbench/contrib/terminal/browser/terminal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,11 @@ export interface ITerminalService {
splitInstance(instance: ITerminalInstance, profile: ITerminalProfile): ITerminalInstance | null;
unsplitInstance(instance: ITerminalInstance): void;
joinInstances(instances: ITerminalInstance[]): void;
/**
* Moves a terminal instance's group to the target instance group's position.
*/
moveGroup(instance: ITerminalInstance, target: ITerminalInstance): void;
moveInstance(source: ITerminalInstance, target: ITerminalInstance, side: 'left' | 'right'): void;

/**
* Perform an action with the active terminal instance, if the terminal does
Expand Down Expand Up @@ -317,6 +322,11 @@ export interface ITerminalInstance {

onFocus: Event<ITerminalInstance>;

/**
* An event that fires when a terminal is dropped on this instance via drag and drop.
*/
onRequestAddInstanceToGroup: Event<IRequestAddInstanceToGroupEvent>;

/**
* Attach a listener to the raw data stream coming from the pty, including ANSI escape
* sequences.
Expand Down Expand Up @@ -595,6 +605,11 @@ export interface ITerminalInstance {
changeColor(): Promise<void>;
}

export interface IRequestAddInstanceToGroupEvent {
uri: URI;
side: 'left' | 'right'
}

export const enum LinuxDistro {
Unknown = 1,
Fedora = 2,
Expand Down
30 changes: 28 additions & 2 deletions src/vs/workbench/contrib/terminal/browser/terminalInstance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ import { ansiColorIdentifiers, ansiColorMap, TERMINAL_BACKGROUND_COLOR, TERMINAL
import { TerminalConfigHelper } from 'vs/workbench/contrib/terminal/browser/terminalConfigHelper';
import { TerminalLinkManager } from 'vs/workbench/contrib/terminal/browser/links/terminalLinkManager';
import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility';
import { ITerminalInstanceService, ITerminalInstance, ITerminalExternalLinkProvider } from 'vs/workbench/contrib/terminal/browser/terminal';
import { ITerminalInstanceService, ITerminalInstance, ITerminalExternalLinkProvider, IRequestAddInstanceToGroupEvent } from 'vs/workbench/contrib/terminal/browser/terminal';
import { TerminalProcessManager } from 'vs/workbench/contrib/terminal/browser/terminalProcessManager';
import type { Terminal as XTermTerminal, IBuffer, ITerminalAddon, RendererType, ITheme } from 'xterm';
import type { SearchAddon, ISearchOptions } from 'xterm-addon-search';
Expand Down Expand Up @@ -216,6 +216,8 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
get onMaximumDimensionsChanged(): Event<void> { return this._onMaximumDimensionsChanged.event; }
private readonly _onFocus = new Emitter<ITerminalInstance>();
get onFocus(): Event<ITerminalInstance> { return this._onFocus.event; }
private readonly _onRequestAddInstanceToGroup = new Emitter<IRequestAddInstanceToGroupEvent>();
get onRequestAddInstanceToGroup(): Event<IRequestAddInstanceToGroupEvent> { return this._onRequestAddInstanceToGroup.event; }

constructor(
private readonly _terminalFocusContextKey: IContextKey<boolean>,
Expand Down Expand Up @@ -749,7 +751,20 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
this._refreshSelectionContextKey();
}));

this._register(dom.addDisposableListener(xterm.element, dom.EventType.DRAG_OVER, (dragEvent: DragEvent) => {
dragEvent.preventDefault();
if (!dragEvent.dataTransfer) {
return;
}
if ((dragEvent.dataTransfer?.types || []).includes('terminals')) {
this._container?.parentElement?.classList.add('drop-target');
}
}));
this._register(dom.addDisposableListener(xterm.element, dom.EventType.DRAG_LEAVE, (dragEvent: DragEvent) => {
this._container?.parentElement?.classList.remove('drop-target');
}));
this._register(dom.addDisposableListener(xterm.element, dom.EventType.DROP, async (dragEvent: DragEvent) => {
this._container?.parentElement?.classList.remove('drop-target');
if (!dragEvent.dataTransfer) {
return;
}
Expand All @@ -758,7 +773,18 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
let path: string | undefined;
const resources = dragEvent.dataTransfer.getData(DataTransfers.RESOURCES);
if (resources) {
path = URI.parse(JSON.parse(resources)[0]).fsPath;
const uri = URI.parse(JSON.parse(resources)[0]);
if (uri.scheme === Schemas.vscodeTerminal) {
console.log('drop event', dragEvent);
this._onRequestAddInstanceToGroup.fire({
uri,
// TODO: Get side
side: 'right'
});
return;
} else {
path = uri.fsPath;
}
} else if (dragEvent.dataTransfer.files?.[0].path /* Electron only */) {
// Check if the file was dragged from the filesystem
path = URI.file(dragEvent.dataTransfer.files[0].path).fsPath;
Expand Down
26 changes: 26 additions & 0 deletions src/vs/workbench/contrib/terminal/browser/terminalService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -687,6 +687,24 @@ export class TerminalService implements ITerminalService {
}
}

moveGroup(instance: ITerminalInstance, target: ITerminalInstance): void {
const sourceGroup = this.getGroupForInstance(instance);
const targetGroup = this.getGroupForInstance(target);
if (!sourceGroup || !targetGroup) {
return;
}
const sourceGroupIndex = this._terminalGroups.indexOf(sourceGroup);
const targetGroupIndex = this._terminalGroups.indexOf(targetGroup);
this._terminalGroups.splice(sourceGroupIndex, 1);
this._terminalGroups.splice(targetGroupIndex, 0, sourceGroup);
this._onInstancesChanged.fire();
}

moveInstance(source: ITerminalInstance, target: ITerminalInstance, side: 'left' | 'right'): void {
// TODO: Implement properly
this.joinInstances([source, target]);
}

protected _initInstanceListeners(instance: ITerminalInstance): void {
instance.addDisposable(instance.onDisposed(this._onInstanceDisposed.fire, this._onInstanceDisposed));
instance.addDisposable(instance.onTitleChanged(this._onInstanceTitleChanged.fire, this._onInstanceTitleChanged));
Expand All @@ -703,6 +721,14 @@ export class TerminalService implements ITerminalService {
}));
instance.addDisposable(instance.onMaximumDimensionsChanged(() => this._onInstanceMaximumDimensionsChanged.fire(instance)));
instance.addDisposable(instance.onFocus(this._onActiveInstanceChanged.fire, this._onActiveInstanceChanged));
instance.addDisposable(instance.onRequestAddInstanceToGroup(e => {
const sourceInstance = this.getInstanceFromId(parseInt(e.uri.path));
console.log('source', sourceInstance, 'join', instance);
if (sourceInstance) {
// TODO: Pass in side
this.moveInstance(sourceInstance, instance, e.side);
}
}));
}

registerProcessSupport(isSupported: boolean): void {
Expand Down
64 changes: 53 additions & 11 deletions src/vs/workbench/contrib/terminal/browser/terminalTabsList.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,12 @@ import { IDecorationsService } from 'vs/workbench/services/decorations/browser/d
import { IHoverAction, IHoverService } from 'vs/workbench/services/hover/browser/hover';
import Severity from 'vs/base/common/severity';
import { Disposable, DisposableStore, IDisposable } from 'vs/base/common/lifecycle';
import { IListDragAndDrop, IListRenderer } from 'vs/base/browser/ui/list/list';
import { IListDragAndDrop, IListDragOverReaction, IListRenderer, ListDragOverEffect } from 'vs/base/browser/ui/list/list';
import { DataTransfers, IDragAndDropData } from 'vs/base/browser/dnd';
import { disposableTimeout } from 'vs/base/common/async';
import { ElementsDragAndDropData } from 'vs/base/browser/ui/list/listView';
import { URI } from 'vs/base/common/uri';
import { Schemas } from 'vs/base/common/network';

const $ = DOM.$;

Expand Down Expand Up @@ -78,7 +79,7 @@ export class TerminalTabList extends WorkbenchList<ITerminalInstance> {
smoothScrolling: configurationService.getValue<boolean>('workbench.list.smoothScrolling'),
multipleSelectionSupport: true,
additionalScrollHeight: TerminalTabsListSizes.TabHeight,
dnd: new TerminalTabsDragAndDrop(_terminalService, _terminalInstanceService)
dnd: instantiationService.createInstance(TerminalTabsDragAndDrop)
},
contextKeyService,
listService,
Expand Down Expand Up @@ -403,15 +404,33 @@ class TerminalTabsDragAndDrop implements IListDragAndDrop<ITerminalInstance> {
private _autoFocusDisposable: IDisposable = Disposable.None;

constructor(
private _terminalService: ITerminalService,
private _terminalInstanceService: ITerminalInstanceService
@ITerminalService private _terminalService: ITerminalService,
@ITerminalInstanceService private _terminalInstanceService: ITerminalInstanceService
) { }

getDragURI(instance: ITerminalInstance): string | null {
return null;
return URI.from({
scheme: Schemas.vscodeTerminal,
path: instance.instanceId.toString()
}).toString();
}

onDragOver(data: IDragAndDropData, targetInstance: ITerminalInstance | undefined, targetIndex: number | undefined, originalEvent: DragEvent): boolean {
onDragStart(data: IDragAndDropData, originalEvent: DragEvent): void {
if (!originalEvent.dataTransfer) {
return;
}
const dndData: unknown = data.getData();
if (!Array.isArray(dndData)) {
return;
}
// Attach terminals type to event
const terminals: ITerminalInstance[] = dndData.filter(e => 'instanceId' in (e as any));
if (terminals.length > 0) {
originalEvent.dataTransfer.setData('terminals', JSON.stringify(terminals.map(e => e.instanceId)));
}
}

onDragOver(data: IDragAndDropData, targetInstance: ITerminalInstance | undefined, targetIndex: number | undefined, originalEvent: DragEvent): boolean | IListDragOverReaction {
let result = true;

const didChangeAutoFocusInstance = this._autoFocusInstance !== targetInstance;
Expand All @@ -424,24 +443,47 @@ class TerminalTabsDragAndDrop implements IListDragAndDrop<ITerminalInstance> {
return result;
}

const isExternalDragOver = !(data instanceof ElementsDragAndDropData);
if (didChangeAutoFocusInstance && isExternalDragOver) {
if (didChangeAutoFocusInstance) {
this._autoFocusDisposable = disposableTimeout(() => {
this._terminalService.setActiveInstance(targetInstance);
this._autoFocusInstance = undefined;
}, 500);
}

return result;
return {
feedback: targetIndex ? [targetIndex] : undefined,
accept: true,
effect: ListDragOverEffect.Move
};
}

drop(data: IDragAndDropData, targetInstance: ITerminalInstance | undefined, targetIndex: number | undefined, originalEvent: DragEvent): void {
this._autoFocusDisposable.dispose();
this._autoFocusInstance = undefined;

const isExternalDrop = !(data instanceof ElementsDragAndDropData);
if (isExternalDrop) {
if (!(data instanceof ElementsDragAndDropData)) {
this._handleExternalDrop(targetInstance, originalEvent);
return;
}

const draggedElement = data.getData();
if (!draggedElement || !Array.isArray(draggedElement)) {
return;
}
let focused = false;
if (!targetInstance) {
// TODO: Support dropping on empty
return;
}
for (const e of draggedElement) {
if ('instanceId' in e) {
const instance = e as ITerminalInstance;
this._terminalService.moveGroup(instance, targetInstance);
if (!focused) {
this._terminalService.setActiveInstance(instance);
focused = true;
}
}
}
}

Expand Down

0 comments on commit 45e5c50

Please sign in to comment.