Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions src/vs/workbench/parts/terminal/common/terminal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -265,4 +265,11 @@ export interface ITerminalInstance {
* null means the process was killed as a result of the ITerminalInstance being disposed.
*/
onExit(listener: (exitCode: number) => void): void;

/**
* Immediately kills the terminal's current pty process and launches a new one to replace it.
*
* @param shell The new launch configuration.
*/
reuseTerminal(shell?: IShellLaunchConfig): void;
}
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,8 @@ export class TerminalInstance implements ITerminalInstance {
private _processId: number;
private _skipTerminalCommands: string[];
private _title: string;
private _toDispose: lifecycle.IDisposable[];
private _instanceDisposables: lifecycle.IDisposable[];
private _processDisposables: lifecycle.IDisposable[];
private _wrapperElement: HTMLDivElement;
private _xterm: any;
private _xtermElement: HTMLDivElement;
Expand All @@ -71,7 +72,8 @@ export class TerminalInstance implements ITerminalInstance {
@IPanelService private _panelService: IPanelService,
@IWorkspaceContextService private _contextService: IWorkspaceContextService
) {
this._toDispose = [];
this._instanceDisposables = [];
this._processDisposables = [];
this._skipTerminalCommands = [];
this._isExiting = false;
this._hadFocusOnExit = false;
Expand All @@ -92,7 +94,7 @@ export class TerminalInstance implements ITerminalInstance {
}

public addDisposable(disposable: lifecycle.IDisposable): void {
this._toDispose.push(disposable);
this._instanceDisposables.push(disposable);
}

public attachToElement(container: HTMLElement): void {
Expand All @@ -110,19 +112,14 @@ export class TerminalInstance implements ITerminalInstance {
});
this._xterm.open(this._xtermElement);

this._process.on('message', (message) => {
if (!this._xterm) {
return;
}
if (message.type === 'data') {
this._xterm.write(message.content);
}
});
this._process.on('message', (message) => this._sendPtyDataToXterm(message));
this._xterm.on('data', (data) => {
this._process.send({
event: 'input',
data: this._sanitizeInput(data)
});
if (this._process) {
this._process.send({
event: 'input',
data: this._sanitizeInput(data)
});
}
return false;
});
this._xterm.attachCustomKeydownHandler((event: KeyboardEvent) => {
Expand All @@ -146,48 +143,48 @@ export class TerminalInstance implements ITerminalInstance {
return false;
}
});
(<HTMLElement>this._xterm.element).addEventListener('mouseup', event => {
this._instanceDisposables.push(DOM.addDisposableListener(this._xterm.element, 'mouseup', (event: KeyboardEvent) => {
// Wait until mouseup has propogated through the DOM before evaluating the new selection
// state.
setTimeout(() => {
this._refreshSelectionContextKey();
}, 0);
});
}));

// xterm.js currently drops selection on keyup as we need to handle this case.
(<HTMLElement>this._xterm.element).addEventListener('keyup', event => {
this._instanceDisposables.push(DOM.addDisposableListener(this._xterm.element, 'keyup', (event: KeyboardEvent) => {
// Wait until keyup has propogated through the DOM before evaluating the new selection
// state.
setTimeout(() => {
this._refreshSelectionContextKey();
}, 0);
});
}));

const xtermHelper: HTMLElement = this._xterm.element.querySelector('.xterm-helpers');
const focusTrap: HTMLElement = document.createElement('div');
focusTrap.setAttribute('tabindex', '0');
DOM.addClass(focusTrap, 'focus-trap');
focusTrap.addEventListener('focus', function (event: FocusEvent) {
this._instanceDisposables.push(DOM.addDisposableListener(focusTrap, 'focus', (event: FocusEvent) => {
let currentElement = focusTrap;
while (!DOM.hasClass(currentElement, 'part')) {
currentElement = currentElement.parentElement;
}
const hidePanelElement = <HTMLElement>currentElement.querySelector('.hide-panel-action');
hidePanelElement.focus();
});
}));
xtermHelper.insertBefore(focusTrap, this._xterm.textarea);

this._toDispose.push(DOM.addDisposableListener(this._xterm.textarea, 'focus', (event: KeyboardEvent) => {
this._instanceDisposables.push(DOM.addDisposableListener(this._xterm.textarea, 'focus', (event: KeyboardEvent) => {
this._terminalFocusContextKey.set(true);
}));
this._toDispose.push(DOM.addDisposableListener(this._xterm.textarea, 'blur', (event: KeyboardEvent) => {
this._instanceDisposables.push(DOM.addDisposableListener(this._xterm.textarea, 'blur', (event: KeyboardEvent) => {
this._terminalFocusContextKey.reset();
this._refreshSelectionContextKey();
}));
this._toDispose.push(DOM.addDisposableListener(this._xterm.element, 'focus', (event: KeyboardEvent) => {
this._instanceDisposables.push(DOM.addDisposableListener(this._xterm.element, 'focus', (event: KeyboardEvent) => {
this._terminalFocusContextKey.set(true);
}));
this._toDispose.push(DOM.addDisposableListener(this._xterm.element, 'blur', (event: KeyboardEvent) => {
this._instanceDisposables.push(DOM.addDisposableListener(this._xterm.element, 'blur', (event: KeyboardEvent) => {
this._terminalFocusContextKey.reset();
this._refreshSelectionContextKey();
}));
Expand Down Expand Up @@ -238,7 +235,8 @@ export class TerminalInstance implements ITerminalInstance {
this._process = null;
}
this._onDisposed.fire(this);
this._toDispose = lifecycle.dispose(this._toDispose);
this._processDisposables = lifecycle.dispose(this._processDisposables);
this._instanceDisposables = lifecycle.dispose(this._instanceDisposables);
}

public focus(force?: boolean): void {
Expand Down Expand Up @@ -371,6 +369,15 @@ export class TerminalInstance implements ITerminalInstance {
}, LAUNCHING_DURATION);
}

private _sendPtyDataToXterm(message: { type: string, content: string }): void {
if (!this._xterm) {
return;
}
if (message.type === 'data') {
this._xterm.write(message.content);
}
}

private _onPtyProcessExit(exitCode: number): void {
// Prevent dispose functions being triggered multiple times
if (this._isExiting) {
Expand All @@ -394,9 +401,9 @@ export class TerminalInstance implements ITerminalInstance {
this._xterm.writeln(nls.localize('terminal.integrated.waitOnExit', 'Press any key to close the terminal'));
// Disable all input if the terminal is exiting and listen for next keypress
this._xterm.setOption('disableStdin', true);
(<HTMLElement>this._xterm.textarea).addEventListener('keypress', (data) => {
this._processDisposables.push(DOM.addDisposableListener(this._xterm.textarea, 'keypress', () => {
this.dispose();
});
}));
} else {
this.dispose();
if (exitCode) {
Expand All @@ -418,6 +425,35 @@ export class TerminalInstance implements ITerminalInstance {
}
}

public reuseTerminal(shell?: IShellLaunchConfig): void {
// Kill and clean up old process
if (this._process) {
this._process.removeAllListeners('exit');
if (this._process.connected) {
this._process.kill();
}
this._process = null;
}
lifecycle.dispose(this._processDisposables);
this._processDisposables = [];

// Ensure new processes' output starts at start of new line
this._xterm.write('\n\x1b[G');

// Initialize new process
this._createProcess(this._contextService.getWorkspace(), shell.name, shell);
this._process.on('message', (message) => this._sendPtyDataToXterm(message));

// Clean up waitOnExit state
if (this._isExiting && this._shellLaunchConfig.waitOnExit) {
this._xterm.setOption('disableStdin', false);
this._isExiting = false;
}

// Set the new shell launch config
this._shellLaunchConfig = shell;
}

// TODO: This should be private/protected
// TODO: locale should not be optional
public static createTerminalEnv(parentEnv: IStringDictionary<string>, shell: IShellLaunchConfig, cwd: string, locale?: string): IStringDictionary<string> {
Expand Down