Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 8 additions & 4 deletions contract.lock.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@
"source": {
"repository": "https://github.com/1lck/Lithe-IDEA",
"directory": "shared/contracts/plugins/v1",
"revision": "f64d262c48f1ff5ad1778b301138596493a09ad5"
"revision": "ced042289b5fbf9b6562eacd8da1f15428b5849d"
},
"files": [
{
"path": "README.md",
"sha256": "4cfe0f5f6cb15980bf9db4b19fe5ed80082c417132ccad04dee5959d7efdef72"
"sha256": "b30695e1c297812ad009937b78daf12f23b74d4c111c602614e393c25d5b5693"
},
{
"path": "fixtures/backend-workspace.plugin.json",
Expand Down Expand Up @@ -100,7 +100,7 @@
},
{
"path": "fixtures/command-plans.json",
"sha256": "a188fd65f6cdb47f9e9ad4b0de3a7ddcd17e4e577b34f6e25b96d2c1f2bdca7c"
"sha256": "d268b4d3966c73031b1876c11cec9a2f6ee6dcc27b8bee687c9a349c147610f4"
},
{
"path": "fixtures/debug-adapter-language-server.plugin.json",
Expand Down Expand Up @@ -138,6 +138,10 @@
"path": "fixtures/invalid-manifests.json",
"sha256": "3859c99928158e44fc1f9f25caff5289ed3c2f8e3af200108ab17051915246c2"
},
{
"path": "fixtures/keymap-import-provider.plugin.json",
"sha256": "03d3843f30edd82fd02652a6f800eca9d7afcddc7b9d2a0bd1b3726c2dfb8eae"
},
{
"path": "fixtures/language-grammar.plugin.json",
"sha256": "9c55a3ea5f075328fca87c845f04f680e35fb283a39283c742fb5d435be05faa"
Expand Down Expand Up @@ -256,7 +260,7 @@
},
{
"path": "plugin-manifest.schema.json",
"sha256": "fba04d8c2c053cec877ab0419e1236810a75368fb3536b271b6a6301ba750339"
"sha256": "7ed0714e4d6120dc4098410db07ca5286f64677b05f03b7532f0ca73bdda2e5d"
},
{
"path": "proposals/toolchains/README.md",
Expand Down
97 changes: 83 additions & 14 deletions contract/v1/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ This directory is the cross-platform source of truth:
| `plugin-manifest.schema.json` | JSON shape of `plugin.json` |
| `fixtures/*.plugin.json` | Valid example manifests, named after the capability they exercise rather than a published plugin: `language-*` (packaged, system-command, and host-integration language servers; `node-runtime` for a discovered runtime), `hello-lithe` and `web-panel` (UI), `settings-v2`, and `build-*` (level 3 build systems and the two forms of a build provider) |
| `fixtures/tool-definition.plugin.json`, `fixtures/toolchain-provider.plugin.json` | Production-shape Core fixtures for tool definitions and a packaged toolchain provider; require explicit new host capabilities, not yet supplied by native products |
| `fixtures/keymap-import-provider.plugin.json` | Two independent shortcut import providers sharing one packaged backend; native source access is not implemented yet |
| `fixtures/invalid-manifests.json` | Edits to a valid manifest and the rejection code every host must report |
| `fixtures/command-plans.json` | Golden run/test command expansions and language-server launch plans |
| `fixtures/runtime-versions.json` | Golden outcomes of the launch runtime version check |
Expand Down Expand Up @@ -56,13 +57,77 @@ admit new sources into existing Build candidate sets. Definitions are entirely
plugin-owned, with reserved short IDs checked against host-verified official
provenance when constructing the registry.

Neither native host advertises or integrates this foundation yet. Core consumes
captured host facts; it does not start a provider, run a probe, watch files, scan
the system, persist pins, or change current Java/Maven selection. The broader
proposal remains unsupported where it exceeds this implemented slice. In
particular, `toolchainConsumers`, generic runtime launches, settings pages, mise
and SDKMAN providers, and live backend activation are not implemented here.
Proposal envelopes remain separate from production manifests.
macOS advertises these capabilities for its read-only toolchain settings pages.
The native adapter scans declarations, probes checked executables, queries plugin
backends and watches their inputs; Core consumes captured facts and owns validation
and resolution. Windows does not advertise the capabilities. Saving pins,
`toolchainConsumers`, generic runtime launches and live backend activation remain
unsupported, and current Java/Maven/Build/Run selection is unchanged. Version-manager
implementations such as mise belong to their own plugin packages. Proposal
envelopes remain separate from production manifests. See [the implemented slice](toolchains.md).

## Shortcut import providers (Core foundation)

`keymapImportProviders` contains one to eight `{id, displayName, recognition, inputs}` entries.
IDs are unique within the plugin, start with a lowercase letter, contain only
lowercase ASCII letters, digits and hyphens, and are at most 64 bytes. The
plugin requires `keymapImportProviders: 1`, `backend: 2`, `serverProcess: 1`,
`backend.launch`, `keymaps.provide` and `keymaps.readSelectedSources`. Its backend starts from a packaged
executable or packaged runtime without discovered runtime dependencies. The
host starts it for an explicit import; it is not a startup or workspace-open
activation event. The permission permits offering mappings; it grants no file
access on its own. `keymaps.readSelectedSources` authorizes only the provider's
declared inputs under the source directory the user explicitly selected for this
import, and only for the lifetime of that import.

`recognition` lists one to sixteen distinct metadata-only probes. A
`{kind: "file" | "directory", path}` probe matches an entry of that kind
beneath the selected root, using the input path restrictions and refusing
symlinks. A `{kind: "root", base: "userConfig", components}` probe matches
the selected directory's exact path beneath the platform's user configuration
directory (macOS Application Support, Windows roaming AppData). Components
contain one to eight safe literal names and may end in one `*`, matching
exactly one directory component; the first component must be literal. The
source matches any probe. Recognition never authorizes reading file contents
or discloses the absolute path to the provider. A host may use probes without
launching backends, and must recheck them on import.

`inputs` lists one to sixteen unique selectors. `{kind: "file", path}` selects
one relative file; `{kind: "directory", path, extension}` selects immediate
children of that directory with the exact lowercase alphanumeric extension,
never recursive descendants. Paths have at most 256 characters, contain no
empty, `.` or `..` segments, backslashes, colons, wildcards, control characters,
or segments ending in a dot or space. Directory selectors have at most 64
matches after scanning at most 4096 directory entries. The native reader must reject symlinks and paths escaping the
selected root, bound each file to 1 MiB and the snapshot to 8 MiB, and expose
only scoped opaque handles in chunks of at most 128 KiB. A manifest cannot
authorize arbitrary paths or access an unselected source.

Core offers `experimental.litheKeymapImport: {versions: [1], providers}` only
to a plugin-owned session with declared providers. The backend selects a
nonempty duplicate-free subset as `{version: 1, providers}`. A request to an
unselected provider is refused. `lithe/keymap/import` carries only
`{providerID, snapshotID}`. The snapshot is an opaque host-owned reference;
the provider never interprets it as a path. Core bounds the operation and
validates normalized results against the host's action catalog. Providers own
source formats and must not silently promote conditional or multi-stroke rules
to global shortcuts. The exact Core commands, limits and fixtures are in the
[Rust Core API](https://github.com/1lck/Lithe-IDEA/blob/preview/shared/contracts/rust-core-api.md#normalized-shortcut-import-validation).

When granted individually, protocol 3 methods `lithe/host/keymapImport/listInputs`
and `lithe/host/keymapImport/readChunk` take `snapshotID` and `providerID`.
Listing returns `{files: [{id, path, byteCount}]}`; a read additionally takes
`fileID`, a nonnegative `offset` and `maximumBytes` from 1 through 131072 and
returns `{data}` containing base64 bytes. Paths are relative to the selected
root and file IDs are opaque. The host binds snapshots to their plugin, session
and provider, revokes them when the import or session ends, and rejects files
replaced or changed after capture. A new snapshot for that provider revokes the
previous one. Errors never disclose absolute paths.

This is a foundation, not an available product extension: macOS implements the
scoped reader, but provider discovery, the product import workflow and Windows
source access are not implemented. Hosts must not advertise this capability
until they implement the complete workflow. No existing SDK advertises it.

## Package layout

Expand Down Expand Up @@ -96,8 +161,9 @@ symbolic links. A host treats an installed package as read-only.
| `settings` | User-adjustable plugin options (level 2 and later). |
| `ui` | Host-rendered tool windows and commands (level 2 and later). |
| `buildSystems` | Project-level build tools (level 3). |
| `toolDefinitions` | Plugin-owned tool policy ([Core foundation](toolchains.md)); requires `toolDefinitions: 1` and `executableResolution: 2`; native adapters are not connected. |
| `toolDefinitions` | Plugin-owned tool policy ([Core foundation](toolchains.md)); requires `toolDefinitions: 1` and `executableResolution: 2`; macOS hosts read-only discovery. |
| `toolchainProviders` | Backend fact-provider declarations ([Core foundation](toolchains.md)); requires `toolchainProviders: 1`, backend/server-process capabilities and `toolchains.provide`. |
| `keymapImportProviders` | Independent shortcut import providers; requires `keymapImportProviders: 1`, `backend: 2`, `serverProcess: 1` and `keymaps.provide`. macOS provides access to explicitly selected sources; Windows does not yet advertise this capability. |

Unknown fields are refused (`plugin.manifest.invalid`) so an older host
rejects a package instead of ignoring part of it. At levels 1 and 2 a new
Expand Down Expand Up @@ -295,17 +361,18 @@ contains a machine path, and the host decides where state lives.
| `${pluginDir}` | Root of the installed, read-only package. It must be used at most once per value and be followed by a `/`-rooted path inside the package; that path may vary only by `${arch}`. |
| `${stateDir}` | Writable directory the host keeps for this plugin's server and removes when the plugin is uninstalled. Not available in `validationArguments`. |
| `${workspaceStateDir}` | Writable directory for the server's state of the current workspace. Not available in `validationArguments`, nor in a [plugin backend](#plugin-backend), which belongs to no workspace. |
| `${logDir}` | Absolute current-session `plugins/<id>/files/` directory, explicitly supplied by the host as `logDirectory`. Available for language servers and plugin backends, never in `validationArguments`. The host creates it in writable log storage, outside the installed package; Core does not derive it from package resources. |
| `${arch}` | Host CPU architecture: `arm64` or `x86_64`. |
| `${setting:<id>}` | Current value of a declared [plugin setting](#plugin-settings-api-2). Not available in `validationArguments`, and never inside a package path. |

`plugins.declarative.languageServerLaunchPlan` expands a server's launch. The
host selects the process with exactly one of `languageID` (a language server),
`buildSystemID` (a build provider), `vcsProviderID` (a VCS provider), and
`backend: true` (the [plugin backend](#plugin-backend)), passes `{pluginDirectory,
stateDirectory, workspaceStateDirectory?, architecture, settings?}`, and
stateDirectory, workspaceStateDirectory?, logDirectory?, architecture, settings?}`, and
receives. `workspaceStateDirectory` may be omitted only when the selected
process does not use `${workspaceStateDir}`; otherwise the request is
`plugin.launch.invalidRequest`.
`plugin.launch.invalidRequest`. The same error applies when a template uses `${logDir}` but `logDirectory` is absent, empty, or not absolute.

| Field | Meaning |
| --- | --- |
Expand All @@ -314,7 +381,7 @@ process does not use `${workspaceStateDir}`; otherwise the request is
| `validationArguments` | Expanded probe arguments. |
| `runtimeRequirements` | `{tool, minimumVersion?}` for every runtime the host must discover, a discovered launch tool included, sorted by tool. A packaged runtime is not listed. Only the launch tool can carry a minimum version. |
| `initializationOptions` | Expanded options; absent when the server declares none. |
| `requiredDirectories` | `state` and/or `workspaceState`: the directories the host must create before starting the server. |
| `requiredDirectories` | `state`, `workspaceState`, and/or `log`: the directories the host must create before starting the server. |
| `requiredPackagePaths` | Sorted package-relative paths the launch points at. The host verifies each is inside the package and not a symbolic link before starting the server. |
| `settings` | Effective value of every declared setting that produced this plan; absent when the plugin declares none. |
| `buildProvider` | Present only for a `buildSystemID` selection: `{buildSystemID, executesProjectCode, mayUseNetwork, languageServer?}`. `languageServer` names the language whose server is the provider; the rest of the plan is then that server's plan, and the host reuses its running session instead of starting a second process. |
Expand Down Expand Up @@ -513,9 +580,9 @@ hosts. Level 3 versions each section on its own. A level 3 manifest lists in
| `debugAdapters` | [`debugAdapters[]`](#api-level-4-debug-adapters); v2 adds target arguments/environment | `1`, `2` |
| `projectGenerators` | [`projectGenerators[]`](#project-generators): entries of the New Project window | `1` |
| `backend` | [`backend`](#plugin-backend): the plugin's own long-lived process | `1`, `2` (adds [start without a page and workspace contexts](#backend-2-workspace-contexts)) |
| `toolDefinitions` | [Tool identities, layouts, discovery and four-layer candidates](toolchains.md) | `1` (Core only) |
| `toolchainProviders` | [Provider declarations and permission-bound answer validation](toolchains.md) | `1` (Core only) |
| `executableResolution` | [Shared executable candidates](#shared-executable-candidates-executableresolution-1); v2 adds tool-definition candidates without changing existing Build candidate sets | `1`, `2` (v2 Core only) |
| `toolDefinitions` | [Tool identities, layouts, discovery and four-layer candidates](toolchains.md) | `1` (Core; macOS read-only settings) |
| `toolchainProviders` | [Provider declarations and permission-bound answer validation](toolchains.md) | `1` (Core; macOS read-only settings) |
| `executableResolution` | [Shared executable candidates](#shared-executable-candidates-executableresolution-1); v2 adds tool-definition candidates without changing existing Build candidate sets | `1`, `2` (v2 Core; macOS read-only settings) |

Version 1 of every capability means what its section means at level 2
(runtime launches, launch templates, `initializationOptions`, and so on), so
Expand Down Expand Up @@ -603,6 +670,8 @@ Rules for publishing across levels and capability versions:
| `ui.contribute` | `ui` is declared |
| `ui.web` | a tool window shows [web content](#web-content-ui-2) |
| `backend.launch` | `backend` is declared |
| `keymaps.provide` | `keymapImportProviders` is nonempty; permits offering mappings, not reading files by itself |
| `keymaps.readSelectedSources` | `keymapImportProviders` is nonempty; permits reading only declared files in an explicitly selected import source |
| `credentials.store` | the backend uses the [credentials host service](#host-services-protocol-version-2); refused without a `backend` |
| `workspace.metadata` | the backend uses the [workspace host service](#workspace-contexts-and-capability-discovery-protocol-version-3); refused without `backend` 2 |
| `editor.actions` | the backend registers independent editor actions; requires `backend` 2 and `documents.read`, grants only `editor/registerActions`, not document edits or domain execution |
Expand Down
64 changes: 64 additions & 0 deletions contract/v1/fixtures/command-plans.json
Original file line number Diff line number Diff line change
Expand Up @@ -855,6 +855,70 @@
"status": "rejected",
"code": "plugin.launch.unavailable"
}
},
{
"name": "node runtime receives an explicit session plugin log directory",
"plugin": "node-runtime.plugin.json",
"languageID": "fixture",
"operation": "languageServerLaunchPlan",
"request": {
"pluginDirectory": "/plugins/node/versions/1.0.0",
"stateDirectory": "/state/node",
"workspaceStateDirectory": "/state/node/workspaces/abc",
"architecture": "x86_64",
"logDirectory": "/logs/sessions/current/plugins/io.example/files"
},
"expected": {
"status": "ok",
"plan": {
"launch": {
"kind": "runtime",
"tool": "node",
"entry": "language-server/lib/server.js",
"minimumVersion": "18.17",
"versionArguments": [
"--version"
]
},
"arguments": [
"--max-old-space-size=2048",
"--require=/plugins/node/versions/1.0.0/language-server/lib/preload.js",
"/plugins/node/versions/1.0.0/language-server/lib/server.js",
"--stdio",
"--log=/logs/sessions/current/plugins/io.example/files/server.log"
],
"validationArguments": [],
"runtimeRequirements": [
{
"tool": "node",
"minimumVersion": "18.17"
}
],
"initializationOptions": {
"stubs": [
"/plugins/node/versions/1.0.0/language-server/stubs/x86_64"
],
"telemetry": false
},
"requiredDirectories": [
"log"
],
"requiredPackagePaths": [
"language-server/lib/preload.js",
"language-server/lib/server.js",
"language-server/stubs/x86_64"
],
"settings": {
"heapMB": 2048,
"logLevel": "info",
"telemetry": false
}
}
},
"manifestArguments": [
"--stdio",
"--log=${logDir}/server.log"
]
}
]
}
18 changes: 18 additions & 0 deletions contract/v1/fixtures/keymap-import-provider.plugin.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"schemaVersion": 2,
"id": "dev.lithe.fixture.import",
"displayName": "Shortcut import fixture",
"version": "1.0.0",
"pluginAPIVersion": 3,
"requires": {"keymapImportProviders": 1, "backend": 2, "serverProcess": 1},
"vendor": {"id": "org.example", "displayName": "Fixture publisher", "signatureRequirement": "publisherPackage"},
"permissions": ["backend.launch", "keymaps.provide", "keymaps.readSelectedSources"],
"keymapImportProviders": [
{"id": "first", "displayName": "First editor", "recognition": [{"kind": "file", "path": "bindings.json"}, {"kind": "root", "base": "userConfig", "components": ["FirstEditor", "User"]}, {"kind": "root", "base": "userConfig", "components": ["FirstEditor", "User", "profiles", "*"]}], "inputs": [{"kind": "file", "path": "bindings.json"}]},
{"id": "second", "displayName": "Second editor", "recognition": [{"kind": "directory", "path": "keymaps"}], "inputs": [
{"kind": "file", "path": "options/map.xml"},
{"kind": "directory", "path": "keymaps", "extension": "xml"}
]}
],
"backend": {"launch": {"kind": "packaged", "path": "bin/import-provider"}, "arguments": ["--stdio"]}
}
Loading
Loading