Skip to content

Commit

Permalink
Tab drag and drop mostly working
Browse files Browse the repository at this point in the history
Part of #121500
  • Loading branch information
Tyriar committed Apr 16, 2021
1 parent 1845b9d commit c76d062
Show file tree
Hide file tree
Showing 8 changed files with 129 additions and 14 deletions.
15 changes: 14 additions & 1 deletion src/vs/workbench/contrib/terminal/browser/terminal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ export const enum Direction {
Down = 3
}

export interface ITerminalTab {
export interface ITerminalTab extends IDisposable {
activeInstance: ITerminalInstance | null;
terminalInstances: ITerminalInstance[];
title: string;
Expand All @@ -68,6 +68,8 @@ export interface ITerminalTab {
addDisposable(disposable: IDisposable): void;
split(shellLaunchConfig: IShellLaunchConfig): ITerminalInstance;
getLayoutInfo(isActive: boolean): ITerminalTabLayoutInfoById;
addInstance(instance: ITerminalInstance, targetPosition?: ITerminalInstance): void;
removeInstance(instance: ITerminalInstance): void;
}

export const enum TerminalConnectionState {
Expand Down Expand Up @@ -129,6 +131,17 @@ export interface ITerminalService {
splitInstance(instance: ITerminalInstance, shell?: IShellLaunchConfig): ITerminalInstance | null;
splitInstance(instance: ITerminalInstance, profile: ITerminalProfile): ITerminalInstance | null;

/**
* Unsplits an instance, moving it into its own tab.
*/
unsplitInstance(instance: ITerminalInstance): void;

/**
* Moves a terminal instance to the position of another, pushing the target instance to the
* right.
*/
moveInstance(instance: ITerminalInstance, target: ITerminalInstance): void;

/**
* Perform an action with the active terminal instance, if the terminal does
* not exist the callback will not be called.
Expand Down
15 changes: 15 additions & 0 deletions src/vs/workbench/contrib/terminal/browser/terminalActions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1232,6 +1232,21 @@ export function registerTerminalActions() {
});
}
});
registerAction2(class extends Action2 {
constructor() {
super({
id: TERMINAL_COMMAND_ID.UNSPLIT,
title: { value: localize('workbench.action.terminal.unsplit', "Unsplit Terminal"), original: 'Unsplit Terminal' },
f1: true,
category,
precondition: KEYBINDING_CONTEXT_TERMINAL_PROCESS_SUPPORTED
});
}
async run(accessor: ServicesAccessor) {
const terminalService = accessor.get(ITerminalService);
terminalService.doWithActiveInstance(t => terminalService.unsplitInstance(t));
}
});
registerAction2(class extends Action2 {
constructor() {
super({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { IDecorationData, IDecorationsProvider } from 'vs/workbench/services/dec
import { Event, Emitter } from 'vs/base/common/event';
import { Codicon } from 'vs/base/common/codicons';
import { listErrorForeground, listWarningForeground } from 'vs/platform/theme/common/colorRegistry';
import { TERMINAL_DECORATIONS_SCHEME } from 'vs/workbench/contrib/terminal/common/terminal';
import { terminalUrlScheme } from 'vs/workbench/contrib/terminal/common/terminal';

export interface ITerminalDecorationData {
tooltip: string,
Expand All @@ -33,7 +33,7 @@ export class TerminalDecorationsProvider implements IDecorationsProvider {
}

provideDecorations(resource: URI): IDecorationData | undefined {
if (resource.scheme !== TERMINAL_DECORATIONS_SCHEME || !parseInt(resource.path)) {
if (resource.scheme !== terminalUrlScheme || !parseInt(resource.path)) {
return;
}

Expand Down
35 changes: 35 additions & 0 deletions src/vs/workbench/contrib/terminal/browser/terminalService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -626,6 +626,41 @@ export class TerminalService implements ITerminalService {
return instance;
}

public unsplitInstance(instance: ITerminalInstance): void {
const oldTab = this.getTabForInstance(instance);
if (!oldTab || oldTab.terminalInstances.length < 2) {
return;
}

oldTab.removeInstance(instance);

const newTab = this._instantiationService.createInstance(TerminalTab, this._terminalContainer, instance);
this._terminalTabs.push(newTab);

newTab.addDisposable(newTab.onDisposed(this._onTabDisposed.fire, this._onTabDisposed));
newTab.addDisposable(newTab.onInstancesChanged(this._onInstancesChanged.fire, this._onInstancesChanged));
this._onInstancesChanged.fire();
}

public moveInstance(instance: ITerminalInstance, target: ITerminalInstance): void {
const oldTab = this.getTabForInstance(instance);
const newTab = this.getTabForInstance(target);
if (!oldTab || !newTab) {
return;
}

// Remove from old tab, disposing of it if now empty
oldTab.removeInstance(instance);
if (oldTab.terminalInstances.length === 0) {
console.log('dispose oldTab');
oldTab.dispose();
}

newTab.addInstance(instance, target);

this._onInstancesChanged.fire();
}

protected _initInstanceListeners(instance: ITerminalInstance): void {
instance.addDisposable(instance.onDisposed(this._onInstanceDisposed.fire, this._onInstanceDisposed));
instance.addDisposable(instance.onTitleChanged(this._onInstanceTitleChanged.fire, this._onInstanceTitleChanged));
Expand Down
17 changes: 16 additions & 1 deletion src/vs/workbench/contrib/terminal/browser/terminalTab.ts
Original file line number Diff line number Diff line change
Expand Up @@ -264,7 +264,7 @@ export class TerminalTab extends Disposable implements ITerminalTab {
}
}

public addInstance(shellLaunchConfigOrInstance: IShellLaunchConfig | ITerminalInstance): void {
public addInstance(shellLaunchConfigOrInstance: IShellLaunchConfig | ITerminalInstance, targetPosition?: ITerminalInstance): void {
let instance: ITerminalInstance;
if ('instanceId' in shellLaunchConfigOrInstance) {
instance = shellLaunchConfigOrInstance;
Expand All @@ -281,6 +281,21 @@ export class TerminalTab extends Disposable implements ITerminalTab {
this._onInstancesChanged.fire();
}

public removeInstance(instance: ITerminalInstance): void {
const index = this._terminalInstances.indexOf(instance);
if (index === -1) {
return;
}

this._terminalInstances.splice(index, 1);
this._splitPaneContainer?.remove(instance);

// TODO: Remove listeners

// TODO: Call from ITerminalService.moveInstance
this._onInstancesChanged.fire();
}

public override dispose(): void {
super.dispose();
if (this._container && this._tabElement) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,8 +88,8 @@ export class TerminalTabbedView extends Disposable {
this._instanceMenu = this._register(menuService.createMenu(MenuId.TerminalContext, contextKeyService));
// this._dropdownMenu = this._register(menuService.createMenu(MenuId.TerminalTabsContext, contextKeyService));

this._register(this._tabsWidget = this._instantiationService.createInstance(TerminalTabsWidget, this._terminalTabTree));
this._register(this._findWidget = this._instantiationService.createInstance(TerminalFindWidget, this._terminalService.getFindState()));
this._tabsWidget = this._register(this._instantiationService.createInstance(TerminalTabsWidget, this._terminalTabTree));
this._findWidget = this._register(this._instantiationService.createInstance(TerminalFindWidget, this._terminalService.getFindState()));
parentElement.appendChild(this._findWidget.getDomNode());

this._terminalContainer = document.createElement('div');
Expand Down
50 changes: 43 additions & 7 deletions src/vs/workbench/contrib/terminal/browser/terminalTabsWidget.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/

import { IListService, WorkbenchObjectTree } from 'vs/platform/list/browser/listService';
import { ITreeNode, ITreeRenderer } from 'vs/base/browser/ui/tree/tree';
import { ITreeDragOverReaction, ITreeNode, ITreeRenderer } from 'vs/base/browser/ui/tree/tree';
import { DefaultStyleController } from 'vs/base/browser/ui/list/listWidget';
import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
Expand All @@ -18,7 +18,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti
import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar';
import { MenuItemAction } from 'vs/platform/actions/common/actions';
import { MenuEntryActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem';
import { TERMINAL_COMMAND_ID, TERMINAL_DECORATIONS_SCHEME } from 'vs/workbench/contrib/terminal/common/terminal';
import { TERMINAL_COMMAND_ID, terminalUrlScheme } from 'vs/workbench/contrib/terminal/common/terminal';
import { Codicon } from 'vs/base/common/codicons';
import { Action } from 'vs/base/common/actions';
import { MarkdownString } from 'vs/base/common/htmlContent';
Expand All @@ -28,11 +28,13 @@ import { IDecorationsService } from 'vs/workbench/services/decorations/browser/d
import { IHoverService } from 'vs/workbench/services/hover/browser/hover';
import { URI } from 'vs/base/common/uri';
import Severity from 'vs/base/common/severity';
import { ListDragOverEffect } from 'vs/base/browser/ui/list/list';

const $ = DOM.$;
export const MIN_TABS_WIDGET_WIDTH = 46;
export const DEFAULT_TABS_WIDGET_WIDTH = 80;
export const MIDPOINT_WIDGET_WIDTH = (MIN_TABS_WIDGET_WIDTH + DEFAULT_TABS_WIDGET_WIDTH) / 2;
const TAB_HEIGHT = 22;

export class TerminalTabsWidget extends WorkbenchObjectTree<ITerminalInstance> {
private _decorationsProvider: TerminalDecorationsProvider | undefined;
Expand All @@ -51,7 +53,7 @@ export class TerminalTabsWidget extends WorkbenchObjectTree<ITerminalInstance>
) {
super('TerminalTabsTree', container,
{
getHeight: () => 22,
getHeight: () => TAB_HEIGHT,
getTemplateId: () => 'terminal.tabs'
},
[instantiationService.createInstance(TerminalTabsRenderer, container, instantiationService.createInstance(ResourceLabels, DEFAULT_LABELS_CONTAINER))],
Expand All @@ -70,7 +72,43 @@ export class TerminalTabsWidget extends WorkbenchObjectTree<ITerminalInstance>
smoothScrolling: configurationService.getValue<boolean>('workbench.list.smoothScrolling'),
multipleSelectionSupport: false,
expandOnlyOnTwistieClick: true,
selectionNavigation: true
selectionNavigation: true,
dnd: {
drop: (data, targetElement) => {
if (!targetElement) {
return;
}
const draggedElement = data.getData();
if (!draggedElement || !Array.isArray(draggedElement)) {
return;
}
let focused = false;
for (const e of draggedElement) {
if ('instanceId' in e) {
const instance = e as ITerminalInstance;
this._terminalService.moveInstance(instance, targetElement);
if (!focused) {
this._terminalService.setActiveInstance(instance);
focused = true;
}
}
}
},
getDragURI: (instance) => {
return URI.from({
scheme: terminalUrlScheme,
path: instance.instanceId.toString()
}).path;
},
onDragOver(data, targetElement, targetIndex, originalEvent): boolean | ITreeDragOverReaction {
return {
feedback: targetIndex ? [targetIndex] : undefined,
accept: true,
effect: ListDragOverEffect.Move
};
}
},
additionalScrollHeight: TAB_HEIGHT
},
contextKeyService,
listService,
Expand Down Expand Up @@ -160,9 +198,7 @@ class TerminalTabsRenderer implements ITreeRenderer<ITerminalInstance, never, IT
return this._container ? this._container.clientWidth < MIDPOINT_WIDGET_WIDTH : false;
}


renderElement(node: ITreeNode<ITerminalInstance>, index: number, template: ITerminalTabEntryTemplate): void {

let instance = node.element;

const tab = this._terminalService.getTabForInstance(instance);
Expand Down Expand Up @@ -216,7 +252,7 @@ class TerminalTabsRenderer implements ITreeRenderer<ITerminalInstance, never, IT
});

if (instance.statusList.statuses.length && hasText) {
const labelProps: IResourceLabelProps = { resource: URI.from({ scheme: TERMINAL_DECORATIONS_SCHEME, path: instance.instanceId.toString() }), name: label };
const labelProps: IResourceLabelProps = { resource: URI.from({ scheme: terminalUrlScheme, path: instance.instanceId.toString() }), name: label };
const options: IResourceLabelOptions = { fileDecorations: { colors: true, badges: true } };
template.label.setResource(labelProps, options);
}
Expand Down
3 changes: 2 additions & 1 deletion src/vs/workbench/contrib/terminal/common/terminal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ export interface IShellLaunchConfigResolveOptions {
allowAutomationShell?: boolean;
}

export const TERMINAL_DECORATIONS_SCHEME = 'vscode-terminal';
export const terminalUrlScheme = 'vscode-terminal';

export type FontWeight = 'normal' | 'bold' | number;

Expand Down Expand Up @@ -429,6 +429,7 @@ export const enum TERMINAL_COMMAND_ID {
NEW_WITH_PROFILE = 'workbench.action.terminal.newWithProfile',
SPLIT = 'workbench.action.terminal.split',
SPLIT_IN_ACTIVE_WORKSPACE = 'workbench.action.terminal.splitInActiveWorkspace',
UNSPLIT = 'workbench.action.terminal.unsplit',
RELAUNCH = 'workbench.action.terminal.relaunch',
FOCUS_PREVIOUS_PANE = 'workbench.action.terminal.focusPreviousPane',
FOCUS_NEXT_PANE = 'workbench.action.terminal.focusNextPane',
Expand Down

0 comments on commit c76d062

Please sign in to comment.