Skip to content

Commit

Permalink
fix #7892: support vsc ext create pseudo terminal
Browse files Browse the repository at this point in the history
Signed-off-by: 二凢 <dingyu.wdy@alibaba-inc.com>
  • Loading branch information
datou0412 authored and akosyakov committed Jun 4, 2020
1 parent c80f3fe commit 3079416
Show file tree
Hide file tree
Showing 11 changed files with 356 additions and 17 deletions.
22 changes: 19 additions & 3 deletions packages/plugin-ext/src/common/plugin-api-rpc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -217,11 +217,12 @@ export interface CommandRegistryExt {
export interface TerminalServiceExt {
$terminalCreated(id: string, name: string): void;
$terminalNameChanged(id: string, name: string): void;
$terminalOpened(id: string, processId: number): void;
$terminalOpened(id: string, processId: number, cols: number, rows: number): void;
$terminalClosed(id: string): void;
$terminalOnInput(id: string, data: string): void;
$terminalSizeChanged(id: string, cols: number, rows: number): void;
$currentTerminalChanged(id: string | undefined): void;
}

export interface OutputChannelRegistryExt {
createOutputChannel(name: string, pluginInfo: PluginInfo): theia.OutputChannel
}
Expand All @@ -245,7 +246,7 @@ export interface TerminalServiceMain {
* Create new Terminal with Terminal options.
* @param options - object with parameters to create new terminal.
*/
$createTerminal(id: string, options: theia.TerminalOptions): Promise<string>;
$createTerminal(id: string, options: theia.TerminalOptions, isPseudoTerminal?: boolean): Promise<string>;

/**
* Send text to the terminal by id.
Expand All @@ -255,6 +256,21 @@ export interface TerminalServiceMain {
*/
$sendText(id: string, text: string, addNewLine?: boolean): void;

/**
* Write data to the terminal by id.
* @param id - terminal id.
* @param data - data.
*/
$write(id: string, data: string): void;

/**
* Resize the terminal by id.
* @param id - terminal id.
* @param cols - columns.
* @param rows - rows.
*/
$resize(id: string, cols: number, rows: number): void;

/**
* Show terminal on the UI panel.
* @param id - terminal id.
Expand Down
29 changes: 26 additions & 3 deletions packages/plugin-ext/src/main/browser/terminal-main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,15 +68,37 @@ export class TerminalServiceMainImpl implements TerminalServiceMain, Disposable
this.toDispose.push(Disposable.create(() => terminal.title.changed.disconnect(updateTitle)));

const updateProcessId = () => terminal.processId.then(
processId => this.extProxy.$terminalOpened(terminal.id, processId),
processId => this.extProxy.$terminalOpened(terminal.id, processId, terminal.dimensions.cols, terminal.dimensions.rows),
() => {/* no-op */ }
);
updateProcessId();
this.toDispose.push(terminal.onDidOpen(() => updateProcessId()));
this.toDispose.push(terminal.onTerminalDidClose(() => this.extProxy.$terminalClosed(terminal.id)));
this.toDispose.push(terminal.onSizeChanged(({ cols, rows }) => {
this.extProxy.$terminalSizeChanged(terminal.id, cols, rows);
}));
this.toDispose.push(terminal.onData(data => {
this.extProxy.$terminalOnInput(terminal.id, data);
}));
}

