Skip to content

Commit

Permalink
Unsplit terminals
Browse files Browse the repository at this point in the history
Fixes #121278
  • Loading branch information
Tyriar committed May 17, 2021
1 parent 45769b8 commit 327690a
Show file tree
Hide file tree
Showing 6 changed files with 95 additions and 5 deletions.
4 changes: 4 additions & 0 deletions src/vs/workbench/contrib/terminal/browser/terminal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ export interface ITerminalGroup {
resizePanes(relativeSizes: number[]): void;
setActiveInstanceByIndex(index: number): void;
attachToElement(element: HTMLElement): void;
removeInstance(instance: ITerminalInstance): void;
setVisible(visible: boolean): void;
layout(width: number, height: number): void;
addDisposable(disposable: IDisposable): void;
Expand Down Expand Up @@ -143,6 +144,7 @@ export interface ITerminalService {
getActiveOrCreateInstance(): ITerminalInstance;
splitInstance(instance: ITerminalInstance, shell?: IShellLaunchConfig, cwd?: string | URI): ITerminalInstance | null;
splitInstance(instance: ITerminalInstance, profile: ITerminalProfile): ITerminalInstance | null;
unsplitInstance(instance: ITerminalInstance): void;

/**
* Perform an action with the active terminal instance, if the terminal does
Expand Down Expand Up @@ -524,6 +526,8 @@ export interface ITerminalInstance {
*/
attachToElement(container: HTMLElement): Promise<void> | void;

detachFromElement(): void;

/**
* Configure the dimensions of the terminal instance.
*
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 @@ -1395,6 +1395,21 @@ export function registerTerminalActions() {
return undefined;
}
});
registerAction2(class extends Action2 {
constructor() {
super({
id: TerminalCommandId.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);
await terminalService.doWithActiveInstance(async t => terminalService.unsplitInstance(t));
}
});
registerAction2(class extends Action2 {
constructor() {
super({
Expand Down
45 changes: 42 additions & 3 deletions src/vs/workbench/contrib/terminal/browser/terminalGroup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

import { TERMINAL_VIEW_ID } from 'vs/workbench/contrib/terminal/common/terminal';
import { Event, Emitter } from 'vs/base/common/event';
import { IDisposable, Disposable, DisposableStore } from 'vs/base/common/lifecycle';
import { IDisposable, Disposable, DisposableStore, dispose } from 'vs/base/common/lifecycle';
import { SplitView, Orientation, IView, Sizing } from 'vs/base/browser/ui/splitview/splitview';
import { IWorkbenchLayoutService, Parts, Position } from 'vs/workbench/services/layout/browser/layoutService';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
Expand Down Expand Up @@ -233,6 +233,7 @@ export class TerminalGroup extends Disposable implements ITerminalGroup {
private _groupElement: HTMLElement | undefined;
private _panelPosition: Position = Position.BOTTOM;
private _terminalLocation: ViewContainerLocation = ViewContainerLocation.Panel;
private _instanceDisposables: Map<number, IDisposable[]> = new Map();

private _activeInstanceIndex: number;
private _isVisible: boolean = false;
Expand Down Expand Up @@ -317,8 +318,10 @@ export class TerminalGroup extends Disposable implements ITerminalGroup {
}

private _initInstanceListeners(instance: ITerminalInstance): void {
instance.addDisposable(instance.onDisposed(instance => this._onInstanceDisposed(instance)));
instance.addDisposable(instance.onFocused(instance => this._setActiveInstance(instance)));
this._instanceDisposables.set(instance.instanceId, [
instance.onDisposed(instance => this._onInstanceDisposed(instance)),
instance.onFocused(instance => this._setActiveInstance(instance))
]);
}

private _onInstanceDisposed(instance: ITerminalInstance): void {
Expand Down Expand Up @@ -408,6 +411,42 @@ export class TerminalGroup extends Disposable implements ITerminalGroup {
this.setVisible(this._isVisible);
}

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

const wasActiveInstance = instance === this.activeInstance;
this._terminalInstances.splice(index, 1);
instance.detachFromElement();

// TODO: Share code with onInstanceDisposed
// Adjust focus if the instance was active
if (wasActiveInstance && this._terminalInstances.length > 0) {
const newIndex = index < this._terminalInstances.length ? index : this._terminalInstances.length - 1;
this.setActiveInstanceByIndex(newIndex);
// TODO: Only focus the new instance if the group had focus?
if (this.activeInstance) {
this.activeInstance.focus(true);
}
} else if (index < this._activeInstanceIndex) {
// Adjust active instance index if needed
this._activeInstanceIndex--;
}

this._splitPaneContainer?.remove(instance);

// Dispose listeners
const disposables = this._instanceDisposables.get(instance.instanceId);
if (disposables) {
dispose(disposables);
this._instanceDisposables.delete(instance.instanceId);
}

this._onInstancesChanged.fire();
}

get title(): string {
if (this._terminalInstances.length === 0) {
// Normally consumers should not call into title at all after the group is disposed but
Expand Down
17 changes: 15 additions & 2 deletions src/vs/workbench/contrib/terminal/browser/terminalInstance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -604,6 +604,16 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
this._container.appendChild(this._wrapperElement);
}

detachFromElement(): void {
if (!this._wrapperElement) {
return;
}
this._container?.removeChild(this._wrapperElement);
this._wrapperElement = undefined;

// TODO: Don't allow reattach init of xterm
}

private async _attachToElement(container: HTMLElement): Promise<void> {
if (this._wrapperElement) {
throw new Error('The terminal instance has already been attached to a container');
Expand Down Expand Up @@ -900,7 +910,6 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {

override dispose(immediate?: boolean): void {
this._logService.trace(`terminalInstance#dispose (instanceId: ${this.instanceId})`);

dispose(this._linkManager);
this._linkManager = undefined;
dispose(this._commandTrackerAddon);
Expand All @@ -921,7 +930,11 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
if (this._xterm) {
const buffer = this._xterm.buffer;
this._sendLineData(buffer.active, buffer.active.baseY + buffer.active.cursorY);
this._xterm.dispose();
try {
this._xterm.dispose();
} catch {
// No op, exception may get thrown from removeChild
}
}

if (this._pressAnyKeyToCloseListener) {
Expand Down
18 changes: 18 additions & 0 deletions src/vs/workbench/contrib/terminal/browser/terminalService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -621,6 +621,23 @@ export class TerminalService implements ITerminalService {
return instance;
}

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

oldGroup.removeInstance(instance);

const newGroup = this._instantiationService.createInstance(TerminalGroup, this._terminalContainer, instance);
newGroup.onPanelOrientationChanged((orientation) => this._onPanelOrientationChanged.fire(orientation));
this._terminalGroups.push(newGroup);

newGroup.addDisposable(newGroup.onDisposed(this._onGroupDisposed.fire, this._onGroupDisposed));
newGroup.addDisposable(newGroup.onInstancesChanged(this._onInstancesChanged.fire, this._onInstancesChanged));
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 Expand Up @@ -969,6 +986,7 @@ export class TerminalService implements ITerminalService {
instance.shellLaunchConfig.hideFromUser = false;
const terminalGroup = this._instantiationService.createInstance(TerminalGroup, this._terminalContainer, instance);
this._terminalGroups.push(terminalGroup);
terminalGroup.onPanelOrientationChanged((orientation) => this._onPanelOrientationChanged.fire(orientation));
terminalGroup.addDisposable(terminalGroup.onDisposed(this._onGroupDisposed.fire, this._onGroupDisposed));
terminalGroup.addDisposable(terminalGroup.onInstancesChanged(this._onInstancesChanged.fire, this._onInstancesChanged));
if (this.terminalInstances.length === 1) {
Expand Down
1 change: 1 addition & 0 deletions src/vs/workbench/contrib/terminal/common/terminal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -397,6 +397,7 @@ export const enum TerminalCommandId {
Split = 'workbench.action.terminal.split',
SplitInstance = 'workbench.action.terminal.splitInstance',
SplitInActiveWorkspace = 'workbench.action.terminal.splitInActiveWorkspace',
Unsplit = 'workbench.action.terminal.unsplit',
Relaunch = 'workbench.action.terminal.relaunch',
FocusPreviousPane = 'workbench.action.terminal.focusPreviousPane',
ShowTabs = 'workbench.action.terminal.showTabs',
Expand Down

0 comments on commit 327690a

Please sign in to comment.