Skip to content

Commit

Permalink
Implement "headless plugins" in new host outside of frontend connections
Browse files Browse the repository at this point in the history
- define a 'theiaHeadlessPlugin' engine to identify headless plugins
  that support only the headless plugin host.
  Ordinary backend plugins that also run in the headless host can add
  a 'headless' entry-point in the 'theiaPlugin' object in package.json
- update the Theia application webpack generator to handle packing the
  headless-plugin API init script
- implement a headless entry-point in the PluginModel as a peer to
  frontend and backend entry-points
- factor out common plugin manager behaviour into an abstract class
  - define distinct plugin manager (vscode/theia) and headless plugin
    manager implementations
  - similiarly for the HostedPluginSupport
- define the minimal TerminalExt/Main APIs for access to the default
  shell to support the 'env.shell' API
- implement nested Inversify container for headless plugins to isolate
  them and their plugin host from the connection-scoped plugins
- add examples for headless plugin
  - add a "Greeting of the Day" example custom API provider
  - add a plugin-gotd example headless plugin that uses the custom API
    example to greet the world on activation and also illustrates
    simple vscode API usage

Support headless entrypoint in VSCode plugins

- support the headless entrypoint in an otherwise VS Code plugin
- prefer the VS Code names for start/stop functions
- update the example plugin to use be a dual VSCode/headless plugin
  using the vscode API in the usual backend entrypoint

Support distinct and application-specific headless activation events

- extend the PluginPackage interface to add a "headless" property
  initially defining only an optional "activationEvents" string array
  property for headless mode. The idea being that eventually other
  things like "contributions" might also be defined, here
- support application injection of activation events that it will
  trigger in its plugins via the HeadlessHostedPluginSupport class's
  activateByEvent(activationEvent: string) API
- adapt the example GotD plugin's package manifest to use the new
  "headless" property

Differentiate provision of ext APIs to headless and backend plugin hosts

- define a distinct headless ext API initialization function protocol
  and headless API initialization script path to target the headless
  plugin host specifically in the ext API provider
- refactor the initialization of ext APIs in the plugin host to make use
  of this new distinction
- update the Greeting-of-the-Day example API provider to support both
  headless and backend plugins
- define the index for the common headless-plugin-ext APIs
- fix up other minor details

Signed-off-by: Christian W. Damus <cdamus.ext@eclipsesource.com>
  • Loading branch information
cdamus committed Jan 3, 2024
1 parent b18be89 commit dab5684
Show file tree
Hide file tree
Showing 68 changed files with 3,458 additions and 505 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@

