Skip to content

Commit

Permalink
[plugin] fix #4024 support terminal APIs on window
Browse files Browse the repository at this point in the history
Signed-off-by: Anton Kosyakov <anton.kosyakov@typefox.io>
  • Loading branch information
akosyakov committed Jan 14, 2019
1 parent 66fb68c commit 45d806d
Show file tree
Hide file tree
Showing 11 changed files with 326 additions and 122 deletions.
16 changes: 10 additions & 6 deletions packages/plugin-ext/src/api/plugin-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,11 @@ export interface CommandRegistryExt {
}

export interface TerminalServiceExt {
$terminalClosed(id: number): void;
$terminalCreated(id: string, name: string): void;
$terminalNameChanged(id: string, name: string): void;
$terminalOpened(id: string, processId: number): void;
$terminalClosed(id: string): void;
$currentTerminalChanged(id: string | undefined): void;
}

export interface ConnectionMain {
Expand All @@ -183,34 +187,34 @@ export interface TerminalServiceMain {
* Create new Terminal with Terminal options.
* @param options - object with parameters to create new terminal.
*/
$createTerminal(options: theia.TerminalOptions): PromiseLike<number>;
$createTerminal(id: string, options: theia.TerminalOptions): Promise<string>;

/**
* Send text to the terminal by id.
* @param id - terminal id.
* @param text - text content.
* @param addNewLine - in case true - add new line after the text, otherwise - don't apply new line.
*/
$sendText(id: number, text: string, addNewLine?: boolean): void;
$sendText(id: string, text: string, addNewLine?: boolean): void;

/**
* Show terminal on the UI panel.
* @param id - terminal id.
* @param preserveFocus - set terminal focus in case true value, and don't set focus otherwise.
*/
$show(id: number, preserveFocus?: boolean): void;
$show(id: string, preserveFocus?: boolean): void;

/**
* Hide UI panel where is located terminal widget.
* @param id - terminal id.
*/
$hide(id: number): void;
$hide(id: string): void;

/**
* Distroy terminal.
* @param id - terminal id.
*/
$dispose(id: number): void;
$dispose(id: string): void;
}

export interface AutoFocus {
Expand Down
119 changes: 70 additions & 49 deletions packages/plugin-ext/src/main/browser/terminal-main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,93 +14,114 @@
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
********************************************************************************/

import { TerminalOptions } from '@theia/plugin';
import { TerminalServiceMain, TerminalServiceExt, MAIN_RPC_CONTEXT } from '../../api/plugin-api';
import { interfaces } from 'inversify';
import { ApplicationShell, WidgetOpenerOptions } from '@theia/core/lib/browser';
import { TerminalOptions } from '@theia/plugin';
import { TerminalWidget } from '@theia/terminal/lib/browser/base/terminal-widget';
import { TerminalService } from '@theia/terminal/lib/browser/base/terminal-service';
import { TerminalWidget, TerminalWidgetOptions } from '@theia/terminal/lib/browser/base/terminal-widget';
import { TerminalServiceMain, TerminalServiceExt, MAIN_RPC_CONTEXT } from '../../api/plugin-api';
import { RPCProtocol } from '../../api/rpc-protocol';
import { ApplicationShell } from '@theia/core/lib/browser';

/**
* Plugin api service allows working with terminal emulator.
*/
export class TerminalServiceMainImpl implements TerminalServiceMain {

private readonly terminalService: TerminalService;
private readonly terminals: TerminalService;
private readonly shell: ApplicationShell;
protected readonly terminals = new Map<number, TerminalWidget>();
private readonly extProxy: TerminalServiceExt;
private terminalNumber = 0;
private readonly TERM_ID_PREFIX = 'plugin-terminal-';

constructor(rpc: RPCProtocol, container: interfaces.Container) {
this.terminalService = container.get(TerminalService);
this.terminals = container.get(TerminalService);
this.shell = container.get(ApplicationShell);
this.extProxy = rpc.getProxy(MAIN_RPC_CONTEXT.TERMINAL_EXT);
this.terminals.onDidCreateTerminal(terminal => this.trackTerminal(terminal));
for (const terminal of this.terminals.all) {
this.trackTerminal(terminal);
}
this.terminals.onDidChangeCurrentTerminal(() => this.updateCurrentTerminal());
this.updateCurrentTerminal();
}

async $createTerminal(options: TerminalOptions): Promise<number> {
const counter = this.terminalNumber++;
const termWidgetOptions: TerminalWidgetOptions = {
title: options.name,
shellPath: options.shellPath,
shellArgs: options.shellArgs,
cwd: options.cwd,
env: options.env,
destroyTermOnClose: true,
useServerTitle: false,
id: this.TERM_ID_PREFIX + counter,
attributes: options.attributes
};
let id: number;
protected updateCurrentTerminal(): void {
const { currentTerminal } = this.terminals;
this.extProxy.$currentTerminalChanged(currentTerminal && currentTerminal.id);
}

protected async trackTerminal(terminal: TerminalWidget): Promise<void> {
let name = terminal.title.label;
this.extProxy.$terminalCreated(terminal.id, name);
terminal.title.changed.connect(() => {
if (name !== terminal.title.label) {
name = terminal.title.label;
this.extProxy.$terminalNameChanged(terminal.id, name);
}
});
const updateProcessId = () => terminal.processId.then(
processId => this.extProxy.$terminalOpened(terminal.id, processId),
() => {/*no-op*/ }
);
updateProcessId();
terminal.onDidOpen(() => updateProcessId());
terminal.onTerminalDidClose(() => this.extProxy.$terminalClosed(terminal.id));
}

async $createTerminal(id: string, options: TerminalOptions): Promise<string> {
try {
const termWidget = await this.terminalService.newTerminal(termWidgetOptions);
id = await termWidget.start();
this.terminals.set(id, termWidget);
termWidget.onTerminalDidClose(() => {
this.extProxy.$terminalClosed(id);
const terminal = await this.terminals.newTerminal({
id,
title: options.name,
shellPath: options.shellPath,
shellArgs: options.shellArgs,
cwd: options.cwd,
env: options.env,
destroyTermOnClose: true,
useServerTitle: false,
attributes: options.attributes
});
terminal.start();
return terminal.id;
} catch (error) {
throw new Error('Failed to create terminal. Cause: ' + error);
}
return id;
}

$sendText(id: number, text: string, addNewLine?: boolean): void {
const termWidget = this.terminals.get(id);
if (termWidget) {
$sendText(id: string, text: string, addNewLine?: boolean): void {
const terminal = this.terminals.getById(id);
if (terminal) {
text = text.replace(/\r?\n/g, '\r');
if (addNewLine && text.charAt(text.length - 1) !== '\r') {
text += '\r';
}
termWidget.sendText(text);
terminal.sendText(text);
}
}

$show(id: number, preserveFocus?: boolean): void {
const termWidget = this.terminals.get(id);
if (termWidget) {
this.terminalService.activateTerminal(termWidget);
$show(id: string, preserveFocus?: boolean): void {
const terminal = this.terminals.getById(id);
if (terminal) {
const options: WidgetOpenerOptions = {};
if (preserveFocus) {
options.mode = 'reveal';
}
this.terminals.open(terminal, options);
}
}

$hide(id: number): void {
const termWidget = this.terminals.get(id);
if (termWidget) {
if (termWidget.isVisible) {
const area = this.shell.getAreaFor(termWidget);
if (area) {
this.shell.collapsePanel(area);
}
$hide(id: string): void {
const terminal = this.terminals.getById(id);
if (terminal && terminal.isVisible) {
const area = this.shell.getAreaFor(terminal);
if (area) {
this.shell.collapsePanel(area);
}
}
}

$dispose(id: number): void {
const termWidget = this.terminals.get(id);
if (termWidget) {
termWidget.dispose();
$dispose(id: string): void {
const terminal = this.terminals.getById(id);
if (terminal) {
terminal.dispose();
}
}
}
16 changes: 10 additions & 6 deletions packages/plugin-ext/src/plugin/plugin-context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -166,13 +166,21 @@ export function createAPIFactory(
}
};

const { onDidChangeActiveTerminal, onDidCloseTerminal, onDidOpenTerminal } = terminalExt;
const window: typeof theia.window = {
get activeTerminal() {
return terminalExt.activeTerminal;
},
get activeTextEditor() {
return editors.getActiveEditor();
},
get visibleTextEditors() {
return editors.getVisibleTextEditors();
},
get terminals() {
return terminalExt.terminals;
},
onDidChangeActiveTerminal,
onDidChangeActiveTextEditor(listener, thisArg?, disposables?) {
return editors.onDidChangeActiveTextEditor(listener, thisArg, disposables);
},
Expand Down Expand Up @@ -293,12 +301,8 @@ export function createAPIFactory(
createTerminal(nameOrOptions: theia.TerminalOptions | (string | undefined), shellPath?: string, shellArgs?: string[]): theia.Terminal {
return terminalExt.createTerminal(nameOrOptions, shellPath, shellArgs);
},
get onDidCloseTerminal(): theia.Event<theia.Terminal> {
return terminalExt.onDidCloseTerminal;
},
set onDidCloseTerminal(event: theia.Event<theia.Terminal>) {
terminalExt.onDidCloseTerminal = event;
},
onDidCloseTerminal,
onDidOpenTerminal,
createTextEditorDecorationType(options: theia.DecorationRenderOptions): theia.TextEditorDecorationType {
return editors.createTextEditorDecorationType(options);
},
Expand Down
Loading

0 comments on commit 45d806d

Please sign in to comment.