提交 e0972ea4 编写于 作者: D Daniel Imms 提交者: GitHub

Merge pull request #18586 from Microsoft/tyriar/18377

Enable reuse of terminal instances
......@@ -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;
}
......@@ -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;
......@@ -70,7 +71,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;
......@@ -91,7 +93,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 {
......@@ -109,19 +111,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) => {
......@@ -145,48 +142,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();
}));
......@@ -237,7 +234,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 {
......@@ -370,6 +368,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) {
......@@ -393,9 +400,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) {
......@@ -417,6 +424,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> {
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册