- [Previous Changelogs](https://github.com/eclipse-theia/theia/tree/master/doc/changelogs/)

## v1.46.0

- [plugin] added support for Headless Plugins, hosted independently of any frontend connection [#13138](https://github.com/eclipse-theia/theia/pull/13138)

## v1.45.0 - 12/21/2023

- [application-manager] updated logic to allow rebinding messaging services in preload [#13199](https://github.com/eclipse-theia/theia/pull/13199)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -375,6 +375,7 @@ for (const [entryPointName, entryPointPath] of Object.entries({
${this.ifPackage('@theia/plugin-ext', "'backend-init-theia': '@theia/plugin-ext/lib/hosted/node/scanners/backend-init-theia',")}
${this.ifPackage('@theia/filesystem', "'nsfw-watcher': '@theia/filesystem/lib/node/nsfw-watcher',")}
${this.ifPackage('@theia/plugin-ext-vscode', "'plugin-vscode-init': '@theia/plugin-ext-vscode/lib/node/plugin-vscode-init',")}
${this.ifPackage('@theia/api-provider-sample', "'gotd-api-init': '@theia/api-provider-sample/lib/plugin/gotd-api-init',")}
})) {
commonJsLibraries[entryPointName] = {
import: require.resolve(entryPointPath),
Expand Down Expand Up @@ -426,6 +427,8 @@ const config = {
'ipc-bootstrap': require.resolve('@theia/core/lib/node/messaging/ipc-bootstrap'),
${this.ifPackage('@theia/plugin-ext', () => `// VS Code extension support:
'plugin-host': require.resolve('@theia/plugin-ext/lib/hosted/node/plugin-host'),`)}
${this.ifPackage('@theia/headless-plugin-ext', () => `// Theia Headless Plugin support:
'headless-plugin-host': require.resolve('@theia/headless-plugin-ext/lib/hosted/node/headless-plugin-host'),`)}
${this.ifPackage('@theia/process', () => `// Make sure the node-pty thread worker can be executed:
'worker/conoutSocketWorker': require.resolve('node-pty/lib/worker/conoutSocketWorker'),`)}
${this.ifPackage('@theia/git', () => `// Ensure the git locator process can the started
Expand Down
10 changes: 10 additions & 0 deletions examples/api-provider-sample/.eslintrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/** @type {import('eslint').Linter.Config} */
module.exports = {
extends: [
'../../configs/build.eslintrc.json'
],
parserOptions: {
tsconfigRootDir: __dirname,
project: 'tsconfig.json'
}
};
48 changes: 48 additions & 0 deletions examples/api-provider-sample/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
<div align='center'>

<br />

<img src='https://raw.githubusercontent.com/eclipse-theia/theia/master/logo/theia.svg?sanitize=true' alt='theia-ext-logo' width='100px' />

<h2>ECLIPSE THEIA - API PROVIDER SAMPLE</h2>

<hr />

</div>

## Description

The `@theia/api-provider-sample` extension is a programming example showing how to define and provide a custom API object for _plugins_ to use.
The purpose of the extension is to:
- provide developers with realistic coding examples of providing custom API objects
- provide easy-to-use and test examples for features when reviewing pull requests

The extension is for reference and test purposes only and is not published on `npm` (`private: true`).

### Greeting of the Day

The sample defines a `gotd` API that plugins can import and use to obtain tailored messages with which to greet the world, for example in their activation function.

The source code is laid out in the `src/` tree as follows:

- `gotd.d.ts` — the TypeScript definition of the `gotd` API object that plugins import to interact with the "Greeting of the Day" service
- `plugin/` — the API initialization script and the implementation of the API objects (`GreetingExt` and similar interfaces).
All code in this directory runs exclusively in the separate plugin-host Node process, isolated from the main Theia process, together with either headless plugins or the backend of VS Code plugins.
The `GreetingExtImpl` and similar classes communicate with the actual API implementation (`GreetingMainImpl` etc.) classes in the main Theia process via RPC
- `node/` — the API classes implementing `GreetingMain` and similar interfaces and the Inversify bindings that register the API provider.
All code in this directory runs in the main Theia Node process
- `common/` — the RPC API Ext/Main interface definitions corresponding to the backend of the `gotd` plugin API

## Additional Information

- [Theia - GitHub](https://github.com/eclipse-theia/theia)
- [Theia - Website](https://theia-ide.org/)

## License

- [Eclipse Public License 2.0](http://www.eclipse.org/legal/epl-2.0/)
- [一 (Secondary) GNU General Public License, version 2 with the GNU Classpath Exception](https://projects.eclipse.org/license/secondary-gpl-2.0-cp)

## Trademark
"Theia" is a trademark of the Eclipse Foundation
https://www.eclipse.org/theia
42 changes: 42 additions & 0 deletions examples/api-provider-sample/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
{
"private": true,
"name": "@theia/api-provider-sample",
"version": "1.45.0",
"description": "Theia - Example code to demonstrate Theia API Provider Extensions",
"dependencies": {
"@theia/core": "1.45.0",
"@theia/headless-plugin-ext": "1.45.0",
"@theia/plugin-ext": "1.45.0"
},
"theiaExtensions": [
{
"backend": "lib/node/gotd-backend-module"
}
],
"keywords": [
"theia-extension"
],
"license": "EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0",
"repository": {
"type": "git",
"url": "https://github.com/eclipse-theia/theia.git"
},
"bugs": {
"url": "https://github.com/eclipse-theia/theia/issues"
},
"homepage": "https://github.com/eclipse-theia/theia",
"files": [
"lib",
"src"
],
"types": "src/gotd.d.ts",
"scripts": {
"lint": "theiaext lint",
"build": "theiaext build",
"watch": "theiaext watch",
"clean": "theiaext clean"
},
"devDependencies": {
"@theia/ext-scripts": "1.45.0"
}
}
68 changes: 68 additions & 0 deletions examples/api-provider-sample/src/common/plugin-api-rpc.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
// *****************************************************************************
// Copyright (C) 2023 EclipseSource and others.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// http://www.eclipse.org/legal/epl-2.0.
//
// This Source Code may also be made available under the following Secondary
// Licenses when the conditions for such availability set forth in the Eclipse
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
// with the GNU Classpath Exception which is available at
// https://www.gnu.org/software/classpath/license.html.
//
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
// *****************************************************************************
import { createProxyIdentifier } from '@theia/plugin-ext/lib/common/rpc-protocol';
import type { greeting } from '../gotd';
import { Event } from '@theia/core';

export enum GreetingKind {
DIRECT = 1,
QUIRKY = 2,
SNARKY = 3,
}

export interface GreeterData {
readonly uuid: string;
greetingKinds: greeting.GreetingKind[];
};

export interface GreetingMain {
$getMessage(greeterId: string): Promise<string>;

$createGreeter(): Promise<GreeterData>;
$destroyGreeter(greeterId: GreeterData['uuid']): Promise<void>;

$updateGreeter(data: GreeterData): void;
}

export interface GreetingExt {

//
// External protocol
//

registerGreeter(): Promise<string>;
unregisterGreeter(uuid: string): Promise<void>;

getMessage(greeterId: string): Promise<string>;
getGreetingKinds(greeterId: string): readonly greeting.GreetingKind[];
setGreetingKindEnabled(greeterId: string, greetingKind: greeting.GreetingKind, enable: boolean): void;
onGreetingKindsChanged(greeterId: string): Event<readonly greeting.GreetingKind[]>;

//
// Internal protocol
//

$greeterUpdated(data: GreeterData): void;

}

export const PLUGIN_RPC_CONTEXT = {
GREETING_MAIN: createProxyIdentifier<GreetingMain>('GreetingMain'),
};

export const MAIN_RPC_CONTEXT = {
GREETING_EXT: createProxyIdentifier<GreetingExt>('GreetingExt'),
};
49 changes: 49 additions & 0 deletions examples/api-provider-sample/src/gotd.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// *****************************************************************************
// Copyright (C) 2023 EclipseSource and others.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// http://www.eclipse.org/legal/epl-2.0.
//
// This Source Code may also be made available under the following Secondary
// Licenses when the conditions for such availability set forth in the Eclipse
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
// with the GNU Classpath Exception which is available at
// https://www.gnu.org/software/classpath/license.html.
//
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
// *****************************************************************************

// Strictly speaking, the 'greeting' namespace is an unnecessary level of organization
// but it serves to illustrate how API namespaces are implemented in the backend.
export namespace greeting {
export function createGreeter(): Promise<greeting.Greeter>;

export enum GreetingKind {
DIRECT = 1,
QUIRKY = 2,
SNARKY = 3,
}

export interface Greeter extends Disposable {
greetingKinds: readonly GreetingKind[];

getMessage(): Promise<string>;

setGreetingKind(kind: GreetingKind, enable = true): void;

onGreetingKindsChanged: Event<readonly GreetingKind[]>;
}
}

export interface Event<T> {
(listener: (e: T) => unknown, thisArg?: unknown): Disposable;
}

export interface Disposable {
dispose(): void;
}

namespace Disposable {
export function create(func: () => void): Disposable;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// *****************************************************************************
// Copyright (C) 2023 EclipseSource and others.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// http://www.eclipse.org/legal/epl-2.0.
//
// This Source Code may also be made available under the following Secondary
// Licenses when the conditions for such availability set forth in the Eclipse
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
// with the GNU Classpath Exception which is available at
// https://www.gnu.org/software/classpath/license.html.
//
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
// *****************************************************************************
import * as path from 'path';
import { injectable } from '@theia/core/shared/inversify';
import { ExtPluginApi, ExtPluginApiProvider } from '@theia/headless-plugin-ext';

@injectable()
export class ExtPluginGotdApiProvider implements ExtPluginApiProvider {
provideApi(): ExtPluginApi {
// We can support both backend plugins and headless plugins
const universalInitPath = path.join(__dirname, '../backend/gotd-api-init');
return {
backendInitPath: universalInitPath,
headlessInitPath: universalInitPath
};
}
}
25 changes: 25 additions & 0 deletions examples/api-provider-sample/src/node/gotd-backend-module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// *****************************************************************************
// Copyright (C) 2023 EclipseSource and others.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// http://www.eclipse.org/legal/epl-2.0.
//
// This Source Code may also be made available under the following Secondary
// Licenses when the conditions for such availability set forth in the Eclipse
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
// with the GNU Classpath Exception which is available at
// https://www.gnu.org/software/classpath/license.html.
//
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
// *****************************************************************************
import { ContainerModule } from '@theia/core/shared/inversify';
import { ExtPluginApiProvider } from '@theia/plugin-ext';
import { ExtPluginGotdApiProvider } from './ext-plugin-gotd-api-provider';
import { MainPluginApiProvider } from '@theia/plugin-ext/lib/common/plugin-ext-api-contribution';
import { GotdMainPluginApiProvider } from './gotd-main-plugin-provider';

export default new ContainerModule(bind => {
bind(Symbol.for(ExtPluginApiProvider)).to(ExtPluginGotdApiProvider).inSingletonScope();
bind(MainPluginApiProvider).to(GotdMainPluginApiProvider).inSingletonScope();
});
28 changes: 28 additions & 0 deletions examples/api-provider-sample/src/node/gotd-main-plugin-provider.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// *****************************************************************************
// Copyright (C) 2023 EclipseSource and others.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// http://www.eclipse.org/legal/epl-2.0.
//
// This Source Code may also be made available under the following Secondary
// Licenses when the conditions for such availability set forth in the Eclipse
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
// with the GNU Classpath Exception which is available at
// https://www.gnu.org/software/classpath/license.html.
//
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
// *****************************************************************************
import { MainPluginApiProvider } from '@theia/plugin-ext/lib/common/plugin-ext-api-contribution';
import { RPCProtocol } from '@theia/plugin-ext/lib/common/rpc-protocol';
import { interfaces, injectable } from '@theia/core/shared/inversify';
import { PLUGIN_RPC_CONTEXT } from '../common/plugin-api-rpc';
import { GreetingMainImpl } from './greeting-main-impl';

@injectable()
export class GotdMainPluginApiProvider implements MainPluginApiProvider {
initialize(rpc: RPCProtocol, container: interfaces.Container): void {
const greetingMain = new GreetingMainImpl(rpc, container);
rpc.set(PLUGIN_RPC_CONTEXT.GREETING_MAIN, greetingMain);
}
}
Loading

0 comments on commit dab5684

Please sign in to comment.