async $createTerminal(id: string, options: TerminalOptions): Promise<string> {
$write(id: string, data: string): void {
const terminal = this.terminals.getById(id);
if (!terminal) {
return;
}
terminal.write(data);
}

$resize(id: string, cols: number, rows: number): void {
const terminal = this.terminals.getById(id);
if (!terminal) {
return;
}
terminal.rezise(cols, rows);
}

async $createTerminal(id: string, options: TerminalOptions, isPseudoTerminal?: boolean): Promise<string> {
try {
const terminal = await this.terminals.newTerminal({
id,
Expand All @@ -87,7 +109,8 @@ export class TerminalServiceMainImpl implements TerminalServiceMain, Disposable
env: options.env,
destroyTermOnClose: true,
useServerTitle: false,
attributes: options.attributes
attributes: options.attributes,
isPseudoTerminal,
});
terminal.start();
return terminal.id;
Expand Down
2 changes: 1 addition & 1 deletion packages/plugin-ext/src/plugin/plugin-context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -354,7 +354,7 @@ export function createAPIFactory(
onDidChangeWindowState(listener, thisArg?, disposables?): theia.Disposable {
return windowStateExt.onDidChangeWindowState(listener, thisArg, disposables);
},
createTerminal(nameOrOptions: theia.TerminalOptions | (string | undefined), shellPath?: string, shellArgs?: string[]): theia.Terminal {
createTerminal(nameOrOptions: theia.TerminalOptions | theia.PseudoTerminalOptions | (string | undefined), shellPath?: string, shellArgs?: string[]): theia.Terminal {
return terminalExt.createTerminal(nameOrOptions, shellPath, shellArgs);
},
onDidCloseTerminal,
Expand Down
95 changes: 89 additions & 6 deletions packages/plugin-ext/src/plugin/terminal-ext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
********************************************************************************/
import { UUID } from '@phosphor/coreutils/lib/uuid';
import { Terminal, TerminalOptions } from '@theia/plugin';
import { Terminal, TerminalOptions, PseudoTerminalOptions } from '@theia/plugin';
import { TerminalServiceExt, TerminalServiceMain, PLUGIN_RPC_CONTEXT } from '../common/plugin-api-rpc';
import { RPCProtocol } from '../common/rpc-protocol';
import { Emitter } from '@theia/core/lib/common/event';
Expand All @@ -31,6 +31,8 @@ export class TerminalServiceExtImpl implements TerminalServiceExt {

private readonly _terminals = new Map<string, TerminalExtImpl>();

private readonly _pseudoTerminals = new Map<string, PseudoTerminal>();

private readonly onDidCloseTerminalEmitter = new Emitter<Terminal>();
readonly onDidCloseTerminal: theia.Event<Terminal> = this.onDidCloseTerminalEmitter.event;

Expand All @@ -48,19 +50,28 @@ export class TerminalServiceExtImpl implements TerminalServiceExt {
return [...this._terminals.values()];
}

createTerminal(nameOrOptions: TerminalOptions | (string | undefined), shellPath?: string, shellArgs?: string[]): Terminal {
createTerminal(nameOrOptions: TerminalOptions | PseudoTerminalOptions | (string | undefined), shellPath?: string, shellArgs?: string[]): Terminal {
let options: TerminalOptions;
let pseudoTerminal: theia.Pseudoterminal | undefined = undefined;
const id = `plugin-terminal-${UUID.uuid4()}`;
if (typeof nameOrOptions === 'object') {
options = nameOrOptions;
if ('pty' in nameOrOptions) {
pseudoTerminal = nameOrOptions.pty;
options = {
name: nameOrOptions.name,
};
this._pseudoTerminals.set(id, new PseudoTerminal(id, this.proxy, pseudoTerminal));
} else {
options = nameOrOptions;
}
} else {
options = {
name: nameOrOptions,
shellPath: shellPath,
shellArgs: shellArgs
};
}
const id = `plugin-terminal-${UUID.uuid4()}`;
this.proxy.$createTerminal(id, options);
this.proxy.$createTerminal(id, options, !!pseudoTerminal);
return this.obtainTerminal(id, options.name || 'Terminal');
}

Expand All @@ -74,6 +85,22 @@ export class TerminalServiceExtImpl implements TerminalServiceExt {
return terminal;
}

$terminalOnInput(id: string, data: string): void {
const terminal = this._pseudoTerminals.get(id);
if (!terminal) {
return;
}
terminal.emitOnInput(data);
}

$terminalSizeChanged(id: string, clos: number, rows: number): void {
const terminal = this._pseudoTerminals.get(id);
if (!terminal) {
return;
}
terminal.emitOnResize(clos, rows);
}

$terminalCreated(id: string, name: string): void {
const terminal = this.obtainTerminal(id, name);
terminal.id.resolve(id);
Expand All @@ -87,7 +114,7 @@ export class TerminalServiceExtImpl implements TerminalServiceExt {
}
}

$terminalOpened(id: string, processId: number): void {
$terminalOpened(id: string, processId: number, cols: number, rows: number): void {
const terminal = this._terminals.get(id);
if (terminal) {
// resolve for existing clients
Expand All @@ -96,6 +123,10 @@ export class TerminalServiceExtImpl implements TerminalServiceExt {
terminal.deferredProcessId = new Deferred<number>();
terminal.deferredProcessId.resolve(processId);
}
const pseudoTerminal = this._pseudoTerminals.get(id);
if (pseudoTerminal) {
pseudoTerminal.emitOnOpen(cols, rows);
}
}

$terminalClosed(id: string): void {
Expand All @@ -104,6 +135,11 @@ export class TerminalServiceExtImpl implements TerminalServiceExt {
this.onDidCloseTerminalEmitter.fire(terminal);
this._terminals.delete(id);
}
const pseudoTerminal = this._pseudoTerminals.get(id);
if (pseudoTerminal) {
pseudoTerminal.emitOnClose();
this._pseudoTerminals.delete(id);
}
}

private activeTerminalId: string | undefined;
Expand Down Expand Up @@ -147,3 +183,50 @@ export class TerminalExtImpl implements Terminal {
}

}

export class PseudoTerminal {
constructor(
id: string,
private readonly proxy: TerminalServiceMain,
private readonly pseudoTerminal: theia.Pseudoterminal
) {
pseudoTerminal.onDidWrite(data => {
this.proxy.$write(id, data);
});
if (pseudoTerminal.onDidClose) {
pseudoTerminal.onDidClose(() => {
this.proxy.$dispose(id);
});
}
if (pseudoTerminal.onDidOverrideDimensions) {
pseudoTerminal.onDidOverrideDimensions(e => {
if (e) {
this.proxy.$resize(id, e.columns, e.rows);
}
});
}
}

emitOnClose(): void {
this.pseudoTerminal.close();
}

emitOnInput(data: string): void {
if (this.pseudoTerminal.handleInput) {
this.pseudoTerminal.handleInput(data);
}
}

emitOnOpen(cols: number, rows: number): void {
this.pseudoTerminal.open({
rows,
columns: cols,
});
}

emitOnResize(cols: number, rows: number): void {
if (this.pseudoTerminal.setDimensions) {
this.pseudoTerminal.setDimensions({ columns: cols, rows });
}
}
}
86 changes: 86 additions & 0 deletions packages/plugin/src/theia.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2629,6 +2629,83 @@ declare module '@theia/plugin' {
attributes?: { [key: string]: string | null };
}

/**
* The dimensions of a terminal.
*/
export interface TerminalDimensions {
/**
* The number of columns of the terminal.
*/
readonly columns: number;

/**
* The number of rows of the terminal.
*/
readonly rows: number;
}

/**
* Options a virtual process terminal.
*/
export interface PseudoTerminalOptions {
/**
* The name of the terminal.
*/
name: string;

/**
* An implementation of [Pseudoterminal](#Pseudoterminal) where an extension can
* control it.
*/
pty: Pseudoterminal;
}

/**
* Defines the interface of a terminal pty, enabling extensions to control a terminal.
*/
interface Pseudoterminal {
/**
* An event that when fired will write data to the terminal.
*/
onDidWrite: Event<string>;

/**
* An event that when fired allows resizing the terminal.
*/
onDidOverrideDimensions?: Event<TerminalDimensions | undefined>;

/**
* An event that when fired will close the pty.
*/
onDidClose?: Event<void | number>;

/**
* Implement to handle when the pty is opened.
*
* @param dimensions The dimensions of the terminal.
*/
open(dimensions: TerminalDimensions | undefined): void;

/**
* Implement to handle when the terminal is closed.
*/
close(): void;

/**
* Implement to handle inputing data in the terminal.
*
* @param data The inputing data.
*/
handleInput?(data: string): void;

/**
* Implement to handle when the number of rows and columns changes.
*
* @param dimensions The new dimensions.
*/
setDimensions?(dimensions: TerminalDimensions): void;
}

/**
* A plug-in context is a collection of utilities private to a
* plug-in.
Expand Down Expand Up @@ -3510,6 +3587,15 @@ declare module '@theia/plugin' {
*/
export function createTerminal(options: TerminalOptions): Terminal;

/**
* Creates a pseudo where an extension controls its input and output.
*
* @param options PseudoTerminalOptions.
* @return A new Terminal.
*/
export function createTerminal(options: PseudoTerminalOptions): Terminal;


/**
* Register a [TreeDataProvider](#TreeDataProvider) for the view contributed using the extension point `views`.
* This will allow you to contribute data to the [TreeView](#TreeView) and update if the data changes.
Expand Down
Loading

0 comments on commit 3079416

Please sign in to comment.