Skip to main content
Everything a plugin runs goes through a process:<program> capability: the user approves the program, the IDE pins it by absolute path and content hash, and checks it again before every start. On top of that, a plugin can:

Run detectors

A run detector is a static configuration offered in projects that contain one of its marker files at the root:
The configuration appears among the detected ones while process:<command> is granted and starts the approved program. No code is needed. Solidity declares detectors for Foundry and Hardhat.

Configurations from code

When the configurations depend on the project’s content (scripts in a package.json, say), declare marker files and export run-configurations:
Each PluginRunConfiguration has id, name, program, args, an optional project-contained cwd, test and an optional framework. Return the complete list for the project root; it is refreshed when configurations are listed. The host keeps at most 1000 entries / 4 MiB, keeps the last valid list when the export fails, and doesn’t offer entries whose program isn’t approved. The export runs without a window and can’t prompt.

Test results

With framework = "plugin" (on a detector or a returned configuration), the IDE feeds the run’s output to the test-results export. Register test_results and implement TestResults:
  • parse(config, chunk) gets the short configuration id and sequential output chunks that need not end at a line break: keep partial lines between calls.
  • Events: SuiteStarted, TestStarted, Passed, Failed (with expected / actual for a comparison), Skipped, Output. Test ids are scoped to the run; a parent must already exist. Locations are absolute project paths, 0-based lines, UTF-16 columns; durations are milliseconds.
  • Each batch runs under the editor provider budget and returns at most 10,000 events / 4 MiB. A missing, failing or invalid parser switches that run to the built-in parsers.
  • select_tests(config, tests) receives test-tree name paths (parent suite to test, as shown in the tree) and returns the full argument list that runs only them. It enables Rerun Failed Tests and Run / Debug on test tree entries. The default returns None (selection unsupported).

Start a run from code

host::start_run(config, &args, debug) (API 1.4) starts one of this plugin’s configurations (a detector id or one the export returned; the short id or plugin:<plugin-id>:<config-id>) in the invoking window’s Run tool window, appending args. It returns an operation id; a run-exited event with {id, config, status, killed} follows, delivered to the owning plugin even without a subscription. From the Test Runner example, where a gutter button passes the test name as the argument:
The IDE owns Stop, Rerun and target handling, and verifies the approved program at launch and on rerun. Debug uses the debugger of the source file named in the arguments (or the one chosen on the saved configuration) and returns invalid without one. Only valid from a command or event, not from activation or a provider.

Run targets

A run target is a project-scoped wrapper: every run, test, script and interpreter launch that selects it is rewritten to go through a program the plugin owns, such as ddev, lando or a container CLI. Targets appear in Settings › Build, Execution, Deployment › Run Targets and in every run configuration’s Run on selector, labelled with their plugin.

How the command line is built

The host builds argv directly, without a shell:
  • No override: program + args + workdir_args + command + command arguments.
  • An override whose name matches the command’s program name (basename without .exe, .cmd or .bat): program + override + workdir_args + command arguments. The override replaces both args and the command name, so it includes the tool itself.
With the target above, php x.php run from public/ becomes ddev exec --dir=/var/www/html/public -- php x.php, and composer install becomes ddev composer --dir=/var/www/html/public -- install.
  • Each workdir_args element may contain {cwd}, replaced by the configuration’s working directory mapped under container_root. All workdir_args are omitted when there is no container_root or the working directory is outside the project. Without workdir_args the wrapper manages its directory itself.
  • When container_root is set, project paths in argv are mapped under it. Local absolute tool paths outside the project become the tool’s basename.
  • The wrapper process always starts in the configuration’s local working directory, so tools like DDEV find their project. Configuration env and env files reach the wrapper; forwarding them into a container is up to the wrapper.
Per-command working directory (API 1.5). host::register_run_target_with_overrides takes PluginRunTargetWithOverrides, whose commands are PluginCommandOverride { name, args, workdir_args }. workdir_args: None inherits the target’s list, Some(vec![]) omits it, Some(args) replaces it for that command:
With that, composer install becomes ddev composer install while php x.php keeps ddev exec --dir=/var/www/html/public -- php x.php.

Selecting a target

Registration never changes a selection. The first registration asks the project’s windows “Use <name> for running PHP / Node / Composer in this project?” with Use and Not now; the answer is remembered per project, plugin and target id. Use makes it the project’s default run target and its PHP and Node run-target choice. Interpreters also get a Run in target choice in Settings (default run target, Local machine, or a named target). Explicit per-configuration targets win.

Rules and limits

  • Each launch checks project trust, the wrapper’s process grant and its executable pin (including a per-project executable choice made in Settings › Plugins).
  • Debugging on a plugin target returns invalid; language servers, tool discovery and version probes stay local.
  • A missing target fails the run rather than silently running locally.
  • At most 32 targets per plugin and project, 64 command overrides, 256 arguments per prefix (16 KiB each), 256 KiB of metadata per registration.
  • Targets survive idle reclaim. Stopping, disabling, reloading or uninstalling the plugin withdraws them; host::unregister_run_target(&project, id) removes one. In remote projects registration and execution happen on the server.
Container Runner is a runnable example with a fake wrapper executable.

Validating a launch (API 1.5)

Export run-target-validation to check the wrapper’s runtime state right before Run or Test:
RunTargetLaunch has operation (Run or Test), the bare program being wrapped and the absolute local cwd; arguments and environment are not passed. Err(message) refuses the launch through the normal error path, prefixed with the plugin name (at most 4096 bytes). A trap or timeout (5 seconds by default) also refuses it. Plugins without the export keep launching as before.

Running programs

  • Only the pinned program of a process: grant runs, in a trusted project. In a remote project it is resolved and run on the server.
  • Environment entries only add variables: a name already in the prepared environment (including the login-shell PATH) is an error. At most 64 entries.
  • Calls are cancelled with the task or call and killed at timeout; run anything slow inside host::begin_task. Dropping a Child kills it, and disabling or reclaiming the plugin ends its children.
  • In Settings › Plugins the user can choose the executable of a process: grant globally or per project; the plugin keeps calling its declared program name.