未验证 提交 8237428f 编写于 作者: D Daniel Imms 提交者: GitHub

Merge pull request #76481 from microsoft/tyriar/virtual_process

Implement terminal virtual processes
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { window, commands, Terminal, TerminalDimensionsChangeEvent, TerminalVirtualProcess, EventEmitter, TerminalDimensions } from 'vscode';
import { doesNotThrow, equal, ok } from 'assert';
suite('window namespace tests', () => {
(process.platform === 'win32' ? suite.skip /* https://github.com/microsoft/vscode/issues/75689 */ : suite)('Terminal', () => {
test('sendText immediately after createTerminal should not throw', () => {
const terminal = window.createTerminal();
doesNotThrow(terminal.sendText.bind(terminal, 'echo "foo"'));
terminal.dispose();
});
test('onDidCloseTerminal event fires when terminal is disposed', (done) => {
const terminal = window.createTerminal();
const reg = window.onDidCloseTerminal((eventTerminal) => {
equal(terminal, eventTerminal);
reg.dispose();
done();
});
terminal.dispose();
});
test('processId immediately after createTerminal should fetch the pid', (done) => {
const terminal = window.createTerminal();
terminal.processId.then(id => {
ok(id > 0);
terminal.dispose();
done();
});
});
test('name in constructor should set terminal.name', () => {
const terminal = window.createTerminal('a');
equal(terminal.name, 'a');
terminal.dispose();
});
test('onDidOpenTerminal should fire when a terminal is created', (done) => {
const reg1 = window.onDidOpenTerminal(term => {
equal(term.name, 'b');
reg1.dispose();
const reg2 = window.onDidCloseTerminal(() => {
reg2.dispose();
done();
});
terminal.dispose();
});
const terminal = window.createTerminal('b');
});
test('Terminal.sendText should fire Terminal.onInput', (done) => {
const reg1 = window.onDidOpenTerminal(terminal => {
reg1.dispose();
const reg2 = renderer.onDidAcceptInput(data => {
equal(data, 'bar');
reg2.dispose();
const reg3 = window.onDidCloseTerminal(() => {
reg3.dispose();
done();
});
terminal.dispose();
});
terminal.sendText('bar', false);
});
const renderer = window.createTerminalRenderer('foo');
});
test('onDidChangeActiveTerminal should fire when new terminals are created', (done) => {
const reg1 = window.onDidChangeActiveTerminal((active: Terminal | undefined) => {
equal(active, terminal);
equal(active, window.activeTerminal);
reg1.dispose();
const reg2 = window.onDidChangeActiveTerminal((active: Terminal | undefined) => {
equal(active, undefined);
equal(active, window.activeTerminal);
reg2.dispose();
done();
});
terminal.dispose();
});
const terminal = window.createTerminal();
terminal.show();
});
test('onDidChangeTerminalDimensions should fire when new terminals are created', (done) => {
const reg1 = window.onDidChangeTerminalDimensions(async (event: TerminalDimensionsChangeEvent) => {
equal(event.terminal, terminal1);
equal(typeof event.dimensions.columns, 'number');
equal(typeof event.dimensions.rows, 'number');
ok(event.dimensions.columns > 0);
ok(event.dimensions.rows > 0);
reg1.dispose();
let terminal2: Terminal;
const reg2 = window.onDidOpenTerminal((newTerminal) => {
// This is guarantees to fire before dimensions change event
if (newTerminal !== terminal1) {
terminal2 = newTerminal;
reg2.dispose();
}
});
let firstCalled = false;
let secondCalled = false;
const reg3 = window.onDidChangeTerminalDimensions((event: TerminalDimensionsChangeEvent) => {
if (event.terminal === terminal1) {
// The original terminal should fire dimension change after a split
firstCalled = true;
} else if (event.terminal !== terminal1) {
// The new split terminal should fire dimension change
secondCalled = true;
}
if (firstCalled && secondCalled) {
terminal1.dispose();
terminal2.dispose();
reg3.dispose();
done();
}
});
await timeout(500);
commands.executeCommand('workbench.action.terminal.split');
});
const terminal1 = window.createTerminal({ name: 'test' });
terminal1.show();
});
suite('hideFromUser', () => {
test('should fire onDidWriteData correctly', done => {
const terminal = window.createTerminal({ name: 'bg', hideFromUser: true });
let data = '';
terminal.onDidWriteData(e => {
data += e;
if (data.indexOf('foo') !== -1) {
terminal.dispose();
done();
}
});
terminal.sendText('foo');
});
test('should be available to terminals API', done => {
const terminal = window.createTerminal({ name: 'bg', hideFromUser: true });
window.onDidOpenTerminal(t => {
equal(t, terminal);
equal(t.name, 'bg');
ok(window.terminals.indexOf(terminal) !== -1);
done();
});
});
});
suite('Terminal renderers (deprecated)', () => {
test('should fire onDidOpenTerminal and onDidCloseTerminal from createTerminalRenderer terminal', (done) => {
const reg1 = window.onDidOpenTerminal(term => {
equal(term.name, 'c');
reg1.dispose();
const reg2 = window.onDidCloseTerminal(() => {
reg2.dispose();
done();
});
term.dispose();
});
window.createTerminalRenderer('c');
});
test('should get maximum dimensions set when shown', (done) => {
let terminal: Terminal;
const reg1 = window.onDidOpenTerminal(term => {
reg1.dispose();
term.show();
terminal = term;
});
const renderer = window.createTerminalRenderer('foo');
const reg2 = renderer.onDidChangeMaximumDimensions(dimensions => {
ok(dimensions.columns > 0);
ok(dimensions.rows > 0);
reg2.dispose();
const reg3 = window.onDidCloseTerminal(() => {
reg3.dispose();
done();
});
terminal.dispose();
});
});
test('should fire Terminal.onData on write', (done) => {
const reg1 = window.onDidOpenTerminal(terminal => {
reg1.dispose();
const reg2 = terminal.onDidWriteData(data => {
equal(data, 'bar');
reg2.dispose();
const reg3 = window.onDidCloseTerminal(() => {
reg3.dispose();
done();
});
terminal.dispose();
});
renderer.write('bar');
});
const renderer = window.createTerminalRenderer('foo');
});
})
suite('Virtual process terminals', () => {
test('should fire onDidOpenTerminal and onDidCloseTerminal', (done) => {
const reg1 = window.onDidOpenTerminal(term => {
equal(term.name, 'c');
reg1.dispose();
const reg2 = window.onDidCloseTerminal(() => {
reg2.dispose();
done();
});
term.dispose();
});
const virtualProcess: TerminalVirtualProcess = {
write: new EventEmitter<string>().event
};
window.createTerminal({ name: 'c', virtualProcess });
});
test('should get dimensions event when shown', (done) => {
const reg1 = window.onDidOpenTerminal(term => {
reg1.dispose();
equal(terminal, term);
term.show();
});
const virtualProcess: TerminalVirtualProcess = {
write: new EventEmitter<string>().event,
onDidChangeDimensions: dimensions => {
ok(dimensions.columns > 0);
ok(dimensions.rows > 0);
const reg2 = window.onDidCloseTerminal(() => {
reg2.dispose();
done();
});
terminal.dispose();
}
};
const terminal = window.createTerminal({ name: 'foo', virtualProcess });
});
test('should fire Terminal.onData on write', (done) => {
const reg1 = window.onDidOpenTerminal(term => {
equal(terminal, term);
reg1.dispose();
const reg2 = terminal.onDidWriteData(data => {
equal(data, 'bar');
reg2.dispose();
const reg3 = window.onDidCloseTerminal(() => {
reg3.dispose();
done();
});
terminal.dispose();
});
writeEmitter.fire('bar');
});
const writeEmitter = new EventEmitter<string>();
const virtualProcess: TerminalVirtualProcess = {
write: writeEmitter.event
};
const terminal = window.createTerminal({ name: 'foo', virtualProcess });
});
test('should respect dimension overrides', (done) => {
const reg1 = window.onDidOpenTerminal(term => {
equal(terminal, term);
reg1.dispose();
term.show();
const reg2 = window.onDidChangeTerminalDimensions(e => {
equal(e.dimensions.columns, 10);
equal(e.dimensions.rows, 5);
equal(e.terminal, terminal)
reg2.dispose();
const reg3 = window.onDidCloseTerminal(() => {
reg3.dispose();
done();
});
terminal.dispose();
});
overrideDimensionsEmitter.fire({ columns: 10, rows: 5 });
});
const writeEmitter = new EventEmitter<string>();
const overrideDimensionsEmitter = new EventEmitter<TerminalDimensions>();
const virtualProcess: TerminalVirtualProcess = {
write: writeEmitter.event,
overrideDimensions: overrideDimensionsEmitter.event
};
const terminal = window.createTerminal({ name: 'foo', virtualProcess });
});
});
});
});
async function timeout(ms = 0): Promise<void> {
return new Promise<void>(resolve => setTimeout(() => resolve(), ms));
}
......@@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import { workspace, window, commands, ViewColumn, TextEditorViewColumnChangeEvent, Uri, Selection, Position, CancellationTokenSource, TextEditorSelectionChangeKind, Terminal, TerminalDimensionsChangeEvent } from 'vscode';
import { workspace, window, commands, ViewColumn, TextEditorViewColumnChangeEvent, Uri, Selection, Position, CancellationTokenSource, TextEditorSelectionChangeKind } from 'vscode';
import { join } from 'path';
import { closeAllEditors, pathEquals, createRandomFile } from '../utils';
......@@ -568,201 +568,4 @@ suite('window namespace tests', () => {
});
});
(process.platform === 'win32' ? suite.skip /* https://github.com/microsoft/vscode/issues/75689 */ : suite)('Terminal', () => {
test('sendText immediately after createTerminal should not throw', () => {
const terminal = window.createTerminal();
assert.doesNotThrow(terminal.sendText.bind(terminal, 'echo "foo"'));
terminal.dispose();
});
test('onDidCloseTerminal event fires when terminal is disposed', (done) => {
const terminal = window.createTerminal();
const reg = window.onDidCloseTerminal((eventTerminal) => {
assert.equal(terminal, eventTerminal);
reg.dispose();
done();
});
terminal.dispose();
});
test('processId immediately after createTerminal should fetch the pid', (done) => {
const terminal = window.createTerminal();
terminal.processId.then(id => {
assert.ok(id > 0);
terminal.dispose();
done();
});
});
test('name in constructor should set terminal.name', () => {
const terminal = window.createTerminal('a');
assert.equal(terminal.name, 'a');
terminal.dispose();
});
test('onDidOpenTerminal should fire when a terminal is created', (done) => {
const reg1 = window.onDidOpenTerminal(term => {
assert.equal(term.name, 'b');
reg1.dispose();
const reg2 = window.onDidCloseTerminal(() => {
reg2.dispose();
done();
});
terminal.dispose();
});
const terminal = window.createTerminal('b');
});
test('createTerminalRenderer should fire onDidOpenTerminal and onDidCloseTerminal', (done) => {
const reg1 = window.onDidOpenTerminal(term => {
assert.equal(term.name, 'c');
reg1.dispose();
const reg2 = window.onDidCloseTerminal(() => {
reg2.dispose();
done();
});
term.dispose();
});
window.createTerminalRenderer('c');
});
test('terminal renderers should get maximum dimensions set when shown', (done) => {
let terminal: Terminal;
const reg1 = window.onDidOpenTerminal(term => {
reg1.dispose();
term.show();
terminal = term;
});
const renderer = window.createTerminalRenderer('foo');
const reg2 = renderer.onDidChangeMaximumDimensions(dimensions => {
assert.ok(dimensions.columns > 0);
assert.ok(dimensions.rows > 0);
reg2.dispose();
const reg3 = window.onDidCloseTerminal(() => {
reg3.dispose();
done();
});
terminal.dispose();
});
});
test('TerminalRenderer.write should fire Terminal.onData', (done) => {
const reg1 = window.onDidOpenTerminal(terminal => {
reg1.dispose();
const reg2 = terminal.onDidWriteData(data => {
assert.equal(data, 'bar');
reg2.dispose();
const reg3 = window.onDidCloseTerminal(() => {
reg3.dispose();
done();
});
terminal.dispose();
});
renderer.write('bar');
});
const renderer = window.createTerminalRenderer('foo');
});
test('Terminal.sendText should fire Terminal.onInput', (done) => {
const reg1 = window.onDidOpenTerminal(terminal => {
reg1.dispose();
const reg2 = renderer.onDidAcceptInput(data => {
assert.equal(data, 'bar');
reg2.dispose();
const reg3 = window.onDidCloseTerminal(() => {
reg3.dispose();
done();
});
terminal.dispose();
});
terminal.sendText('bar', false);
});
const renderer = window.createTerminalRenderer('foo');
});
test('onDidChangeActiveTerminal should fire when new terminals are created', (done) => {
const reg1 = window.onDidChangeActiveTerminal((active: Terminal | undefined) => {
assert.equal(active, terminal);
assert.equal(active, window.activeTerminal);
reg1.dispose();
const reg2 = window.onDidChangeActiveTerminal((active: Terminal | undefined) => {
assert.equal(active, undefined);
assert.equal(active, window.activeTerminal);
reg2.dispose();
done();
});
terminal.dispose();
});
const terminal = window.createTerminal();
terminal.show();
});
test('onDidChangeTerminalDimensions should fire when new terminals are created', (done) => {
const reg1 = window.onDidChangeTerminalDimensions(async (event: TerminalDimensionsChangeEvent) => {
assert.equal(event.terminal, terminal1);
assert.equal(typeof event.dimensions.columns, 'number');
assert.equal(typeof event.dimensions.rows, 'number');
assert.ok(event.dimensions.columns > 0);
assert.ok(event.dimensions.rows > 0);
reg1.dispose();
let terminal2: Terminal;
const reg2 = window.onDidOpenTerminal((newTerminal) => {
// This is guarantees to fire before dimensions change event
if (newTerminal !== terminal1) {
terminal2 = newTerminal;
reg2.dispose();
}
});
let firstCalled = false;
let secondCalled = false;
const reg3 = window.onDidChangeTerminalDimensions((event: TerminalDimensionsChangeEvent) => {
if (event.terminal === terminal1) {
// The original terminal should fire dimension change after a split
firstCalled = true;
} else if (event.terminal !== terminal1) {
// The new split terminal should fire dimension change
secondCalled = true;
}
if (firstCalled && secondCalled) {
terminal1.dispose();
terminal2.dispose();
reg3.dispose();
done();
}
});
await timeout(500);
commands.executeCommand('workbench.action.terminal.split');
});
const terminal1 = window.createTerminal({ name: 'test' });
terminal1.show();
});
test('hideFromUser terminal: onDidWriteData should work', done => {
const terminal = window.createTerminal({ name: 'bg', hideFromUser: true });
let data = '';
terminal.onDidWriteData(e => {
data += e;
if (data.indexOf('foo') !== -1) {
terminal.dispose();
done();
}
});
terminal.sendText('foo');
});
test('hideFromUser terminal: should be available to terminals API', done => {
const terminal = window.createTerminal({ name: 'bg', hideFromUser: true });
window.onDidOpenTerminal(t => {
assert.equal(t, terminal);
assert.equal(t.name, 'bg');
assert.ok(window.terminals.indexOf(terminal) !== -1);
done();
});
});
});
});
async function timeout(ms = 0): Promise<void> {
return new Promise<void>(resolve => setTimeout(() => resolve(), ms));
}
\ No newline at end of file
......@@ -1162,6 +1162,8 @@ declare module 'vscode' {
* [Terminal.sendText](#Terminal.sendText) is triggered that will fire the
* [TerminalRenderer.onDidAcceptInput](#TerminalRenderer.onDidAcceptInput) event.
*
* @deprecated Use [virtual processes](#TerminalVirtualProcess) instead.
*
* **Example:** Create a terminal renderer, show it and write hello world in red
* ```typescript
* const renderer = window.createTerminalRenderer('foo');
......@@ -1172,6 +1174,7 @@ declare module 'vscode' {
export interface TerminalRenderer {
/**
* The name of the terminal, this will appear in the terminal selector.
* @deprecated Use [virtual processes](#TerminalVirtualProcess) instead.
*/
name: string;
......@@ -1180,6 +1183,8 @@ declare module 'vscode' {
* a value smaller than the maximum value, if this is undefined the terminal will auto fit
* to the maximum value [maximumDimensions](TerminalRenderer.maximumDimensions).
*
* @deprecated Use [virtual processes](#TerminalVirtualProcess) instead.
*
* **Example:** Override the dimensions of a TerminalRenderer to 20 columns and 10 rows
* ```typescript
* terminalRenderer.dimensions = {
......@@ -1195,11 +1200,15 @@ declare module 'vscode' {
* terminal renderer is created and also until the terminal becomes visible in the UI.
* Listen to [onDidChangeMaximumDimensions](TerminalRenderer.onDidChangeMaximumDimensions)
* to get notified when this value changes.
*
* @deprecated Use [virtual processes](#TerminalVirtualProcess) instead.
*/
readonly maximumDimensions: TerminalDimensions | undefined;
/**
* The corresponding [Terminal](#Terminal) for this TerminalRenderer.
*
* @deprecated Use [virtual processes](#TerminalVirtualProcess) instead.
*/
readonly terminal: Terminal;
......@@ -1207,6 +1216,9 @@ declare module 'vscode' {
* Write text to the terminal. Unlike [Terminal.sendText](#Terminal.sendText) which sends
* text to the underlying _process_, this will write the text to the terminal itself.
*
* @param text The text to write.
* @deprecated Use [virtual processes](#TerminalVirtualProcess) instead.
*
* **Example:** Write red text to the terminal
* ```typescript
* terminalRenderer.write('\x1b[31mHello world\x1b[0m');
......@@ -1216,8 +1228,6 @@ declare module 'vscode' {
* ```typescript
* terminalRenderer.write('\x1b[10;20H*');
* ```
*
* @param text The text to write.
*/
write(text: string): void;
......@@ -1226,6 +1236,8 @@ declare module 'vscode' {
* [Terminal.sendText](#Terminal.sendText). Keystrokes are converted into their
* corresponding VT sequence representation.
*
* @deprecated Use [virtual processes](#TerminalVirtualProcess) instead.
*
* **Example:** Simulate interaction with the terminal from an outside extension or a
* workbench command such as `workbench.action.terminal.runSelectedText`
* ```typescript
......@@ -1241,6 +1253,8 @@ declare module 'vscode' {
/**
* An event which fires when the [maximum dimensions](#TerminalRenderer.maximumDimensions) of
* the terminal renderer change.
*
* @deprecated Use [virtual processes](#TerminalVirtualProcess) instead.
*/
readonly onDidChangeMaximumDimensions: Event<TerminalDimensions>;
}
......@@ -1250,12 +1264,135 @@ declare module 'vscode' {
* Create a [TerminalRenderer](#TerminalRenderer).
*
* @param name The name of the terminal renderer, this shows up in the terminal selector.
* @deprecated Use [virtual processes](#TerminalVirtualProcess) instead.
*/
export function createTerminalRenderer(name: string): TerminalRenderer;
}
//#endregion
//#region Terminal virtual process
export namespace window {
/**
* Creates a [Terminal](#Terminal) where an extension acts as the process.
*
* @param options A [TerminalVirtualProcessOptions](#TerminalVirtualProcessOptions) object describing the
* characteristics of the new terminal.
* @return A new Terminal.
*/
export function createTerminal(options: TerminalVirtualProcessOptions): Terminal;
}
/**
* Value-object describing what options a virtual process terminal should use.
*/
export interface TerminalVirtualProcessOptions {
/**
* A human-readable string which will be used to represent the terminal in the UI.
*/
name: string;
/**
* An implementation of [TerminalVirtualProcess](#TerminalVirtualProcess) that allows an
* extension to act as a terminal's backing process.
*/
virtualProcess: TerminalVirtualProcess;
}
/**
* Defines the interface of a terminal virtual process, enabling extensions to act as a process
* in the terminal.
*/
interface TerminalVirtualProcess {
/**
* An event that when fired will write data to the terminal. Unlike
* [Terminal.sendText](#Terminal.sendText) which sends text to the underlying _process_,
* this will write the text to the terminal itself.
*
* **Example:** Write red text to the terminal
* ```typescript
* const writeEmitter = new vscode.EventEmitter<string>();
* const virtualProcess: TerminalVirtualProcess = {
* write: writeEmitter.event
* };
* vscode.window.createTerminal({ name: 'My terminal', virtualProcess });
* writeEmitter.fire('\x1b[31mHello world\x1b[0m');
* ```
*
* **Example:** Move the cursor to the 10th row and 20th column and write an asterisk
* ```typescript
* writeEmitter.fire('\x1b[10;20H*');
* ```
*/
write: Event<string>;
/**
* An event that when fired allows overriding the [dimensions](#Terminal.dimensions) of the
* terminal. Note that when set the overridden dimensions will only take effect when they
* are lower than the actual dimensions of the terminal (ie. there will never be a scroll
* bar). Set to `undefined` for the terminal to go back to the regular dimensions.
*
* **Example:** Override the dimensions of a terminal to 20 columns and 10 rows
* ```typescript
* const dimensionsEmitter = new vscode.EventEmitter<string>();
* const virtualProcess: TerminalVirtualProcess = {
* write: writeEmitter.event,
* overrideDimensions: dimensionsEmitter.event
* };
* vscode.window.createTerminal({ name: 'My terminal', virtualProcess });
* dimensionsEmitter.fire({
* columns: 20,
* rows: 10
* });
* ```
*/
overrideDimensions?: Event<TerminalDimensions | undefined>;
/**
* An event that when fired will exit the process with an exit code, this will behave the
* same for a virtual process as when a regular process exits with an exit code.
*/
exit?: Event<number>;
/**
* Implement to handle keystrokes in the terminal or when an extension calls
* [Terminal.sendText](#Terminal.sendText). Keystrokes are converted into their
* corresponding VT sequence representation.
*
* @param data The sent data.
*
* **Example:** Echo input in the terminal. The sequence for enter (`\r`) is translated to
* CRLF to go to a new line and move the cursor to the start of the line.
* ```typescript
* const writeEmitter = new vscode.EventEmitter<string>();
* const virtualProcess: TerminalVirtualProcess = {
* write: writeEmitter.event,
* onDidAcceptInput: data => writeEmitter.fire(data === '\r' ? '\r\n' : data);
* };
* vscode.window.createTerminal({ name: 'Local echo', virtualProcess });
* ```
*/
onDidAcceptInput?(data: string): void;
/**
* Implement to handle when the number of rows and columns that fit into the terminal panel
* changes, for example when font size changes or when the panel is resized. The initial
* state of a terminal's dimensions should be treated as `undefined` until this is triggered
* as the size of a terminal isn't know until it shows up in the user interface.
*
* @param dimensions The new dimensions.
*/
onDidChangeDimensions?(dimensions: TerminalDimensions): void;
/**
* Implement to handle when the terminal shuts down by an act of the user.
*/
onDidShutdownTerminal?(): void;
}
//#endregion
//#region Joh -> exclusive document filters
export interface DocumentFilter {
......
......@@ -5,9 +5,9 @@
import { IDisposable, DisposableStore } from 'vs/base/common/lifecycle';
import { ITerminalService, ITerminalInstance, IShellLaunchConfig, ITerminalProcessExtHostProxy, ITerminalProcessExtHostRequest, ITerminalDimensions, EXT_HOST_CREATION_DELAY, IAvailableShellsRequest, IDefaultShellAndArgsRequest } from 'vs/workbench/contrib/terminal/common/terminal';
import { ExtHostContext, ExtHostTerminalServiceShape, MainThreadTerminalServiceShape, MainContext, IExtHostContext, ShellLaunchConfigDto } from 'vs/workbench/api/common/extHost.protocol';
import { ExtHostContext, ExtHostTerminalServiceShape, MainThreadTerminalServiceShape, MainContext, IExtHostContext, ShellLaunchConfigDto, TerminalLaunchConfig } from 'vs/workbench/api/common/extHost.protocol';
import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers';
import { UriComponents, URI } from 'vs/base/common/uri';
import { URI } from 'vs/base/common/uri';
import { StopWatch } from 'vs/base/common/stopwatch';
import { ITerminalInstanceService } from 'vs/workbench/contrib/terminal/browser/terminal';
import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService';
......@@ -18,7 +18,8 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape
private _proxy: ExtHostTerminalServiceShape;
private _remoteAuthority: string | null;
private readonly _toDispose = new DisposableStore();
private _terminalProcesses: { [id: number]: ITerminalProcessExtHostProxy } = {};
private _terminalProcesses: { [id: number]: Promise<ITerminalProcessExtHostProxy> } = {};
private _terminalProcessesReady: { [id: number]: (proxy: ITerminalProcessExtHostProxy) => void } = {};
private _terminalOnDidWriteDataListeners: { [id: number]: IDisposable } = {};
private _terminalOnDidAcceptInputListeners: { [id: number]: IDisposable } = {};
......@@ -41,11 +42,13 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape
this._onInstanceDimensionsChanged(instance);
}, EXT_HOST_CREATION_DELAY);
}));
this._toDispose.add(_terminalService.onInstanceDisposed(instance => this._onTerminalDisposed(instance)));
this._toDispose.add(_terminalService.onInstanceProcessIdReady(instance => this._onTerminalProcessIdReady(instance)));
this._toDispose.add(_terminalService.onInstanceDimensionsChanged(instance => this._onInstanceDimensionsChanged(instance)));
this._toDispose.add(_terminalService.onInstanceMaximumDimensionsChanged(instance => this._onInstanceMaximumDimensionsChanged(instance)));
this._toDispose.add(_terminalService.onInstanceRequestExtHostProcess(request => this._onTerminalRequestExtHostProcess(request)));
this._toDispose.add(_terminalService.onInstanceRequestVirtualProcess(proxy => this._onTerminalRequestVirtualProcess(proxy)));
this._toDispose.add(_terminalService.onActiveInstanceChanged(instance => this._onActiveTerminalChanged(instance ? instance.id : null)));
this._toDispose.add(_terminalService.onInstanceTitleChanged(instance => this._onTitleChanged(instance.id, instance.title)));
this._toDispose.add(_terminalService.configHelper.onWorkspacePermissionsChanged(isAllowed => this._onWorkspacePermissionsChanged(isAllowed)));
......@@ -76,19 +79,21 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape
// when the extension host process goes down ?
}
public $createTerminal(name?: string, shellPath?: string, shellArgs?: string[] | string, cwd?: string | UriComponents, env?: { [key: string]: string }, waitOnExit?: boolean, strictEnv?: boolean, hideFromUser?: boolean): Promise<{ id: number, name: string }> {
public $createTerminal(launchConfig: TerminalLaunchConfig): Promise<{ id: number, name: string }> {
const shellLaunchConfig: IShellLaunchConfig = {
name,
executable: shellPath,
args: shellArgs,
cwd: typeof cwd === 'string' ? cwd : URI.revive(cwd),
waitOnExit,
name: launchConfig.name,
executable: launchConfig.shellPath,
args: launchConfig.shellArgs,
cwd: typeof launchConfig.cwd === 'string' ? launchConfig.cwd : URI.revive(launchConfig.cwd),
waitOnExit: launchConfig.waitOnExit,
ignoreConfigurationCwd: true,
env,
strictEnv,
hideFromUser
env: launchConfig.env,
strictEnv: launchConfig.strictEnv,
hideFromUser: launchConfig.hideFromUser,
isVirtualProcess: launchConfig.isVirtualProcess
};
const terminal = this._terminalService.createTerminal(shellLaunchConfig);
this._terminalProcesses[terminal.id] = new Promise<ITerminalProcessExtHostProxy>(r => this._terminalProcessesReady[terminal.id] = r);
return Promise.resolve({
id: terminal.id,
name: terminal.title
......@@ -239,7 +244,7 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape
return;
}
this._terminalProcesses[request.proxy.terminalId] = request.proxy;
this._terminalProcessesReady[request.proxy.terminalId](request.proxy);
const shellLaunchConfigDto: ShellLaunchConfigDto = {
name: request.shellLaunchConfig.name,
executable: request.shellLaunchConfig.executable,
......@@ -256,29 +261,46 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape
request.proxy.onRequestLatency(() => this._onRequestLatency(request.proxy.terminalId));
}
private _onTerminalRequestVirtualProcess(proxy: ITerminalProcessExtHostProxy): void {
this._terminalProcessesReady[proxy.terminalId](proxy);
delete this._terminalProcessesReady[proxy.terminalId];
// Note that onReisze is not being listened to here as it needs to fire when max dimensions
// change, excluding the dimension override
proxy.onInput(data => this._proxy.$acceptProcessInput(proxy.terminalId, data));
proxy.onShutdown(immediate => this._proxy.$acceptProcessShutdown(proxy.terminalId, immediate));
proxy.onRequestCwd(() => this._proxy.$acceptProcessRequestCwd(proxy.terminalId));
proxy.onRequestInitialCwd(() => this._proxy.$acceptProcessRequestInitialCwd(proxy.terminalId));
proxy.onRequestLatency(() => this._onRequestLatency(proxy.terminalId));
}
public $sendProcessTitle(terminalId: number, title: string): void {
this._terminalProcesses[terminalId].emitTitle(title);
this._terminalProcesses[terminalId].then(e => e.emitTitle(title));
}
public $sendProcessData(terminalId: number, data: string): void {
this._terminalProcesses[terminalId].emitData(data);
this._terminalProcesses[terminalId].then(e => e.emitData(data));
}
public $sendProcessReady(terminalId: number, pid: number, cwd: string): void {
this._terminalProcesses[terminalId].emitReady(pid, cwd);
this._terminalProcesses[terminalId].then(e => e.emitReady(pid, cwd));
}
public $sendProcessExit(terminalId: number, exitCode: number): void {
this._terminalProcesses[terminalId].emitExit(exitCode);
this._terminalProcesses[terminalId].then(e => e.emitExit(exitCode));
delete this._terminalProcesses[terminalId];
}
public $sendOverrideDimensions(terminalId: number, dimensions: ITerminalDimensions | undefined): void {
this._terminalProcesses[terminalId].then(e => e.emitOverrideDimensions(dimensions));
}
public $sendProcessInitialCwd(terminalId: number, initialCwd: string): void {
this._terminalProcesses[terminalId].emitInitialCwd(initialCwd);
this._terminalProcesses[terminalId].then(e => e.emitInitialCwd(initialCwd));
}
public $sendProcessCwd(terminalId: number, cwd: string): void {
this._terminalProcesses[terminalId].emitCwd(cwd);
this._terminalProcesses[terminalId].then(e => e.emitCwd(cwd));
}
private async _onRequestLatency(terminalId: number): Promise<void> {
......@@ -290,7 +312,7 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape
sw.stop();
sum += sw.elapsed();
}
this._terminalProcesses[terminalId].emitLatency(sum / COUNT);
this._terminalProcesses[terminalId].then(e => e.emitLatency(sum / COUNT));
}
private _isPrimaryExtHost(): boolean {
......
......@@ -389,8 +389,20 @@ export interface MainThreadProgressShape extends IDisposable {
$progressEnd(handle: number): void;
}
export interface TerminalLaunchConfig {
name?: string;
shellPath?: string;
shellArgs?: string[] | string;
cwd?: string | UriComponents;
env?: { [key: string]: string | null };
waitOnExit?: boolean;
strictEnv?: boolean;
hideFromUser?: boolean;
isVirtualProcess?: boolean;
}
export interface MainThreadTerminalServiceShape extends IDisposable {
$createTerminal(name?: string, shellPath?: string, shellArgs?: string[] | string, cwd?: string | UriComponents, env?: { [key: string]: string | null }, waitOnExit?: boolean, strictEnv?: boolean, hideFromUser?: boolean): Promise<{ id: number, name: string }>;
$createTerminal(config: TerminalLaunchConfig): Promise<{ id: number, name: string }>;
$createTerminalRenderer(name: string): Promise<number>;
$dispose(terminalId: number): void;
$hide(terminalId: number): void;
......@@ -403,6 +415,7 @@ export interface MainThreadTerminalServiceShape extends IDisposable {
$sendProcessData(terminalId: number, data: string): void;
$sendProcessReady(terminalId: number, pid: number, cwd: string): void;
$sendProcessExit(terminalId: number, exitCode: number): void;
$sendOverrideDimensions(terminalId: number, dimensions: ITerminalDimensions | undefined): void;
$sendProcessInitialCwd(terminalId: number, cwd: string): void;
$sendProcessCwd(terminalId: number, initialCwd: string): void;
......
......@@ -524,10 +524,14 @@ export function createApiFactory(
checkProposedApiEnabled(extension);
return extHostEditorInsets.createWebviewEditorInset(editor, line, height, options, extension);
},
createTerminal(nameOrOptions?: vscode.TerminalOptions | string, shellPath?: string, shellArgs?: string[] | string): vscode.Terminal {
createTerminal(nameOrOptions?: vscode.TerminalOptions | vscode.TerminalVirtualProcessOptions | string, shellPath?: string, shellArgs?: string[] | string): vscode.Terminal {
if (typeof nameOrOptions === 'object') {
nameOrOptions.hideFromUser = nameOrOptions.hideFromUser || (nameOrOptions.runInBackground && extension.enableProposedApi);
return extHostTerminalService.createTerminalFromOptions(nameOrOptions);
if ('virtualProcess' in nameOrOptions) {
return extHostTerminalService.createVirtualProcessTerminal(nameOrOptions);
} else {
nameOrOptions.hideFromUser = nameOrOptions.hideFromUser || (nameOrOptions.runInBackground && extension.enableProposedApi);
return extHostTerminalService.createTerminalFromOptions(nameOrOptions);
}
}
return extHostTerminalService.createTerminal(<string>nameOrOptions, shellPath, shellArgs);
},
......
......@@ -13,7 +13,7 @@ import { Event, Emitter } from 'vs/base/common/event';
import { ExtHostTerminalServiceShape, MainContext, MainThreadTerminalServiceShape, IMainContext, ShellLaunchConfigDto, IShellDefinitionDto, IShellAndArgsDto } from 'vs/workbench/api/common/extHost.protocol';
import { ExtHostConfiguration, ExtHostConfigProvider } from 'vs/workbench/api/common/extHostConfiguration';
import { ILogService } from 'vs/platform/log/common/log';
import { EXT_HOST_CREATION_DELAY, IShellLaunchConfig, ITerminalEnvironment } from 'vs/workbench/contrib/terminal/common/terminal';
import { EXT_HOST_CREATION_DELAY, IShellLaunchConfig, ITerminalEnvironment, ITerminalChildProcess, ITerminalDimensions } from 'vs/workbench/contrib/terminal/common/terminal';
import { TerminalProcess } from 'vs/workbench/contrib/terminal/node/terminalProcess';
import { timeout } from 'vs/base/common/async';
import { ExtHostWorkspace } from 'vs/workbench/api/common/extHostWorkspace';
......@@ -22,6 +22,7 @@ import { ExtHostVariableResolverService } from 'vs/workbench/api/node/extHostDeb
import { ExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocumentsAndEditors';
import { getSystemShell, detectAvailableShells } from 'vs/workbench/contrib/terminal/node/terminal';
import { getMainProcessParentEnv } from 'vs/workbench/contrib/terminal/node/terminalEnvironment';
import { IDisposable } from 'vs/base/common/lifecycle';
const RENDERER_NO_PROCESS_ID = -1;
......@@ -118,7 +119,14 @@ export class ExtHostTerminal extends BaseExtHostTerminal implements vscode.Termi
strictEnv?: boolean,
hideFromUser?: boolean
): void {
this._proxy.$createTerminal(this._name, shellPath, shellArgs, cwd, env, waitOnExit, strictEnv, hideFromUser).then(terminal => {
this._proxy.$createTerminal({ name: this._name, shellPath, shellArgs, cwd, env, waitOnExit, strictEnv, hideFromUser }).then(terminal => {
this._name = terminal.name;
this._runQueuedRequests(terminal.id);
});
}
public createVirtualProcess(): Promise<void> {
return this._proxy.$createTerminal({ name: this._name, isVirtualProcess: true }).then(terminal => {
this._name = terminal.name;
this._runQueuedRequests(terminal.id);
});
......@@ -275,7 +283,7 @@ export class ExtHostTerminalService implements ExtHostTerminalServiceShape {
private _proxy: MainThreadTerminalServiceShape;
private _activeTerminal: ExtHostTerminal | undefined;
private _terminals: ExtHostTerminal[] = [];
private _terminalProcesses: { [id: number]: TerminalProcess } = {};
private _terminalProcesses: { [id: number]: ITerminalChildProcess } = {};
private _terminalRenderers: ExtHostTerminalRenderer[] = [];
private _getTerminalPromises: { [id: number]: Promise<ExtHostTerminal> } = {};
......@@ -318,6 +326,17 @@ export class ExtHostTerminalService implements ExtHostTerminalServiceShape {
return terminal;
}
public createVirtualProcessTerminal(options: vscode.TerminalVirtualProcessOptions): vscode.Terminal {
const terminal = new ExtHostTerminal(this._proxy, options.name);
const p = new ExtHostVirtualProcess(options.virtualProcess);
terminal.createVirtualProcess().then(() => {
this._setupExtHostProcessListeners(terminal._id, p);
p.startSendingEvents();
});
this._terminals.push(terminal);
return terminal;
}
public createTerminalRenderer(name: string): vscode.TerminalRenderer {
const terminal = new ExtHostTerminal(this._proxy, name);
terminal._setProcessId(undefined);
......@@ -414,13 +433,20 @@ export class ExtHostTerminalService implements ExtHostTerminalServiceShape {
}
public $acceptTerminalMaximumDimensions(id: number, cols: number, rows: number): void {
this._getTerminalByIdEventually(id).then(() => {
// When a terminal's dimensions change, a renderer's _maximum_ dimensions change
const renderer = this._getTerminalRendererById(id);
if (renderer) {
renderer._setMaximumDimensions(cols, rows);
}
});
if (this._terminalProcesses[id]) {
// Virtual processes only - when virtual process resize fires it means that the
// terminal's maximum dimensions changed
this._terminalProcesses[id].resize(cols, rows);
} else {
// Terminal renderer
this._getTerminalByIdEventually(id).then(() => {
// When a terminal's dimensions change, a renderer's _maximum_ dimensions change
const renderer = this._getTerminalRendererById(id);
if (renderer) {
renderer._setMaximumDimensions(cols, rows);
}
});
}
}
public $acceptTerminalRendererInput(id: number, data: string): void {
......@@ -546,11 +572,17 @@ export class ExtHostTerminalService implements ExtHostTerminalServiceShape {
// TODO: Support conpty on remote, it doesn't seem to work for some reason?
// TODO: When conpty is enabled, only enable it when accessibilityMode is off
const enableConpty = false; //terminalConfig.get('windowsEnableConpty') as boolean;
const p = new TerminalProcess(shellLaunchConfig, initialCwd, cols, rows, env, enableConpty, this._logService);
this._setupExtHostProcessListeners(id, new TerminalProcess(shellLaunchConfig, initialCwd, cols, rows, env, enableConpty, this._logService));
}
private _setupExtHostProcessListeners(id: number, p: ITerminalChildProcess): void {
p.onProcessReady((e: { pid: number, cwd: string }) => this._proxy.$sendProcessReady(id, e.pid, e.cwd));
p.onProcessTitleChanged(title => this._proxy.$sendProcessTitle(id, title));
p.onProcessData(data => this._proxy.$sendProcessData(id, data));
p.onProcessExit(exitCode => this._onProcessExit(id, exitCode));
if (p.onProcessOverrideDimensions) {
p.onProcessOverrideDimensions(e => this._proxy.$sendOverrideDimensions(id, e));
}
this._terminalProcesses[id] = p;
}
......@@ -598,9 +630,6 @@ export class ExtHostTerminalService implements ExtHostTerminalServiceShape {
}
private _onProcessExit(id: number, exitCode: number): void {
// Remove listeners
this._terminalProcesses[id].dispose();
// Remove process reference
delete this._terminalProcesses[id];
......@@ -681,3 +710,83 @@ class ApiRequest {
this._callback.apply(proxy, [id].concat(this._args));
}
}
class ExtHostVirtualProcess implements ITerminalChildProcess {
private _queuedEvents: (IQueuedEvent<string> | IQueuedEvent<number> | IQueuedEvent<{ pid: number, cwd: string }> | IQueuedEvent<ITerminalDimensions | undefined>)[] = [];
private _queueDisposables: IDisposable[] | undefined;
private readonly _onProcessData = new Emitter<string>();
public get onProcessData(): Event<string> { return this._onProcessData.event; }
private readonly _onProcessExit = new Emitter<number>();
public get onProcessExit(): Event<number> { return this._onProcessExit.event; }
private readonly _onProcessReady = new Emitter<{ pid: number, cwd: string }>();
public get onProcessReady(): Event<{ pid: number, cwd: string }> { return this._onProcessReady.event; }
private readonly _onProcessTitleChanged = new Emitter<string>();
public get onProcessTitleChanged(): Event<string> { return this._onProcessTitleChanged.event; }
private readonly _onProcessOverrideDimensions = new Emitter<ITerminalDimensions | undefined>();
public get onProcessOverrideDimensions(): Event<ITerminalDimensions | undefined> { return this._onProcessOverrideDimensions.event; }
constructor(
private readonly _virtualProcess: vscode.TerminalVirtualProcess
) {
this._queueDisposables = [];
this._queueDisposables.push(this._virtualProcess.write(e => this._queuedEvents.push({ emitter: this._onProcessData, data: e })));
if (this._virtualProcess.exit) {
this._queueDisposables.push(this._virtualProcess.exit(e => this._queuedEvents.push({ emitter: this._onProcessExit, data: e })));
}
if (this._virtualProcess.overrideDimensions) {
this._queueDisposables.push(this._virtualProcess.overrideDimensions(e => this._queuedEvents.push({ emitter: this._onProcessOverrideDimensions, data: e ? { cols: e.columns, rows: e.rows } : undefined })));
}
}
shutdown(): void {
if (this._virtualProcess.onDidShutdownTerminal) {
this._virtualProcess.onDidShutdownTerminal();
}
}
input(data: string): void {
if (this._virtualProcess.onDidAcceptInput) {
this._virtualProcess.onDidAcceptInput(data);
}
}
resize(cols: number, rows: number): void {
if (this._virtualProcess.onDidChangeDimensions) {
this._virtualProcess.onDidChangeDimensions({ columns: cols, rows });
}
}
getInitialCwd(): Promise<string> {
return Promise.resolve('');
}
getCwd(): Promise<string> {
return Promise.resolve('');
}
getLatency(): Promise<number> {
return Promise.resolve(0);
}
startSendingEvents(): void {
// Flush all buffered events
this._queuedEvents.forEach(e => (<any>e.emitter.fire)(e.data));
this._queuedEvents = [];
this._queueDisposables = undefined;
// Attach the real listeners
this._virtualProcess.write(e => this._onProcessData.fire(e));
if (this._virtualProcess.exit) {
this._virtualProcess.exit(e => this._onProcessExit.fire(e));
}
if (this._virtualProcess.overrideDimensions) {
this._virtualProcess.overrideDimensions(e => this._onProcessOverrideDimensions.fire(e ? { cols: e.columns, rows: e.rows } : e));
}
}
}
interface IQueuedEvent<T> {
emitter: Emitter<T>;
data: T;
}
......@@ -174,7 +174,7 @@ export class TerminalInstance implements ITerminalInstance {
private _terminalHasTextContextKey: IContextKey<boolean>;
private _cols: number;
private _rows: number;
private _dimensionsOverride: ITerminalDimensions;
private _dimensionsOverride: ITerminalDimensions | undefined;
private _windowsShellHelper: IWindowsShellHelper | undefined;
private _xtermReadyPromise: Promise<void>;
private _titleReadyPromise: Promise<string>;
......@@ -363,14 +363,17 @@ export class TerminalInstance implements ITerminalInstance {
if (this._cols !== newCols || this._rows !== newRows) {
this._cols = newCols;
this._rows = newRows;
if (this.shellLaunchConfig.isRendererOnly) {
this._onMaximumDimensionsChanged.fire();
}
this._fireMaximumDimensionsChanged();
}
return dimension.width;
}
@debounce(50)
private _fireMaximumDimensionsChanged(): void {
this._onMaximumDimensionsChanged.fire();
}
private _getDimension(width: number, height: number): dom.Dimension | null {
// The font needs to have been initialized
const font = this._configHelper.getFont(this._xterm);
......@@ -945,6 +948,7 @@ export class TerminalInstance implements ITerminalInstance {
this._processManager.onProcessReady(() => this._onProcessIdReady.fire(this));
this._processManager.onProcessExit(exitCode => this._onProcessExit(exitCode));
this._processManager.onProcessData(data => this._onData.fire(data));
this._processManager.onProcessOverrideDimensions(e => this.setDimensions(e));
if (this._shellLaunchConfig.name) {
this.setTitle(this._shellLaunchConfig.name, false);
......@@ -1343,7 +1347,7 @@ export class TerminalInstance implements ITerminalInstance {
return this._titleReadyPromise;
}
public setDimensions(dimensions: ITerminalDimensions): void {
public setDimensions(dimensions: ITerminalDimensions | undefined): void {
this._dimensionsOverride = dimensions;
this._resize();
}
......
......@@ -5,7 +5,7 @@
import * as platform from 'vs/base/common/platform';
import * as terminalEnvironment from 'vs/workbench/contrib/terminal/common/terminalEnvironment';
import { ProcessState, ITerminalProcessManager, IShellLaunchConfig, ITerminalConfigHelper, ITerminalChildProcess, IBeforeProcessDataEvent, ITerminalEnvironment } from 'vs/workbench/contrib/terminal/common/terminal';
import { ProcessState, ITerminalProcessManager, IShellLaunchConfig, ITerminalConfigHelper, ITerminalChildProcess, IBeforeProcessDataEvent, ITerminalEnvironment, ITerminalDimensions } from 'vs/workbench/contrib/terminal/common/terminal';
import { ILogService } from 'vs/platform/log/common/log';
import { Emitter, Event } from 'vs/base/common/event';
import { IHistoryService } from 'vs/workbench/services/history/common/history';
......@@ -29,6 +29,11 @@ const LAUNCHING_DURATION = 500;
*/
const LATENCY_MEASURING_INTERVAL = 1000;
enum ProcessType {
Process,
VirtualProcess
}
/**
* Holds all state related to the creation and management of terminal processes.
*
......@@ -46,6 +51,7 @@ export class TerminalProcessManager implements ITerminalProcessManager {
public userHome: string | undefined;
private _process: ITerminalChildProcess | null = null;
private _processType: ProcessType = ProcessType.Process;
private _preLaunchInputQueue: string[] = [];
private _latency: number = -1;
private _latencyRequest: Promise<number>;
......@@ -62,6 +68,8 @@ export class TerminalProcessManager implements ITerminalProcessManager {
public get onProcessTitle(): Event<string> { return this._onProcessTitle.event; }
private readonly _onProcessExit = new Emitter<number>();
public get onProcessExit(): Event<number> { return this._onProcessExit.event; }
private readonly _onProcessOverrideDimensions = new Emitter<ITerminalDimensions | undefined>();
public get onProcessOverrideDimensions(): Event<ITerminalDimensions | undefined> { return this._onProcessOverrideDimensions.event; }
constructor(
private readonly _terminalId: number,
......@@ -103,32 +111,37 @@ export class TerminalProcessManager implements ITerminalProcessManager {
rows: number,
isScreenReaderModeEnabled: boolean
): Promise<void> {
const forceExtHostProcess = (this._configHelper.config as any).extHostProcess;
if (shellLaunchConfig.cwd && typeof shellLaunchConfig.cwd === 'object') {
this.remoteAuthority = getRemoteAuthority(shellLaunchConfig.cwd);
if (shellLaunchConfig.isVirtualProcess) {
this._processType = ProcessType.VirtualProcess;
this._process = this._instantiationService.createInstance(TerminalProcessExtHostProxy, this._terminalId, shellLaunchConfig, undefined, cols, rows, this._configHelper);
} else {
this.remoteAuthority = this._environmentService.configuration.remoteAuthority;
}
const hasRemoteAuthority = !!this.remoteAuthority;
let launchRemotely = hasRemoteAuthority || forceExtHostProcess;
this.userHome = this._environmentService.userHome;
this.os = platform.OS;
if (launchRemotely) {
if (hasRemoteAuthority) {
this._remoteAgentService.getEnvironment().then(env => {
if (!env) {
return;
}
this.userHome = env.userHome.path;
this.os = env.os;
});
const forceExtHostProcess = (this._configHelper.config as any).extHostProcess;
if (shellLaunchConfig.cwd && typeof shellLaunchConfig.cwd === 'object') {
this.remoteAuthority = getRemoteAuthority(shellLaunchConfig.cwd);
} else {
this.remoteAuthority = this._environmentService.configuration.remoteAuthority;
}
const hasRemoteAuthority = !!this.remoteAuthority;
let launchRemotely = hasRemoteAuthority || forceExtHostProcess;
const activeWorkspaceRootUri = this._historyService.getLastActiveWorkspaceRoot();
this._process = this._instantiationService.createInstance(TerminalProcessExtHostProxy, this._terminalId, shellLaunchConfig, activeWorkspaceRootUri, cols, rows, this._configHelper);
} else {
this._process = await this._launchProcess(shellLaunchConfig, cols, rows, isScreenReaderModeEnabled);
this.userHome = this._environmentService.userHome;
this.os = platform.OS;
if (launchRemotely) {
if (hasRemoteAuthority) {
this._remoteAgentService.getEnvironment().then(env => {
if (!env) {
return;
}
this.userHome = env.userHome.path;
this.os = env.os;
});
}
const activeWorkspaceRootUri = this._historyService.getLastActiveWorkspaceRoot();
this._process = this._instantiationService.createInstance(TerminalProcessExtHostProxy, this._terminalId, shellLaunchConfig, activeWorkspaceRootUri, cols, rows, this._configHelper);
} else {
this._process = await this._launchProcess(shellLaunchConfig, cols, rows, isScreenReaderModeEnabled);
}
}
this.processState = ProcessState.LAUNCHING;
......@@ -154,6 +167,9 @@ export class TerminalProcessManager implements ITerminalProcessManager {
this._process.onProcessTitleChanged(title => this._onProcessTitle.fire(title));
this._process.onProcessExit(exitCode => this._onExit(exitCode));
if (this._process.onProcessOverrideDimensions) {
this._process.onProcessOverrideDimensions(e => this._onProcessOverrideDimensions.fire(e));
}
setTimeout(() => {
if (this.processState === ProcessState.LAUNCHING) {
......@@ -205,7 +221,7 @@ export class TerminalProcessManager implements ITerminalProcessManager {
}
public write(data: string): void {
if (this.shellProcessId) {
if (this.shellProcessId || this._processType === ProcessType.VirtualProcess) {
if (this._process) {
// Send data if the pty is ready
this._process.input(data);
......
......@@ -184,11 +184,15 @@ export interface IShellLaunchConfig {
initialText?: string;
/**
* When true the terminal will be created with no process. This is primarily used to give
* extensions full control over the terminal.
* @deprecated use `isVirtualProcess`
*/
isRendererOnly?: boolean;
/**
* When true an extension is acting as the terminal's process.
*/
isVirtualProcess?: boolean;
/**
* Whether the terminal process environment should be exactly as provided in
* `TerminalOptions.env`. When this is false (default), the environment will be based on the
......@@ -224,6 +228,7 @@ export interface ITerminalService {
onInstanceDimensionsChanged: Event<ITerminalInstance>;
onInstanceMaximumDimensionsChanged: Event<ITerminalInstance>;
onInstanceRequestExtHostProcess: Event<ITerminalProcessExtHostRequest>;
onInstanceRequestVirtualProcess: Event<ITerminalProcessExtHostProxy>;
onInstancesChanged: Event<void>;
onInstanceTitleChanged: Event<ITerminalInstance>;
onActiveInstanceChanged: Event<ITerminalInstance | undefined>;
......@@ -291,6 +296,7 @@ export interface ITerminalService {
extHostReady(remoteAuthority: string): void;
requestExtHostProcess(proxy: ITerminalProcessExtHostProxy, shellLaunchConfig: IShellLaunchConfig, activeWorkspaceRootUri: URI, cols: number, rows: number, isWorkspaceShellAllowed: boolean): void;
requestVirtualProcess(proxy: ITerminalProcessExtHostProxy): void;
}
/**
......@@ -700,6 +706,7 @@ export interface ITerminalProcessManager extends IDisposable {
readonly onProcessData: Event<string>;
readonly onProcessTitle: Event<string>;
readonly onProcessExit: Event<number>;
readonly onProcessOverrideDimensions: Event<ITerminalDimensions | undefined>;
dispose(immediate?: boolean): void;
createProcess(shellLaunchConfig: IShellLaunchConfig, cols: number, rows: number, isScreenReaderModeEnabled: boolean): Promise<void>;
......@@ -737,6 +744,7 @@ export interface ITerminalProcessExtHostProxy extends IDisposable {
emitTitle(title: string): void;
emitReady(pid: number, cwd: string): void;
emitExit(exitCode: number): void;
emitOverrideDimensions(dimensions: ITerminalDimensions | undefined): void;
emitInitialCwd(initialCwd: string): void;
emitCwd(cwd: string): void;
emitLatency(latency: number): void;
......@@ -785,6 +793,7 @@ export interface ITerminalChildProcess {
onProcessExit: Event<number>;
onProcessReady: Event<{ pid: number, cwd: string }>;
onProcessTitleChanged: Event<string>;
onProcessOverrideDimensions?: Event<ITerminalDimensions | undefined>;
/**
* Shutdown the terminal process.
......
......@@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import { Event, Emitter } from 'vs/base/common/event';
import { ITerminalService, ITerminalProcessExtHostProxy, IShellLaunchConfig, ITerminalChildProcess, ITerminalConfigHelper } from 'vs/workbench/contrib/terminal/common/terminal';
import { ITerminalService, ITerminalProcessExtHostProxy, IShellLaunchConfig, ITerminalChildProcess, ITerminalConfigHelper, ITerminalDimensions } from 'vs/workbench/contrib/terminal/common/terminal';
import { Disposable } from 'vs/base/common/lifecycle';
import { URI } from 'vs/base/common/uri';
import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService';
......@@ -22,6 +22,8 @@ export class TerminalProcessExtHostProxy extends Disposable implements ITerminal
public get onProcessReady(): Event<{ pid: number, cwd: string }> { return this._onProcessReady.event; }
private readonly _onProcessTitleChanged = this._register(new Emitter<string>());
public readonly onProcessTitleChanged: Event<string> = this._onProcessTitleChanged.event;
private readonly _onProcessOverrideDimensions = new Emitter<ITerminalDimensions | undefined>();
public get onProcessOverrideDimensions(): Event<ITerminalDimensions | undefined> { return this._onProcessOverrideDimensions.event; }
private readonly _onInput = this._register(new Emitter<string>());
public readonly onInput: Event<string> = this._onInput.event;
......@@ -51,14 +53,21 @@ export class TerminalProcessExtHostProxy extends Disposable implements ITerminal
@IRemoteAgentService readonly remoteAgentService: IRemoteAgentService
) {
super();
remoteAgentService.getEnvironment().then(env => {
if (!env) {
throw new Error('Could not fetch environment');
// Request a process if needed, if this is a virtual process this step can be skipped as
// there is no real "process" and we know it's ready on the ext host already.
if (shellLaunchConfig.isVirtualProcess) {
this._terminalService.requestVirtualProcess(this);
} else {
remoteAgentService.getEnvironment().then(env => {
if (!env) {
throw new Error('Could not fetch environment');
}
this._terminalService.requestExtHostProcess(this, shellLaunchConfig, activeWorkspaceRootUri, cols, rows, configHelper.checkWorkspaceShellPermissions(env.os));
});
if (!hasReceivedResponse) {
setTimeout(() => this._onProcessTitleChanged.fire(nls.localize('terminal.integrated.starting', "Starting...")), 0);
}
this._terminalService.requestExtHostProcess(this, shellLaunchConfig, activeWorkspaceRootUri, cols, rows, configHelper.checkWorkspaceShellPermissions(env.os));
});
if (!hasReceivedResponse) {
setTimeout(() => this._onProcessTitleChanged.fire(nls.localize('terminal.integrated.starting', "Starting...")), 0);
}
}
......@@ -80,6 +89,10 @@ export class TerminalProcessExtHostProxy extends Disposable implements ITerminal
this.dispose();
}
public emitOverrideDimensions(dimensions: ITerminalDimensions | undefined): void {
this._onProcessOverrideDimensions.fire(dimensions);
}
public emitInitialCwd(initialCwd: string): void {
while (this._pendingInitialCwdRequests.length > 0) {
this._pendingInitialCwdRequests.pop()!(initialCwd);
......
......@@ -55,6 +55,8 @@ export abstract class TerminalService implements ITerminalService {
public get onInstanceProcessIdReady(): Event<ITerminalInstance> { return this._onInstanceProcessIdReady.event; }
protected readonly _onInstanceRequestExtHostProcess = new Emitter<ITerminalProcessExtHostRequest>();
public get onInstanceRequestExtHostProcess(): Event<ITerminalProcessExtHostRequest> { return this._onInstanceRequestExtHostProcess.event; }
protected readonly _onInstanceRequestVirtualProcess = new Emitter<ITerminalProcessExtHostProxy>();
public get onInstanceRequestVirtualProcess(): Event<ITerminalProcessExtHostProxy> { return this._onInstanceRequestVirtualProcess.event; }
protected readonly _onInstanceDimensionsChanged = new Emitter<ITerminalInstance>();
public get onInstanceDimensionsChanged(): Event<ITerminalInstance> { return this._onInstanceDimensionsChanged.event; }
protected readonly _onInstanceMaximumDimensionsChanged = new Emitter<ITerminalInstance>();
......@@ -142,6 +144,11 @@ export abstract class TerminalService implements ITerminalService {
});
}
public requestVirtualProcess(proxy: ITerminalProcessExtHostProxy): void {
// Don't need to wait on extensions here as this can only be triggered by an extension
this._onInstanceRequestVirtualProcess.fire(proxy);
}
public extHostReady(remoteAuthority: string): void {
this._extHostsReady[remoteAuthority] = true;
}
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册