merge(sdk): intègre feature/plugin-hosted-windows — runtime React partagé + docs restructurées (vert QA)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
466
README.md
466
README.md
@ -1,55 +1,25 @@
|
|||||||
# IdeA Plugin SDK
|
# IdeA Plugin SDK
|
||||||
|
|
||||||
Minimal public TypeScript SDK for IdeA plugins.
|
Public TypeScript SDK for IdeA plugins.
|
||||||
|
|
||||||
This first version intentionally stays small:
|
The SDK defines the stable manifest and runtime types used by plugins loaded by
|
||||||
|
IdeA. A plugin ships an `idea-plugin.json` manifest plus an ESM entrypoint that
|
||||||
|
exports `activate(ctx)`.
|
||||||
|
|
||||||
- public manifest types for `idea-plugin.json`;
|
## Start Here
|
||||||
- public runtime types for plugin modules exposing `activate(ctx)`;
|
|
||||||
- plugin-owned persistent storage through `ctx.storage`;
|
|
||||||
- a stable `ctx.services` facade for workspace, background task and terminal operations;
|
|
||||||
- public workspace file APIs for reading, writing, listing, stat and path resolution;
|
|
||||||
- a bounded generic project-structure query API;
|
|
||||||
- public command-task APIs for launching and tracking generic tools;
|
|
||||||
- public external-toolchain diagnostics for executables, env vars and files;
|
|
||||||
- public best-effort event subscriptions and workspace watch;
|
|
||||||
- public structured config-document helpers for JSON documents;
|
|
||||||
- a lightweight manifest validator;
|
|
||||||
- a minimal `examples/hello-plugin` plugin.
|
|
||||||
|
|
||||||
## Command And Feedback Contract
|
- [Manifest](docs/manifest.md): required fields, capabilities and contribution ids.
|
||||||
|
- [Activation And Context](docs/activation-context.md): `activate(ctx)`, lifecycle, logging, storage and subscriptions.
|
||||||
|
- [Menus](docs/menus.md): top-level menus, native menu insertion and command registration.
|
||||||
|
- [Commands And Feedback](docs/commands-and-feedback.md): command return values, skipped work and background tasks.
|
||||||
|
- [Layouts With React](docs/layouts-react.md): React/JSX/hooks layout authoring with host-provided React.
|
||||||
|
- [Windows](docs/windows.md): opening plugin layouts in detached OS windows.
|
||||||
|
- [Services](docs/services.md): workspace, tasks, tooling, events, config, terminal and windows facades.
|
||||||
|
- [Packaging And Distribution](docs/packaging-distribution.md): build output, archive layout and dependency rules.
|
||||||
|
|
||||||
Plugin menu actions follow one public contract:
|
The installable example lives in [`examples/hello-plugin`](examples/hello-plugin).
|
||||||
|
It demonstrates a menu command, storage, background-task feedback, a React layout
|
||||||
```text
|
component and `services.windows.open(...)`.
|
||||||
menu click -> registered command handler -> optional background task -> feedback surfaces
|
|
||||||
```
|
|
||||||
|
|
||||||
A menu item only names a command. The registered command handler owns all
|
|
||||||
precondition checks, task launch decisions and feedback. If a command
|
|
||||||
cannot or should not start work, return a small structured result such as
|
|
||||||
`{ status: "skipped", reason, message }` and log the same reason. That return
|
|
||||||
value is useful for programmatic callers, agents and future host surfaces; the
|
|
||||||
current human menu-click UI does not guarantee that handler return values are
|
|
||||||
shown to the user. Do not create a background task just to represent a skipped
|
|
||||||
command.
|
|
||||||
|
|
||||||
Use `ctx.services.tasks.runCommand()` only after required preconditions are
|
|
||||||
true: a focused project exists, the plugin has the `tooling` capability, required
|
|
||||||
executables/files/env are present, and a real `ownerAgentId` is available when
|
|
||||||
the result belongs to an agent workflow. `ownerAgentId` controls Work ownership,
|
|
||||||
cancellation and completion delivery; it must not be a placeholder in production
|
|
||||||
plugin code.
|
|
||||||
|
|
||||||
`recordOnly: true` records completion without waking the owner agent. It is not a
|
|
||||||
silent mode and it does not hide the task from surfaces that show Work state or
|
|
||||||
background-task events. If no task is launched, the baseline feedback surfaces
|
|
||||||
are the command result for programmatic callers and plugin logs; human-visible UI
|
|
||||||
feedback requires a host-supported surface, a real background task, or a
|
|
||||||
plugin-owned UI/file surface.
|
|
||||||
|
|
||||||
Canonical rules and examples live in
|
|
||||||
[`docs/commands-and-feedback.md`](docs/commands-and-feedback.md).
|
|
||||||
|
|
||||||
## Install
|
## Install
|
||||||
|
|
||||||
@ -63,415 +33,15 @@ npm install
|
|||||||
npm run build
|
npm run build
|
||||||
```
|
```
|
||||||
|
|
||||||
## Typecheck the example
|
## Validate The Example
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
npm run typecheck:examples
|
npm run typecheck:examples
|
||||||
```
|
|
||||||
|
|
||||||
## Build the installable hello plugin archive
|
|
||||||
|
|
||||||
```sh
|
|
||||||
npm run package:hello-plugin
|
npm run package:hello-plugin
|
||||||
```
|
```
|
||||||
|
|
||||||
The archive is written to:
|
The example archive is written to:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
examples/hello-plugin/build/hello-plugin-0.1.0.zip
|
examples/hello-plugin/build/hello-plugin-0.1.0.zip
|
||||||
```
|
```
|
||||||
|
|
||||||
Its ZIP root contains `idea-plugin.json` directly, with no wrapping parent directory. The
|
|
||||||
compiled ESM entrypoint is emitted at `dist/index.js`, matching the manifest `main` field,
|
|
||||||
alongside the other compiled files imported by that entrypoint.
|
|
||||||
|
|
||||||
## Plugin shape
|
|
||||||
|
|
||||||
An IdeA plugin ships an `idea-plugin.json` manifest and a JavaScript entrypoint built from
|
|
||||||
TypeScript.
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"ideaPluginManifestVersion": 1,
|
|
||||||
"id": "com.example.hello",
|
|
||||||
"displayName": "Hello Plugin",
|
|
||||||
"version": "0.1.0",
|
|
||||||
"main": "dist/index.js",
|
|
||||||
"trustLevel": "full",
|
|
||||||
"activationScope": "app",
|
|
||||||
"contributes": {}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
`activationScope` is optional and defaults to `"app"`, which activates the
|
|
||||||
plugin at app bootstrap without requiring a focused project. Use
|
|
||||||
`"activationScope": "project"` only when `activate(ctx)` needs project-scoped
|
|
||||||
services immediately; IdeA will keep that plugin pending until a project is
|
|
||||||
focused, then activate it once for the app session.
|
|
||||||
|
|
||||||
The `main` field is the ESM entrypoint loaded by IdeA. It may import other
|
|
||||||
JavaScript files from the same plugin package with relative specifiers:
|
|
||||||
|
|
||||||
```js
|
|
||||||
import { COMMAND_ID } from "./constants.js";
|
|
||||||
import { useWorkspaceSdk } from "./core/workspace.js";
|
|
||||||
```
|
|
||||||
|
|
||||||
Those relative imports are served by IdeA through the `idea-plugin://` protocol,
|
|
||||||
so plugins do not need to be bundled into a single JavaScript file. A plain
|
|
||||||
`tsc` build that emits multiple ESM files under `dist/` is supported, as shown
|
|
||||||
by `examples/hello-plugin`.
|
|
||||||
|
|
||||||
Only package-relative imports are resolved this way. Bare specifiers such as
|
|
||||||
`react`, `lodash` or any dependency expected from `node_modules` are not
|
|
||||||
resolved by the host at runtime. Third-party dependencies must be bundled into
|
|
||||||
the plugin output or vendored as relative files shipped inside the plugin
|
|
||||||
package.
|
|
||||||
|
|
||||||
The entrypoint exports an `activate(ctx)` function:
|
|
||||||
|
|
||||||
```ts
|
|
||||||
import type { ActivateContext } from "@idea/plugin-sdk";
|
|
||||||
|
|
||||||
export function activate(ctx: ActivateContext): void {
|
|
||||||
ctx.logger.info("hello from plugin");
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Layout Runtime
|
|
||||||
|
|
||||||
Plugins can contribute custom layout panels by declaring `contributes.layouts`
|
|
||||||
in `idea-plugin.json` and registering the matching layout type during
|
|
||||||
`activate(ctx)`.
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"contributes": {
|
|
||||||
"layouts": [
|
|
||||||
{
|
|
||||||
"type": "com.example.status",
|
|
||||||
"label": "Status",
|
|
||||||
"component": "StatusPanel"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
```ts
|
|
||||||
import type { ActivateContext, PluginLayoutProps } from "@idea/plugin-sdk";
|
|
||||||
|
|
||||||
function StatusPanel(props: PluginLayoutProps): string {
|
|
||||||
return `status for ${props.projectId}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function activate(ctx: ActivateContext): void {
|
|
||||||
const disposable = ctx.layouts?.register({
|
|
||||||
type: "com.example.status",
|
|
||||||
component: StatusPanel
|
|
||||||
});
|
|
||||||
if (disposable) ctx.subscriptions.push(disposable);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Public layout props are:
|
|
||||||
|
|
||||||
- `projectId`: project hosting the layout cell;
|
|
||||||
- `nodeId`: stable layout node id for that cell instance;
|
|
||||||
- `layoutType`: contributed layout type from the manifest;
|
|
||||||
- `state`: opaque JSON-serializable state persisted by the host;
|
|
||||||
- `setState(next)`: replaces that state;
|
|
||||||
- `availability`: currently `"available"` when the component is mounted.
|
|
||||||
|
|
||||||
Lifecycle: register layouts during `activate(ctx)`, keep the returned disposable
|
|
||||||
in `ctx.subscriptions`, and let the host dispose it on plugin unload. Layout
|
|
||||||
components may be mounted, unmounted and remounted by the host; keep durable UI
|
|
||||||
state in `state` via `setState`, not in module globals. Call `setState` from
|
|
||||||
user actions, effects or asynchronous callbacks, not unconditionally while
|
|
||||||
rendering. Services are available from `ctx.services` to plugins declaring the
|
|
||||||
`tooling` capability; layout props do not expose private runtime gateways.
|
|
||||||
|
|
||||||
## Plugin-Owned Storage
|
|
||||||
|
|
||||||
Use `ctx.storage` for state owned by the plugin itself: internal counters, flags,
|
|
||||||
preferences, small caches and host-facing settings. This is the canonical place
|
|
||||||
for plugin-owned state because the host can scope and persist it outside the
|
|
||||||
user's project files.
|
|
||||||
|
|
||||||
```ts
|
|
||||||
import type { ActivateContext } from "@idea/plugin-sdk";
|
|
||||||
|
|
||||||
const ACTIVATION_COUNT_KEY = "helloPlugin.activationCount";
|
|
||||||
|
|
||||||
export async function activate(ctx: ActivateContext): Promise<void> {
|
|
||||||
const current = await ctx.storage?.get<number>(ACTIVATION_COUNT_KEY);
|
|
||||||
const next = typeof current === "number" ? current + 1 : 1;
|
|
||||||
await ctx.storage?.set(ACTIVATION_COUNT_KEY, next);
|
|
||||||
|
|
||||||
ctx.logger.info("activation count", { next });
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Do not write plugin-internal state into `.ideai/*` or other project files by
|
|
||||||
default. Use workspace files and config-document helpers only when the file is
|
|
||||||
project-owned content or project-owned configuration that the user expects to
|
|
||||||
see, review and version with the project.
|
|
||||||
|
|
||||||
## Runtime Services
|
|
||||||
|
|
||||||
Plugins declaring the `tooling` capability receive `ctx.services`. Plugins
|
|
||||||
without that capability do not receive this facade. Prefer `ctx.services` over
|
|
||||||
IdeA's internal runtime objects when it is available:
|
|
||||||
|
|
||||||
```ts
|
|
||||||
import type { ActivateContext } from "@idea/plugin-sdk";
|
|
||||||
|
|
||||||
export async function activate(ctx: ActivateContext): Promise<void> {
|
|
||||||
const project = await ctx.services?.workspace.getCurrentProject();
|
|
||||||
ctx.logger.info("current project", project);
|
|
||||||
|
|
||||||
const task = await ctx.services?.tasks.getStatus("task-id");
|
|
||||||
ctx.logger.info("task status", task?.status);
|
|
||||||
|
|
||||||
const terminal = await ctx.services?.terminal.open({ rows: 24, cols: 80 });
|
|
||||||
await terminal?.write(new TextEncoder().encode("echo hello\\r"));
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Workspace Files
|
|
||||||
|
|
||||||
Workspace paths are always relative to the project root. Hosts reject absolute
|
|
||||||
paths, `..`, empty path segments and paths outside the sandbox. Text APIs use
|
|
||||||
UTF-8; binary APIs use `Uint8Array`. Missing files reject on reads and resolve
|
|
||||||
to `{ exists: false }` from `stat`. These APIs are for project-owned files:
|
|
||||||
source files, generated reports or user-visible artifacts. Use `ctx.storage`
|
|
||||||
instead for plugin-owned counters, flags, preferences and caches.
|
|
||||||
|
|
||||||
```ts
|
|
||||||
import type { ActivateContext } from "@idea/plugin-sdk";
|
|
||||||
|
|
||||||
export async function activate(ctx: ActivateContext): Promise<void> {
|
|
||||||
const workspace = ctx.services?.workspace;
|
|
||||||
const project = await workspace?.getCurrentProject();
|
|
||||||
if (!workspace || !project) return;
|
|
||||||
|
|
||||||
await workspace.writeTextFile("hello-plugin-report.txt", "hello\n", project.id);
|
|
||||||
|
|
||||||
const file = await workspace.readTextFile("hello-plugin-report.txt", project.id);
|
|
||||||
const listing = await workspace.listDirectory(".", project.id);
|
|
||||||
const stat = await workspace.stat(file.path, project.id);
|
|
||||||
|
|
||||||
ctx.logger.info("workspace file", {
|
|
||||||
path: file.path,
|
|
||||||
bytes: stat.len,
|
|
||||||
entries: listing.entries.length
|
|
||||||
});
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
`watch(path, handler, projectId?)` subscribes to public workspace file-change
|
|
||||||
events for the given relative path. It is best-effort and bounded: hosts may
|
|
||||||
reject it until workspace watching is implemented, and plugins must treat setup
|
|
||||||
failure as non-fatal. Plugins should also handle missed events by refreshing
|
|
||||||
their own derived state when needed.
|
|
||||||
|
|
||||||
### Project Structure
|
|
||||||
|
|
||||||
`queryStructure()` returns a bounded, generic read model so plugins do not each
|
|
||||||
need to rescan the whole workspace for common markers:
|
|
||||||
|
|
||||||
```ts
|
|
||||||
const structure = await ctx.services?.workspace.queryStructure({
|
|
||||||
maxDepth: 3,
|
|
||||||
maxEntries: 500
|
|
||||||
});
|
|
||||||
|
|
||||||
for (const convention of structure?.conventions ?? []) {
|
|
||||||
console.log(convention.id, convention.markerPath);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
The MVP detects generic marker-file conventions such as `package.json`,
|
|
||||||
`Cargo.toml`, `pyproject.toml`, `go.mod`, `Makefile` and `.git`. It deliberately
|
|
||||||
does not expose language-specific ASTs or Android-specific concepts.
|
|
||||||
|
|
||||||
Current terminal scope is intentionally minimal: it opens or reattaches a shell
|
|
||||||
PTY, writes bytes, resizes, detaches and closes.
|
|
||||||
|
|
||||||
### Command Tasks
|
|
||||||
|
|
||||||
Use `ctx.services.tasks.runCommand()` for non-interactive tools that should be
|
|
||||||
tracked as IdeA background tasks instead of opening a raw PTY. `command` and
|
|
||||||
`args` are passed separately, `cwd` is relative to the project root, and `env`
|
|
||||||
adds process environment variables. The current host requires an `ownerAgentId`
|
|
||||||
so the task can appear in Work and completion can be correlated to an agent.
|
|
||||||
|
|
||||||
```ts
|
|
||||||
import type { ActivateContext } from "@idea/plugin-sdk";
|
|
||||||
|
|
||||||
export async function activate(ctx: ActivateContext): Promise<void> {
|
|
||||||
const project = await ctx.services?.workspace.getCurrentProject();
|
|
||||||
if (!project) return;
|
|
||||||
|
|
||||||
const task = await ctx.services?.tasks.runCommand({
|
|
||||||
projectId: project.id,
|
|
||||||
ownerAgentId: "00000000-0000-0000-0000-000000000000",
|
|
||||||
label: "Check npm",
|
|
||||||
command: "npm",
|
|
||||||
args: ["--version"],
|
|
||||||
cwd: ".",
|
|
||||||
env: { CI: "1" },
|
|
||||||
recordOnly: true
|
|
||||||
});
|
|
||||||
|
|
||||||
const status = await ctx.services?.tasks.getCommandStatus(task.taskId);
|
|
||||||
ctx.logger.info("command task", {
|
|
||||||
taskId: task.taskId,
|
|
||||||
state: status?.state,
|
|
||||||
exitCode: status?.exitCode
|
|
||||||
});
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
`list`, `getStatus`, `attachOutput`, `cancel` and `retry` continue to operate on
|
|
||||||
tasks visible through IdeA's Work read model. `getCommandStatus` reads a launched
|
|
||||||
command task directly from the host task store.
|
|
||||||
|
|
||||||
### Toolchain Diagnostics
|
|
||||||
|
|
||||||
Use `ctx.services.tooling.diagnose()` to check external prerequisites without
|
|
||||||
hard-coding one stack into the SDK. A request can probe executables, inspect
|
|
||||||
environment variables and validate workspace files in one structured result.
|
|
||||||
|
|
||||||
```ts
|
|
||||||
import type { ActivateContext } from "@idea/plugin-sdk";
|
|
||||||
|
|
||||||
export async function activate(ctx: ActivateContext): Promise<void> {
|
|
||||||
const diagnostic = await ctx.services?.tooling.diagnose({
|
|
||||||
tools: [
|
|
||||||
{
|
|
||||||
id: "node",
|
|
||||||
executable: "node",
|
|
||||||
versionArgs: ["--version"],
|
|
||||||
required: true
|
|
||||||
}
|
|
||||||
],
|
|
||||||
env: [{ name: "PATH", required: true }],
|
|
||||||
files: [{ path: "package.json", kind: "file" }]
|
|
||||||
});
|
|
||||||
|
|
||||||
const node = diagnostic?.tools.find((tool) => tool.id === "node");
|
|
||||||
ctx.logger.info("tooling diagnostic", {
|
|
||||||
ok: diagnostic?.ok,
|
|
||||||
nodePresent: node?.present,
|
|
||||||
nodeVersion: node?.version,
|
|
||||||
messages: diagnostic?.messages
|
|
||||||
});
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
The diagnostic API is intentionally generic: it does not install tools, does not
|
|
||||||
model Android devices or emulators, and does not expose language-specific ASTs.
|
|
||||||
|
|
||||||
### Events And Watch
|
|
||||||
|
|
||||||
Use `ctx.services.events.subscribe()` for stable public host/project events. The
|
|
||||||
runtime hides the host polling details and returns a disposable subscription.
|
|
||||||
|
|
||||||
```ts
|
|
||||||
import type { ActivateContext } from "@idea/plugin-sdk";
|
|
||||||
|
|
||||||
export async function activate(ctx: ActivateContext): Promise<void> {
|
|
||||||
const subscription = await ctx.services?.events.subscribe(
|
|
||||||
{
|
|
||||||
eventTypes: ["backgroundTaskChanged"],
|
|
||||||
capacity: 100,
|
|
||||||
onDropped: (count) => ctx.logger.warn("plugin events dropped", { count })
|
|
||||||
},
|
|
||||||
(event) => {
|
|
||||||
if (event.type === "backgroundTaskChanged") {
|
|
||||||
ctx.logger.info("task changed", {
|
|
||||||
taskId: event.taskId,
|
|
||||||
state: event.state
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
if (subscription) ctx.subscriptions.push(subscription);
|
|
||||||
|
|
||||||
const watch = await ctx.services?.workspace.watch("src", (event) => {
|
|
||||||
ctx.logger.info("workspace changed", {
|
|
||||||
path: event.path,
|
|
||||||
kind: event.kind,
|
|
||||||
operation: event.operation
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
if (watch) ctx.subscriptions.push(watch);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Public event retention is `bestEffortBounded`: events are retained per
|
|
||||||
subscription up to the requested/host-capped capacity, drained oldest-first, and
|
|
||||||
`onDropped` reports when older retained events were overwritten.
|
|
||||||
|
|
||||||
### Structured Config Documents
|
|
||||||
|
|
||||||
Use `ctx.services.config` when a plugin needs to read or update a structured
|
|
||||||
project-owned configuration file without reimplementing parsing and
|
|
||||||
serialization. Do not use project config documents as the default persistence
|
|
||||||
mechanism for plugin-internal state; use `ctx.storage` for that.
|
|
||||||
|
|
||||||
First-lot format support is deliberately narrow:
|
|
||||||
|
|
||||||
- `json` only;
|
|
||||||
- inferred from `.json` when `format` is omitted;
|
|
||||||
- serialized as pretty JSON with a trailing newline;
|
|
||||||
- update modes: `mergePatch` and `replace`;
|
|
||||||
- `mergePatch` follows JSON merge-patch semantics: object keys are merged
|
|
||||||
recursively and `null` removes a key.
|
|
||||||
|
|
||||||
```ts
|
|
||||||
import type { ActivateContext } from "@idea/plugin-sdk";
|
|
||||||
|
|
||||||
export async function activate(ctx: ActivateContext): Promise<void> {
|
|
||||||
const config = await ctx.services?.config.readDocument({
|
|
||||||
path: "tooling.config.json"
|
|
||||||
});
|
|
||||||
|
|
||||||
await ctx.services?.config.updateDocument({
|
|
||||||
path: "tooling.config.json",
|
|
||||||
mode: "mergePatch",
|
|
||||||
value: {
|
|
||||||
enabled: true,
|
|
||||||
lastReadFormat: config?.format ?? "json"
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
YAML, TOML, XML, `.properties` and stack-specific config models are not part of
|
|
||||||
this first lot.
|
|
||||||
|
|
||||||
Declare the additive `tooling` capability to receive `ctx.services` at runtime:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"capabilities": ["ui", "tooling"]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Manifest Validation
|
|
||||||
|
|
||||||
```ts
|
|
||||||
import { validatePluginManifest } from "@idea/plugin-sdk";
|
|
||||||
|
|
||||||
const result = validatePluginManifest(manifestJson);
|
|
||||||
if (!result.success) {
|
|
||||||
console.error(result.errors);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
This validator is deliberately strict for core fields and permissive about future unknown fields.
|
|
||||||
It is not a security boundary.
|
|
||||||
|
|||||||
60
docs/activation-context.md
Normal file
60
docs/activation-context.md
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
# Activation And Context
|
||||||
|
|
||||||
|
The plugin entrypoint exports `activate(ctx)`.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import type { ActivateContext, IdeAPluginModule } from "@idea/plugin-sdk";
|
||||||
|
|
||||||
|
export function activate(ctx: ActivateContext): void {
|
||||||
|
ctx.logger.info("activated", { pluginId: ctx.pluginId });
|
||||||
|
}
|
||||||
|
|
||||||
|
export default { activate } satisfies IdeAPluginModule;
|
||||||
|
```
|
||||||
|
|
||||||
|
IdeA accepts either a named `activate` export or a default export containing
|
||||||
|
`activate`.
|
||||||
|
|
||||||
|
## Activation Scope
|
||||||
|
|
||||||
|
`activationScope` defaults to `"app"`. App-scoped plugins activate during app
|
||||||
|
bootstrap. Project-scoped plugins are kept pending until a project is focused,
|
||||||
|
then activated once for the app session.
|
||||||
|
|
||||||
|
Choose `"project"` only when `activate(ctx)` must immediately read project
|
||||||
|
state. Menu commands and layouts can usually stay app-scoped and check for a
|
||||||
|
focused project at invocation/render time.
|
||||||
|
|
||||||
|
## Context Fields
|
||||||
|
|
||||||
|
- `pluginId`, `pluginDisplayName`, `version`: host-provided identity.
|
||||||
|
- `logger`: `debug`, `info`, `warn`, `error`.
|
||||||
|
- `subscriptions`: push disposables returned by command/layout/watch
|
||||||
|
registrations.
|
||||||
|
- `commands`: command registry for declared menu commands.
|
||||||
|
- `layouts`: layout registry for declared layout types.
|
||||||
|
- `menu`: marker for the plugin-owned menu surface.
|
||||||
|
- `storage`: plugin-owned persistent key/value storage.
|
||||||
|
- `services`: public host service facade for plugins declaring `ui` or `tooling`
|
||||||
|
capabilities.
|
||||||
|
|
||||||
|
The context never exposes internal IdeA gateways or Tauri commands. Use
|
||||||
|
`ctx.services` and the registration APIs instead.
|
||||||
|
|
||||||
|
## Disposal
|
||||||
|
|
||||||
|
Push every returned disposable to `ctx.subscriptions`:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
const disposable = ctx.commands?.registerCommand("com.example.run", run);
|
||||||
|
if (disposable) ctx.subscriptions.push(disposable);
|
||||||
|
```
|
||||||
|
|
||||||
|
IdeA disposes these handles best-effort when the plugin is unloaded or the app
|
||||||
|
session ends.
|
||||||
|
|
||||||
|
## Storage
|
||||||
|
|
||||||
|
Use `ctx.storage` for plugin-owned counters, flags, preferences and small caches.
|
||||||
|
Use workspace/config services only for project-owned files or configuration that
|
||||||
|
the user expects to see in the project.
|
||||||
@ -27,8 +27,8 @@ menu item -> command id -> registered command handler -> optional task -> feedba
|
|||||||
|
|
||||||
Check preconditions before calling `runCommand()`:
|
Check preconditions before calling `runCommand()`:
|
||||||
|
|
||||||
- `ctx.services` exists. Plugins need the `tooling` capability for the service
|
- `ctx.services` exists. Plugins need the `ui` or `tooling` capability for the
|
||||||
facade.
|
service facade.
|
||||||
- `ctx.services.workspace.getCurrentProject()` returned a project, or the caller
|
- `ctx.services.workspace.getCurrentProject()` returned a project, or the caller
|
||||||
supplied a valid `projectId`.
|
supplied a valid `projectId`.
|
||||||
- Required executables, environment variables and workspace files were validated,
|
- Required executables, environment variables and workspace files were validated,
|
||||||
|
|||||||
85
docs/layouts-react.md
Normal file
85
docs/layouts-react.md
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
# Layouts With React
|
||||||
|
|
||||||
|
Plugin layouts are real React components. They can use JSX and hooks.
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import type { PluginLayoutProps } from "@idea/plugin-sdk";
|
||||||
|
import { useState } from "react";
|
||||||
|
|
||||||
|
export function Dashboard(props: PluginLayoutProps): React.ReactElement {
|
||||||
|
const [localClicks, setLocalClicks] = useState(0);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => {
|
||||||
|
setLocalClicks((value) => value + 1);
|
||||||
|
props.setState({ localClicks: localClicks + 1 });
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Clicked {localClicks}
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Register the component for a manifest-declared layout type:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
ctx.layouts?.register({
|
||||||
|
type: "hello-plugin.dashboard",
|
||||||
|
component: Dashboard
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
## Props
|
||||||
|
|
||||||
|
- `projectId`: project hosting the layout.
|
||||||
|
- `nodeId`: stable host node id for this layout instance.
|
||||||
|
- `layoutType`: manifest layout `type`.
|
||||||
|
- `state`: JSON-serializable host-persisted state.
|
||||||
|
- `setState(next)`: replace host-persisted state.
|
||||||
|
- `availability`: currently `"available"` while mounted.
|
||||||
|
|
||||||
|
Call `setState` from user actions, effects or async callbacks. Do not call it
|
||||||
|
unconditionally during render.
|
||||||
|
|
||||||
|
## Host React
|
||||||
|
|
||||||
|
Plugins must import React normally:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { useEffect, useMemo, useState } from "react";
|
||||||
|
```
|
||||||
|
|
||||||
|
At runtime IdeA resolves these bare imports to the host instance:
|
||||||
|
|
||||||
|
- `react`
|
||||||
|
- `react-dom`
|
||||||
|
- `react-dom/client`
|
||||||
|
- `react/jsx-runtime`
|
||||||
|
- `react/jsx-dev-runtime`
|
||||||
|
|
||||||
|
Do not bundle your own copy of React or ReactDOM into the plugin. Declare them
|
||||||
|
as `peerDependencies` and `devDependencies` for local typechecking/builds.
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^18.2.0 || ^19.0.0",
|
||||||
|
"react-dom": "^18.2.0 || ^19.0.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/react": "^19.0.0",
|
||||||
|
"@types/react-dom": "^19.0.0",
|
||||||
|
"react": "^19.0.0",
|
||||||
|
"react-dom": "^19.0.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Build
|
||||||
|
|
||||||
|
Use `jsx: "react-jsx"` in `tsconfig`. A plain multi-file ESM `tsc` build is
|
||||||
|
supported as long as every emitted file imported by `main` is included in the
|
||||||
|
archive.
|
||||||
68
docs/manifest.md
Normal file
68
docs/manifest.md
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
# Manifest
|
||||||
|
|
||||||
|
Every plugin package has an `idea-plugin.json` file at the archive root.
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"ideaPluginManifestVersion": 1,
|
||||||
|
"id": "com.example.hello-plugin",
|
||||||
|
"displayName": "Hello Plugin",
|
||||||
|
"publisher": "Example",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"description": "Example IdeA plugin.",
|
||||||
|
"main": "dist/index.js",
|
||||||
|
"engines": {
|
||||||
|
"idea": ">=0.1.0"
|
||||||
|
},
|
||||||
|
"trustLevel": "full",
|
||||||
|
"capabilities": ["ui", "tooling"],
|
||||||
|
"activationScope": "app",
|
||||||
|
"contributes": {
|
||||||
|
"menus": [],
|
||||||
|
"menuItems": [],
|
||||||
|
"layouts": [],
|
||||||
|
"mcpServers": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Required Fields
|
||||||
|
|
||||||
|
- `ideaPluginManifestVersion`: currently `1`.
|
||||||
|
- `id`: stable lowercase id using letters, digits, dots and dashes. It must start
|
||||||
|
and end with an alphanumeric character.
|
||||||
|
- `displayName`: human-readable plugin name.
|
||||||
|
- `version`: semver version.
|
||||||
|
- `main`: package-relative ESM entrypoint loaded by IdeA.
|
||||||
|
- `trustLevel`: currently `"full"`.
|
||||||
|
|
||||||
|
## Optional Fields
|
||||||
|
|
||||||
|
- `description`, `publisher`.
|
||||||
|
- `engines.idea`: host compatibility hint.
|
||||||
|
- `activationScope`: `"app"` by default, or `"project"` when activation needs a
|
||||||
|
focused project immediately.
|
||||||
|
- `capabilities`: `"ui"`, `"tooling"` and/or `"mcp"`.
|
||||||
|
|
||||||
|
## Contributions
|
||||||
|
|
||||||
|
`contributes.layouts` is the only surface for plugin layouts. The same layout
|
||||||
|
contribution can be mounted inside an IdeA layout cell or opened in a detached OS
|
||||||
|
window. Do not add a separate manifest contribution type for windows.
|
||||||
|
|
||||||
|
Contribution ids are part of the runtime contract:
|
||||||
|
|
||||||
|
- commands can only register ids declared by `contributes.menuItems[*].command`;
|
||||||
|
- layouts can only register `type` values declared by `contributes.layouts`;
|
||||||
|
- `services.windows.open({ layoutType })` only accepts a layout type declared by
|
||||||
|
the calling plugin.
|
||||||
|
|
||||||
|
## Validation
|
||||||
|
|
||||||
|
Use the SDK validator in tests or build tooling:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { assertPluginManifest } from "@idea/plugin-sdk";
|
||||||
|
|
||||||
|
assertPluginManifest(JSON.parse(manifestText));
|
||||||
|
```
|
||||||
56
docs/menus.md
Normal file
56
docs/menus.md
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
# Menus
|
||||||
|
|
||||||
|
Menus are declared in the manifest and implemented by registering command
|
||||||
|
handlers during activation.
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"contributes": {
|
||||||
|
"menus": [
|
||||||
|
{
|
||||||
|
"id": "hello-plugin.menu",
|
||||||
|
"label": "Hello Plugin",
|
||||||
|
"topLevel": true,
|
||||||
|
"order": 100
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"menuItems": [
|
||||||
|
{
|
||||||
|
"id": "hello-plugin.open.item",
|
||||||
|
"targetMenuId": "hello-plugin.menu",
|
||||||
|
"label": "Open Dashboard",
|
||||||
|
"command": "hello-plugin.open",
|
||||||
|
"order": 10,
|
||||||
|
"when": "projectOpen"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Then register the command id:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
export function activate(ctx: ActivateContext): void {
|
||||||
|
const disposable = ctx.commands?.registerCommand("hello-plugin.open", async () => {
|
||||||
|
await ctx.services?.windows.open({ layoutType: "hello-plugin.dashboard" });
|
||||||
|
});
|
||||||
|
if (disposable) ctx.subscriptions.push(disposable);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Rules
|
||||||
|
|
||||||
|
- A command can only be registered if at least one manifest menu item declares
|
||||||
|
that exact `command` id.
|
||||||
|
- Missing command handlers are a no-op when the user clicks the menu item.
|
||||||
|
- A handler owns precondition checks and feedback. See
|
||||||
|
[Commands And Feedback](commands-and-feedback.md).
|
||||||
|
- Menu item `when` expressions are evaluated by the host; unsupported or false
|
||||||
|
conditions hide/disable the item according to host policy.
|
||||||
|
|
||||||
|
## Targets
|
||||||
|
|
||||||
|
`targetMenuId` can refer to a plugin top-level menu id or a host menu id exposed
|
||||||
|
by IdeA. Prefer a plugin top-level menu for plugin-specific workflows and host
|
||||||
|
menus only when the action naturally belongs beside native actions.
|
||||||
76
docs/packaging-distribution.md
Normal file
76
docs/packaging-distribution.md
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
# Packaging And Distribution
|
||||||
|
|
||||||
|
A plugin archive is a ZIP file whose root contains `idea-plugin.json`.
|
||||||
|
|
||||||
|
```text
|
||||||
|
hello-plugin-0.1.0.zip
|
||||||
|
├── idea-plugin.json
|
||||||
|
├── README.md
|
||||||
|
└── dist/
|
||||||
|
├── index.js
|
||||||
|
├── constants.js
|
||||||
|
└── core/
|
||||||
|
├── layout.js
|
||||||
|
├── storage.js
|
||||||
|
└── workspace.js
|
||||||
|
```
|
||||||
|
|
||||||
|
The manifest `main` field must point to an emitted file inside the archive:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"main": "dist/index.js"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Module Resolution
|
||||||
|
|
||||||
|
IdeA loads `main` as ESM and serves package-relative imports from the plugin
|
||||||
|
package. This is supported:
|
||||||
|
|
||||||
|
```js
|
||||||
|
import { Dashboard } from "./core/layout.js";
|
||||||
|
```
|
||||||
|
|
||||||
|
For React, import bare host modules normally:
|
||||||
|
|
||||||
|
```js
|
||||||
|
import { useState } from "react";
|
||||||
|
import { jsx } from "react/jsx-runtime";
|
||||||
|
```
|
||||||
|
|
||||||
|
IdeA resolves React/ReactDOM bare imports to the host instance. Other bare
|
||||||
|
dependencies are not host-resolved. Bundle or vendor third-party dependencies
|
||||||
|
other than React/ReactDOM into package-relative files.
|
||||||
|
|
||||||
|
## TypeScript Build
|
||||||
|
|
||||||
|
The hello plugin uses plain `tsc`:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm run build
|
||||||
|
npm run build:hello-plugin
|
||||||
|
```
|
||||||
|
|
||||||
|
For React layouts, configure JSX:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"jsx": "react-jsx",
|
||||||
|
"module": "NodeNext",
|
||||||
|
"moduleResolution": "NodeNext"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Archive Build
|
||||||
|
|
||||||
|
The SDK example can be packaged with:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm run package:hello-plugin
|
||||||
|
```
|
||||||
|
|
||||||
|
The resulting archive has no wrapping parent directory and is ready for IdeA's
|
||||||
|
plugin installer.
|
||||||
68
docs/services.md
Normal file
68
docs/services.md
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
# Services
|
||||||
|
|
||||||
|
`ctx.services` is the stable public host facade. It is available to plugins that
|
||||||
|
declare `ui` or `tooling` capabilities.
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"capabilities": ["ui", "tooling"]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Workspace
|
||||||
|
|
||||||
|
`services.workspace` reads the focused/current project, project context and
|
||||||
|
project-owned files. Paths are relative to the project root; hosts reject
|
||||||
|
absolute paths and traversal outside the workspace.
|
||||||
|
|
||||||
|
Key APIs:
|
||||||
|
|
||||||
|
- `getCurrentProject()`
|
||||||
|
- `getProjectRoot(projectId?)`
|
||||||
|
- `readProjectContext(projectId?)`
|
||||||
|
- `updateProjectContext(content, projectId?)`
|
||||||
|
- `readTextFile(path, projectId?)`
|
||||||
|
- `writeTextFile(path, content, projectId?)`
|
||||||
|
- `readBinaryFile(path, projectId?)`
|
||||||
|
- `writeBinaryFile(path, bytes, projectId?)`
|
||||||
|
- `listDirectory(path?, projectId?)`
|
||||||
|
- `stat(path, projectId?)`
|
||||||
|
- `watch(path, handler, projectId?)`
|
||||||
|
- `queryStructure(query?)`
|
||||||
|
|
||||||
|
## Tasks
|
||||||
|
|
||||||
|
`services.tasks` launches and inspects host-managed command tasks.
|
||||||
|
|
||||||
|
Use `runCommand()` only after preconditions are satisfied. `ownerAgentId` must be
|
||||||
|
a real agent id when work belongs to an agent workflow.
|
||||||
|
|
||||||
|
## Tooling
|
||||||
|
|
||||||
|
`services.tooling.diagnose()` checks executables, environment values and files
|
||||||
|
from the host-controlled runtime.
|
||||||
|
|
||||||
|
## Events
|
||||||
|
|
||||||
|
`services.events.subscribe()` provides best-effort bounded subscriptions to
|
||||||
|
public plugin events such as workspace file changes and background task changes.
|
||||||
|
Dispose subscriptions when no longer needed.
|
||||||
|
|
||||||
|
## Config
|
||||||
|
|
||||||
|
`services.config` reads and updates structured JSON documents in project-owned
|
||||||
|
locations. Use it for configuration the user expects to review/version.
|
||||||
|
|
||||||
|
## Terminal
|
||||||
|
|
||||||
|
`services.terminal` opens, reattaches and closes terminal sessions:
|
||||||
|
|
||||||
|
- `open({ cwd?, rows?, cols?, onData? })`
|
||||||
|
- `reattach(sessionId, { onData? })`
|
||||||
|
- `close(sessionId)`
|
||||||
|
|
||||||
|
## Windows
|
||||||
|
|
||||||
|
`services.windows.open({ layoutType, state? })` opens a detached OS window for
|
||||||
|
one of the calling plugin's declared layout contributions. See
|
||||||
|
[Windows](windows.md).
|
||||||
46
docs/windows.md
Normal file
46
docs/windows.md
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
# Windows
|
||||||
|
|
||||||
|
Plugin windows are detached OS windows that host an existing
|
||||||
|
`contributes.layouts` layout. There is no separate window contribution type.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
await ctx.services?.windows.open({
|
||||||
|
layoutType: "hello-plugin.dashboard",
|
||||||
|
state: { openedFrom: "menu" }
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
## Contract
|
||||||
|
|
||||||
|
- `layoutType` must match a layout type declared by the calling plugin.
|
||||||
|
- The host validates the plugin is runtime-active and the layout exists before
|
||||||
|
opening or focusing the window.
|
||||||
|
- Reopening the same plugin/layout pair focuses the existing window.
|
||||||
|
- `state` is JSON-serializable initial window state. The mounted component can
|
||||||
|
later call `setState(next)` to update its local host state.
|
||||||
|
- The window follows IdeA's focused project, matching detached native panel
|
||||||
|
behavior. If no project is focused, the host shows a neutral shell until one
|
||||||
|
is focused.
|
||||||
|
|
||||||
|
## Return Value
|
||||||
|
|
||||||
|
```ts
|
||||||
|
const win = await ctx.services.windows.open({ layoutType: "hello-plugin.dashboard" });
|
||||||
|
|
||||||
|
win.label;
|
||||||
|
win.alreadyOpen;
|
||||||
|
win.surface.layoutType;
|
||||||
|
```
|
||||||
|
|
||||||
|
The returned `label` is a host-owned window identity. Treat it as opaque.
|
||||||
|
|
||||||
|
## Typical Menu Flow
|
||||||
|
|
||||||
|
```ts
|
||||||
|
ctx.commands?.registerCommand("hello-plugin.open-dashboard", async () => {
|
||||||
|
return ctx.services?.windows.open({
|
||||||
|
layoutType: "hello-plugin.dashboard",
|
||||||
|
state: { source: "menu" }
|
||||||
|
});
|
||||||
|
});
|
||||||
|
```
|
||||||
@ -10,7 +10,10 @@ It exercises the current plugin primitives end to end:
|
|||||||
`{ status: "launched", taskId, state, message }` when it starts a background
|
`{ status: "launched", taskId, state, message }` when it starts a background
|
||||||
task, or `{ status: "skipped", reason, message }` when a precondition is not
|
task, or `{ status: "skipped", reason, message }` when a precondition is not
|
||||||
met;
|
met;
|
||||||
- layout contribution: `hello-plugin.hello-world`, rendered as `hello-world`.
|
- layout contribution: `hello-plugin.hello-world`, rendered by a React/JSX
|
||||||
|
component with hooks.
|
||||||
|
- plugin window: the menu command opens the layout through
|
||||||
|
`ctx.services.windows.open(...)` when services are available.
|
||||||
- plugin-owned storage: activation count, command run count and initialization flag;
|
- plugin-owned storage: activation count, command run count and initialization flag;
|
||||||
- tooling capability: logs the focused workspace project when `ctx.services` is available.
|
- tooling capability: logs the focused workspace project when `ctx.services` is available.
|
||||||
|
|
||||||
@ -18,13 +21,14 @@ The source is intentionally split across multiple TypeScript modules:
|
|||||||
|
|
||||||
- `src/index.ts` is the manifest entrypoint and imports relative ESM modules;
|
- `src/index.ts` is the manifest entrypoint and imports relative ESM modules;
|
||||||
- `src/constants.ts` owns shared command/layout identifiers;
|
- `src/constants.ts` owns shared command/layout identifiers;
|
||||||
- `src/core/layout.ts` and `src/core/workspace.ts` hold feature logic.
|
- `src/core/layout.tsx` and `src/core/workspace.ts` hold feature logic.
|
||||||
- `src/core/storage.ts` keeps plugin-owned counters and flags in `ctx.storage`.
|
- `src/core/storage.ts` keeps plugin-owned counters and flags in `ctx.storage`.
|
||||||
|
|
||||||
The build uses plain `tsc`; it does not bundle the plugin into one file. The archive includes all
|
The build uses plain `tsc`; it does not bundle the plugin into one file. The archive includes all
|
||||||
compiled `dist/**/*.js` files so IdeA can load `dist/index.js` and serve its package-relative imports
|
compiled `dist/**/*.js` files so IdeA can load `dist/index.js` and serve its package-relative imports
|
||||||
through `idea-plugin://`. Runtime imports from `node_modules` are outside this contract: vendor them
|
through `idea-plugin://`. React and ReactDOM are peer dependencies resolved to the host instance at
|
||||||
as relative files or bundle them into the plugin output before packaging.
|
runtime. Other runtime imports from `node_modules` are outside this contract: vendor them as relative
|
||||||
|
files or bundle them into the plugin output before packaging.
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
npm run typecheck:examples
|
npm run typecheck:examples
|
||||||
@ -46,7 +50,8 @@ During activation the plugin logs:
|
|||||||
- successful registration of the `hello-plugin.hello-world` layout;
|
- successful registration of the `hello-plugin.hello-world` layout;
|
||||||
- availability of the workspace service from the `tooling` runtime capability;
|
- availability of the workspace service from the `tooling` runtime capability;
|
||||||
- best-effort workspace watch setup, including a non-fatal log when unavailable;
|
- best-effort workspace watch setup, including a non-fatal log when unavailable;
|
||||||
- the first layout render, including project/node identifiers.
|
- the first layout render, including project/node identifiers;
|
||||||
|
- plugin window open/focus results from `ctx.services.windows.open(...)`.
|
||||||
|
|
||||||
During command invocation the plugin logs and returns structured feedback for
|
During command invocation the plugin logs and returns structured feedback for
|
||||||
programmatic callers. The current human menu-click UI does not guarantee display
|
programmatic callers. The current human menu-click UI does not guarantee display
|
||||||
|
|||||||
75
examples/hello-plugin/package-lock.json
generated
75
examples/hello-plugin/package-lock.json
generated
@ -9,6 +9,16 @@
|
|||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@idea/plugin-sdk": "file:../.."
|
"@idea/plugin-sdk": "file:../.."
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/react": "^19.0.0",
|
||||||
|
"@types/react-dom": "^19.0.0",
|
||||||
|
"react": "^19.0.0",
|
||||||
|
"react-dom": "^19.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^18.2.0 || ^19.0.0",
|
||||||
|
"react-dom": "^18.2.0 || ^19.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"../..": {
|
"../..": {
|
||||||
@ -16,12 +26,77 @@
|
|||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@types/react": "^19.0.0",
|
||||||
|
"@types/react-dom": "^19.0.0",
|
||||||
|
"react": "^19.0.0",
|
||||||
|
"react-dom": "^19.0.0",
|
||||||
"typescript": "^5.5.0"
|
"typescript": "^5.5.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^18.2.0 || ^19.0.0",
|
||||||
|
"react-dom": "^18.2.0 || ^19.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@idea/plugin-sdk": {
|
"node_modules/@idea/plugin-sdk": {
|
||||||
"resolved": "../..",
|
"resolved": "../..",
|
||||||
"link": true
|
"link": true
|
||||||
|
},
|
||||||
|
"node_modules/@types/react": {
|
||||||
|
"version": "19.2.18",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.18.tgz",
|
||||||
|
"integrity": "sha512-AnzbBERsrLKtk2XSfTbYRLjQPdy116Sty4q+T+Bp3IC4l6jNBvreVPAHmpq9qhXQM7CXZPjLVmGMw9sy+hxQ3w==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"csstype": "^3.2.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@types/react-dom": {
|
||||||
|
"version": "19.2.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.4.tgz",
|
||||||
|
"integrity": "sha512-Bsc+QHgp+P/F02XDzNCY9jnZNCUuLki36KT7VKrTXXLdHf+vHMNZnW1rVu5DNW/rCK+fya3DATySbLM4yhtKUw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "^19.2.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/csstype": {
|
||||||
|
"version": "3.2.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
|
||||||
|
"integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/react": {
|
||||||
|
"version": "19.2.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/react/-/react-19.2.8.tgz",
|
||||||
|
"integrity": "sha512-PWaYA1L/q9u2u7xYQi+Y3L3Yfnie7XyLeaJICV1MGD6LprsBxcAqGjYyr0eY3p+QdsA+x/Irkt4Qif8D63+Sbw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/react-dom": {
|
||||||
|
"version": "19.2.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.8.tgz",
|
||||||
|
"integrity": "sha512-rVprimfGBG3DR+Tq0IQG2DT5PxKth1WIGDmj5yPmlzr4YBe7uyE+Du4oVqTDXZSHGGGXRtTJEGSSePyQCMBglQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"scheduler": "^0.27.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^19.2.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/scheduler": {
|
||||||
|
"version": "0.27.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz",
|
||||||
|
"integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -9,6 +9,15 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@idea/plugin-sdk": "file:../.."
|
"@idea/plugin-sdk": "file:../.."
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^18.2.0 || ^19.0.0",
|
||||||
|
"react-dom": "^18.2.0 || ^19.0.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/react": "^19.0.0",
|
||||||
|
"@types/react-dom": "^19.0.0",
|
||||||
|
"react": "^19.0.0",
|
||||||
|
"react-dom": "^19.0.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,17 +0,0 @@
|
|||||||
import type { PluginLayoutProps } from "@idea/plugin-sdk";
|
|
||||||
|
|
||||||
let hasLoggedFirstLayoutRender = false;
|
|
||||||
|
|
||||||
export function HelloWorldLayout(props: PluginLayoutProps): string {
|
|
||||||
if (!hasLoggedFirstLayoutRender) {
|
|
||||||
hasLoggedFirstLayoutRender = true;
|
|
||||||
console.info("[hello-plugin] layout first render", {
|
|
||||||
projectId: props.projectId,
|
|
||||||
nodeId: props.nodeId,
|
|
||||||
layoutType: props.layoutType,
|
|
||||||
hasState: props.state !== undefined
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return "hello-world";
|
|
||||||
}
|
|
||||||
58
examples/hello-plugin/src/core/layout.tsx
Normal file
58
examples/hello-plugin/src/core/layout.tsx
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
import type { PluginLayoutProps } from "@idea/plugin-sdk";
|
||||||
|
import type { ReactElement } from "react";
|
||||||
|
import { useMemo, useState } from "react";
|
||||||
|
|
||||||
|
let hasLoggedFirstLayoutRender = false;
|
||||||
|
|
||||||
|
export function HelloWorldLayout(props: PluginLayoutProps): ReactElement {
|
||||||
|
const [localClicks, setLocalClicks] = useState(0);
|
||||||
|
const hostState = useMemo(() => {
|
||||||
|
return typeof props.state === "object" && props.state !== null && !Array.isArray(props.state)
|
||||||
|
? (props.state as Record<string, unknown>)
|
||||||
|
: {};
|
||||||
|
}, [props.state]);
|
||||||
|
const persistedClicks =
|
||||||
|
typeof hostState.clicks === "number" && Number.isFinite(hostState.clicks)
|
||||||
|
? hostState.clicks
|
||||||
|
: 0;
|
||||||
|
|
||||||
|
if (!hasLoggedFirstLayoutRender) {
|
||||||
|
hasLoggedFirstLayoutRender = true;
|
||||||
|
console.info("[hello-plugin] layout first render", {
|
||||||
|
projectId: props.projectId,
|
||||||
|
nodeId: props.nodeId,
|
||||||
|
layoutType: props.layoutType,
|
||||||
|
hasState: props.state !== undefined
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section style={{ display: "grid", gap: 12, fontFamily: "system-ui, sans-serif" }}>
|
||||||
|
<header>
|
||||||
|
<h2 style={{ margin: 0, fontSize: 16 }}>Hello Plugin</h2>
|
||||||
|
<p style={{ margin: "4px 0 0", color: "#667085", fontSize: 13 }}>
|
||||||
|
React layout rendered by IdeA host React.
|
||||||
|
</p>
|
||||||
|
</header>
|
||||||
|
<dl style={{ display: "grid", gap: 4, margin: 0, fontSize: 13 }}>
|
||||||
|
<div>
|
||||||
|
<dt style={{ color: "#667085" }}>Project</dt>
|
||||||
|
<dd style={{ margin: 0 }}>{props.projectId}</dd>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<dt style={{ color: "#667085" }}>Layout</dt>
|
||||||
|
<dd style={{ margin: 0 }}>{props.layoutType}</dd>
|
||||||
|
</div>
|
||||||
|
</dl>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => {
|
||||||
|
setLocalClicks((current) => current + 1);
|
||||||
|
props.setState({ ...hostState, clicks: persistedClicks + 1 });
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Persist click {persistedClicks} / local click {localClicks}
|
||||||
|
</button>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -15,7 +15,17 @@ export function activate(ctx: ActivateContext): void {
|
|||||||
const commandDisposable = ctx.commands?.registerCommand(COMMAND_ID, async () => {
|
const commandDisposable = ctx.commands?.registerCommand(COMMAND_ID, async () => {
|
||||||
const commandRunCount = await recordCommandRun(ctx);
|
const commandRunCount = await recordCommandRun(ctx);
|
||||||
const feedback = await runHelloCommandTask(ctx);
|
const feedback = await runHelloCommandTask(ctx);
|
||||||
|
const opened = await ctx.services?.windows.open({
|
||||||
|
layoutType: LAYOUT_TYPE,
|
||||||
|
state: { openedFromCommand: commandRunCount ?? null }
|
||||||
|
});
|
||||||
ctx.logger.info("command executed", { commandId: COMMAND_ID, commandRunCount, feedback });
|
ctx.logger.info("command executed", { commandId: COMMAND_ID, commandRunCount, feedback });
|
||||||
|
if (opened) {
|
||||||
|
ctx.logger.info("layout window opened", {
|
||||||
|
label: opened.label,
|
||||||
|
alreadyOpen: opened.alreadyOpen
|
||||||
|
});
|
||||||
|
}
|
||||||
return feedback;
|
return feedback;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -14,6 +14,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"include": [
|
"include": [
|
||||||
"src/**/*.ts"
|
"src/**/*.ts",
|
||||||
|
"src/**/*.tsx"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,6 +3,7 @@
|
|||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"declaration": false,
|
"declaration": false,
|
||||||
"declarationMap": false,
|
"declarationMap": false,
|
||||||
|
"jsx": "react-jsx",
|
||||||
"noEmit": true,
|
"noEmit": true,
|
||||||
"rootDir": "../..",
|
"rootDir": "../..",
|
||||||
"paths": {
|
"paths": {
|
||||||
@ -13,6 +14,7 @@
|
|||||||
},
|
},
|
||||||
"include": [
|
"include": [
|
||||||
"src/**/*.ts",
|
"src/**/*.ts",
|
||||||
|
"src/**/*.tsx",
|
||||||
"../../src/**/*.ts"
|
"../../src/**/*.ts"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
65
package-lock.json
generated
65
package-lock.json
generated
@ -9,9 +9,74 @@
|
|||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@types/react": "^19.0.0",
|
||||||
|
"@types/react-dom": "^19.0.0",
|
||||||
|
"react": "^19.0.0",
|
||||||
|
"react-dom": "^19.0.0",
|
||||||
"typescript": "^5.5.0"
|
"typescript": "^5.5.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^18.2.0 || ^19.0.0",
|
||||||
|
"react-dom": "^18.2.0 || ^19.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/react": {
|
||||||
|
"version": "19.2.18",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.18.tgz",
|
||||||
|
"integrity": "sha512-AnzbBERsrLKtk2XSfTbYRLjQPdy116Sty4q+T+Bp3IC4l6jNBvreVPAHmpq9qhXQM7CXZPjLVmGMw9sy+hxQ3w==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"csstype": "^3.2.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@types/react-dom": {
|
||||||
|
"version": "19.2.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.4.tgz",
|
||||||
|
"integrity": "sha512-Bsc+QHgp+P/F02XDzNCY9jnZNCUuLki36KT7VKrTXXLdHf+vHMNZnW1rVu5DNW/rCK+fya3DATySbLM4yhtKUw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "^19.2.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/csstype": {
|
||||||
|
"version": "3.2.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
|
||||||
|
"integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/react": {
|
||||||
|
"version": "19.2.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/react/-/react-19.2.8.tgz",
|
||||||
|
"integrity": "sha512-PWaYA1L/q9u2u7xYQi+Y3L3Yfnie7XyLeaJICV1MGD6LprsBxcAqGjYyr0eY3p+QdsA+x/Irkt4Qif8D63+Sbw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/react-dom": {
|
||||||
|
"version": "19.2.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.8.tgz",
|
||||||
|
"integrity": "sha512-rVprimfGBG3DR+Tq0IQG2DT5PxKth1WIGDmj5yPmlzr4YBe7uyE+Du4oVqTDXZSHGGGXRtTJEGSSePyQCMBglQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"scheduler": "^0.27.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^19.2.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/scheduler": {
|
||||||
|
"version": "0.27.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz",
|
||||||
|
"integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/typescript": {
|
"node_modules/typescript": {
|
||||||
"version": "5.9.3",
|
"version": "5.9.3",
|
||||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
|
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
|
||||||
|
|||||||
10
package.json
10
package.json
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@idea/plugin-sdk",
|
"name": "@idea/plugin-sdk",
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"description": "Minimal public TypeScript SDK for IdeA plugins.",
|
"description": "Public TypeScript SDK for IdeA plugins.",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "./dist/index.js",
|
"main": "./dist/index.js",
|
||||||
"types": "./dist/index.d.ts",
|
"types": "./dist/index.d.ts",
|
||||||
@ -29,7 +29,15 @@
|
|||||||
"sdk"
|
"sdk"
|
||||||
],
|
],
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^18.2.0 || ^19.0.0",
|
||||||
|
"react-dom": "^18.2.0 || ^19.0.0"
|
||||||
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@types/react": "^19.0.0",
|
||||||
|
"@types/react-dom": "^19.0.0",
|
||||||
|
"react": "^19.0.0",
|
||||||
|
"react-dom": "^19.0.0",
|
||||||
"typescript": "^5.5.0"
|
"typescript": "^5.5.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -43,6 +43,7 @@ export type {
|
|||||||
IdeAPluginModule,
|
IdeAPluginModule,
|
||||||
JsonValue,
|
JsonValue,
|
||||||
LayoutRegistry,
|
LayoutRegistry,
|
||||||
|
OpenPluginWindowOptions,
|
||||||
PluginLogger,
|
PluginLogger,
|
||||||
PluginLayoutAvailability,
|
PluginLayoutAvailability,
|
||||||
PluginLayoutComponent,
|
PluginLayoutComponent,
|
||||||
@ -52,6 +53,7 @@ export type {
|
|||||||
PluginLayoutState,
|
PluginLayoutState,
|
||||||
PluginServices,
|
PluginServices,
|
||||||
PluginStorage,
|
PluginStorage,
|
||||||
|
PluginWindow,
|
||||||
ProjectConvention,
|
ProjectConvention,
|
||||||
ProjectModule,
|
ProjectModule,
|
||||||
ProjectStructure,
|
ProjectStructure,
|
||||||
@ -82,5 +84,6 @@ export type {
|
|||||||
WorkspaceTextFile,
|
WorkspaceTextFile,
|
||||||
WorkspaceWatch,
|
WorkspaceWatch,
|
||||||
WorkspaceWatchEvent,
|
WorkspaceWatchEvent,
|
||||||
WorkspaceWatchHandler
|
WorkspaceWatchHandler,
|
||||||
|
WindowService
|
||||||
} from "./runtime.js";
|
} from "./runtime.js";
|
||||||
|
|||||||
@ -1,3 +1,5 @@
|
|||||||
|
import type { ComponentType, ReactNode } from "react";
|
||||||
|
|
||||||
export interface ActivateContext {
|
export interface ActivateContext {
|
||||||
pluginId: string;
|
pluginId: string;
|
||||||
logger: PluginLogger;
|
logger: PluginLogger;
|
||||||
@ -47,7 +49,7 @@ export interface PluginStorage {
|
|||||||
|
|
||||||
export type PluginLayoutState = JsonValue | undefined;
|
export type PluginLayoutState = JsonValue | undefined;
|
||||||
export type PluginLayoutAvailability = "available";
|
export type PluginLayoutAvailability = "available";
|
||||||
export type PluginLayoutRenderResult = unknown;
|
export type PluginLayoutRenderResult = ReactNode;
|
||||||
|
|
||||||
export interface PluginLayoutProps<TState extends PluginLayoutState = PluginLayoutState> {
|
export interface PluginLayoutProps<TState extends PluginLayoutState = PluginLayoutState> {
|
||||||
/** Project currently hosting this layout cell. */
|
/** Project currently hosting this layout cell. */
|
||||||
@ -64,9 +66,8 @@ export interface PluginLayoutProps<TState extends PluginLayoutState = PluginLayo
|
|||||||
availability: PluginLayoutAvailability;
|
availability: PluginLayoutAvailability;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type PluginLayoutComponent<TState extends PluginLayoutState = PluginLayoutState> = (
|
export type PluginLayoutComponent<TState extends PluginLayoutState = PluginLayoutState> =
|
||||||
props: PluginLayoutProps<TState>,
|
ComponentType<PluginLayoutProps<TState>>;
|
||||||
) => PluginLayoutRenderResult;
|
|
||||||
|
|
||||||
export interface PluginLayoutDefinition<TState extends PluginLayoutState = PluginLayoutState> {
|
export interface PluginLayoutDefinition<TState extends PluginLayoutState = PluginLayoutState> {
|
||||||
/** Must match a layout `type` declared in this plugin's manifest. */
|
/** Must match a layout `type` declared in this plugin's manifest. */
|
||||||
@ -87,6 +88,7 @@ export interface PluginServices {
|
|||||||
events: EventService;
|
events: EventService;
|
||||||
config: ConfigDocumentService;
|
config: ConfigDocumentService;
|
||||||
terminal: TerminalService;
|
terminal: TerminalService;
|
||||||
|
windows: WindowService;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface WorkspaceProject {
|
export interface WorkspaceProject {
|
||||||
@ -570,3 +572,32 @@ export interface TerminalService {
|
|||||||
/** Kills a PTY by id. */
|
/** Kills a PTY by id. */
|
||||||
close(sessionId: string): Promise<void>;
|
close(sessionId: string): Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface OpenPluginWindowOptions {
|
||||||
|
/** Layout `type` declared by this plugin in `contributes.layouts`. */
|
||||||
|
layoutType: string;
|
||||||
|
/** Initial opaque state copied into the detached window surface. */
|
||||||
|
state?: JsonValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PluginWindow {
|
||||||
|
label: string;
|
||||||
|
url: string;
|
||||||
|
alreadyOpen: boolean;
|
||||||
|
providerPluginDisplayName: string;
|
||||||
|
layoutLabel: string;
|
||||||
|
surface: {
|
||||||
|
pluginId: string;
|
||||||
|
layoutType: string;
|
||||||
|
state: JsonValue;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface WindowService {
|
||||||
|
/**
|
||||||
|
* Opens or focuses a detached IdeA OS window hosting one of this plugin's
|
||||||
|
* declared layout contributions. The host rejects layout ids absent from this
|
||||||
|
* plugin's manifest.
|
||||||
|
*/
|
||||||
|
open(options: OpenPluginWindowOptions): Promise<PluginWindow>;
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user