未验证 提交 8d4466df 编写于 作者: D Daniel Imms 提交者: GitHub

Merge pull request #52143 from Microsoft/46192_terminal_renderer

Terminal renderer and activeTerminal APIs
......@@ -6,7 +6,7 @@
'use strict';
import * as assert from 'assert';
import { workspace, window, commands, ViewColumn, TextEditorViewColumnChangeEvent, Uri, Selection, Position, CancellationTokenSource, TextEditorSelectionChangeKind } from 'vscode';
import { workspace, window, commands, ViewColumn, TextEditorViewColumnChangeEvent, Uri, Selection, Position, CancellationTokenSource, TextEditorSelectionChangeKind, Terminal } from 'vscode';
import { join } from 'path';
import { closeAllEditors, pathEquals, createRandomFile } from '../utils';
......@@ -557,37 +557,143 @@ suite('window namespace tests', () => {
});
});
test('createTerminal, Terminal.name', () => {
const terminal = window.createTerminal('foo');
assert.equal(terminal.name, 'foo');
suite('Terminal', () => {
test('createTerminal, Terminal.name', () => {
const terminal = window.createTerminal('foo');
assert.equal(terminal.name, 'foo');
assert.throws(() => {
(<any>terminal).name = 'bar';
}, 'Terminal.name should be readonly');
});
assert.throws(() => {
(<any>terminal).name = 'bar';
}, 'Terminal.name should be readonly');
terminal.dispose();
});
test('terminal, sendText immediately after createTerminal should not throw', () => {
const terminal = window.createTerminal();
assert.doesNotThrow(terminal.sendText.bind(terminal, 'echo "foo"'));
});
test('sendText immediately after createTerminal should not throw', () => {
const terminal = window.createTerminal();
assert.doesNotThrow(terminal.sendText.bind(terminal, 'echo "foo"'));
terminal.dispose();
});
test('terminal, onDidCloseTerminal event fires when terminal is disposed', (done) => {
const terminal = window.createTerminal();
window.onDidCloseTerminal((eventTerminal) => {
assert.equal(terminal, eventTerminal);
done();
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();
});
terminal.dispose();
});
test('terminal, processId immediately after createTerminal should fetch the pid', (done) => {
window.createTerminal().processId.then(id => {
assert.ok(id > 0);
done();
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, name should set terminal.name', () => {
assert.equal(window.createTerminal('foo').name, 'foo');
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.cols > 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.onData(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 Termnial.onInput', (done) => {
const reg1 = window.onDidOpenTerminal(terminal => {
reg1.dispose();
const reg2 = renderer.onInput(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();
});
});
});
......@@ -339,11 +339,123 @@ declare module 'vscode' {
/**
* Fires when the terminal's pty slave pseudo-device is written to. In other words, this
* provides access to the raw data stream from the process running within the terminal,
* including ANSI sequences.
* including VT sequences.
*/
onData: Event<string>;
}
/**
* Represents the dimensions of a terminal.
*/
export interface TerminalDimensions {
/**
* The number of columns in the terminal.
*/
cols: number;
/**
* The number of rows in the terminal.
*/
rows: number;
}
/**
* Represents a terminal without a process where all interaction and output in the terminal is
* controlled by an extension. This is similar to an output window but has the same VT sequence
* compatility as the regular terminal.
*
* Note that an instance of [Terminal](#Terminal) will be created when a TerminalRenderer is
* created with all its APIs available for use by extensions. When using the Terminal object
* of a TerminalRenderer it acts just like normal only the extension that created the
* TerminalRenderer essentially acts as a process. For example when an
* [Terminal.onData](#Terminal.onData) listener is registered, that will fire when
* [TerminalRenderer.write](#TerminalRenderer.write) is called. Similarly when
* [Terminal.sendText](#Terminal.sendText) is triggered that will fire the
* [TerminalRenderer.onInput](#TerminalRenderer.onInput) event.
*
* **Example:** Create a terminal renderer, show it and write hello world in red
* ```typescript
* const renderer = window.createTerminalRenderer('foo');
* renderer.terminal.then(t => t.show());
* renderer.write('\x1b[31mHello world\x1b[0m');
* ```
*/
export interface TerminalRenderer {
/**
* The name of the terminal, this will appear in the terminal selector.
*/
name: string;
/**
* The dimensions of the terminal, the rows and columns of the terminal can only be set to
* a value smaller than the maximum value, if this is undefined the terminal will auto fit
* to the maximum value [maximumDimensions](TerminalRenderer.maximumDimensions).
*
* **Example:** Override the dimensions of a TerminalRenderer to 20 columns and 10 rows
* ```typescript
* terminalRenderer.dimensions = {
* cols: 20,
* rows: 10
* };
* ```
*/
dimensions: TerminalDimensions;
/**
* The maximum dimensions of the terminal, this will be undefined immediately after a
* 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.
*/
readonly maximumDimensions: TerminalDimensions;
/**
* The corressponding [Terminal](#Terminal) for this TerminalRenderer.
*/
readonly terminal: Thenable<Terminal>;
/**
* 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.
*
* **Example:** Write red text to the terminal
* ```typescript
* terminalRenderer.write('\x1b[31mHello world\x1b[0m');
* ```
*
* **Example:** Move the cursor to the 10th row and 20th column and write an asterisk
* ```typescript
* terminalRenderer.write('\x1b[10;20H*');
* ```
*
* @param text The text to write.
*/
write(text: string): void;
/**
* An event which fires on keystrokes in the terminal or when an extension calls
* [Terminal.sendText](#Terminal.sendText). Keystrokes are converted into their
* corresponding VT sequence representation.
*
* **Example:** Simulate interaction with the terminal from an outside extension or a
* workbench command such as `workbench.action.terminal.runSelectedText`
* ```typescript
* const terminalRenderer = window.createTerminalRenderer('test');
* terminalRenderer.onInput(data => {
* cosole.log(data); // 'Hello world'
* });
* terminalRenderer.terminal.then(t => t.sendText('Hello world'));
* ```
*/
onInput: Event<string>;
/**
* An event which fires when the [maximum dimensions](#TerminalRenderer.maimumDimensions) of
* the terminal renderer change.
*/
onDidChangeMaximumDimensions: Event<TerminalDimensions>;
}
export namespace window {
/**
* The currently opened terminals or an empty array.
......@@ -352,11 +464,33 @@ declare module 'vscode' {
*/
export let terminals: Terminal[];
/**
* The currently active terminal or `undefined`. The active terminal is the one that
* currently has focus or most recently had focus.
*
* @readonly
*/
export let activeTerminal: Terminal | undefined;
/**
* An [event](#Event) which fires when the [active terminal](#window.activeTerminal)
* has changed. *Note* that the event also fires when the active editor changes
* to `undefined`.
*/
export const onDidChangeActiveTerminal: Event<Terminal | undefined>;
/**
* An [event](#Event) which fires when a terminal has been created, either through the
* [createTerminal](#window.createTerminal) API or commands.
*/
export const onDidOpenTerminal: Event<Terminal>;
/**
* Create a [TerminalRenderer](#TerminalRenderer).
*
* @param name The name of the terminal renderer, this shows up in the terminal selector.
*/
export function createTerminalRenderer(name: string): TerminalRenderer;
}
//#endregion
......
......@@ -5,7 +5,7 @@
'use strict';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { ITerminalService, ITerminalInstance, IShellLaunchConfig, ITerminalProcessExtHostProxy, ITerminalProcessExtHostRequest } from 'vs/workbench/parts/terminal/common/terminal';
import { ITerminalService, ITerminalInstance, IShellLaunchConfig, ITerminalProcessExtHostProxy, ITerminalProcessExtHostRequest, ITerminalDimensions, EXT_HOST_CREATION_DELAY } from 'vs/workbench/parts/terminal/common/terminal';
import { TPromise } from 'vs/base/common/winjs.base';
import { ExtHostContext, ExtHostTerminalServiceShape, MainThreadTerminalServiceShape, MainContext, IExtHostContext, ShellLaunchConfigDto } from '../node/extHost.protocol';
import { extHostNamedCustomer } from 'vs/workbench/api/electron-browser/extHostCustomers';
......@@ -16,22 +16,23 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape
private _proxy: ExtHostTerminalServiceShape;
private _toDispose: IDisposable[] = [];
private _terminalProcesses: { [id: number]: ITerminalProcessExtHostProxy } = {};
private _dataListeners: { [id: number]: IDisposable } = {};
constructor(
extHostContext: IExtHostContext,
@ITerminalService private terminalService: ITerminalService
) {
this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostTerminalService);
this._toDispose.push(terminalService.onInstanceCreated((terminalInstance) => {
this._toDispose.push(terminalService.onInstanceCreated((instance) => {
// Delay this message so the TerminalInstance constructor has a chance to finish and
// return the ID normally to the extension host. The ID that is passed here will be used
// to register non-extension API terminals in the extension host.
setTimeout(() => this._onTerminalOpened(terminalInstance), 100);
setTimeout(() => this._onTerminalOpened(instance), EXT_HOST_CREATION_DELAY);
}));
this._toDispose.push(terminalService.onInstanceDisposed(terminalInstance => this._onTerminalDisposed(terminalInstance)));
this._toDispose.push(terminalService.onInstanceProcessIdReady(terminalInstance => this._onTerminalProcessIdReady(terminalInstance)));
this._toDispose.push(terminalService.onInstanceDisposed(instance => this._onTerminalDisposed(instance)));
this._toDispose.push(terminalService.onInstanceProcessIdReady(instance => this._onTerminalProcessIdReady(instance)));
this._toDispose.push(terminalService.onInstanceDimensionsChanged(instance => this._onInstanceDimensionsChanged(instance)));
this._toDispose.push(terminalService.onInstanceRequestExtHostProcess(request => this._onTerminalRequestExtHostProcess(request)));
this._toDispose.push(terminalService.onActiveInstanceChanged(instance => this._onActiveTerminalChanged(instance ? instance.id : undefined)));
// Set initial ext host state
this.terminalService.terminalInstances.forEach(t => {
......@@ -60,8 +61,13 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape
return TPromise.as(this.terminalService.createTerminal(shellLaunchConfig).id);
}
public $createTerminalRenderer(name: string): TPromise<number> {
const instance = this.terminalService.createTerminalRenderer(name);
return TPromise.as(instance.id);
}
public $show(terminalId: number, preserveFocus: boolean): void {
let terminalInstance = this.terminalService.getInstanceFromId(terminalId);
const terminalInstance = this.terminalService.getInstanceFromId(terminalId);
if (terminalInstance) {
this.terminalService.setActiveInstance(terminalInstance);
this.terminalService.showPanel(!preserveFocus);
......@@ -75,31 +81,68 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape
}
public $dispose(terminalId: number): void {
let terminalInstance = this.terminalService.getInstanceFromId(terminalId);
const terminalInstance = this.terminalService.getInstanceFromId(terminalId);
if (terminalInstance) {
terminalInstance.dispose();
}
}
public $terminalRendererWrite(terminalId: number, text: string): void {
const terminalInstance = this.terminalService.getInstanceFromId(terminalId);
if (terminalInstance && terminalInstance.shellLaunchConfig.isRendererOnly) {
terminalInstance.write(text);
}
}
public $terminalRendererSetName(terminalId: number, name: string): void {
const terminalInstance = this.terminalService.getInstanceFromId(terminalId);
if (terminalInstance && terminalInstance.shellLaunchConfig.isRendererOnly) {
terminalInstance.setTitle(name, false);
}
}
public $terminalRendererSetDimensions(terminalId: number, dimensions: ITerminalDimensions): void {
const terminalInstance = this.terminalService.getInstanceFromId(terminalId);
if (terminalInstance && terminalInstance.shellLaunchConfig.isRendererOnly) {
terminalInstance.setDimensions(dimensions);
}
}
public $terminalRendererRegisterOnInputListener(terminalId: number): void {
const terminalInstance = this.terminalService.getInstanceFromId(terminalId);
if (terminalInstance) {
terminalInstance.addDisposable(terminalInstance.onRendererInput(data => this._onTerminalRendererInput(terminalId, data)));
}
}
public $sendText(terminalId: number, text: string, addNewLine: boolean): void {
let terminalInstance = this.terminalService.getInstanceFromId(terminalId);
const terminalInstance = this.terminalService.getInstanceFromId(terminalId);
if (terminalInstance) {
terminalInstance.sendText(text, addNewLine);
}
}
public $registerOnDataListener(terminalId: number): void {
let terminalInstance = this.terminalService.getInstanceFromId(terminalId);
const terminalInstance = this.terminalService.getInstanceFromId(terminalId);
if (terminalInstance) {
this._dataListeners[terminalId] = terminalInstance.onData(data => this._onTerminalData(terminalId, data));
terminalInstance.onDisposed(instance => delete this._dataListeners[terminalId]);
terminalInstance.addDisposable(terminalInstance.onData(data => {
this._onTerminalData(terminalId, data);
}));
}
}
private _onActiveTerminalChanged(terminalId: number | undefined): void {
this._proxy.$acceptActiveTerminalChanged(terminalId);
}
private _onTerminalData(terminalId: number, data: string): void {
this._proxy.$acceptTerminalProcessData(terminalId, data);
}
private _onTerminalRendererInput(terminalId: number, data: string): void {
this._proxy.$acceptTerminalRendererInput(terminalId, data);
}
private _onTerminalDisposed(terminalInstance: ITerminalInstance): void {
this._proxy.$acceptTerminalClosed(terminalInstance.id);
}
......@@ -112,6 +155,14 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape
this._proxy.$acceptTerminalProcessId(terminalInstance.id, terminalInstance.processId);
}
private _onInstanceDimensionsChanged(instance: ITerminalInstance): void {
// Only send the dimensions if the terminal is a renderer only as there is no API to access
// dimensions on a plain Terminal.
if (instance.shellLaunchConfig.isRendererOnly) {
this._proxy.$acceptTerminalRendererDimensions(instance.id, instance.cols, instance.rows);
}
}
private _onTerminalRequestExtHostProcess(request: ITerminalProcessExtHostRequest): void {
this._terminalProcesses[request.proxy.terminalId] = request.proxy;
const shellLaunchConfigDto: ShellLaunchConfigDto = {
......
......@@ -331,6 +331,9 @@ export function createApiFactory(
get visibleTextEditors() {
return extHostEditors.getVisibleTextEditors();
},
get activeTerminal() {
return proposedApiFunction(extension, extHostTerminalService.activeTerminal);
},
get terminals() {
return proposedApiFunction(extension, extHostTerminalService.terminals);
},
......@@ -372,6 +375,9 @@ export function createApiFactory(
onDidOpenTerminal: proposedApiFunction(extension, (listener, thisArg?, disposables?) => {
return extHostTerminalService.onDidOpenTerminal(listener, thisArg, disposables);
}),
onDidChangeActiveTerminal: proposedApiFunction(extension, (listener, thisArg?, disposables?) => {
return extHostTerminalService.onDidChangeActiveTerminal(listener, thisArg, disposables);
}),
get state() {
return extHostWindow.state;
},
......@@ -427,6 +433,9 @@ export function createApiFactory(
}
return extHostTerminalService.createTerminal(<string>nameOrOptions, shellPath, shellArgs);
},
createTerminalRenderer(name: string): vscode.TerminalRenderer {
return extHostTerminalService.createTerminalRenderer(name);
},
registerTreeDataProvider(viewId: string, treeDataProvider: vscode.TreeDataProvider<any>): vscode.Disposable {
return extHostTreeViews.registerTreeDataProvider(viewId, treeDataProvider);
},
......
......@@ -49,6 +49,7 @@ import { ISingleEditOperation } from 'vs/editor/common/model';
import { IPatternInfo, IRawSearchQuery, IRawFileMatch2, ISearchCompleteStats } from 'vs/platform/search/common/search';
import { LogLevel } from 'vs/platform/log/common/log';
import { TaskExecutionDTO, TaskDTO, TaskHandleDTO, TaskFilterDTO, TaskProcessStartedDTO, TaskProcessEndedDTO, TaskSystemInfoDTO } from 'vs/workbench/api/shared/tasks';
import { ITerminalDimensions } from 'vs/workbench/parts/terminal/common/terminal';
export interface IEnvironment {
isExtensionDevelopmentDebug: boolean;
......@@ -324,16 +325,24 @@ export interface MainThreadProgressShape extends IDisposable {
export interface MainThreadTerminalServiceShape extends IDisposable {
$createTerminal(name?: string, shellPath?: string, shellArgs?: string[], cwd?: string, env?: { [key: string]: string }, waitOnExit?: boolean): TPromise<number>;
$createTerminalRenderer(name: string): TPromise<number>;
$dispose(terminalId: number): void;
$hide(terminalId: number): void;
$sendText(terminalId: number, text: string, addNewLine: boolean): void;
$show(terminalId: number, preserveFocus: boolean): void;
$registerOnDataListener(terminalId: number): void;
// Process
$sendProcessTitle(terminalId: number, title: string): void;
$sendProcessData(terminalId: number, data: string): void;
$sendProcessPid(terminalId: number, pid: number): void;
$sendProcessExit(terminalId: number, exitCode: number): void;
// Renderer
$terminalRendererSetName(terminalId: number, name: string): void;
$terminalRendererSetDimensions(terminalId: number, dimensions: ITerminalDimensions): void;
$terminalRendererWrite(terminalId: number, text: string): void;
$terminalRendererRegisterOnInputListener(terminalId: number): void;
}
export interface TransferQuickPickItems extends IQuickPickItem {
......@@ -841,8 +850,11 @@ export interface ShellLaunchConfigDto {
export interface ExtHostTerminalServiceShape {
$acceptTerminalClosed(id: number): void;
$acceptTerminalOpened(id: number, name: string): void;
$acceptActiveTerminalChanged(id: number | null): void;
$acceptTerminalProcessId(id: number, processId: number): void;
$acceptTerminalProcessData(id: number, data: string): void;
$acceptTerminalRendererInput(id: number, data: string): void;
$acceptTerminalRendererDimensions(id: number, cols: number, rows: number): void;
$createProcess(id: number, shellLaunchConfig: ShellLaunchConfigDto, cols: number, rows: number): void;
$acceptProcessInput(id: number, data: string): void;
$acceptProcessResize(id: number, cols: number, rows: number): void;
......
......@@ -15,13 +15,64 @@ import { ExtHostTerminalServiceShape, MainContext, MainThreadTerminalServiceShap
import { IMessageFromTerminalProcess } from 'vs/workbench/parts/terminal/node/terminal';
import { ExtHostConfiguration } from 'vs/workbench/api/node/extHostConfiguration';
import { ILogService } from 'vs/platform/log/common/log';
import { EXT_HOST_CREATION_DELAY } from 'vs/workbench/parts/terminal/common/terminal';
export class ExtHostTerminal implements vscode.Terminal {
private _name: string;
private _id: number;
private _proxy: MainThreadTerminalServiceShape;
private _disposed: boolean;
private _queuedRequests: ApiRequest[];
const RENDERER_NO_PROCESS_ID = -1;
export class BaseExtHostTerminal {
public _id: number;
protected _idPromise: Promise<number>;
private _idPromiseComplete: (value: number) => any;
private _disposed: boolean = false;
private _queuedRequests: ApiRequest[] = [];
constructor(
protected _proxy: MainThreadTerminalServiceShape,
id?: number
) {
this._idPromise = new Promise<number>(c => {
if (id !== undefined) {
this._id = id;
c(id);
} else {
this._idPromiseComplete = c;
}
});
}
public dispose(): void {
if (!this._disposed) {
this._disposed = true;
this._queueApiRequest(this._proxy.$dispose, []);
}
}
protected _checkDisposed() {
if (this._disposed) {
throw new Error('Terminal has already been disposed');
}
}
protected _queueApiRequest(callback: (...args: any[]) => void, args: any[]): void {
const request: ApiRequest = new ApiRequest(callback, args);
if (!this._id) {
this._queuedRequests.push(request);
return;
}
request.run(this._proxy, this._id);
}
protected _runQueuedRequests(id: number): void {
this._id = id;
this._idPromiseComplete(id);
this._queuedRequests.forEach((r) => {
r.run(this._proxy, this._id);
});
this._queuedRequests.length = 0;
}
}
export class ExtHostTerminal extends BaseExtHostTerminal implements vscode.Terminal {
private _pidPromise: Promise<number>;
private _pidPromiseComplete: (value: number) => any;
......@@ -34,17 +85,17 @@ export class ExtHostTerminal implements vscode.Terminal {
constructor(
proxy: MainThreadTerminalServiceShape,
name: string = '',
id?: number
private _name: string,
id?: number,
pid?: number
) {
this._proxy = proxy;
this._name = name;
if (id) {
this._id = id;
}
this._queuedRequests = [];
super(proxy, id);
this._pidPromise = new Promise<number>(c => {
this._pidPromiseComplete = c;
if (pid === RENDERER_NO_PROCESS_ID) {
c(undefined);
} else {
this._pidPromiseComplete = c;
}
});
}
......@@ -56,11 +107,7 @@ export class ExtHostTerminal implements vscode.Terminal {
waitOnExit?: boolean
): void {
this._proxy.$createTerminal(this._name, shellPath, shellArgs, cwd, env, waitOnExit).then((id) => {
this._id = id;
this._queuedRequests.forEach((r) => {
r.run(this._proxy, this._id);
});
this._queuedRequests = [];
this._runQueuedRequests(id);
});
}
......@@ -87,13 +134,6 @@ export class ExtHostTerminal implements vscode.Terminal {
this._queueApiRequest(this._proxy.$hide, []);
}
public dispose(): void {
if (!this._disposed) {
this._disposed = true;
this._queueApiRequest(this._proxy.$dispose, []);
}
}
public _setProcessId(processId: number): void {
// The event may fire 2 times when the panel is restored
if (this._pidPromiseComplete) {
......@@ -105,34 +145,95 @@ export class ExtHostTerminal implements vscode.Terminal {
public _fireOnData(data: string): void {
this._onData.fire(data);
}
}
private _queueApiRequest(callback: (...args: any[]) => void, args: any[]) {
let request: ApiRequest = new ApiRequest(callback, args);
if (!this._id) {
this._queuedRequests.push(request);
return;
}
request.run(this._proxy, this._id);
export class ExtHostTerminalRenderer extends BaseExtHostTerminal implements vscode.TerminalRenderer {
public get name(): string { return this._name; }
public set name(newName: string) {
this._name = newName;
this._checkDisposed();
this._queueApiRequest(this._proxy.$terminalRendererSetName, [this._name]);
}
private _checkDisposed() {
if (this._disposed) {
throw new Error('Terminal has already been disposed');
private readonly _onInput: Emitter<string> = new Emitter<string>();
public get onInput(): Event<string> {
this._checkDisposed();
this._queueApiRequest(this._proxy.$terminalRendererRegisterOnInputListener, [this._id]);
// Tell the main side to start sending data if it's not already
// this._proxy.$terminalRendererRegisterOnDataListener(this._id);
return this._onInput && this._onInput.event;
}
private _dimensions: vscode.TerminalDimensions | undefined;
public get dimensions(): vscode.TerminalDimensions { return this._dimensions; }
public set dimensions(dimensions: vscode.TerminalDimensions) {
this._checkDisposed();
this._dimensions = dimensions;
this._queueApiRequest(this._proxy.$terminalRendererSetDimensions, [dimensions]);
}
private _maximumDimensions: vscode.TerminalDimensions;
public get maximumDimensions(): vscode.TerminalDimensions {
if (!this._maximumDimensions) {
return undefined;
}
return {
rows: this._maximumDimensions.rows,
cols: this._maximumDimensions.cols
};
}
private readonly _onDidChangeMaximumDimensions: Emitter<vscode.TerminalDimensions> = new Emitter<vscode.TerminalDimensions>();
public get onDidChangeMaximumDimensions(): Event<vscode.TerminalDimensions> {
return this._onDidChangeMaximumDimensions && this._onDidChangeMaximumDimensions.event;
}
public get terminal(): Promise<ExtHostTerminal> {
return this._idPromise.then(id => this._fetchTerminal(id));
}
constructor(
proxy: MainThreadTerminalServiceShape,
private _name: string,
private _fetchTerminal: (id: number) => Promise<ExtHostTerminal>
) {
super(proxy);
this._proxy.$createTerminalRenderer(this._name).then(id => {
this._runQueuedRequests(id);
});
}
public write(data: string): void {
this._checkDisposed();
this._queueApiRequest(this._proxy.$terminalRendererWrite, [data]);
}
public _fireOnInput(data: string): void {
this._onInput.fire(data);
}
public _setMaximumDimensions(cols: number, rows: number): void {
this._maximumDimensions = { cols, rows };
this._onDidChangeMaximumDimensions.fire(this.maximumDimensions);
}
}
export class ExtHostTerminalService implements ExtHostTerminalServiceShape {
private _proxy: MainThreadTerminalServiceShape;
private _activeTerminal: ExtHostTerminal;
private _terminals: ExtHostTerminal[] = [];
private _terminalProcesses: { [id: number]: cp.ChildProcess } = {};
private _terminalRenderers: ExtHostTerminalRenderer[] = [];
public get activeTerminal(): ExtHostTerminal { return this._activeTerminal; }
public get terminals(): ExtHostTerminal[] { return this._terminals; }
private readonly _onDidCloseTerminal: Emitter<vscode.Terminal> = new Emitter<vscode.Terminal>();
public get onDidCloseTerminal(): Event<vscode.Terminal> { return this._onDidCloseTerminal && this._onDidCloseTerminal.event; }
private readonly _onDidOpenTerminal: Emitter<vscode.Terminal> = new Emitter<vscode.Terminal>();
public get onDidOpenTerminal(): Event<vscode.Terminal> { return this._onDidOpenTerminal && this._onDidOpenTerminal.event; }
private readonly _onDidChangeActiveTerminal: Emitter<vscode.Terminal | undefined> = new Emitter<vscode.Terminal | undefined>();
public get onDidChangeActiveTerminal(): Event<vscode.Terminal | undefined> { return this._onDidChangeActiveTerminal && this._onDidChangeActiveTerminal.event; }
constructor(
mainContext: IMainContext,
......@@ -143,53 +244,94 @@ export class ExtHostTerminalService implements ExtHostTerminalServiceShape {
}
public createTerminal(name?: string, shellPath?: string, shellArgs?: string[]): vscode.Terminal {
let terminal = new ExtHostTerminal(this._proxy, name);
const terminal = new ExtHostTerminal(this._proxy, name);
terminal.create(shellPath, shellArgs);
this._terminals.push(terminal);
return terminal;
}
public createTerminalFromOptions(options: vscode.TerminalOptions): vscode.Terminal {
let terminal = new ExtHostTerminal(this._proxy, options.name);
const terminal = new ExtHostTerminal(this._proxy, options.name);
terminal.create(options.shellPath, options.shellArgs, options.cwd, options.env /*, options.waitOnExit*/);
this._terminals.push(terminal);
return terminal;
}
public createTerminalRenderer(name: string): vscode.TerminalRenderer {
const renderer = new ExtHostTerminalRenderer(this._proxy, name, (id) => this._getTerminalByIdEventually(id));
this._terminalRenderers.push(renderer);
return renderer;
}
public $acceptActiveTerminalChanged(id: number | null): void {
const original = this._activeTerminal;
if (id === null) {
this._activeTerminal = undefined;
} else {
const terminal = this._getTerminalById(id);
if (terminal) {
this._activeTerminal = terminal;
}
}
if (original !== this._activeTerminal) {
this._onDidChangeActiveTerminal.fire(this._activeTerminal);
}
}
public $acceptTerminalProcessData(id: number, data: string): void {
let index = this._getTerminalIndexById(id);
if (index === null) {
return;
const terminal = this._getTerminalById(id);
if (terminal) {
terminal._fireOnData(data);
}
}
public $acceptTerminalRendererDimensions(id: number, cols: number, rows: number): void {
const renderer = this._getTerminalRendererById(id);
if (renderer) {
renderer._setMaximumDimensions(cols, rows);
}
}
public $acceptTerminalRendererInput(id: number, data: string): void {
const renderer = this._getTerminalRendererById(id);
if (renderer) {
renderer._fireOnInput(data);
}
const terminal = this._terminals[index];
terminal._fireOnData(data);
}
public $acceptTerminalClosed(id: number): void {
let index = this._getTerminalIndexById(id);
const index = this._getTerminalObjectIndexById(this.terminals, id);
if (index === null) {
return;
}
let terminal = this._terminals.splice(index, 1)[0];
const terminal = this._terminals.splice(index, 1)[0];
this._onDidCloseTerminal.fire(terminal);
}
public $acceptTerminalOpened(id: number, name: string): void {
let index = this._getTerminalIndexById(id);
const index = this._getTerminalObjectIndexById(this._terminals, id);
if (index !== null) {
// The terminal has already been created (via createTerminal*), only fire the event
this._onDidOpenTerminal.fire(this.terminals[index]);
return;
}
let terminal = new ExtHostTerminal(this._proxy, name, id);
const renderer = this._getTerminalRendererById(id);
const terminal = new ExtHostTerminal(this._proxy, name, id, renderer ? RENDERER_NO_PROCESS_ID : undefined);
this._terminals.push(terminal);
this._onDidOpenTerminal.fire(terminal);
}
public $acceptTerminalProcessId(id: number, processId: number): void {
let terminal = this._getTerminalById(id);
if (terminal) {
terminal._setProcessId(processId);
} else {
// Retry one more time in case the terminal has not yet been initialized.
setTimeout(() => {
terminal = this._getTerminalById(id);
terminal._setProcessId(processId);
}, EXT_HOST_CREATION_DELAY);
}
}
......@@ -232,7 +374,7 @@ export class ExtHostTerminalService implements ExtHostTerminalServiceShape {
// Continue env initialization, merging in the env from the launch
// config and adding keys that are needed to create the process
const env = terminalEnvironment.createTerminalEnv(parentEnv, shellLaunchConfig, initialCwd, locale, cols, rows);
let cwd = Uri.parse(require.toUrl('../../parts/terminal/node')).fsPath;
const cwd = Uri.parse(require.toUrl('../../parts/terminal/node')).fsPath;
const options = { env, cwd, execArgv: [] };
// Fork the process and listen for messages
......@@ -286,16 +428,43 @@ export class ExtHostTerminalService implements ExtHostTerminalServiceShape {
this._proxy.$sendProcessExit(id, exitCode);
}
private _getTerminalByIdEventually(id: number, retries: number = 5): Promise<ExtHostTerminal> {
return new Promise(c => {
if (retries === 0) {
c(undefined);
return;
}
const terminal = this._getTerminalById(id);
if (terminal) {
c(terminal);
} else {
// This should only be needed immediately after createTerminalRenderer is called as
// the ExtHostTerminal has not yet been iniitalized
setTimeout(() => {
c(this._getTerminalByIdEventually(id, retries - 1));
}, 200);
}
});
}
private _getTerminalById(id: number): ExtHostTerminal {
let index = this._getTerminalIndexById(id);
return index !== null ? this._terminals[index] : null;
return this._getTerminalObjectById(this._terminals, id);
}
private _getTerminalRendererById(id: number): ExtHostTerminalRenderer {
return this._getTerminalObjectById(this._terminalRenderers, id);
}
private _getTerminalObjectById<T extends ExtHostTerminal | ExtHostTerminalRenderer>(array: T[], id: number): T {
const index = this._getTerminalObjectIndexById(array, id);
return index !== null ? array[index] : null;
}
private _getTerminalIndexById(id: number): number {
private _getTerminalObjectIndexById<T extends ExtHostTerminal | ExtHostTerminalRenderer>(array: T[], id: number): number {
let index: number = null;
this._terminals.some((terminal, i) => {
// TODO: This shouldn't be cas
let thisId = (<any>terminal)._id;
array.some((item, i) => {
const thisId = item._id;
if (thisId === id) {
index = i;
return true;
......
......@@ -21,13 +21,12 @@ export class TerminalFindWidget extends SimpleFindWidget {
}
public find(previous: boolean) {
let val = this.inputValue;
let instance = this._terminalService.getActiveInstance();
const instance = this._terminalService.getActiveInstance();
if (instance !== null) {
if (previous) {
instance.findPrevious(val);
instance.findPrevious(this.inputValue);
} else {
instance.findNext(val);
instance.findNext(this.inputValue);
}
}
}
......
......@@ -278,7 +278,7 @@ export class TerminalTab extends Disposable implements ITerminalTab {
// Adjust focus if the instance was active
if (wasActiveInstance && this._terminalInstances.length > 0) {
let newIndex = index < this._terminalInstances.length ? index : this._terminalInstances.length - 1;
const newIndex = index < this._terminalInstances.length ? index : this._terminalInstances.length - 1;
this.setActiveInstanceByIndex(newIndex);
// TODO: Only focus the new instance if the tab had focus?
this.activeInstance.focus(true);
......
......@@ -7,7 +7,7 @@ import { IDisposable, dispose } from 'vs/base/common/lifecycle';
const WIDGET_HEIGHT = 29;
export class TerminalWidgetManager {
export class TerminalWidgetManager implements IDisposable {
private _container: HTMLElement;
private _xtermViewport: HTMLElement;
......@@ -24,6 +24,14 @@ export class TerminalWidgetManager {
this._initTerminalHeightWatcher(terminalWrapper);
}
public dispose(): void {
if (this._container) {
this._container.parentElement.removeChild(this._container);
this._container = null;
}
this._xtermViewport = null;
}
private _initTerminalHeightWatcher(terminalWrapper: HTMLElement) {
// Watch the xterm.js viewport for style changes and do a layout if it changes
this._xtermViewport = <HTMLElement>terminalWrapper.querySelector('.xterm-viewport');
......
......@@ -37,6 +37,11 @@ export const IS_WORKSPACE_SHELL_ALLOWED_STORAGE_KEY = 'terminal.integrated.isWor
export const NEVER_SUGGEST_SELECT_WINDOWS_SHELL_STORAGE_KEY = 'terminal.integrated.neverSuggestSelectWindowsShell';
export const NEVER_MEASURE_RENDER_TIME_STORAGE_KEY = 'terminal.integrated.neverMeasureRenderTime';
// The creation of extension host terminals is delayed by this value (milliseconds). The purpose of
// this delay is to allow the terminal instance to initialize correctly and have its ID set before
// trying to create the corressponding object on the ext host.
export const EXT_HOST_CREATION_DELAY = 100;
export const ITerminalService = createDecorator<ITerminalService>(TERMINAL_SERVICE_ID);
export const TerminalCursorStyle = {
......@@ -159,6 +164,12 @@ export interface IShellLaunchConfig {
* of the terminal. Use \x1b over \033 or \e for the escape control character.
*/
initialText?: string;
/**
* When true the terminal will be created with no process. This is primarily used to give
* extensions full control over the terminal.
*/
isRendererOnly?: boolean;
}
export interface ITerminalService {
......@@ -171,9 +182,11 @@ export interface ITerminalService {
onInstanceCreated: Event<ITerminalInstance>;
onInstanceDisposed: Event<ITerminalInstance>;
onInstanceProcessIdReady: Event<ITerminalInstance>;
onInstanceDimensionsChanged: Event<ITerminalInstance>;
onInstanceRequestExtHostProcess: Event<ITerminalProcessExtHostRequest>;
onInstancesChanged: Event<void>;
onInstanceTitleChanged: Event<string>;
onActiveInstanceChanged: Event<ITerminalInstance>;
terminalInstances: ITerminalInstance[];
terminalTabs: ITerminalTab[];
......@@ -184,6 +197,12 @@ export interface ITerminalService {
* default shell selection dialog may display.
*/
createTerminal(shell?: IShellLaunchConfig, wasNewTerminalAction?: boolean): ITerminalInstance;
/**
* Creates a terminal renderer.
* @param name The name of the terminal.
*/
createTerminalRenderer(name: string): ITerminalInstance;
/**
* Creates a raw terminal instance, this should not be used outside of the terminal part.
*/
......@@ -239,12 +258,27 @@ export interface ITerminalTab {
split(terminalFocusContextKey: IContextKey<boolean>, configHelper: ITerminalConfigHelper, shellLaunchConfig: IShellLaunchConfig): ITerminalInstance;
}
export interface ITerminalDimensions {
/**
* The columns of the terminal.
*/
readonly cols: number;
/**
* The rows of the terminal.
*/
readonly rows: number;
}
export interface ITerminalInstance {
/**
* The ID of the terminal instance, this is an arbitrary number only used to identify the
* terminal instance.
*/
id: number;
readonly id: number;
readonly cols: number;
readonly rows: number;
/**
* The process ID of the shell process, this is undefined when there is no process associated
......@@ -268,6 +302,42 @@ export interface ITerminalInstance {
onRequestExtHostProcess: Event<ITerminalInstance>;
onDimensionsChanged: Event<void>;
onFocus: Event<ITerminalInstance>;
/**
* Attach a listener to the raw data stream coming from the pty, including ANSI escape
* sequences.
*/
onData: Event<string>;
/**
* Attach a listener to the "renderer" input event, this event fires for terminal renderers on
* keystrokes and when the Terminal.sendText extension API is used.
* @param listener The listener function.
*/
onRendererInput: Event<string>;
/**
* Attach a listener to listen for new lines added to this terminal instance.
*
* @param listener The listener function which takes new line strings added to the terminal,
* excluding ANSI escape sequences. The line event will fire when an LF character is added to
* the terminal (ie. the line is not wrapped). Note that this means that the line data will
* not fire for the last line, until either the line is ended with a LF character of the process
* is exited. The lineData string will contain the fully wrapped line, not containing any LF/CR
* characters.
*/
onLineData: Event<string>;
/**
* Attach a listener that fires when the terminal's pty process exits. The number in the event
* is the processes' exit code, an exit code of null means the process was killed as a result of
* the ITerminalInstance being disposed.
*/
onExit: Event<number>;
processReady: TPromise<void>;
/**
......@@ -294,7 +364,7 @@ export interface ITerminalInstance {
/**
* The shell launch config used to launch the shell.
*/
shellLaunchConfig: IShellLaunchConfig;
readonly shellLaunchConfig: IShellLaunchConfig;
/**
* Whether to disable layout for the terminal. This is useful when the size of the terminal is
......@@ -397,6 +467,12 @@ export interface ITerminalInstance {
*/
sendText(text: string, addNewLine: boolean): void;
/**
* Write text directly to the terminal, skipping the process if it exists.
* @param text The text to write.
*/
write(text: string): void;
/** Scroll the terminal buffer down 1 line. */
scrollDownLine(): void;
/** Scroll the terminal buffer down 1 page. */
......@@ -448,33 +524,6 @@ export interface ITerminalInstance {
*/
setVisible(visible: boolean): void;
/**
* Attach a listener to the raw data stream coming from the pty, including ANSI escape
* sequecnes.
* @param listener The listener function.
*/
onData(listener: (data: string) => void): IDisposable;
/**
* Attach a listener to listen for new lines added to this terminal instance.
*
* @param listener The listener function which takes new line strings added to the terminal,
* excluding ANSI escape sequences. The line event will fire when an LF character is added to
* the terminal (ie. the line is not wrapped). Note that this means that the line data will
* not fire for the last line, until either the line is ended with a LF character of the process
* is exited. The lineData string will contain the fully wrapped line, not containing any LF/CR
* characters.
*/
onLineData(listener: (lineData: string) => void): IDisposable;
/**
* Attach a listener that fires when the terminal's pty process exits.
*
* @param listener The listener function which takes the processes' exit code, an exit code of
* null means the process was killed as a result of the ITerminalInstance being disposed.
*/
onExit(listener: (exitCode: number) => void): IDisposable;
/**
* Immediately kills the terminal's current pty process and launches a new one to replace it.
*
......@@ -487,6 +536,8 @@ export interface ITerminalInstance {
*/
setTitle(title: string, eventFromProcess: boolean): void;
setDimensions(dimensions: ITerminalDimensions): void;
addDisposable(disposable: IDisposable): void;
}
......
......@@ -160,9 +160,9 @@ const ansiColorMap = {
};
export function registerColors(): void {
for (let id in ansiColorMap) {
let entry = ansiColorMap[id];
let colorName = id.substring(13);
for (const id in ansiColorMap) {
const entry = ansiColorMap[id];
const colorName = id.substring(13);
ansiColorIdentifiers[entry.index] = registerColor(id, entry.defaults, nls.localize('terminal.ansiColor', '\'{0}\' ANSI color in the terminal.', colorName));
}
}
......@@ -41,10 +41,14 @@ export abstract class TerminalService implements ITerminalService {
public get onInstanceProcessIdReady(): Event<ITerminalInstance> { return this._onInstanceProcessIdReady.event; }
protected readonly _onInstanceRequestExtHostProcess: Emitter<ITerminalProcessExtHostRequest> = new Emitter<ITerminalProcessExtHostRequest>();
public get onInstanceRequestExtHostProcess(): Event<ITerminalProcessExtHostRequest> { return this._onInstanceRequestExtHostProcess.event; }
protected readonly _onInstanceDimensionsChanged: Emitter<ITerminalInstance> = new Emitter<ITerminalInstance>();
public get onInstanceDimensionsChanged(): Event<ITerminalInstance> { return this._onInstanceDimensionsChanged.event; }
protected readonly _onInstancesChanged: Emitter<void> = new Emitter<void>();
public get onInstancesChanged(): Event<void> { return this._onInstancesChanged.event; }
protected readonly _onInstanceTitleChanged: Emitter<string> = new Emitter<string>();
public get onInstanceTitleChanged(): Event<string> { return this._onInstanceTitleChanged.event; }
protected readonly _onActiveInstanceChanged: Emitter<ITerminalInstance> = new Emitter<ITerminalInstance>();
public get onActiveInstanceChanged(): Event<ITerminalInstance> { return this._onActiveInstanceChanged.event; }
protected readonly _onTabDisposed: Emitter<ITerminalTab> = new Emitter<ITerminalTab>();
public get onTabDisposed(): Event<ITerminalTab> { return this._onTabDisposed.event; }
......@@ -71,6 +75,7 @@ export abstract class TerminalService implements ITerminalService {
protected abstract _showTerminalCloseConfirmation(): TPromise<boolean>;
public abstract createTerminal(shell?: IShellLaunchConfig, wasNewTerminalAction?: boolean): ITerminalInstance;
public abstract createTerminalRenderer(name: string): ITerminalInstance;
public abstract createInstance(terminalFocusContextKey: IContextKey<boolean>, configHelper: ITerminalConfigHelper, container: HTMLElement, shellLaunchConfig: IShellLaunchConfig, doCreateProcess: boolean): ITerminalInstance;
public abstract getActiveOrCreateInstance(wasNewTerminalAction?: boolean): ITerminalInstance;
public abstract selectDefaultWindowsShell(): TPromise<string>;
......@@ -152,7 +157,7 @@ export abstract class TerminalService implements ITerminalService {
if (wasActiveTab && this._terminalTabs.length > 0) {
// TODO: Only focus the new tab if the removed tab had focus?
// const hasFocusOnExit = tab.activeInstance.hadFocusOnExit;
let newIndex = index < this._terminalTabs.length ? index : this._terminalTabs.length - 1;
const newIndex = index < this._terminalTabs.length ? index : this._terminalTabs.length - 1;
this.setActiveTabByIndex(newIndex);
this.getActiveInstance().focus(true);
}
......@@ -162,6 +167,7 @@ export abstract class TerminalService implements ITerminalService {
// launch.
if (this._terminalTabs.length === 0 && !this._isShuttingDown) {
this.hidePanel();
this._onActiveInstanceChanged.fire(undefined);
}
// Fire events
......@@ -287,6 +293,8 @@ export abstract class TerminalService implements ITerminalService {
instance.addDisposable(instance.onDisposed(this._onInstanceDisposed.fire, this._onInstanceDisposed));
instance.addDisposable(instance.onTitleChanged(this._onInstanceTitleChanged.fire, this._onInstanceTitleChanged));
instance.addDisposable(instance.onProcessIdReady(this._onInstanceProcessIdReady.fire, this._onInstanceProcessIdReady));
instance.addDisposable(instance.onDimensionsChanged(() => this._onInstanceDimensionsChanged.fire(instance)));
instance.addDisposable(instance.onFocus(this._onActiveInstanceChanged.fire, this._onActiveInstanceChanged));
}
private _getTabForInstance(instance: ITerminalInstance): ITerminalTab {
......@@ -301,7 +309,7 @@ export abstract class TerminalService implements ITerminalService {
public showPanel(focus?: boolean): TPromise<void> {
return new TPromise<void>((complete) => {
let panel = this._panelService.getActivePanel();
const panel = this._panelService.getActivePanel();
if (!panel || panel.getId() !== TERMINAL_PANEL_ID) {
return this._panelService.openPanel(TERMINAL_PANEL_ID, focus).then(() => {
if (focus) {
......
......@@ -66,7 +66,7 @@ registry.registerWorkbenchAction(new SyncActionDescriptor(QuickOpenTermAction, Q
const actionBarRegistry = Registry.as<IActionBarRegistry>(ActionBarExtensions.Actionbar);
actionBarRegistry.registerActionBarContributor(Scope.VIEWER, QuickOpenActionTermContributor);
let configurationRegistry = Registry.as<IConfigurationRegistry>(Extensions.Configuration);
const configurationRegistry = Registry.as<IConfigurationRegistry>(Extensions.Configuration);
configurationRegistry.registerConfiguration({
'id': 'terminal',
'order': 100,
......@@ -368,7 +368,7 @@ registerSingleton(ITerminalService, TerminalService);
// On mac cmd+` is reserved to cycle between windows, that's why the keybindings use WinCtrl
const category = nls.localize('terminalCategory', "Terminal");
let actionRegistry = Registry.as<IWorkbenchActionRegistry>(ActionExtensions.WorkbenchActions);
const actionRegistry = Registry.as<IWorkbenchActionRegistry>(ActionExtensions.WorkbenchActions);
actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(KillTerminalAction, KillTerminalAction.ID, KillTerminalAction.LABEL), 'Terminal: Kill the Active Terminal Instance', category);
actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(CopyTerminalSelectionAction, CopyTerminalSelectionAction.ID, CopyTerminalSelectionAction.LABEL, {
primary: KeyMod.CtrlCmd | KeyCode.KEY_C,
......
......@@ -71,9 +71,10 @@ export class KillTerminalAction extends Action {
}
public run(event?: any): TPromise<any> {
let terminalInstance = this.terminalService.getActiveInstance();
if (terminalInstance) {
this.terminalService.getActiveInstance().dispose();
console.log('kill');
const instance = this.terminalService.getActiveInstance();
if (instance) {
instance.dispose();
if (this.terminalService.terminalInstances.length > 0) {
this.terminalService.showPanel(true);
}
......@@ -121,7 +122,7 @@ export class CopyTerminalSelectionAction extends Action {
}
public run(event?: any): TPromise<any> {
let terminalInstance = this.terminalService.getActiveInstance();
const terminalInstance = this.terminalService.getActiveInstance();
if (terminalInstance) {
terminalInstance.copySelection();
}
......@@ -142,7 +143,7 @@ export class SelectAllTerminalAction extends Action {
}
public run(event?: any): TPromise<any> {
let terminalInstance = this.terminalService.getActiveInstance();
const terminalInstance = this.terminalService.getActiveInstance();
if (terminalInstance) {
terminalInstance.selectAll();
}
......@@ -161,7 +162,7 @@ export abstract class BaseSendTextTerminalAction extends Action {
}
public run(event?: any): TPromise<any> {
let terminalInstance = this._terminalService.getActiveInstance();
const terminalInstance = this._terminalService.getActiveInstance();
if (terminalInstance) {
terminalInstance.sendText(this._text, false);
}
......@@ -598,7 +599,7 @@ export class RunSelectedTextInTerminalAction extends Action {
if (selection.isEmpty()) {
text = editor.getModel().getLineContent(selection.selectionStartLineNumber).trim();
} else {
let endOfLinePreference = os.EOL === '\n' ? EndOfLinePreference.LF : EndOfLinePreference.CRLF;
const endOfLinePreference = os.EOL === '\n' ? EndOfLinePreference.LF : EndOfLinePreference.CRLF;
text = editor.getModel().getValueInRange(selection, endOfLinePreference);
}
instance.sendText(text, true);
......@@ -695,7 +696,7 @@ export class ScrollDownTerminalAction extends Action {
}
public run(event?: any): TPromise<any> {
let terminalInstance = this.terminalService.getActiveInstance();
const terminalInstance = this.terminalService.getActiveInstance();
if (terminalInstance) {
terminalInstance.scrollDownLine();
}
......@@ -716,7 +717,7 @@ export class ScrollDownPageTerminalAction extends Action {
}
public run(event?: any): TPromise<any> {
let terminalInstance = this.terminalService.getActiveInstance();
const terminalInstance = this.terminalService.getActiveInstance();
if (terminalInstance) {
terminalInstance.scrollDownPage();
}
......@@ -737,7 +738,7 @@ export class ScrollToBottomTerminalAction extends Action {
}
public run(event?: any): TPromise<any> {
let terminalInstance = this.terminalService.getActiveInstance();
const terminalInstance = this.terminalService.getActiveInstance();
if (terminalInstance) {
terminalInstance.scrollToBottom();
}
......@@ -758,7 +759,7 @@ export class ScrollUpTerminalAction extends Action {
}
public run(event?: any): TPromise<any> {
let terminalInstance = this.terminalService.getActiveInstance();
const terminalInstance = this.terminalService.getActiveInstance();
if (terminalInstance) {
terminalInstance.scrollUpLine();
}
......@@ -779,7 +780,7 @@ export class ScrollUpPageTerminalAction extends Action {
}
public run(event?: any): TPromise<any> {
let terminalInstance = this.terminalService.getActiveInstance();
const terminalInstance = this.terminalService.getActiveInstance();
if (terminalInstance) {
terminalInstance.scrollUpPage();
}
......@@ -800,7 +801,7 @@ export class ScrollToTopTerminalAction extends Action {
}
public run(event?: any): TPromise<any> {
let terminalInstance = this.terminalService.getActiveInstance();
const terminalInstance = this.terminalService.getActiveInstance();
if (terminalInstance) {
terminalInstance.scrollToTop();
}
......@@ -821,7 +822,7 @@ export class ClearTerminalAction extends Action {
}
public run(event?: any): TPromise<any> {
let terminalInstance = this.terminalService.getActiveInstance();
const terminalInstance = this.terminalService.getActiveInstance();
if (terminalInstance) {
terminalInstance.clear();
}
......@@ -842,7 +843,7 @@ export class ClearSelectionTerminalAction extends Action {
}
public run(event?: any): TPromise<any> {
let terminalInstance = this.terminalService.getActiveInstance();
const terminalInstance = this.terminalService.getActiveInstance();
if (terminalInstance && terminalInstance.hasSelection()) {
terminalInstance.clearSelection();
}
......@@ -959,7 +960,7 @@ export class QuickOpenActionTermContributor extends ActionBarContributor {
}
public getActions(context: any): IAction[] {
let actions: Action[] = [];
const actions: Action[] = [];
if (context.element instanceof TerminalEntry) {
actions.push(this.instantiationService.createInstance(RenameTerminalQuickOpenAction, RenameTerminalQuickOpenAction.ID, RenameTerminalQuickOpenAction.LABEL, context.element));
actions.push(this.instantiationService.createInstance(QuickKillTerminalAction, QuickKillTerminalAction.ID, QuickKillTerminalAction.LABEL, context.element));
......
......@@ -50,12 +50,12 @@ export class TerminalConfigHelper implements ITerminalConfigHelper {
public configFontIsMonospace(): boolean {
this._createCharMeasureElementIfNecessary();
let fontSize = 15;
let fontFamily = this.config.fontFamily || this._configurationService.getValue<IEditorOptions>('editor').fontFamily;
let i_rect = this._getBoundingRectFor('i', fontFamily, fontSize);
let w_rect = this._getBoundingRectFor('w', fontFamily, fontSize);
const fontSize = 15;
const fontFamily = this.config.fontFamily || this._configurationService.getValue<IEditorOptions>('editor').fontFamily;
const i_rect = this._getBoundingRectFor('i', fontFamily, fontSize);
const w_rect = this._getBoundingRectFor('w', fontFamily, fontSize);
let invalidBounds = !i_rect.width || !w_rect.width;
const invalidBounds = !i_rect.width || !w_rect.width;
if (invalidBounds) {
// There is no reason to believe the font is not Monospace.
return true;
......@@ -88,7 +88,7 @@ export class TerminalConfigHelper implements ITerminalConfigHelper {
private _measureFont(fontFamily: string, fontSize: number, letterSpacing: number, lineHeight: number): ITerminalFont {
this._createCharMeasureElementIfNecessary();
let rect = this._getBoundingRectFor('X', fontFamily, fontSize);
const rect = this._getBoundingRectFor('X', fontFamily, fontSize);
// Bounding client rect was invalid, use last font measurement if available.
if (this._lastFontMeasurement && !rect.width && !rect.height) {
......@@ -122,7 +122,7 @@ export class TerminalConfigHelper implements ITerminalConfigHelper {
}
}
let fontSize = this._toInteger(this.config.fontSize, MINIMUM_FONT_SIZE, MAXIMUM_FONT_SIZE, EDITOR_FONT_DEFAULTS.fontSize);
const fontSize = this._toInteger(this.config.fontSize, MINIMUM_FONT_SIZE, MAXIMUM_FONT_SIZE, EDITOR_FONT_DEFAULTS.fontSize);
const letterSpacing = this.config.letterSpacing ? Math.max(Math.floor(this.config.letterSpacing), MINIMUM_LETTER_SPACING) : DEFAULT_LETTER_SPACING;
const lineHeight = this.config.lineHeight ? Math.max(this.config.lineHeight, 1) : DEFAULT_LINE_HEIGHT;
......
......@@ -14,7 +14,7 @@ import { Terminal as XTermTerminal } from 'vscode-xterm';
import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { IPanelService } from 'vs/workbench/services/panel/common/panelService';
import { ITerminalInstance, KEYBINDING_CONTEXT_TERMINAL_TEXT_SELECTED, TERMINAL_PANEL_ID, IShellLaunchConfig, ITerminalProcessManager, ProcessState, NEVER_MEASURE_RENDER_TIME_STORAGE_KEY } from 'vs/workbench/parts/terminal/common/terminal';
import { ITerminalInstance, KEYBINDING_CONTEXT_TERMINAL_TEXT_SELECTED, TERMINAL_PANEL_ID, IShellLaunchConfig, ITerminalProcessManager, ProcessState, NEVER_MEASURE_RENDER_TIME_STORAGE_KEY, ITerminalDimensions } from 'vs/workbench/parts/terminal/common/terminal';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
import { TabFocus } from 'vs/editor/common/config/commonEditorConfig';
......@@ -63,8 +63,8 @@ export class TerminalInstance implements ITerminalInstance {
private _terminalHasTextContextKey: IContextKey<boolean>;
private _cols: number;
private _rows: number;
private _dimensionsOverride: ITerminalDimensions;
private _windowsShellHelper: WindowsShellHelper;
private _onLineDataListeners: ((lineData: string) => void)[];
private _xtermReadyPromise: TPromise<void>;
private _disposables: lifecycle.IDisposable[];
......@@ -76,6 +76,8 @@ export class TerminalInstance implements ITerminalInstance {
public disableLayout: boolean;
public get id(): number { return this._id; }
public get cols(): number { return this._cols; }
public get rows(): number { return this._rows; }
// TODO: Ideally processId would be merged into processReady
public get processId(): number | undefined { return this._processManager ? this._processManager.shellProcessId : undefined; }
// TODO: How does this work with detached processes?
......@@ -84,10 +86,11 @@ export class TerminalInstance implements ITerminalInstance {
public get title(): string { return this._title; }
public get hadFocusOnExit(): boolean { return this._hadFocusOnExit; }
public get isTitleSetByProcess(): boolean { return !!this._messageTitleDisposable; }
public get shellLaunchConfig(): IShellLaunchConfig { return Object.freeze(this._shellLaunchConfig); }
public get shellLaunchConfig(): IShellLaunchConfig { return this._shellLaunchConfig; }
public get commandTracker(): TerminalCommandTracker { return this._commandTracker; }
private readonly _onExit: Emitter<number> = new Emitter<number>();
public get onExit(): Event<number> { return this._onExit.event; }
private readonly _onDisposed: Emitter<ITerminalInstance> = new Emitter<ITerminalInstance>();
public get onDisposed(): Event<ITerminalInstance> { return this._onDisposed.event; }
private readonly _onFocused: Emitter<ITerminalInstance> = new Emitter<ITerminalInstance>();
......@@ -96,15 +99,24 @@ export class TerminalInstance implements ITerminalInstance {
public get onProcessIdReady(): Event<ITerminalInstance> { return this._onProcessIdReady.event; }
private readonly _onTitleChanged: Emitter<string> = new Emitter<string>();
public get onTitleChanged(): Event<string> { return this._onTitleChanged.event; }
private readonly _onData: Emitter<string> = new Emitter<string>();
public get onData(): Event<string> { return this._onData.event; }
private readonly _onLineData: Emitter<string> = new Emitter<string>();
public get onLineData(): Event<string> { return this._onLineData.event; }
private readonly _onRendererInput: Emitter<string> = new Emitter<string>();
public get onRendererInput(): Event<string> { return this._onRendererInput.event; }
private readonly _onRequestExtHostProcess: Emitter<ITerminalInstance> = new Emitter<ITerminalInstance>();
public get onRequestExtHostProcess(): Event<ITerminalInstance> { return this._onRequestExtHostProcess.event; }
private readonly _onDimensionsChanged: Emitter<void> = new Emitter<void>();
public get onDimensionsChanged(): Event<void> { return this._onDimensionsChanged.event; }
private readonly _onFocus: Emitter<ITerminalInstance> = new Emitter<ITerminalInstance>();
public get onFocus(): Event<ITerminalInstance> { return this._onFocus.event; }
public constructor(
private _terminalFocusContextKey: IContextKey<boolean>,
private _configHelper: TerminalConfigHelper,
private readonly _terminalFocusContextKey: IContextKey<boolean>,
private readonly _configHelper: TerminalConfigHelper,
private _container: HTMLElement,
private _shellLaunchConfig: IShellLaunchConfig,
doCreateProcess: boolean,
@IContextKeyService private readonly _contextKeyService: IContextKeyService,
@IKeybindingService private readonly _keybindingService: IKeybindingService,
@INotificationService private readonly _notificationService: INotificationService,
......@@ -118,7 +130,6 @@ export class TerminalInstance implements ITerminalInstance {
) {
this._disposables = [];
this._skipTerminalCommands = [];
this._onLineDataListeners = [];
this._isExiting = false;
this._hadFocusOnExit = false;
this._isVisible = false;
......@@ -130,8 +141,10 @@ export class TerminalInstance implements ITerminalInstance {
this._logService.trace(`terminalInstance#ctor (id: ${this.id})`, this._shellLaunchConfig);
this._initDimensions();
if (doCreateProcess) {
if (!this.shellLaunchConfig.isRendererOnly) {
this._createProcess();
} else {
this.setTitle(this._shellLaunchConfig.name, false);
}
this._xtermReadyPromise = this._createXterm();
......@@ -142,14 +155,14 @@ export class TerminalInstance implements ITerminalInstance {
}
});
this._configurationService.onDidChangeConfiguration(e => {
this.addDisposable(this._configurationService.onDidChangeConfiguration(e => {
if (e.affectsConfiguration('terminal.integrated')) {
this.updateConfig();
}
if (e.affectsConfiguration('editor.accessibilitySupport')) {
this.updateAccessibilitySupport();
}
});
}));
}
public addDisposable(disposable: lifecycle.IDisposable): void {
......@@ -286,6 +299,13 @@ export class TerminalInstance implements ITerminalInstance {
// TODO: How does the cwd work on detached processes?
this._linkHandler = this._instantiationService.createInstance(TerminalLinkHandler, this._xterm, platform.platform, this._processManager.initialCwd);
}
this._xterm.on('focus', () => this._onFocus.fire(this));
// Register listener to trigger the onInput ext API if the terminal is a renderer only
if (this._shellLaunchConfig.isRendererOnly) {
this._xterm.on('data', (data) => this._sendRendererInput(data));
}
this._commandTracker = new TerminalCommandTracker(this._xterm);
this._disposables.push(this._themeService.onThemeChange(theme => this._updateTheme(theme)));
}
......@@ -439,7 +459,7 @@ export class TerminalInstance implements ITerminalInstance {
}
private _measureRenderTime(): void {
let frameTimes: number[] = [];
const frameTimes: number[] = [];
const textRenderLayer = (<any>this._xterm).renderer._renderLayers[0];
const originalOnGridChanged = textRenderLayer.onGridChanged;
......@@ -538,18 +558,21 @@ export class TerminalInstance implements ITerminalInstance {
public dispose(): void {
this._logService.trace(`terminalInstance#dispose (id: ${this.id})`);
if (this._windowsShellHelper) {
this._windowsShellHelper.dispose();
}
if (this._linkHandler) {
this._linkHandler.dispose();
}
this._windowsShellHelper = lifecycle.dispose(this._windowsShellHelper);
this._linkHandler = lifecycle.dispose(this._linkHandler);
this._commandTracker = lifecycle.dispose(this._commandTracker);
this._widgetManager = lifecycle.dispose(this._widgetManager);
if (this._xterm && this._xterm.element) {
this._hadFocusOnExit = dom.hasClass(this._xterm.element, 'focus');
}
if (this._wrapperElement) {
if ((<any>this._wrapperElement).xterm) {
(<any>this._wrapperElement).xterm = null;
}
this._container.removeChild(this._wrapperElement);
this._wrapperElement = null;
this._xtermElement = null;
}
if (this._xterm) {
const buffer = (<any>this._xterm.buffer);
......@@ -557,9 +580,7 @@ export class TerminalInstance implements ITerminalInstance {
this._xterm.dispose();
this._xterm = null;
}
if (this._processManager) {
this._processManager.dispose();
}
this._processManager = lifecycle.dispose(this._processManager);
if (!this._isDisposed) {
this._isDisposed = true;
this._onDisposed.fire(this);
......@@ -584,17 +605,39 @@ export class TerminalInstance implements ITerminalInstance {
document.execCommand('paste');
}
public sendText(text: string, addNewLine: boolean): void {
this._processManager.ptyProcessReady.then(() => {
// Normalize line endings to 'enter' press.
text = text.replace(TerminalInstance.EOL_REGEX, '\r');
if (addNewLine && text.substr(text.length - 1) !== '\r') {
text += '\r';
public write(text: string): void {
this._xtermReadyPromise.then(() => {
if (!this._xterm) {
return;
}
this._xterm.write(text);
if (this._shellLaunchConfig.isRendererOnly) {
// Fire onData API in the extension host
this._onData.fire(text);
}
this._processManager.write(text);
});
}
public sendText(text: string, addNewLine: boolean): void {
// Normalize line endings to 'enter' press.
text = text.replace(TerminalInstance.EOL_REGEX, '\r');
if (addNewLine && text.substr(text.length - 1) !== '\r') {
text += '\r';
}
if (this._shellLaunchConfig.isRendererOnly) {
// If the terminal is a renderer only, fire the onInput ext API
this._sendRendererInput(text);
} else {
// If the terminal has a process, send it to the process
if (this._processManager) {
this._processManager.ptyProcessReady.then(() => {
this._processManager.write(text);
});
}
}
}
public setVisible(visible: boolean): void {
this._isVisible = visible;
if (this._wrapperElement) {
......@@ -660,6 +703,8 @@ export class TerminalInstance implements ITerminalInstance {
this._processManager.onProcessExit(exitCode => this._onProcessExit(exitCode));
this._processManager.createProcess(this._shellLaunchConfig, this._cols, this._rows);
this._processManager.onProcessData(data => this._onData.fire(data));
if (this._shellLaunchConfig.name) {
this.setTitle(this._shellLaunchConfig.name, false);
} else {
......@@ -751,6 +796,8 @@ export class TerminalInstance implements ITerminalInstance {
}
}
}
this._onExit.fire(exitCode);
}
private _attachPressAnyKeyToCloseListener() {
......@@ -791,26 +838,16 @@ export class TerminalInstance implements ITerminalInstance {
this._shellLaunchConfig = shell;
}
public onData(listener: (data: string) => void): lifecycle.IDisposable {
return this._processManager.onProcessData(data => listener(data));
}
private _sendRendererInput(input: string): void {
if (this._processManager) {
throw new Error('onRendererInput attempted to be used on a regular terminal');
}
public onLineData(listener: (lineData: string) => void): lifecycle.IDisposable {
this._onLineDataListeners.push(listener);
return {
dispose: () => {
const i = this._onLineDataListeners.indexOf(listener);
if (i >= 0) {
this._onLineDataListeners.splice(i, 1);
}
}
};
// For terminal renderers onData fires on keystrokes and when sendText is called.
this._onRendererInput.fire(input);
}
private _onLineFeed(): void {
if (this._onLineDataListeners.length === 0) {
return;
}
const buffer = (<any>this._xterm.buffer);
const newLine = buffer.lines.get(buffer.ybase + buffer.y);
if (!newLine.isWrapped) {
......@@ -823,17 +860,7 @@ export class TerminalInstance implements ITerminalInstance {
while (lineIndex >= 0 && buffer.lines.get(lineIndex--).isWrapped) {
lineData = buffer.translateBufferLineToString(lineIndex, false) + lineData;
}
this._onLineDataListeners.forEach(listener => {
try {
listener(lineData);
} catch (err) {
console.error(`onLineData listener threw`, err);
}
});
}
public onExit(listener: (exitCode: number) => void): lifecycle.IDisposable {
return this._processManager.onProcessExit(listener);
this._onLineData.fire(lineData);
}
public updateConfig(): void {
......@@ -905,6 +932,21 @@ export class TerminalInstance implements ITerminalInstance {
return;
}
if (this._xterm) {
this._xterm.element.style.width = terminalWidth + 'px';
}
this._resize();
}
private _resize(): void {
let cols = this._cols;
let rows = this._rows;
if (this._dimensionsOverride && this._dimensionsOverride.cols && this._dimensionsOverride.rows) {
cols = Math.min(Math.max(this._dimensionsOverride.cols, 2), this._cols);
rows = Math.min(Math.max(this._dimensionsOverride.rows, 2), this._rows);
}
if (this._xterm) {
const font = this._configHelper.getFont(this._xterm);
......@@ -921,8 +963,11 @@ export class TerminalInstance implements ITerminalInstance {
this._safeSetOption('drawBoldTextInBrightColors', config.drawBoldTextInBrightColors);
}
this._xterm.resize(this._cols, this._rows);
this._xterm.element.style.width = terminalWidth + 'px';
if (cols !== this._xterm.getOption('cols') || rows !== this._xterm.getOption('rows')) {
this._onDimensionsChanged.fire();
}
this._xterm.resize(cols, rows);
if (this._isVisible) {
// Force the renderer to unpause by simulating an IntersectionObserver event. This
// is to fix an issue where dragging the window to the top of the screen to maximize
......@@ -935,7 +980,7 @@ export class TerminalInstance implements ITerminalInstance {
}
if (this._processManager) {
this._processManager.ptyProcessReady.then(() => this._processManager.setDimensions(this._cols, this._rows));
this._processManager.ptyProcessReady.then(() => this._processManager.setDimensions(cols, rows));
}
}
......@@ -964,6 +1009,11 @@ export class TerminalInstance implements ITerminalInstance {
}
}
public setDimensions(dimensions: ITerminalDimensions): void {
this._dimensionsOverride = dimensions;
this._resize();
}
private _getXtermTheme(theme?: ITheme): any {
if (!theme) {
theme = this._themeService.getTheme();
......
......@@ -121,6 +121,7 @@ export class TerminalLinkHandler {
}
public dispose(): void {
this._xterm = null;
this._hoverDisposables = dispose(this._hoverDisposables);
this._mouseMoveDisposable = dispose(this._mouseMoveDisposable);
}
......@@ -172,7 +173,7 @@ export class TerminalLinkHandler {
}
private _handleHypertextLink(url: string): void {
let uri = Uri.parse(url);
const uri = Uri.parse(url);
this._openerService.open(uri);
}
......
......@@ -77,7 +77,7 @@ export class TerminalPanel extends Panel {
}
if (e.affectsConfiguration('terminal.integrated.fontFamily') || e.affectsConfiguration('editor.fontFamily')) {
let configHelper = this._terminalService.configHelper;
const configHelper = this._terminalService.configHelper;
if (configHelper instanceof TerminalConfigHelper) {
if (!configHelper.configFontIsMonospace()) {
const choices: IPromptChoice[] = [{
......@@ -203,7 +203,7 @@ export class TerminalPanel extends Panel {
this._terminalService.getActiveInstance().focus();
} else if (event.which === 3) {
if (this._terminalService.configHelper.config.rightClickBehavior === 'copyPaste') {
let terminal = this._terminalService.getActiveInstance();
const terminal = this._terminalService.getActiveInstance();
if (terminal.hasSelection()) {
terminal.copySelection();
terminal.clearSelection();
......@@ -230,7 +230,7 @@ export class TerminalPanel extends Panel {
}
if (event.which === 1) {
let terminal = this._terminalService.getActiveInstance();
const terminal = this._terminalService.getActiveInstance();
if (terminal.hasSelection()) {
terminal.copySelection();
}
......@@ -240,7 +240,7 @@ export class TerminalPanel extends Panel {
this._register(dom.addDisposableListener(this._parentDomElement, 'contextmenu', (event: MouseEvent) => {
if (!this._cancelContextMenu) {
const standardEvent = new StandardMouseEvent(event);
let anchor: { x: number, y: number } = { x: standardEvent.posx, y: standardEvent.posy };
const anchor: { x: number, y: number } = { x: standardEvent.posx, y: standardEvent.posy };
this._contextMenuService.showContextMenu({
getAnchor: () => anchor,
getActions: () => TPromise.as(this._getContextMenuActions()),
......@@ -269,7 +269,7 @@ export class TerminalPanel extends Panel {
// Check if files were dragged from the tree explorer
let path: string;
let resources = e.dataTransfer.getData(DataTransfers.RESOURCES);
const resources = e.dataTransfer.getData(DataTransfers.RESOURCES);
if (resources) {
path = URI.parse(JSON.parse(resources)[0]).path;
} else if (e.dataTransfer.files.length > 0) {
......@@ -315,15 +315,15 @@ registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => {
}
// Borrow the editor's hover background for now
let hoverBackground = theme.getColor(editorHoverBackground);
const hoverBackground = theme.getColor(editorHoverBackground);
if (hoverBackground) {
collector.addRule(`.monaco-workbench .panel.integrated-terminal .terminal-message-widget { background-color: ${hoverBackground}; }`);
}
let hoverBorder = theme.getColor(editorHoverBorder);
const hoverBorder = theme.getColor(editorHoverBorder);
if (hoverBorder) {
collector.addRule(`.monaco-workbench .panel.integrated-terminal .terminal-message-widget { border: 1px solid ${hoverBorder}; }`);
}
let hoverForeground = theme.getColor(editorForeground);
const hoverForeground = theme.getColor(editorForeground);
if (hoverForeground) {
collector.addRule(`.monaco-workbench .panel.integrated-terminal .terminal-message-widget { color: ${hoverForeground}; }`);
}
......
......@@ -91,8 +91,12 @@ export class TerminalService extends AbstractTerminalService implements ITermina
return instance;
}
public createTerminalRenderer(name: string): ITerminalInstance {
return this.createTerminal({ name, isRendererOnly: true });
}
public createInstance(terminalFocusContextKey: IContextKey<boolean>, configHelper: ITerminalConfigHelper, container: HTMLElement, shellLaunchConfig: IShellLaunchConfig, doCreateProcess: boolean): ITerminalInstance {
const instance = this._instantiationService.createInstance(TerminalInstance, terminalFocusContextKey, configHelper, container, shellLaunchConfig, true);
const instance = this._instantiationService.createInstance(TerminalInstance, terminalFocusContextKey, configHelper, container, shellLaunchConfig);
this._onInstanceCreated.fire(instance);
return instance;
}
......@@ -109,7 +113,7 @@ export class TerminalService extends AbstractTerminalService implements ITermina
public focusFindWidget(): TPromise<void> {
return this.showPanel(false).then(() => {
let panel = this._panelService.getActivePanel() as TerminalPanel;
const panel = this._panelService.getActivePanel() as TerminalPanel;
panel.focusFindWidget();
this._findWidgetVisible.set(true);
});
......
......@@ -5,6 +5,7 @@
import { Terminal, IMarker } from 'vscode-xterm';
import { ITerminalCommandTracker } from 'vs/workbench/parts/terminal/common/terminal';
import { IDisposable } from 'vs/base/common/lifecycle';
/**
* The minimize size of the prompt in which to assume the line is a command.
......@@ -21,7 +22,7 @@ export enum ScrollPosition {
Middle
}
export class TerminalCommandTracker implements ITerminalCommandTracker {
export class TerminalCommandTracker implements ITerminalCommandTracker, IDisposable {
private _currentMarker: IMarker | Boundary = Boundary.Bottom;
private _selectionStart: IMarker | Boundary | null = null;
private _isDisposable: boolean = false;
......@@ -32,6 +33,10 @@ export class TerminalCommandTracker implements ITerminalCommandTracker {
this._xterm.on('key', key => this._onKey(key));
}
public dispose(): void {
this._xterm = null;
}
private _onKey(key: string): void {
if (key === '\x0d') {
this._onEnter();
......@@ -194,7 +199,7 @@ export class TerminalCommandTracker implements ITerminalCommandTracker {
if (this._currentMarker === Boundary.Bottom) {
this._currentMarker = this._xterm.addMarker(this._getOffset() - 1);
} else {
let offset = this._getOffset();
const offset = this._getOffset();
if (this._isDisposable) {
this._currentMarker.dispose();
}
......@@ -217,7 +222,7 @@ export class TerminalCommandTracker implements ITerminalCommandTracker {
if (this._currentMarker === Boundary.Top) {
this._currentMarker = this._xterm.addMarker(this._getOffset() + 1);
} else {
let offset = this._getOffset();
const offset = this._getOffset();
if (this._isDisposable) {
this._currentMarker.dispose();
}
......
......@@ -25,9 +25,9 @@ export function mergeEnvironments(parent: IStringDictionary<string>, other: IStr
// On Windows apply the new values ignoring case, while still retaining
// the case of the original key.
if (platform.isWindows) {
for (let configKey in other) {
for (const configKey in other) {
let actualKey = configKey;
for (let envKey in parent) {
for (const envKey in parent) {
if (configKey.toLowerCase() === envKey.toLowerCase()) {
actualKey = envKey;
break;
......
......@@ -9,7 +9,7 @@ import * as pty from 'node-pty';
// The pty process needs to be run in its own child process to get around maxing out CPU on Mac,
// see https://github.com/electron/electron/issues/38
var shellName: string;
let shellName: string;
if (os.platform() === 'win32') {
shellName = path.basename(process.env.PTYSHELL);
} else {
......@@ -17,12 +17,12 @@ if (os.platform() === 'win32') {
// color prompt as defined in the default ~/.bashrc file.
shellName = 'xterm-256color';
}
var shell = process.env.PTYSHELL;
var args = getArgs();
var cwd = process.env.PTYCWD;
var cols = process.env.PTYCOLS;
var rows = process.env.PTYROWS;
var currentTitle = '';
const shell = process.env.PTYSHELL;
const args = getArgs();
const cwd = process.env.PTYCWD;
const cols = process.env.PTYCOLS;
const rows = process.env.PTYROWS;
let currentTitle = '';
setupPlanB(Number(process.env.PTYPID));
cleanEnv();
......@@ -34,7 +34,7 @@ interface IOptions {
rows?: number;
}
var options: IOptions = {
const options: IOptions = {
name: shellName,
cwd
};
......@@ -43,10 +43,10 @@ if (cols && rows) {
options.rows = parseInt(rows, 10);
}
var ptyProcess = pty.spawn(shell, args, options);
const ptyProcess = pty.spawn(shell, args, options);
var closeTimeout: number;
var exitCode: number;
let closeTimeout: number;
let exitCode: number;
// Allow any trailing data events to be sent before the exit event is sent.
// See https://github.com/Tyriar/node-pty/issues/72
......@@ -95,8 +95,8 @@ function getArgs(): string | string[] {
if (process.env['PTYSHELLCMDLINE']) {
return process.env['PTYSHELLCMDLINE'];
}
var args = [];
var i = 0;
const args = [];
let i = 0;
while (process.env['PTYSHELLARG' + i]) {
args.push(process.env['PTYSHELLARG' + i]);
i++;
......@@ -105,7 +105,7 @@ function getArgs(): string | string[] {
}
function cleanEnv() {
var keys = [
const keys = [
'AMD_ENTRYPOINT',
'ELECTRON_NO_ASAR',
'ELECTRON_RUN_AS_NODE',
......@@ -124,7 +124,7 @@ function cleanEnv() {
delete process.env[key];
}
});
var i = 0;
let i = 0;
while (process.env['PTYSHELLARG' + i]) {
delete process.env['PTYSHELLARG' + i];
i++;
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册