Skip to content

Commit

Permalink
Implement CustomExecution extension API.
Browse files Browse the repository at this point in the history
issue: eclipse-theia#7185, eclipse-theia#8767

Signed-off-by: Lewin Tan <e_tanhaiyang@163.com>
  • Loading branch information
谭海洋 authored and shyshywhy committed Mar 14, 2021
1 parent 0dbc7ef commit 1dcf079
Show file tree
Hide file tree
Showing 19 changed files with 549 additions and 38 deletions.
60 changes: 52 additions & 8 deletions packages/plugin-ext/src/common/plugin-api-rpc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -248,7 +248,7 @@ export interface CommandRegistryExt {
export interface TerminalServiceExt {
$terminalCreated(id: string, name: string): void;
$terminalNameChanged(id: string, name: string): void;
$terminalOpened(id: string, processId: number, cols: number, rows: number): void;
$terminalOpened(id: string, processId: number, terminalId: number, cols: number, rows: number): void;
$terminalClosed(id: string): void;
$terminalOnInput(id: string, data: string): void;
$terminalSizeChanged(id: string, cols: number, rows: number): void;
Expand Down Expand Up @@ -283,46 +283,89 @@ export interface TerminalServiceMain {

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

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

/**
* Resize the terminal by id.
* @param id - terminal id.
* @param id - terminal widget 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.
* @param id - terminal widget id.
* @param preserveFocus - set terminal focus in case true value, and don't set focus otherwise.
*/
$show(id: string, preserveFocus?: boolean): void;

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

/**
* Destroy terminal.
* @param id - terminal id.
* @param id - terminal widget id.
*/
$dispose(id: string): void;

/**
* 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.
*/
$sendTextByTerminalId(id: number, text: string, addNewLine?: boolean): void;

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

/**
* Resize the terminal by id.
* @param id - terminal id.
* @param cols - columns.
* @param rows - rows.
*/
$resizeByTerminalId(id: number, cols: number, rows: number): 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.
*/
$showByTerminalId(id: number, preserveFocus?: boolean): void;

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

/**
* Destroy terminal.
* @param id - terminal id.
* @param waitOnExit - Whether to wait for a key press before closing the terminal.
*/
$disposeByTerminalId(id: number, waitOnExit?: boolean | string): void;

$setEnvironmentVariableCollection(extensionIdentifier: string, persistent: boolean, collection: SerializableEnvironmentVariableCollection | undefined): void;
}

Expand Down Expand Up @@ -1664,7 +1707,7 @@ export const MAIN_RPC_CONTEXT = {
export interface TasksExt {
$provideTasks(handle: number, token?: CancellationToken): Promise<TaskDto[] | undefined>;
$resolveTask(handle: number, task: TaskDto, token?: CancellationToken): Promise<TaskDto | undefined>;
$onDidStartTask(execution: TaskExecutionDto): void;
$onDidStartTask(execution: TaskExecutionDto, terminalId: number, resolvedDefinition: theia.TaskDefinition): void;
$onDidEndTask(id: number): void;
$onDidStartTaskProcess(processId: number | undefined, execution: TaskExecutionDto): void;
$onDidEndTaskProcess(exitCode: number | undefined, taskId: number): void;
Expand All @@ -1677,6 +1720,7 @@ export interface TasksMain {
$taskExecutions(): Promise<TaskExecutionDto[]>;
$unregister(handle: number): void;
$terminateTask(id: number): void;
$customExecutionComplete(id: number, exitCode: number | undefined): void;
}

export interface AuthenticationExt {
Expand Down
16 changes: 15 additions & 1 deletion packages/plugin-ext/src/main/browser/tasks-main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import { TaskInfo, TaskExitedEvent, TaskConfiguration, TaskCustomization } from
import { TaskWatcher } from '@theia/task/lib/common/task-watcher';
import { TaskService } from '@theia/task/lib/browser/task-service';
import { TaskDefinitionRegistry } from '@theia/task/lib/browser';
import * as theia from '@theia/plugin';

export class TasksMainImpl implements TasksMain, Disposable {
private readonly proxy: TasksExt;
Expand All @@ -50,10 +51,19 @@ export class TasksMainImpl implements TasksMain, Disposable {
this.taskDefinitionRegistry = container.get(TaskDefinitionRegistry);

this.toDispose.push(this.taskWatcher.onTaskCreated((event: TaskInfo) => {
const taskDefinition = {
type: event.config.type
} as theia.TaskDefinition;
const { type, ...properties } = event.config;
for (const key in properties) {
if (properties.hasOwnProperty(key)) {
taskDefinition[key] = properties[key];
}
}
this.proxy.$onDidStartTask({
id: event.taskId,
task: this.fromTaskConfiguration(event.config)
});
}, event.terminalId!!, taskDefinition);
}));

this.toDispose.push(this.taskWatcher.onTaskExit((event: TaskExitedEvent) => {
Expand Down Expand Up @@ -154,6 +164,10 @@ export class TasksMainImpl implements TasksMain, Disposable {
this.taskService.kill(id);
}

async $customExecutionComplete(id: number, exitCode: number | undefined): Promise<void> {
this.taskService.customExecutionComplete(id, exitCode);
}

protected createTaskProvider(handle: number): TaskProvider {
return {
provideTasks: () =>
Expand Down
56 changes: 55 additions & 1 deletion packages/plugin-ext/src/main/browser/terminal-main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ 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, terminal.dimensions.cols, terminal.dimensions.rows),
processId => this.extProxy.$terminalOpened(terminal.id, processId, terminal.terminalId, terminal.dimensions.cols, terminal.dimensions.rows),
() => {/* no-op */ }
);
updateProcessId();
Expand Down Expand Up @@ -174,4 +174,58 @@ export class TerminalServiceMainImpl implements TerminalServiceMain, Disposable
terminal.dispose();
}
}

$sendTextByTerminalId(id: number, text: string, addNewLine?: boolean): void {
const terminal = this.terminals.getByTerminalId(id);
if (terminal) {
text = text.replace(/\r?\n/g, '\r');
if (addNewLine && text.charAt(text.length - 1) !== '\r') {
text += '\r';
}
terminal.sendText(text);
}
}
$writeByTerminalId(id: number, data: string): void {
const terminal = this.terminals.getByTerminalId(id);
if (!terminal) {
return;
}
terminal.write(data);
}
$resizeByTerminalId(id: number, cols: number, rows: number): void {
const terminal = this.terminals.getByTerminalId(id);
if (!terminal) {
return;
}
terminal.resize(cols, rows);
}
$showByTerminalId(id: number, preserveFocus?: boolean): void {
const terminal = this.terminals.getByTerminalId(id);
if (terminal) {
const options: WidgetOpenerOptions = {};
if (preserveFocus) {
options.mode = 'reveal';
}
this.terminals.open(terminal, options);
}
}
$hideByTerminalId(id: number): void {
const terminal = this.terminals.getByTerminalId(id);
if (terminal && terminal.isVisible) {
const area = this.shell.getAreaFor(terminal);
if (area) {
this.shell.collapsePanel(area);
}
}
}
$disposeByTerminalId(id: number, waitOnExit?: boolean | string): void {
const terminal = this.terminals.getByTerminalId(id);
if (terminal) {
if (waitOnExit) {
terminal.waitOnExit(waitOnExit);
return;
}
terminal.dispose();
}
}
}
4 changes: 3 additions & 1 deletion packages/plugin-ext/src/plugin/plugin-context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ import {
ShellQuoting,
ShellExecution,
ProcessExecution,
CustomExecution,
TaskScope,
TaskPanelKind,
TaskRevealKind,
Expand Down Expand Up @@ -193,7 +194,7 @@ export function createAPIFactory(
const outputChannelRegistryExt = rpc.set(MAIN_RPC_CONTEXT.OUTPUT_CHANNEL_REGISTRY_EXT, new OutputChannelRegistryExtImpl(rpc));
const languagesExt = rpc.set(MAIN_RPC_CONTEXT.LANGUAGES_EXT, new LanguagesExtImpl(rpc, documents, commandRegistry));
const treeViewsExt = rpc.set(MAIN_RPC_CONTEXT.TREE_VIEWS_EXT, new TreeViewsExtImpl(rpc, commandRegistry));
const tasksExt = rpc.set(MAIN_RPC_CONTEXT.TASKS_EXT, new TasksExtImpl(rpc));
const tasksExt = rpc.set(MAIN_RPC_CONTEXT.TASKS_EXT, new TasksExtImpl(rpc, terminalExt));
const connectionExt = rpc.set(MAIN_RPC_CONTEXT.CONNECTION_EXT, new ConnectionExtImpl(rpc));
const fileSystemExt = rpc.set(MAIN_RPC_CONTEXT.FILE_SYSTEM_EXT, new FileSystemExtImpl(rpc, languagesExt));
const extHostFileSystemEvent = rpc.set(MAIN_RPC_CONTEXT.ExtHostFileSystemEventService, new ExtHostFileSystemEventService(rpc, editorsAndDocumentsExt));
Expand Down Expand Up @@ -909,6 +910,7 @@ export function createAPIFactory(
ShellQuoting,
ShellExecution,
ProcessExecution,
CustomExecution,
TaskScope,
TaskRevealKind,
TaskPanelKind,
Expand Down
47 changes: 43 additions & 4 deletions packages/plugin-ext/src/plugin/tasks/tasks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,17 +23,20 @@ import {
} from '../../common/plugin-api-rpc';
import * as theia from '@theia/plugin';
import * as converter from '../type-converters';
import { Disposable } from '../types-impl';
import { CustomExecution, Disposable } from '../types-impl';
import { RPCProtocol, ConnectionClosedError } from '../../common/rpc-protocol';
import { TaskProviderAdapter } from './task-provider';
import { Emitter, Event } from '@theia/core/lib/common/event';
import { TerminalServiceExtImpl } from '../terminal-ext';

export class TasksExtImpl implements TasksExt {
private proxy: TasksMain;

private callId = 0;
private adaptersMap = new Map<number, TaskProviderAdapter>();
private executions = new Map<number, theia.TaskExecution>();
protected providedCustomExecutions: Map<string, CustomExecution>;
protected activeCustomExecutions: Map<number, CustomExecution>;

private readonly onDidExecuteTask: Emitter<theia.TaskStartEvent> = new Emitter<theia.TaskStartEvent>();
private readonly onDidTerminateTask: Emitter<theia.TaskEndEvent> = new Emitter<theia.TaskEndEvent>();
Expand All @@ -42,8 +45,10 @@ export class TasksExtImpl implements TasksExt {

private disposed = false;

constructor(rpc: RPCProtocol) {
constructor(rpc: RPCProtocol, readonly terminalExt: TerminalServiceExtImpl) {
this.proxy = rpc.getProxy(PLUGIN_RPC_CONTEXT.TASKS_MAIN);
this.providedCustomExecutions = new Map<string, CustomExecution>();
this.activeCustomExecutions = new Map<number, CustomExecution>();
this.fetchTaskExecutions();
}

Expand All @@ -59,7 +64,24 @@ export class TasksExtImpl implements TasksExt {
return this.onDidExecuteTask.event;
}

$onDidStartTask(execution: TaskExecutionDto): void {
async $onDidStartTask(execution: TaskExecutionDto, terminalId: number, resolvedDefinition: theia.TaskDefinition): Promise<void> {
const customExecution: CustomExecution | undefined = this.providedCustomExecutions.get(execution.task.id);
if (customExecution) {
if (this.activeCustomExecutions.get(execution.id) !== undefined) {
throw new Error('We should not be trying to start the same custom task executions twice.');
}

// Clone the custom execution to keep the original untouched. This is important for multiple runs of the same task.
this.activeCustomExecutions.set(execution.id, customExecution);
const pty = await customExecution.callback(resolvedDefinition);
this.terminalExt.attachPtyToTerminal(terminalId, pty);
if (pty.onDidClose) {
pty.onDidClose((e: number | void = undefined) => {
// eslint-disable-next-line no-void
this.proxy.$customExecutionComplete(execution.id, e === void 0 ? undefined : e);
});
}
}
this.onDidExecuteTask.fire({
execution: this.getTaskExecution(execution)
});
Expand All @@ -76,6 +98,7 @@ export class TasksExtImpl implements TasksExt {
}

this.executions.delete(id);
this.customExecutionComplete(id);

this.onDidTerminateTask.fire({
execution: taskExecution
Expand Down Expand Up @@ -138,7 +161,16 @@ export class TasksExtImpl implements TasksExt {
$provideTasks(handle: number, token: theia.CancellationToken): Promise<TaskDto[] | undefined> {
const adapter = this.adaptersMap.get(handle);
if (adapter) {
return adapter.provideTasks(token);
return adapter.provideTasks(token).then(tasks => {
if (tasks) {
for (const task of tasks) {
if (task.type === 'customExecution') {
this.providedCustomExecutions.set(task.id, new CustomExecution(task.callback));
}
}
}
return tasks;
});
} else {
return Promise.reject(new Error('No adapter found to provide tasks'));
}
Expand Down Expand Up @@ -198,4 +230,11 @@ export class TasksExtImpl implements TasksExt {
this.executions.set(executionId, result);
return result;
}

private customExecutionComplete(id: number): void {
const extensionCallback2: CustomExecution | undefined = this.activeCustomExecutions.get(id);
if (extensionCallback2) {
this.activeCustomExecutions.delete(id);
}
}
}
Loading

0 comments on commit 1dcf079

Please sign in to comment.