Compare commits

...
21 Commits
Author SHA1 Message Date
l0ng-ai bc42133a36 fix(debugger): keep the selected tab when a step finishes
Every pause brought Threads & Variables to the front, so stepping from the
Console tab switched away from the output after each Step Over. Only a stop
at a breakpoint, exception or pause request does that now; finishing a step
keeps the tab the user is on.

Refs #2494
2026-11-10 23:22:32 +08:00
Clarence a60293929a fix(ui): the main menu button follows the tool window stripe (#2538)
On Windows and Linux the main menu button's size, slot and the frameless bar's left padding were hardcoded, so the button sat 6px right of the stripe icons below it and stayed 30px in compact mode. It now reads the stripe tokens and uses the 20px glyph of the other title bar tool buttons.

Co-authored-by: Clarence404 <[email protected]>
2026-11-10 23:16:39 +08:00
Ernstjan FreriksandEjaYF 284a287a0f fix(ui): keep dialogs below the macOS window buttons (#2523)
With the overlay title bar, macOS draws the window buttons over the page, so a tall dialog (e.g. Push) or one dragged to the top edge had its title bar covered. The dialog layer now keeps a top margin of at least the title bar height on macOS, and dragging stops there. Windows and Linux are unchanged.

Co-authored-by: EjaYF <[email protected]>
2026-11-10 23:16:32 +08:00
Muuandmuutot a58e3a45f0 fix(vcs): offer both ignore destinations in the changes view (#2499)
Add to .gitignore in the changes view is now a submenu with .gitignore and .git/info/exclude, as in the project view's Git menu, and the file the rule was written to opens in both cases.

Co-authored-by: muutot <[email protected]>
2026-11-10 23:16:24 +08:00
Moler1995 ca9cc64ee8 fix(plugins): keep line-marker gutter icons inside their column (#2481)
Line-marker buttons reuse the indexed-marker button class, whose theme sized every icon to 16px; in the 14px line-marker column, which clips to its width, every icon lost a pixel column on both sides. The 16px size now applies only inside the indexed-marker gutter.

Fixes #2479

{
  "message": "Not Found",
  "documentation_url": "https://docs.github.com/rest",
  "status": "404"
}
2026-11-10 23:16:16 +08:00
xtr-hubandxtr-hub 8b32df9773 fix(archive): handle multibyte ZIP paths on Windows (#2516)
zip 9.1.3's reserved-device-name check sliced entry names at byte offsets, so a WAR or tool archive with a multi-byte file name (e.g. `ZC机柜.jpg`) panicked with "end byte index is not a char boundary" on Windows. Raise zip to 9.2.3, which checks on character boundaries; this covers Tomcat WAR deployment and ZIP/VSIX tool extraction, the two enclosed_name callers.

Fixes #2456

Co-authored-by: xtr-hub <[email protected]>
2026-11-10 23:14:04 +08:00
l0ng-ai 636b2e1572 fix(vcs): handle repositories nested below a non-Git project root (#2546)
- Project view: the Git submenu was gated on the project root being a
  repository, so it never appeared when the repositories are found later
  below a non-Git root. It now also follows the VCS availability that the
  Commit tool window uses.
- Commit: when only one repository had included changes, every staged but
  excluded file of every repository was passed to it, and the commit failed
  with "<path> is outside the repository". Paths that belong to another known
  repository are now left to that repository.

Fixes #2517
Fixes #2518
2026-11-10 23:09:35 +08:00
l0ng-ai 834a54c69b fix(boot): keep starting after an unhandled rejection (#2545)
The startup failure screen treated every unhandled promise rejection before
the workbench was ready as fatal. A rejected background request (WebKit
reports it as "TypeError: Load failed") then stopped startup on machines
where the app had started fine before. Such a rejection is now remembered and
only reported if startup also times out.

Errors without a stack (WebKit's network TypeErrors) were serialised with
JSON.stringify and showed as "{}"; they now show their name and message.

Fixes #2445
2026-11-10 23:03:52 +08:00
arookieofc 9cb8aed512 fix(run): retry the port-file read in stop_tree tests (#2541) 2026-11-10 23:01:28 +08:00
arookieofc 4779d84571 feat(mybatis): navigate and complete @ResultMap ids (#2461) (#2533)
* feat(mybatis): navigate and complete @ResultMap ids (#2461)

@ResultMap on a method jumps to @Results(id) in the same file, else to
the XML <resultMap>; resultMap= on XML statements jumps to <resultMap>;
all three get gutter markers and @ResultMap( completes ids (annotation
ids plus XML ids, merged). Unterminated strings now end at the newline
and interface-level ; resyncs paren tracking, so typing half-written
annotations no longer swallows the rest of the file.

* chore(mybatis): retrigger CI after retargeting to main
2026-11-10 22:49:39 +08:00
silencezhangandsilencezhang7 31464cebea feat(agents): add OMP as a built-in agent (#2458)
OMP (Oh My Pi) ships its own ACP server (`omp acp`), so it runs through the same ACP pipeline as the other built-in agents. Adds it to the built-in providers with its logo, so an installed `omp` shows up in the agent picker without a custom agent definition.

Co-authored-by: silencezhang7 <[email protected]>
2026-11-10 22:36:50 +08:00
arookieofc 3bf921dfe4 feat(mybatis): navigate and complete #{} in annotation SQL (#2461) (#2530)
* fix(java): 重启后外部改动不增量编译导致 NoClassDefFoundError (#2527)

* feat(scheme): support a selection foreground colour (#2446)

Settings › Editor › Color Scheme › General › Editor gains a
Selection foreground attribute next to Selection background, wired
to a new --fg-editor-selection token. The editor paints selected
text with it (.cm-openidea-selected, including the diff viewer);
unset it keeps the previous rendering, with syntax colours showing
through (token default: inherit). .icls import/export round trips
it via SELECTION_FOREGROUND.

* test(themes): palette themes inherit selection foreground from base

Solarized/One keep the base default (preserve syntax colours on
selection); record that intent in INHERITED_FROM_BASE.

* fix(java): re-stamp mtime-preserving external edits before launch builds

Launch fingerprints detect external edits via content hash, but the Maven
rebuild that triggers is timestamp-incremental: sources whose mtime did not
advance are skipped, so the rebuild compiles nothing and the stale snapshot
is re-recorded as fresh. After an IDE restart the launch then reuses those
stale classes (NoClassDefFoundError).

Session::build now diffs the stored fingerprint against the fresh one,
re-stamps (bumps mtime on) sources whose content changed while
(length, mtime) stayed the same, and re-scans before building, so the
rebuild actually recompiles them and the record written afterwards matches.

Related to #2476

* feat(mybatis): navigate and complete #{} in annotation SQL (#2461)

Annotation @Select/@Insert/@Update/@Delete SQL strings now resolve
Definition, and complete parameter names after #{. Works without
any XML (pure-annotation mappers). Also fixes parse_java splitting
statements on braces/semicolons inside string literals.

* feat(mybatis): silence clippy type-complexity with LitSpan alias
2026-11-10 22:18:04 +08:00
l0ng-ai 646530312d fix(libraries): find Maven jars in the local repository set by ~/.m2/settings.xml (#2540)
External Libraries and published version catalogs fell back to ~/.m2/repository when Settings › Maven configured no local repository, ignoring <localRepository> in ~/.m2/settings.xml, so only jars that also existed under ~/.m2/repository were listed. The default lookup now lives in ide_sdk::apply and is shared with Lombok agent detection. Sibling: Gradle user home (#2466).
2026-11-10 21:58:33 +08:00
l0ng-ai 50c2435b17 docs(readme): drop the portable zip note and link contributing (#2539) 2026-11-10 21:57:31 +08:00
Zhu Minhaoandzmh69695328 51c256db97 fix(libraries): read dependency jars from the configured Gradle home (#2466)
External Libraries looked up Gradle dependency jars only under $GRADLE_USER_HOME or ~/.gradle, ignoring Settings › Gradle › user home, so projects whose Gradle cache lives elsewhere listed no Gradle dependencies. The scan now resolves the home like published_version_catalog: the setting first, then the environment / default home.

Co-authored-by: zmh69695328 <[email protected]>
2026-11-10 21:56:34 +08:00
l0ng-ai ba8a7555cd docs(readme): remove the Contributors section (#2537) 2026-11-10 21:54:53 +08:00
l0ng-ai ca602586d1 docs(readme): link the documentation site (#2536) 2026-11-10 21:53:42 +08:00
NealimeKennaandNealimeKenna 72b32c9faa fix(agents): let F7 move on to the next changed file in an agent task's diff (#2534)
Co-authored-by: NealimeKenna <[email protected]>
2026-11-10 21:52:10 +08:00
arookieofc 6908c0f1b8 fix(java): recompile external edits that keep their mtime after a restart (#2527)
Launch fingerprints detect external edits via content hash, but the Maven
rebuild that triggers is timestamp-incremental: sources whose mtime did not
advance are skipped, so the rebuild compiles nothing and the stale snapshot
is re-recorded as fresh. After an IDE restart the launch then reuses those
stale classes (NoClassDefFoundError).

Session::build now diffs the stored fingerprint against the fresh one,
re-stamps (bumps mtime on) sources whose content changed while
(length, mtime) stayed the same, and re-scans before building, so the
rebuild actually recompiles them and the record written afterwards matches.

Related to #2476
2026-11-10 21:35:12 +08:00
l0ng-ai 19fca27359 fix(i18n): translate data-held labels and unify zh terms (#2529) 2026-11-10 21:06:33 +08:00
l0ng-ai dbdf610ffa docs: add the Mintlify documentation site (#2528)
Closes #1543
2026-11-10 21:06:30 +08:00
255 changed files with 25838 additions and 159 deletions

No files matched your search

+2
View File
@@ -26,6 +26,8 @@ Visual style: dark, low-contrast greys, one cool accent
Lombok agent, embedded into the app by `ide-sdk` / `ide-tools`.
- `e2e/` — Playwright UI tests (`pnpm e2e`). `fixtures/` — sample projects per language
for manual and automated testing.
- `docs/` — the Mintlify documentation site (`docs.json` navigation, `en/` and `zh/` pages);
preview with `npx mint dev` in `docs/`.
## Rules
Generated
+3 -2
View File
@@ -12152,14 +12152,15 @@ dependencies = [
[[package]]
name = "zip"
version = "9.1.3"
version = "9.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1156b39d142a34b999c2310a50455e0cb3760bf7efad0122f796d813bee26ccb"
checksum = "00fb4744e923442ecadfd3f85f51a5d24e03bf43d53813b81ebb4c91d02baa62"
dependencies = [
"crc32fast",
"flate2",
"getrandom 0.4.3",
"indexmap 2.14.2",
"libc",
"memchr",
"nix 0.31.3",
"typed-path",
+1 -1
View File
@@ -82,7 +82,7 @@ trash = "5"
blake3 = "1"
dunce = "1"
dirs = "6"
zip = { version = "9", default-features = false, features = ["deflate"] }
zip = { version = "9.2.3", default-features = false, features = ["deflate"] }
# search, index, language
grep-searcher = "0.1"
+5 -9
View File
@@ -12,6 +12,8 @@
<p align="center">
<a href="https://github.com/l0ng-ai/openidea/releases/latest"><b>Download</b></a>
&nbsp;·&nbsp;
<a href="https://openidea.sh">Docs</a>
&nbsp;·&nbsp;
<a href="#benchmark">Benchmark</a>
&nbsp;·&nbsp;
<a href="#plugins">Plugins</a>
@@ -25,9 +27,7 @@ OpenIDEA brings the IntelliJ IDEA way of working — its keymap, its tool window
its search-everything habits — to a native app with a Rust core.
Editor, code insight, debugger, Git, terminal, database and HTTP clients,
for 60+ languages. Builds for macOS, Windows and Linux. On Windows, if you'd
rather skip the installer, each release also ships a portable zip — unpack it
and run `openidea.exe`.
for 60+ languages. Builds for macOS, Windows and Linux.
## Benchmark
@@ -56,7 +56,7 @@ themes, file icons and languages. They run as sandboxed WebAssembly components;
themes, icon themes and languages need no code at all.
Install them from Settings › Plugins, or start your own with
`openidea-plugin new` — see [Writing OpenIDEA plugins](plugins/README.md) and
`openidea-plugin new` — see [Writing OpenIDEA plugins](https://openidea.sh/en/plugins) and
the [official plugins](plugins/official). The plugin API is still a preview.
## Community
@@ -66,11 +66,7 @@ the [official plugins](plugins/official). The plugin API is still a preview.
<img src=".github/assets/wechat-group.png" alt="WeChat group QR code" width="200">
### Contributors
<a href="https://github.com/l0ng-ai/openidea/graphs/contributors">
<img src="https://contrib.rocks/image?repo=l0ng-ai/openidea&max=500" alt="OpenIDEA contributors">
</a>
- Contributing: see [CONTRIBUTING.md](CONTRIBUTING.md) and the [contributor docs](https://openidea.sh/en/contribute/setup).
## License
+1
View File
@@ -64,6 +64,7 @@ const BUILTIN: &[(&str, &str, Driver, &[&str], &str)] = &[
("auggie", "Auggie", Driver::Acp, &["auggie", "--acp"], "Run `auggie login` in a terminal."),
("kiro", "Kiro", Driver::Acp, &["kiro-cli", "acp"], "Run `kiro-cli login` in a terminal."),
("vibe", "Mistral Vibe", Driver::Acp, &["vibe-acp"], "Run `vibe` in a terminal and sign in."),
("omp", "OMP", Driver::Acp, &["omp", "acp"], "Run `omp` in a terminal and sign in."),
];
fn builtins() -> Vec<ProviderSpec> {
@@ -184,6 +184,30 @@ pub fn launch_fingerprint(
Ok(Some(LaunchFingerprint { version: 2, parameters, files }))
}
impl LaunchFingerprint {
/// Relative paths whose content changed while `(length, mtime)` stayed the same
/// compared to `previous` (timestamp-preserving external edits, coarse-granularity
/// filesystems). Timestamp-incremental builders (Maven, the Kotlin plugin) would
/// skip these sources as "unchanged", so the rebuild the fingerprint triggers
/// compiles nothing and the stale outputs get re-recorded as fresh. Callers
/// re-stamp the returned paths before building. Inputs without a content hash
/// (directories, files outside the project) are never reported.
pub fn mtime_preserved_edits(&self, current: &Self) -> Vec<PathBuf> {
let mut edits = Vec::new();
for (path, current_stamp) in &current.files {
let Some(current_stamp) = current_stamp else { continue };
let Some(previous_stamp) = self.files.get(path).and_then(|stamp| stamp.as_ref()) else { continue };
let (len, mtime, Some(current_hash)) = current_stamp else { continue };
let (prev_len, prev_mtime, Some(prev_hash)) = previous_stamp else { continue };
if current_hash != prev_hash && len == prev_len && mtime == prev_mtime {
edits.push(path.clone());
}
}
edits.sort();
edits
}
}
#[cfg(test)]
mod tests {
use super::*;
@@ -282,6 +306,60 @@ mod tests {
assert_ne!(before, after, "a same-length, same-mtime content change must invalidate the fingerprint");
}
#[test]
fn mtime_preserved_edits_only_reports_lying_timestamps() {
fn stamp(len: u64, hash: &str) -> Option<Stamp> {
Some((len, SystemTime::UNIX_EPOCH + std::time::Duration::from_secs(42), Some(hash.into())))
}
fn prints(
previous: BTreeMap<PathBuf, Option<Stamp>>,
current: BTreeMap<PathBuf, Option<Stamp>>,
) -> Vec<PathBuf> {
let previous = LaunchFingerprint { version: 2, parameters: String::new(), files: previous };
let current = LaunchFingerprint { version: 2, parameters: String::new(), files: current };
previous.mtime_preserved_edits(&current)
}
// Same length and mtime, different content: the builder would skip it.
assert_eq!(
prints([("a.java".into(), stamp(5, "old"))].into(), [("a.java".into(), stamp(5, "new"))].into()),
vec![PathBuf::from("a.java")]
);
// A normal edit bumps the mtime: the builder already sees it.
let mut touched = stamp(5, "new").unwrap();
touched.1 += std::time::Duration::from_secs(1);
assert!(
prints([("a.java".into(), stamp(5, "old"))].into(), [("a.java".into(), Some(touched))].into()).is_empty()
);
// Added, deleted, and hash-less inputs (directories, files outside the
// project) carry no comparable content: never reported.
assert!(
prints(
[("gone.java".into(), stamp(5, "old")), ("dir".into(), None)].into(),
[("new.java".into(), stamp(5, "new")), ("dir".into(), None)].into()
)
.is_empty()
);
}
#[test]
fn lying_source_is_found_against_a_real_fingerprint() {
let dir = tempfile::tempdir().unwrap();
let root = dir.path();
let model = fixture(root);
let source = root.join("lib/src/main/kotlin/Channel.kt");
let before = fingerprint(root, &model).unwrap();
let mtime = fs::metadata(&source).unwrap().modified().unwrap();
// Same 5-byte length, different content, preserved mtime.
write(root, "lib/src/main/kotlin/Channel.kt", "valve");
fs::File::options().write(true).open(&source).unwrap().set_modified(mtime).unwrap();
let after = fingerprint(root, &model).unwrap();
assert_eq!(before.mtime_preserved_edits(&after), [PathBuf::from("lib/src/main/kotlin/Channel.kt")]);
// A normal edit (mtime advances) needs no repair.
write(root, "lib/src/main/kotlin/Channel.kt", "valve!");
let normal = fingerprint(root, &model).unwrap();
assert!(before.mtime_preserved_edits(&normal).is_empty());
}
#[test]
fn profile_options_jdk_goal_and_wrapper_invalidate_without_using_class_outputs() {
let dir = tempfile::tempdir().unwrap();
@@ -368,7 +368,9 @@ fn check(dir: &Path) -> Result<(), String> {
let manifest = Manifest::load(&dir)?;
println!("{} {} — {}", preview.id, preview.version, preview.name);
if manifest.legacy {
println!(" note: plugin.toml uses the 0.1 format (`openidea-api`); see plugins/README.md to move to 0.2");
println!(
" note: plugin.toml uses the 0.1 format (`openidea-api`); its code no longer runs: rebuild it against the 1.x SDK and declare `engines.openidea` (see plugins/README.md)"
);
}
if let Some(wasm) = &manifest.wasm {
let engine = PluginEngine::new()?;
+76 -1
View File
@@ -88,8 +88,28 @@ impl Session {
Ok(None)
}
};
let before = fingerprint()?;
let mut before = fingerprint()?;
let previous = fs::read(&record).ok().and_then(|bytes| serde_json::from_slice::<Outputs>(&bytes).ok());
// Timestamp-incremental builders (Maven, the Kotlin plugin) skip sources whose
// mtime did not advance. An external edit that preserves both length and mtime
// invalidates the fingerprint but would still compile to nothing below, and the
// stale outputs would be re-recorded as fresh. Re-stamp those sources first so
// the rebuild sees them; then re-scan so the record written afterwards matches.
let restamp: Vec<PathBuf> = match (before.as_ref(), previous.as_ref().and_then(|o| o.fingerprint.as_ref())) {
(Some(current), Some(recorded)) => recorded.mtime_preserved_edits(current),
_ => Vec::new(),
};
if !restamp.is_empty() {
let now = std::time::SystemTime::now();
for rel in &restamp {
let path = root.join(rel);
if let Err(error) = fs::File::options().write(true).open(&path).and_then(|file| file.set_modified(now))
{
tracing::debug!(path = %path.display(), %error, "Cannot re-stamp externally edited source");
}
}
before = fingerprint()?;
}
if let Some(outputs) = previous.as_ref()
&& policy != BuildPolicy::Rebuild
&& outputs.reusable(&before, &snapshots)
@@ -234,6 +254,22 @@ mod tests {
ide_build::load(root, &root.join("cache"), None)
}
/// Like [`fixture`], but the build script skips sources whose mtime did not advance,
/// mirroring Maven's (and the Kotlin plugin's) timestamp-incremental compilation.
fn fixture_timestamp_incremental(root: &Path) -> ide_build::BuildModel {
fs::write(
root.join("pom.xml"),
r#"<project><groupId>demo</groupId><artifactId>app</artifactId><version>1</version></project>"#,
)
.unwrap();
let script = root.join("mvnw");
fs::write(&script, "#!/bin/sh\necho build \"$@\" >> calls\nif [ -f src/main/kotlin/broken ]; then exit 1; fi\nmkdir -p target/classes\nif [ ! -f target/classes/Main.class ] || [ src/main/kotlin/Main.kt -nt target/classes/Main.class ]; then\ncp src/main/kotlin/Main.kt target/classes/Main.class\nfi\n").unwrap();
fs::set_permissions(&script, fs::Permissions::from_mode(0o755)).unwrap();
fs::create_dir_all(root.join("src/main/kotlin")).unwrap();
fs::write(root.join("src/main/kotlin/Main.kt"), "first").unwrap();
ide_build::load(root, &root.join("cache"), None)
}
#[test]
fn rebuild_bypasses_snapshots_and_error_continuation_retains_previous_classes() {
let dir = tempfile::tempdir().unwrap();
@@ -357,6 +393,45 @@ mod tests {
assert_eq!(fs::read_to_string(rebuilt.snapshot.join("0/Main.class")).unwrap(), "other");
}
#[test]
fn restart_after_mtime_preserving_external_edit_actually_recompiles() {
// The content hash triggers a rebuild for a same-length, same-mtime external
// edit, but a timestamp-incremental builder (real Maven) would still compile
// nothing and the stale snapshot would be re-recorded as fresh. Dropping the
// session simulates the IDE restart from the report.
let dir = tempfile::tempdir().unwrap();
let root = dir.path();
let model = fixture_timestamp_incremental(root);
let cache = root.join("cache");
let cancel = CancellationToken::new();
let mut session = Session::new(cache.clone());
let first = session.build(root, &model, root, BuildOptions::default(), None, &cancel).unwrap();
assert_eq!(fs::read_to_string(first.snapshot.join("0/Main.class")).unwrap(), "first");
// Pin the old output's mtime so the `-nt` check below is deterministic even on
// coarse-granularity filesystems.
fs::File::options()
.write(true)
.open(root.join("target/classes/Main.class"))
.unwrap()
.set_modified(std::time::SystemTime::UNIX_EPOCH + std::time::Duration::from_secs(42))
.unwrap();
drop(session);
let source = root.join("src/main/kotlin/Main.kt");
let mtime = fs::metadata(&source).unwrap().modified().unwrap();
fs::write(&source, "other").unwrap(); // 5 bytes, same length as "first"
fs::File::options().write(true).open(&source).unwrap().set_modified(mtime).unwrap();
let mut session = Session::new(cache.clone());
let rebuilt = session.build(root, &model, root, BuildOptions::default(), None, &cancel).unwrap();
assert_ne!(rebuilt.snapshot, first.snapshot);
assert_eq!(
fs::read_to_string(rebuilt.snapshot.join("0/Main.class")).unwrap(),
"other",
"the re-stamped source must actually recompile instead of re-recording stale outputs"
);
}
#[test]
fn session_drop_removes_superseded_snapshots_and_preserves_records_across_reopen() {
let dir = tempfile::tempdir().unwrap();
+42
View File
@@ -1228,6 +1228,48 @@ mod tests {
}
zip.finish().unwrap();
}
#[test]
fn explodes_wars_with_multibyte_path_components() {
let tmp = tempfile::tempdir().unwrap();
let war = tmp.path().join("app.war");
let entries = [
("bdms300-front/dist/img/ZC机柜.jpg", "reported case"),
("img/A机柜.jpg", "one ASCII byte"),
("img/😀.jpg", "four-byte character"),
("img/abé.jpg", "two-byte character"),
("ZC机柜/index.jsp", "multibyte directory"),
("img/普通.jpg", "character boundary"),
];
archive(&war, &entries);
for resources_only in [false, true] {
let destination = tmp.path().join(format!("out-{resources_only}"));
explode(&war, &destination, resources_only).unwrap();
for (name, text) in entries {
assert_eq!(fs::read_to_string(destination.join(name)).unwrap(), text, "{name}");
}
}
}
#[test]
fn rejects_war_path_traversal() {
let tmp = tempfile::tempdir().unwrap();
let war = tmp.path().join("app.war");
archive(&war, &[("../escape.txt", "outside")]);
assert!(explode(&war, &tmp.path().join("out"), false).is_err());
assert!(!tmp.path().join("escape.txt").exists());
}
#[cfg(windows)]
#[test]
fn rejects_war_reserved_device_paths() {
let tmp = tempfile::tempdir().unwrap();
let war = tmp.path().join("app.war");
for name in ["CON.txt", "NUL", "COM1.txt", "LPT¹", "img/aux.jpg"] {
archive(&war, &[(name, "device")]);
assert!(explode(&war, &tmp.path().join("out"), false).is_err(), "{name}");
}
}
#[test]
fn keeps_the_apps_own_context_descriptor() {
let tmp = tempfile::tempdir().unwrap();
+9 -2
View File
@@ -99,8 +99,15 @@ fn config(line: &str) -> RunConfig {
}
fn holder_port(dir: &std::path::Path) -> u16 {
wait_for("the holder's port", || std::fs::metadata(dir.join("port")).is_ok());
std::fs::read_to_string(dir.join("port")).unwrap().trim().parse().unwrap()
// The file appears as soon as the holder creates it, before the port is written and
// the handle closed: on a loaded machine (defender, indexer) the first reads can hit
// a sharing violation or a partial write, so keep reading until it parses.
let mut port = None;
wait_for("the holder's port", || {
port = std::fs::read_to_string(dir.join("port")).ok().and_then(|s| s.trim().parse().ok());
port.is_some()
});
port.unwrap()
}
#[test]
+41 -5
View File
@@ -544,14 +544,30 @@ pub fn prepare_maven_run(config: &mut RunConfig, root: &Path, s: &Settings) {
pub fn maven_local_repository(root: &Path, s: &Settings) -> Option<PathBuf> {
let m = &s.languages.java.maven;
setting_path(root, &m.local_repository).or_else(|| {
let text = std::fs::read_to_string(setting_path(root, &m.user_settings)?).ok()?;
let repo = crate::java::xml_value(&crate::lombok::strip_xml_comments(&text), "localRepository")?;
let home = crate::home_dir().map(|h| h.to_string_lossy().into_owned()).unwrap_or_default();
let repo = repo.replace("${user.home}", &home);
(!repo.is_empty() && !repo.contains("${")).then(|| PathBuf::from(repo))
let home = crate::home_dir().unwrap_or_default();
settings_local_repository(&setting_path(root, &m.user_settings)?, &home)
})
}
/// The local repository a plain `mvn` uses when nothing is configured: `<localRepository>` of
/// `~/.m2/settings.xml`, else `~/.m2/repository`.
pub fn default_maven_repository() -> Option<PathBuf> {
default_maven_repository_in(&crate::home_dir()?)
}
pub(crate) fn default_maven_repository_in(home: &Path) -> Option<PathBuf> {
let m2 = home.join(".m2");
Some(settings_local_repository(&m2.join("settings.xml"), home).unwrap_or_else(|| m2.join("repository")))
}
/// `<localRepository>` of a Maven `settings.xml`, with `${user.home}` expanded.
fn settings_local_repository(file: &Path, home: &Path) -> Option<PathBuf> {
let text = std::fs::read_to_string(file).ok()?;
let repo = crate::java::xml_value(&crate::lombok::strip_xml_comments(&text), "localRepository")?;
let repo = repo.replace("${user.home}", &home.to_string_lossy());
(!repo.is_empty() && !repo.contains("${")).then(|| PathBuf::from(repo))
}
fn xml_escape(text: &str) -> String {
text.replace('&', "&amp;").replace('<', "&lt;").replace('>', "&gt;")
}
@@ -1758,6 +1774,26 @@ mod gradle_runtime_tests {
mod maven_linkage_tests {
use super::*;
#[test]
fn default_maven_repository_reads_the_user_settings() {
let tmp = tempfile::tempdir().unwrap();
let home = tmp.path();
assert_eq!(default_maven_repository_in(home), Some(home.join(".m2/repository")));
std::fs::create_dir_all(home.join(".m2")).unwrap();
let settings = home.join(".m2/settings.xml");
std::fs::write(
&settings,
"<settings><!-- <localRepository>/old</localRepository> --><localRepository>/repo</localRepository></settings>",
)
.unwrap();
assert_eq!(default_maven_repository_in(home), Some(PathBuf::from("/repo")));
std::fs::write(&settings, "<settings><localRepository>${user.home}/m2repo</localRepository></settings>")
.unwrap();
assert_eq!(default_maven_repository_in(home), Some(home.join("m2repo")));
std::fs::write(&settings, "<settings><localRepository>${env.REPO}</localRepository></settings>").unwrap();
assert_eq!(default_maven_repository_in(home), Some(home.join(".m2/repository")));
}
#[test]
fn explicit_gradle_links_scope_jdt_import_and_unlink_all_disables_gradle() {
let mut cfg = custom_server("jdtls", &LanguageServerSettings::default());
+25 -9
View File
@@ -583,8 +583,9 @@ pub fn dependency_groups(root: &Path, s: &Settings, deps: &[BuildDependency]) ->
if deps.is_empty() {
return Vec::new();
}
let maven_repo = crate::apply::maven_local_repository(root, s).or_else(default_maven_repository);
let gradle_home = gradle_user_home();
let maven_repo = crate::apply::maven_local_repository(root, s).or_else(crate::apply::default_maven_repository);
// The home Gradle runs with, not just the one this process inherited.
let gradle_home = crate::apply::setting_path(root, &s.languages.java.gradle.user_home).or_else(gradle_user_home);
let files: Vec<PathBuf> = JAVA_MANIFESTS.iter().map(|n| root.join(n)).collect();
let key = format!("{maven_repo:?}\n{gradle_home:?}\n{deps:?}");
cached(root, "buildDependencies", key, &files, || {
@@ -592,12 +593,8 @@ pub fn dependency_groups(root: &Path, s: &Settings, deps: &[BuildDependency]) ->
})
}
/// `~/.m2/repository` (Settings › Maven › Local repository overrides it).
fn default_maven_repository() -> Option<PathBuf> {
Some(crate::home_dir()?.join(".m2/repository"))
}
/// `$GRADLE_USER_HOME`, else `~/.gradle`.
/// `$GRADLE_USER_HOME`, else `~/.gradle`: what Gradle uses when Settings › Gradle ›
/// user home is empty.
fn gradle_user_home() -> Option<PathBuf> {
std::env::var_os("GRADLE_USER_HOME").map(PathBuf::from).or_else(|| Some(crate::home_dir()?.join(".gradle")))
}
@@ -652,7 +649,7 @@ pub fn published_version_catalog(root: &Path, s: &Settings, coordinates: &str) -
found.into_iter().next()
});
cached.or_else(|| {
let repo = crate::apply::maven_local_repository(root, s).or_else(default_maven_repository)?;
let repo = crate::apply::maven_local_repository(root, s).or_else(crate::apply::default_maven_repository)?;
let path = repo.join(d.group.replace('.', "/")).join(&d.artifact).join(&d.version).join(&file);
path.is_file().then_some(path)
})
@@ -1172,6 +1169,25 @@ mod go_cache_tests {
assert_eq!(published_version_catalog(root, &s, missing), None, "{missing}");
}
}
#[test]
fn dependency_jars_come_from_the_configured_gradle_home() {
let tmp = tempfile::tempdir().unwrap();
let root = tmp.path();
let mut s = Settings::default();
s.languages.java.gradle.user_home = root.join("gradle").to_string_lossy().into_owned();
// Only in the home Gradle runs with, not in `$GRADLE_USER_HOME` / `~/.gradle`.
let jar = root.join("gradle/caches/modules-2/files-2.1/com.acme/lib/1.0/abc123/lib-1.0.jar");
std::fs::create_dir_all(jar.parent().unwrap()).unwrap();
std::fs::write(&jar, b"jar").unwrap();
let deps =
[BuildDependency { gradle: true, group: "com.acme".into(), artifact: "lib".into(), version: "1.0".into() }];
let groups = dependency_groups(root, &s, &deps);
assert_eq!(groups.len(), 1, "{groups:?}");
assert_eq!(groups[0].name, "Gradle: com.acme:lib:1.0");
assert_eq!(groups[0].roots[0].name, "lib-1.0.jar");
}
#[test]
fn vendor_roots_include_local_replacements_once() {
let dir = tempfile::tempdir().unwrap();
+1 -26
View File
@@ -85,7 +85,7 @@ pub fn lombok_agent(
return LombokAgent::Found { jar, version: None };
}
let home = home_dir();
let maven_repo = maven_repo.or_else(|| maven_repository(home.as_deref()));
let maven_repo = maven_repo.or_else(|| home.as_deref().and_then(crate::apply::default_maven_repository_in));
if !uses_lombok(root, maven_repo.as_deref()) {
return LombokAgent::Unused;
}
@@ -268,17 +268,6 @@ fn sources_import_lombok(root: &Path) -> bool {
.any(|e| imports_lombok(e.path()))
}
/// `<localRepository>` of `~/.m2/settings.xml`, else `~/.m2/repository`.
fn maven_repository(home: Option<&Path>) -> Option<PathBuf> {
let m2 = home?.join(".m2");
let custom = std::fs::read_to_string(m2.join("settings.xml"))
.ok()
.and_then(|s| crate::java::xml_value(&strip_xml_comments(&s), "localRepository"))
.filter(|v| !v.is_empty() && !v.contains("${"))
.map(PathBuf::from);
Some(custom.unwrap_or_else(|| m2.join("repository")))
}
pub(crate) fn strip_xml_comments(text: &str) -> String {
let mut out = String::with_capacity(text.len());
let mut rest = text;
@@ -560,18 +549,4 @@ mod tests {
let plain = lombok_agent(tmp.path(), agent_in_args(["-javaagent:/opt/lombok.jar"]), None, None);
assert_eq!(plain.problem(), None, "a user agent is never reported missing");
}
#[test]
fn maven_settings_local_repository() {
let tmp = tempfile::tempdir().unwrap();
let home = tmp.path();
assert_eq!(maven_repository(Some(home)), Some(home.join(".m2/repository")));
fs::create_dir_all(home.join(".m2")).unwrap();
fs::write(
home.join(".m2/settings.xml"),
"<settings><!-- <localRepository>/old</localRepository> --><localRepository>/repo</localRepository></settings>",
)
.unwrap();
assert_eq!(maven_repository(Some(home)), Some(PathBuf::from("/repo")));
}
}
+18
View File
@@ -443,6 +443,24 @@ mod tests {
}
}
#[test]
fn extracts_zip_with_multibyte_path_components() {
let tmp = tempfile::tempdir().unwrap();
let archive = tmp.path().join("tool.zip");
let entries: &[(&str, &[u8], u32)] = &[
("bdms300-front/dist/img/ZC机柜.jpg", b"reported case", 0o644),
("A机柜/file", b"directory", 0o644),
("abé.txt", b"two-byte character", 0o644),
("😀.txt", b"four-byte character", 0o644),
];
zip_file(&archive, entries);
let dest = tmp.path().join("out");
extract(&archive, ArchiveKind::Zip, &dest, "x", &CancellationToken::new()).unwrap();
for (name, data, _) in entries {
assert_eq!(fs::read(dest.join(name)).unwrap(), *data, "{name}");
}
}
#[test]
fn extracts_zip_with_modes_and_nested_dirs() {
let tmp = tempfile::tempdir().unwrap();
+443
View File
@@ -0,0 +1,443 @@
{
"$schema": "https://mintlify.com/docs.json",
"theme": "mint",
"name": "OpenIDEA",
"description": "The IDE you know, rebuilt in Rust.",
"colors": {
"primary": "#3871E1",
"light": "#8FB3FF",
"dark": "#3871E1"
},
"favicon": "/favicon.svg",
"logo": {
"light": "/logo/light.svg",
"dark": "/logo/dark.svg"
},
"appearance": {
"default": "dark"
},
"navbar": {
"links": [
{
"label": "GitHub",
"href": "https://github.com/l0ng-ai/openidea"
}
],
"primary": {
"type": "button",
"label": "Download",
"href": "https://github.com/l0ng-ai/openidea/releases/latest"
}
},
"navigation": {
"languages": [
{
"language": "en",
"default": true,
"tabs": [
{
"tab": "Guide",
"groups": [
{
"group": "Get started",
"pages": ["en/index", "en/guide/install", "en/guide/quickstart", "en/guide/migrate", "en/roadmap"]
},
{
"group": "Editing",
"pages": [
"en/guide/editor",
"en/guide/code-intelligence",
"en/guide/refactoring",
"en/guide/inspections",
"en/guide/search",
"en/guide/templates",
"en/guide/vim",
"en/guide/scratch-todo"
]
},
{
"group": "Run and debug",
"pages": [
"en/guide/run",
"en/guide/build-tools",
"en/guide/testing",
"en/guide/debugging",
"en/guide/coverage",
"en/guide/profiler"
]
},
{
"group": "Tools",
"pages": [
"en/guide/vcs",
"en/guide/github",
"en/guide/terminal",
"en/guide/database",
"en/guide/http-client",
"en/guide/services",
"en/guide/notebook",
"en/guide/android"
]
},
{
"group": "AI",
"pages": ["en/guide/agents", "en/guide/mcp"]
},
{
"group": "Remote development",
"pages": ["en/guide/remote/ssh", "en/guide/remote/server", "en/guide/remote/containers"]
},
{
"group": "Customize",
"pages": ["en/guide/settings", "en/guide/keymap", "en/guide/themes", "en/guide/locale"]
},
{
"group": "Reference",
"pages": [
"en/reference/cli",
"en/reference/shortcuts",
"en/reference/settings",
"en/reference/data-directories",
"en/reference/troubleshooting"
]
}
]
},
{
"tab": "Languages",
"groups": [
{
"group": "Overview",
"pages": ["en/languages/index"]
},
{
"group": "Languages",
"pages": [
"en/languages/java",
"en/languages/python",
"en/languages/go",
"en/languages/javascript",
"en/languages/php",
"en/languages/ruby",
"en/languages/cpp",
"en/languages/csharp",
"en/languages/rust",
"en/languages/sql"
]
},
{
"group": "Game engines",
"pages": ["en/languages/unity", "en/languages/godot"]
},
{
"group": "Extend",
"pages": ["en/languages/adding"]
}
]
},
{
"tab": "Plugins",
"groups": [
{
"group": "Get started",
"pages": ["en/plugins/index", "en/plugins/quickstart", "en/plugins/examples"]
},
{
"group": "Concepts",
"pages": [
"en/plugins/architecture",
"en/plugins/manifest",
"en/plugins/capabilities",
"en/plugins/lifecycle",
"en/plugins/versioning"
]
},
{
"group": "Extension points",
"pages": [
"en/plugins/guides/commands",
"en/plugins/guides/language-features",
"en/plugins/guides/diagnostics",
"en/plugins/guides/views",
"en/plugins/guides/file-actions",
"en/plugins/guides/run-targets",
"en/plugins/guides/indexing",
"en/plugins/guides/database",
"en/plugins/guides/mcp",
"en/plugins/guides/themes",
"en/plugins/guides/icon-themes"
]
},
{
"group": "Develop",
"pages": ["en/plugins/testing"]
},
{
"group": "Publish",
"pages": [
"en/plugins/publishing/packaging",
"en/plugins/publishing/marketplace",
"en/plugins/publishing/self-hosted",
"en/plugins/publishing/policy"
]
},
{
"group": "Reference",
"pages": ["en/plugins/api/index"]
}
]
},
{
"tab": "Contribute",
"groups": [
{
"group": "Get started",
"pages": ["en/contribute/setup", "en/contribute/workflow", "en/contribute/security"]
},
{
"group": "Architecture",
"pages": [
"en/contribute/architecture/overview",
"en/contribute/architecture/boundaries",
"en/contribute/architecture/ipc",
"en/contribute/architecture/crates",
"en/contribute/architecture/frontend",
"en/contribute/architecture/code-intelligence",
"en/contribute/architecture/plugin-host",
"en/contribute/architecture/remote"
]
},
{
"group": "Guidelines",
"pages": [
"en/contribute/guidelines/ui",
"en/contribute/guidelines/i18n",
"en/contribute/guidelines/code-style"
]
},
{
"group": "Testing",
"pages": ["en/contribute/testing/unit-e2e", "en/contribute/testing/smoke", "en/contribute/testing/perf"]
}
]
},
{
"tab": "Changelog",
"groups": [
{
"group": "Changelog",
"pages": ["en/changelog"]
}
]
}
]
},
{
"language": "zh",
"tabs": [
{
"tab": "指南",
"groups": [
{
"group": "开始",
"pages": ["zh/index", "zh/guide/install", "zh/guide/quickstart", "zh/guide/migrate", "zh/roadmap"]
},
{
"group": "编辑",
"pages": [
"zh/guide/editor",
"zh/guide/code-intelligence",
"zh/guide/refactoring",
"zh/guide/inspections",
"zh/guide/search",
"zh/guide/templates",
"zh/guide/vim",
"zh/guide/scratch-todo"
]
},
{
"group": "运行与调试",
"pages": [
"zh/guide/run",
"zh/guide/build-tools",
"zh/guide/testing",
"zh/guide/debugging",
"zh/guide/coverage",
"zh/guide/profiler"
]
},
{
"group": "工具",
"pages": [
"zh/guide/vcs",
"zh/guide/github",
"zh/guide/terminal",
"zh/guide/database",
"zh/guide/http-client",
"zh/guide/services",
"zh/guide/notebook",
"zh/guide/android"
]
},
{
"group": "AI",
"pages": ["zh/guide/agents", "zh/guide/mcp"]
},
{
"group": "远程开发",
"pages": ["zh/guide/remote/ssh", "zh/guide/remote/server", "zh/guide/remote/containers"]
},
{
"group": "定制",
"pages": ["zh/guide/settings", "zh/guide/keymap", "zh/guide/themes", "zh/guide/locale"]
},
{
"group": "参考",
"pages": [
"zh/reference/cli",
"zh/reference/shortcuts",
"zh/reference/settings",
"zh/reference/data-directories",
"zh/reference/troubleshooting"
]
}
]
},
{
"tab": "语言",
"groups": [
{
"group": "总览",
"pages": ["zh/languages/index"]
},
{
"group": "主要语言",
"pages": [
"zh/languages/java",
"zh/languages/python",
"zh/languages/go",
"zh/languages/javascript",
"zh/languages/php",
"zh/languages/ruby",
"zh/languages/cpp",
"zh/languages/csharp",
"zh/languages/rust",
"zh/languages/sql"
]
},
{
"group": "游戏引擎",
"pages": ["zh/languages/unity", "zh/languages/godot"]
},
{
"group": "扩展",
"pages": ["zh/languages/adding"]
}
]
},
{
"tab": "插件",
"groups": [
{
"group": "开始",
"pages": ["zh/plugins/index", "zh/plugins/quickstart", "zh/plugins/examples"]
},
{
"group": "概念",
"pages": [
"zh/plugins/architecture",
"zh/plugins/manifest",
"zh/plugins/capabilities",
"zh/plugins/lifecycle",
"zh/plugins/versioning"
]
},
{
"group": "扩展点",
"pages": [
"zh/plugins/guides/commands",
"zh/plugins/guides/language-features",
"zh/plugins/guides/diagnostics",
"zh/plugins/guides/views",
"zh/plugins/guides/file-actions",
"zh/plugins/guides/run-targets",
"zh/plugins/guides/indexing",
"zh/plugins/guides/database",
"zh/plugins/guides/mcp",
"zh/plugins/guides/themes",
"zh/plugins/guides/icon-themes"
]
},
{
"group": "开发",
"pages": ["zh/plugins/testing"]
},
{
"group": "发布与市场",
"pages": [
"zh/plugins/publishing/packaging",
"zh/plugins/publishing/marketplace",
"zh/plugins/publishing/self-hosted",
"zh/plugins/publishing/policy"
]
},
{
"group": "参考",
"pages": ["zh/plugins/api/index"]
}
]
},
{
"tab": "参与开发",
"groups": [
{
"group": "开始",
"pages": ["zh/contribute/setup", "zh/contribute/workflow", "zh/contribute/security"]
},
{
"group": "架构",
"pages": [
"zh/contribute/architecture/overview",
"zh/contribute/architecture/boundaries",
"zh/contribute/architecture/ipc",
"zh/contribute/architecture/crates",
"zh/contribute/architecture/frontend",
"zh/contribute/architecture/code-intelligence",
"zh/contribute/architecture/plugin-host",
"zh/contribute/architecture/remote"
]
},
{
"group": "规范",
"pages": [
"zh/contribute/guidelines/ui",
"zh/contribute/guidelines/i18n",
"zh/contribute/guidelines/code-style"
]
},
{
"group": "测试",
"pages": ["zh/contribute/testing/unit-e2e", "zh/contribute/testing/smoke", "zh/contribute/testing/perf"]
}
]
},
{
"tab": "更新日志",
"groups": [
{
"group": "更新日志",
"pages": ["zh/changelog"]
}
]
}
]
}
]
},
"footer": {
"socials": {
"github": "https://github.com/l0ng-ai/openidea",
"discord": "https://discord.gg/2Zy4yn7gP"
}
}
}
+39
View File
@@ -0,0 +1,39 @@
---
title: "Changelog"
description: "What changed in each OpenIDEA release."
---
Full release notes, downloads and checksums are on [GitHub Releases](https://github.com/l0ng-ai/openidea/releases). OpenIDEA updates itself: use **Check for Updates**.
<Update label="0.1.40" description="2026-11-09">
Adds an opt-in built-in MCP server, Ruby debugging, .NET workflows, Docker dashboards in Services, attached projects and a shared Java language server across builds, makes switching editor tabs and long agent conversations faster, and ships Plugin API 1.4.
[Release notes](https://github.com/l0ng-ai/openidea/releases/tag/v0.1.40)
</Update>
<Update label="0.1.39" description="2026-11-08">
Adds dev containers, an editable database result grid, Tomcat Server run configurations, a Project Structure dialog, JetBrains settings import and a rebuilt agent conversation view, and ships Plugin API 1.3.
[Release notes](https://github.com/l0ng-ai/openidea/releases/tag/v0.1.39)
</Update>
<Update label="0.1.38" description="2026-11-06">
Adds Docker and Compose run targets, Oracle data sources, Astro support, PHP Remote Debug with PHPStan and PHP-CS-Fixer, Ruby test runs with coverage, a choice of update source with a China mirror, and ships Plugin API 1.2.
[Release notes](https://github.com/l0ng-ai/openidea/releases/tag/v0.1.38)
</Update>
<Update label="0.1.37" description="2026-11-05">
Opens WSL projects directly, debugs Flutter apps, adds Git Blame in diffs and an Android Logcat tool window, and ships Plugin API 1.1.
[Release notes](https://github.com/l0ng-ai/openidea/releases/tag/v0.1.37)
</Update>
<Update label="0.1.0 – 0.1.36" description="2026-10-22 – 2026-11-04">
The first public releases. See [GitHub Releases](https://github.com/l0ng-ai/openidea/releases) for each version's
changes.
</Update>
@@ -0,0 +1,56 @@
---
title: "Rust or UI?"
description: "Which work belongs in the Rust backend and which in the frontend, how to decide for a new feature, and examples from the codebase."
---
The short rule: **the frontend presents and interacts; Rust owns data, I/O and anything that must also work on a remote machine.** The frontend never touches the disk, starts a process or opens a network connection itself; it asks the backend through typed commands.
## What belongs in Rust
| Kind of work | Why | Examples |
| ----------------------------------- | --------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| File system access | One place for encodings, watching, ignore rules and remote projects. | `ide-vfs` ("The UI never touches the disk directly"): listing, loading/saving with encoding detection, watching. |
| The authoritative copy of documents | Language servers, refactorings, search and plugins need the same text the editor shows. | `ide-text` keeps a rope per open document. The UI sends CodeMirror change sets tagged with their base version; a stale one is rejected with `ErrorKind::OutOfSync` and the UI resyncs. |
| Processes and protocols | Child processes, pipes and JSON-RPC framing need a real OS. | `ide-lsp`, `ide-dap`, `ide-jsonrpc`, `ide-run`, `ide-terminal` (PTY), `ide-agent` (ACP). |
| Project-wide work | Walking thousands of files must not block a UI thread. | `ide-index` (files and symbols, persisted in redb), `ide-search` (ripgrep engine crates), `ide-analysis` (duplicates, structural search), `ide-spell`. |
| VCS and external tools | Behaviour has to match the command line. | `ide-vcs`: gitoxide for reads, the `git` CLI for writes so hooks, credentials and signing behave as usual. `ide-docker`, `ide-android`, `ide-github` drive their CLIs. |
| Security decisions | The UI can be bypassed; the backend can't. | Project trust is checked for every command in the invoke handler; plugin capabilities and grants live in `ide-extension`; plugin UI contributions are validated in Rust before the client sees them. |
| Anything a remote window needs | The remote server has no webview. | The implementation lives in `ide-host::ops` so `openidea-server` runs the same code. |
Work in Rust also follows the threading rules: long work never blocks the UI thread or a Tauri command thread. Use `spawn_blocking` or rayon and honour the request's `CancellationToken`.
## What belongs in the frontend
| Kind of work | Examples |
| ---------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Rendering, layout, focus | Workbench, tool windows, popups, trees (`src/workbench/`, `src/ui/`). |
| Input and commands | The action registry, keymaps and the keyboard dispatcher, `when` clauses over context keys (`src/platform/`). |
| Editor state and editing behaviour | CodeMirror 6 views and state, CodeMirror language modes for highlighting, the Vim mode (`src/features/vim`), Emmet expansion (`src/features/templates/emmet`), merge views. |
| Presentation of backend results | Merging and ranking for display: Search Everywhere merges the IDE's own symbol index with language-server workspace symbols (`mergeSymbols` in `src/features/search/everywhere/`). |
| Rendering formats | Markdown and Mermaid rendering in viewers and the agent chat. |
| Client preferences | Keymap, appearance, editor fonts and colour scheme stay on the client even in a remote window (`src/platform/clientSettings.ts`). |
## Splitting one feature across both
Many features have a cheap, I/O-heavy half in Rust and a light, data-shaping half in the UI. Spring request mappings in the HTTP Client are a good example:
- Rust (`crates/ide-host/src/ops/routes.rs`): `spring_route_sources` walks the project with the usual ignore rules and returns the text of Java and Kotlin files that could declare a route, with file-count and byte caps. "The UI owns the annotation parser and the index it feeds, so this side stays a cheap scan."
- UI (`src/features/http-client/spring/routeIndex.ts`): parses the annotations, builds the request-path index, registers it as an endpoint provider, and drops the cache on `fsChanged` events for `.java` / `.kt` files.
Another: tree-sitter structure queries (`ide-syntax`) run in Rust against the cached syntax tree, while the editor decides when to use them as a fallback because the language server is unavailable.
## Deciding for a new feature
Put it in Rust if any of these is true:
- it reads or writes files, starts a process, or talks to the network;
- it must work in a remote window (the project and its tools are on the other machine);
- it scans more than the open documents, or can take more than a frame;
- it enforces a security property (trust, permissions, validation of untrusted input);
- a plugin, the MCP server or another backend crate needs the result.
Keep it in the frontend if it is about how something looks or responds to input, and works from data the backend already provides.
When it lands in Rust, place it by domain: logic in the crate that owns the domain (or a new `crates/ide-<domain>`), the shared command implementation in `ide-host::ops`, and a thin adapter in `src-tauri/src/commands/<domain>.rs`. No domain logic in `src-tauri`.
When it lands in the frontend, it is a feature folder under `src/features/<name>/` that registers actions and contributions. It must not import another feature; see [Frontend](/en/contribute/architecture/frontend).
@@ -0,0 +1,95 @@
---
title: "Code intelligence"
description: "Where completion, navigation and other language features come from: language servers, the IDE's own index, tree-sitter syntax and plugins, and how their results are combined."
---
OpenIDEA's language features come from four sources, layered so that something useful is always available and the most precise source wins when it can answer.
| Source | Crates / code | Gives |
| -------------------------- | --------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Language servers (LSP)** | `ide-lsp`, `ide-jsonrpc`, `ide-tools` (installer) | Semantic features: completion, go to definition, find usages, hover, diagnostics and quick fixes, rename, formatting, inlay hints, semantic tokens… |
| **The IDE's own index** | `ide-index` | Files and symbols of the whole project: Go to File / Class / Symbol and Search Everywhere, also before a server is ready. |
| **Tree-sitter syntax** | `ide-language`, `ide-syntax`, `ide-analysis`, `ide-spell` | Outline, folding, highlighting for languages without an editor mode, structural fallbacks, duplicates and structural search, spell checking. |
| **Plugins** | `ide-host::plugins` | Providers for the languages a plugin declares: completion, code actions, definitions, line markers, hover, inlay hints, semantic tokens, diagnostics, indexers. |
The editor itself adds CodeMirror language modes for immediate highlighting (`ui-mode` in a language definition), before any backend answer arrives.
## Language definitions
Every language is a TOML file in `languages/` (embedded at build time; users can add more in their config directory). It names detection, comments, the tree-sitter grammar, the editor mode, and the language servers and debugger to start:
```toml languages/rust.toml
id = "rust"
name = "Rust"
extensions = ["rs"]
line-comment = "//"
block-comment = ["/*", "*/"]
ui-mode = "Rust"
tree-sitter = "rust"
[[language-servers]]
name = "rust-analyzer"
command = "rust-analyzer"
root-markers = ["Cargo.toml", "rust-project.json"]
install-hint = "rustup component add rust-analyzer"
[debugger]
adapter = "codelldb"
command = "codelldb"
```
A language can list several `[[language-servers]]` (for example `vtsls` and `eslint` for TypeScript). `ide-tools` has a registry of installable servers keyed by the same `name`, and installs them into the app's managed tools directory without `sudo`.
## Language servers
`ide-lsp` is a language server manager:
- **Lazy start.** A server starts per `(server name, workspace root)` when a document of a matching language opens. The root comes from `root-markers`.
- **Document sync.** The backend keeps the authoritative copy of each open document (`ide-text`). The editor sends change sets tagged with their base version; the backend applies them and forwards `didChange`. A stale change set is rejected with `outOfSync` and the editor resyncs.
- **IDE types only.** Every request takes IDE coordinates (UTF-16, 0-based) and returns types from `ide_lsp::types`, never `lsp-types`, so the IPC surface stays stable whatever the server sends.
- **Capabilities.** A request a server doesn't support returns `unavailable` with a message such as "jdtls doesn't support inline completion". The editor backs off per document and per feature (`src/editor/lsp/availability.ts`), so a server lacking one capability doesn't switch off the others.
- **Trust.** Servers start only in trusted projects: they execute project build scripts.
Where plain LSP isn't enough, the IDE extends the server it ships. For Java, JDT LS gets app-owned extension bundles from `java/`: `java-usages` (semantic usages with read/write access, configuration navigation), `kotlin-bridge` (Kotlin libraries on the Java classpath), and a patched Lombok agent. They are embedded by `ide-sdk` / `ide-tools`. When the `java-usages` command is missing or fails, find usages falls back to standard LSP references.
## The IDE's own index
`ide-index` indexes the project's files and their symbols in the background. Symbols come from tree-sitter tags (`ide-language`), so the index works for every language with a bundled grammar, without a language server. It is persisted in redb and queried with nucleo's fuzzy matcher.
Indexing never blocks: queries answer from whatever is indexed so far, and `IdeEvent::IndexState` tells the UI when indexing completes (the "dumb mode" indicator).
## Tree-sitter syntax
`ide-language` bundles the tree-sitter grammars and keeps one syntax tree per open document: parsed lazily on the first query, re-parsed incrementally after edits, dropped on close. On top of it:
- outline (Structure), folding, symbol tags, and highlighting for languages that have no CodeMirror mode;
- `ide-syntax`, the **structure service**: node and expression lookup, syntax errors, scopes and locals, identifier occurrences, structural ranges.
The structure service is a **fallback**: features use it only when the language server is unavailable, not ready or failing, or when a refactoring came back empty over code that doesn't parse. Its answers are purely syntactic and only claim what is certain — ambiguous cases are flagged (`certain: false`, `exact: false`, candidate lists) rather than guessed.
`ide-analysis` (Locate Duplicates, structural search and replace) and `ide-spell` (identifiers, comments and strings) also work on these trees.
## How results are combined
| Where | What is merged |
| ------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `ide-lsp` | Results of all servers serving a language. Locations are de-duplicated by file and start; rename edits are merged into one workspace edit. |
| `ide-host::ops::lsp` | Plugin providers' results are added to the servers': plugin completion items are appended to the server's list, plugin locations and highlights are added if not already present. Plugins are called only when their declaration matches the document's language. |
| Search Everywhere (`src/features/search/everywhere/`) | Symbols from the IDE's index and the language servers' workspace symbols, merged and ranked in the UI. The index answers immediately; server results join when they arrive. |
| Editor navigation (`src/editor/navigationProviders.ts`) | Optional asynchronous providers contributed by features, versioned by the editor text so stale answers are dropped. |
## LSP instead of a PSI-like model
OpenIDEA does not have a unified, IDE-owned semantic model of the code (what IntelliJ calls PSI). Semantic knowledge — types, resolution, references — comes from each language's server. The IDE's own layers (index, tree-sitter structure) are syntactic.
What that means in practice:
- **Breadth comes from existing servers.** Supporting a language is mostly a language definition plus a server the IDE can install, not a parser and resolver written inside the IDE.
- **Depth is bounded by the server.** Find usages, rename, refactorings, inspections and quick fixes are as good as the server's implementation of the LSP request. Where a server lacks a feature, the IDE says so (`unavailable`) instead of inventing an answer.
- **No cross-language semantic model.** Refactorings that need to understand several languages at once, or structural search that matches on resolved types, are not possible on this architecture. The [roadmap](/en/roadmap) states this scope, and each [language page](/en/languages/index) lists its limits.
- **Extensions fill gaps per language.** When a server can be extended (JDT LS bundles above) or a plugin adds providers, the IDE goes further for that language without a general semantic model.
- **Syntactic fallbacks are conservative.** The tree-sitter layer never guesses semantics; it marks what it can't be sure of.
## Testing it
The language smoke tests (`crates/ide-host/tests/language_smoke.rs`) run the everyday checklist — completion, go to definition, find usages, hover, diagnostics with a quick fix, an intention, rename, formatting, parameter info, the Structure view, auto-import, Optimize Imports and moving a file — against each language's `fixtures/` project with its real server. See [Smoke tests](/en/contribute/testing/smoke).
@@ -0,0 +1,97 @@
---
title: "Crates"
description: "Every Rust crate in the workspace, what it owns, and the dependency rules between them."
---
The Cargo workspace is `crates/*` plus `src-tauri` (root `Cargo.toml`). Plugins and the guest SDK build for `wasm32-wasip2` in their own workspaces and are excluded.
## Rules
- **One domain per crate.** A feature crate owns its domain end to end: data model, I/O, protocol, and conversion to IPC types.
- **Shared types live in `ide-core`.** Anything that crosses a crate boundary or IPC (ids, `Pos`/`Range`, `IdeError`, `IdeEvent`, settings types) is defined there and derives `specta::Type`.
- **Never reach into another crate's internals.** Use its public API, or move the shared type into `ide-core`.
- **Convert errors at the crate edge** to `IdeError`.
- **`ide-host` assembles; `src-tauri` adapts.** `ide-host` wires the crates into a UI-less backend (`Host`, `Project`, `ops/`); `src-tauri` exposes it as Tauri commands. Neither is the place for domain logic that belongs in a feature crate.
Each crate's `lib.rs` opens with a module doc comment describing its contract; read it before changing the crate.
## Foundation
| Crate | Responsibility |
| --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `ide-core` | Shared vocabulary of every crate: ids, text coordinates (`text.rs`: 0-based lines, UTF-16 columns), `IdeError`/`IdeResult`, `IdeEvent` and `EventSink`, settings types, task cancellation. |
| `ide-text` | Rope-backed documents (the backend's authoritative copy of every open document), change sets with version checks (`OutOfSync`), UTF-16 ↔ byte coordinate conversion. |
| `ide-vfs` | File system access: directory listing, text loading/saving with encoding detection, watching, project walking. The UI never touches the disk directly. |
| `ide-workspace` | The project model: open documents, settings layering, recent projects, session state, local history. |
| `ide-jsonrpc` | `Content-Length` framed transport and a JSON-RPC 2.0 peer, shared by the LSP and DAP clients; newline-delimited JSON for ACP. |
## Code intelligence
| Crate | Responsibility |
| -------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `ide-language` | Language registry from `languages/*.toml` (embedded at build time), language detection, bundled tree-sitter grammars and the per-document syntax cache; tree-sitter outline, folding, highlights and symbol tags. |
| `ide-syntax` | Tree-sitter structure service: node/expression lookup, syntax errors, scopes and locals, identifier occurrences, structural ranges. Used when the language server is unavailable, not ready or failing. |
| `ide-lsp` | Language server manager: lazy start per server and workspace root, document sync, feature requests mapped to IDE types, merging results of several servers for one language. Includes the JDT LS integration. |
| `ide-index` | Background project index of files and symbols, persisted in redb and fuzzy-queried with nucleo. Queries answer from what is indexed so far; `IdeEvent::IndexState` reports completion. |
| `ide-search` | Find / Replace in Files on ripgrep's engine crates. |
| `ide-analysis` | Project-wide analyses over syntax trees: Locate Duplicates and Search / Replace Structurally. |
| `ide-spell` | Spell checking of comments, strings and declared names: bundled Hunspell en_US / en_GB, programming terms, user dictionaries. |
## Run, debug and build
| Crate | Responsibility |
| -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `ide-run` | Run configurations: detection, persistence, process lifecycle and output streaming. |
| `ide-dap` | Debug Adapter Protocol client: sessions, breakpoints, threads, frames, variables, evaluation. |
| `ide-build` | Maven and Gradle tool-window models (POM parsing, Gradle task lists, background sync) and Build Project for Maven, Gradle, Cargo, Go and TypeScript. |
| `ide-coverage` | Run with Coverage: launch plans for coverage tools and report parsers (lcov, Go cover profiles, JaCoCo XML). |
| `ide-profiler` | Run with Profiler: sampling-profiler launch plans and one call-tree model from their outputs. |
| `ide-sdk` | SDKs and toolchains: interpreter discovery (Python, Node.js, Rust, Go, TypeScript), virtualenvs, pip, applying the selected toolchain to language servers, runs and terminals. Embeds the JDT LS extension bundles `java-usages` and `kotlin-bridge` from `java/`. |
| `ide-tools` | Installer for language servers and debug adapters: registry, detection, download/extract into the managed tools directory, never needing `sudo`. Embeds the patched Lombok agent for JDT LS. |
| `ide-terminal` | Integrated terminal sessions over a native PTY (rendered by xterm.js in the UI). |
## Tools and integrations
| Crate | Responsibility |
| -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- |
| `ide-vcs` | Git: gitoxide for fast reads, the git CLI for writes. |
| `ide-github` | GitHub pull requests through the `gh` CLI. |
| `ide-db` | Database tools for SQLite, PostgreSQL and MySQL/MariaDB: data sources, connections, introspection, queries, table editing. |
| `ide-http` | HTTP Client: `.http` request files, environments, execution, response history; builds every HTTP client of the backend with the configured proxy. |
| `ide-docker` | Docker for the Services tool window through the `docker` CLI. |
| `ide-android` | Android devices and logcat through the `adb` CLI. |
| `ide-notebook` | Native notebook documents and owned Jupyter kernel sessions. |
| `ide-agent` | Coding agents over the Agent Client Protocol: tasks, sessions, worktrees and their timelines. |
| `ide-mcp` | The IDE's MCP server for AI agents outside the IDE: Streamable HTTP on `127.0.0.1`, its discovery file and the stdio bridge. |
| `ide-web` | The built-in static web server for project files on `127.0.0.1`, and detection / launching of installed browsers. |
## Remote
| Crate | Responsibility |
| ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `ide-ssh` | SSH for every remote feature: configurations and `~/.ssh/config` hosts, host-key trust, authentication, remote commands, SFTP, port forwarding, askpass for `ssh` / `git`. |
| `ide-remote` | SFTP deployment (mappings, upload, download, sync) and run targets (SSH, Docker, Compose). Not remote development itself. |
| `ide-host` | The IDE backend without a UI toolkit: projects per window, terminals, processes, settings, trust, plugins, MCP and the `ops/` command implementations. Also builds the `openidea-server` binary (remote development) and the `openidea-plugin` CLI (plugin starter templates in `templates.rs`). |
## Plugins
| Crate | Responsibility |
| --------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `ide-extension` | The plugin system's kernel, independent of the Wasm runtime: the `plugin.toml` manifest, capabilities and risk levels, extension points and the contribution registry, UI contributions, the grant ledger, package signatures and the marketplace index. |
| `ide-plugin` | The WebAssembly runtime: wasmtime with the component model and WASI preview 2, one sandboxed store and actor thread per plugin, limits. |
## Desktop and OS
| Crate | Responsibility |
| -------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `ide-cli` | The `openidea` command: argument parsing, the request it sends to a running app over a local socket, installing it on `PATH`. |
| `ide-clipboard` | The OS clipboard's file list (Windows `CF_HDROP`, macOS `NSPasteboard` file URLs) for copying files between the Project view and the file manager. |
| `ide-fonts` | Installed font families for the font pickers, via CoreText, DirectWrite or fontconfig. |
| `src-tauri` (package `openidea`) | The desktop shell: `AppState`, Tauri commands, the event sink, windows, menus, the remote-development client, the `openidea-plugin` URL scheme for plugin web views. Glue only. |
## Outside the workspace
| Path | What it is |
| -------------------------- | -------------------------------------------------------------------------------------------------------------------- |
| `sdk/rust/openidea-plugin` | The guest SDK for plugins: typed host API, plugin trait, and a mock host for `cargo test`. Carries a copy of `wit/`. |
| `plugins/official/*` | Official plugins, each its own crate built for `wasm32-wasip2`; their built components are checked in under `dist/`. |
@@ -0,0 +1,156 @@
---
title: "Frontend architecture"
description: "How the React workbench is put together: features with activate(), the action registry, keymaps and the dispatcher, context keys and when clauses, contributions and services."
---
The frontend is React 19 with zustand stores and CodeMirror 6 for editors, bundled by Vite and run in the system webview.
## Layout of `src/`
| Path | What it holds |
| ------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `platform/` | App-wide infrastructure: `ipc.ts` (`call`, `onIdeEvent`, `nextRequestId`), the action registry, keymaps and the keyboard dispatcher, context keys, i18n, the project store, remote routing. |
| `workbench/` | The shell: title bar, tool window stripes, editor area, status bar, overlays, notifications, and `contributions.ts` — the extension points and cross-feature services. |
| `ui/` | Design-system primitives (`Button`, `Dialog`, `Menu`, `Popup`, `Tree`, `List`, `Select`, `Tooltip`, `T`…) and `icons.tsx`. |
| `editor/` | `core` (EditorView creation, document sync with the backend, tabs and splits), `api.ts` (the contract other code extends editors through), `lsp` (language-server editor features), `refactor`, `navigation`, `structure`. |
| `features/<name>/` | One feature each (`search`, `vcs`, `debugger`, `run`, `terminal`, `database`, `plugins`…), exporting `activate()`. |
| `bindings.ts` | Generated IPC bindings. Never edit. |
## Features and `activate()`
Each feature exports `activate(): void | (() => void)`. It registers actions, tool windows, status items, editor contributions and services, and returns a disposer that undoes all of it. `src/features/index.ts` lists the features in activation order and activates each inside a `try`, so one feature that throws doesn't stop the others. `src/workbench/activate.ts` runs first and provides the shell services (tool windows, notifications) that features rely on.
A whole feature can be this small:
```ts src/features/logcat/index.ts
export function activate(): () => void {
const dispose = registerToolWindow({
id: "logcat",
title: "Logcat",
icon: "toolwindow-logcat",
anchor: "bottom",
stripe: "onUse",
order: 26,
component: lazyComponent(() => import("./LogcatView").then((m) => m.LogcatView)),
requiresProject: false,
});
return () => {
dispose();
resetLogcat();
};
}
```
Load heavy views lazily (`lazyComponent`) so they don't weigh on startup; `pnpm perf` budgets the initial bundle.
## Feature isolation
**Features never import each other.** They communicate through three channels:
- **Actions:** `executeAction("project.dontOpen")` runs another feature's behaviour by its stable id, without importing it.
- **Contributions:** a feature registers into an extension point in `src/workbench/contributions.ts` (or `src/editor/api.ts`); whoever renders that point doesn't know who contributed.
- **Services:** cross-feature services are provided once and looked up by accessor (below).
Shared code that several features need goes into `platform/`, `ui/`, `workbench/` or `editor/api`, not into one feature that the others import.
## Actions
Everything a user can invoke is an action with a stable, dotted id (`src/platform/actions.ts`). Menus, keybindings, toolbar buttons, context menus and the Actions tab of Search Everywhere all resolve to the same registry, so a feature registers its behaviour once:
```ts
registerAction({
id: "editor.formatDocument",
title: "Reformat Code",
category: "Code",
when: "editorFocus",
run: () => {
/* ... */
},
});
```
Main fields:
| Field | Meaning |
| ---------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- |
| `id` | Stable dotted id, e.g. `navigate.searchEverywhere`. Keymaps, menus, plugins and tests refer to it, so don't rename it casually. |
| `title`, `category`, `description` | English; translated through `action.<id>` catalog keys or the source-text catalog (see [Localization](/en/contribute/guidelines/i18n)). |
| `when` | Enablement, a `when` clause over context keys. |
| `enabled` | Extra enablement for a parameterised action or the current selection. |
| `keyWhen` | Extra condition for keyboard shortcuts only, e.g. to yield a shared shortcut to the editor while typing. |
| `toggled` | Marks a toggle action; Search Everywhere shows an ON/OFF switch. |
| `icon` | An icon name from `ui/icons`. |
| `hidden` | Hidden from Search Everywhere (internal or parameterised actions). |
Run actions with `executeAction(id, arg?)`. `registerAction` returns a disposer.
## Keymaps and the dispatcher
Default shortcuts live in `src/platform/keymaps/`, never on the action. A keymap maps action ids to bindings:
```ts src/platform/keymaps/intellij-macos.ts
export const intellijMacos: Record<string, string[]> = {
"navigate.searchEverywhere": ["Shift Shift"],
"navigate.gotoClass": ["Meta+O"],
"navigate.findAction": ["Meta+Shift+A"],
"navigate.back": ["Meta+[", "Meta+Alt+Left", "Button4", "SwipeRight"],
// ...
};
```
Presets: `intellij-macos` (default on macOS), `intellij-windows` (default on Windows and Linux), `vscode-macos`, `vscode-windows`, `eclipse-macos`, `eclipse-windows`. When you add an action with a default shortcut, add it to both IntelliJ presets, and to the others where an equivalent exists.
Binding syntax: modifiers `Meta Ctrl Alt Shift` joined with `+` to a key (`Meta+Shift+O`); a space separates chord strokes (`Meta+K Meta+C`); a doubled modifier is `Shift Shift`; mouse buttons are `Button1`…`Button5`. Keys are matched by physical position (`KeyboardEvent.code`).
`installKeyboardDispatcher` listens to `keydown` in the capture phase so IDE shortcuts win over CodeMirror's defaults, resolves the stroke against the active keymap plus the user's overrides, and runs the first enabled action (`when` and `keyWhen`). A component that needs a keystroke for itself registers a key claim (`registerKeyClaim`), e.g. Vim mode for its shortcut conflicts.
## Context keys and `when` clauses
Components publish facts as context keys (`src/platform/context.ts`); actions and keybindings are enabled by a `when` expression over them.
```ts
// the debugger publishes its state
setContext("debugSessionPaused", s?.status === "paused");
// and its step actions are enabled only while paused
{ id: "debug.stepOver", title: "Step Over", category: "Debug", when: "debugSessionPaused", run }
```
Grammar: `||`, `&&`, `!`, parentheses, and `key`, `key == value`, `key != value`. An empty clause is always true. Clauses are compiled once and cached. Plugin menu items use the same clauses.
## Contributions
`src/workbench/contributions.ts` holds the workbench's extension points. Each `register*` function returns a disposer.
| Function | Contributes |
| ---------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------ |
| `registerToolWindow` | A tool window; a `toolwindow.<id>` action that toggles it is registered automatically. |
| `registerStatusItem` | A status bar item. |
| `registerOverlay` | Popups and dialogs rendered above the workbench (Search Everywhere, Settings…). |
| `registerEditorKind`, `registerFileEditorProvider` | Editor tab types other than text files (diff viewer, settings, welcome…) and which files open in them. |
| `registerTitleItem` | A title bar widget (VCS branch, run configuration, run/debug buttons…). |
| `registerServiceView`, `registerServiceProvider` | Content of the Services tool window. |
| `registerWorkbenchBanner` | A banner component in the workbench. |
| `registerCommitCheckProvider`, `registerEditorDropHandler`, `registerTextEditorWrapper`… | Narrower points used by specific features. |
Editors are extended through `src/editor/api.ts`: `registerEditorContribution({ id, extensions })` adds CodeMirror extensions per document. `editor/core` owns the views; LSP, VCS gutters, breakpoints and inline debug values are all contributions and never import editor internals.
Plugin UI contributions reach these same functions: the backend validates them, and `src/features/plugins/projection.ts` maps each one onto an existing `register*` call. See [Plugin host](/en/contribute/architecture/plugin-host).
## Services
Services are cross-feature APIs with a single implementation, provided by whoever owns it:
| Accessor | Provided by |
| ------------------------------------------ | -------------------------------------------- |
| `editorService()` / `maybeEditorService()` | `editor/core` (`provideEditorService`) |
| `toolWindowService()` | the workbench (`provideToolWindowService`) |
| `notifications()` | the workbench (`provideNotificationService`) |
| `embeddedTerminal()` | the terminal feature |
| `maybeCommitService()` | the VCS feature |
The non-`maybe` accessors throw if nothing provided the service, so call them at use time, not at module load.
## Talking to the backend
Features call the backend only through `commands` from `@/platform/ipc` and listen with `onIdeEvent`; see [IPC](/en/contribute/architecture/ipc). Several features also keep an in-memory backend for the plain-browser mode (`pnpm dev`) that unit and E2E tests use, chosen by `isTauri()`.
+201
View File
@@ -0,0 +1,201 @@
---
title: "IPC between UI and Rust"
description: "The communication contract: typed commands returning IdeResult, generated bindings, text coordinates, streaming channels, cancellable requests and the backend event bus."
---
The frontend and the backend talk over Tauri IPC, with three mechanisms:
| Mechanism | Direction | Use it for |
| -------------------------------------- | -------------------------------------- | -------------------------------------------------------------------------------------- |
| **Command** | UI → Rust, one reply | Every request. Returns `IdeResult<T>`. |
| **Channel** (`tauri::ipc::Channel<T>`) | Rust → UI, many values for one request | Streaming results: search hits, process output, terminal bytes, progress. |
| **Event** (`IdeEvent` via `EventSink`) | Rust → UI, unsolicited | Notifications: files changed on disk, diagnostics, index state, debug session stopped. |
All three are typed end to end: Rust types derive `specta::Type`, and tauri-specta generates `src/bindings.ts`.
## Commands
A command is a thin adapter in `src-tauri/src/commands/<domain>.rs`. It resolves the project from the calling window, calls the shared implementation (usually in `ide_host::ops`), and returns `IdeResult<T>`:
```rust src-tauri/src/commands/app.rs
#[tauri::command]
#[specta::specta]
pub async fn project_trust_set(
state: State<'_, AppState>,
path: PathBuf,
trusted: bool,
folder: bool,
) -> IdeResult<()> {
state.host.set_project_trust(&path, trusted, folder).await?;
// ...
}
```
Register new commands in the `collect_commands!` list of `commands::builder()` (`src-tauri/src/commands/mod.rs`). The module's conventions:
- every command returns `IdeResult<T>` (it rejects with `IdeError` in JS);
- project-scoped commands resolve the project from the calling window;
- streaming results use a `Channel<T>` argument; cancellable requests take a UI-allocated `RequestId` and are cancelled via `cancel_request`.
Before any command runs, the invoke handler in `src-tauri/src/lib.rs` checks [project trust](/en/contribute/security#project-trust-and-restricted-mode) and, for a remote window, routes it to the server.
### Calling a command
The frontend imports `commands` from `@/platform/ipc` (which re-exports the bindings) and unwraps the result with `call`:
```ts
import { call, commands } from "@/platform/ipc";
await call(commands.projectTrustSet(path, trusted, folder));
```
Generated functions return `{ status: "ok", data } | { status: "error", error }`. `call` turns that into a promise that rejects with `IpcError`, which carries the error's `kind` and `data`. Helpers: `isCancelled(e)`, `isNoServer(e)`, `unavailableText(e)`.
## Errors
`IdeError` (`crates/ide-core/src/error.rs`) is the single error type that crosses IPC:
```rust
pub type IdeResult<T> = Result<T, IdeError>;
pub struct IdeError {
pub kind: ErrorKind,
pub message: String,
/// Structured detail for errors the UI acts on; `null` for plain errors.
pub data: Option<ErrorData>,
}
```
`ErrorKind` is one of `notFound`, `alreadyExists`, `permissionDenied`, `invalidInput`, `outOfSync` (document version mismatch; the UI must resync the full text), `cancelled`, `timedOut`, `unavailable` (a required tool such as a language server, git or a debugger is missing), `io`, `protocol`, `internal`.
Crates keep rich internal errors and **convert at their public edge**; the UI switches on `kind` and shows `message`. Don't leak `anyhow` or crate-specific errors through a command.
## Generated bindings
`src/bindings.ts` is generated by tauri-specta from the registered commands plus `IdeEvent`. **Never edit it.**
- `pnpm gen:bindings` regenerates it without launching the app (`cargo test -p openidea --test export_bindings`).
- Every debug launch also re-exports it, rewriting the file only when it changed.
- CI regenerates it in the Rust job and fails if the committed file differs. After changing a command signature or an IPC type, commit `src/bindings.ts`.
Rust doc comments on commands and types become JSDoc in the bindings, so document commands in Rust. 64-bit integers are exported as `number`.
## Text coordinates
Everything that crosses IPC uses the same coordinates (`crates/ide-core/src/text.rs`):
- **Lines are 0-based.**
- **Columns and offsets are UTF-16 code units**, matching JavaScript strings, CodeMirror and the LSP default encoding.
- `Pos { line, col }`; `Range { start, end }` is half-open.
Conversion to and from byte offsets lives in `ide-text`. Convert at the edge of your crate: a tree-sitter byte offset or an LSP position in another encoding must never reach a command's result.
## Streaming with channels
High-volume or incremental results go through a `Channel<T>` argument, not the event bus. Find in Files:
```rust src-tauri/src/commands/search.rs
/// Find in Files. Results stream through `on_event`; ends with `Done`.
/// Cancel with `cancel_request(request)`.
#[tauri::command]
#[specta::specta]
pub async fn search_start(
window: Window,
state: State<'_, AppState>,
request: RequestId,
query: SearchQuery,
on_event: Channel<SearchEvent>,
) -> IdeResult<()> {
let emit = std::sync::Arc::new(move |e| {
let _ = on_event.send(e);
});
ide_host::ops::search::start(&state.host, window.label(), request, query, emit).await
}
```
On the UI side, create a `Channel`, set `onmessage`, and pass it as the argument:
```ts src/platform/textSearch.ts
const channel = new Channel<SearchEvent>();
channel.onmessage = (ev) => {
if (ev.type === "file") onFile(ev.file);
else finish(ev.summary);
};
call(commands.searchStart(request, query, channel));
```
Make the stream self-terminating (a final `Done` value) so the UI knows when it ended.
## Cancellable requests
The UI allocates the id; the backend registers a token for it:
```ts
import { commands, nextRequestId } from "@/platform/ipc";
const request = nextRequestId();
const summary = streamSearch(request, query, onFile);
// later, if the user cancels:
void commands.cancelRequest(request);
```
```rust crates/ide-host/src/ops/search.rs
let cancel = host.begin_request(request);
let events = on_event.clone();
let syntax = host.syntax();
let summary = host
.blocking(move || {
search_project(&project, &syntax, &query, &cancel, &|file| events(SearchEvent::File { file }))
})
.await;
host.end_request(request);
on_event(SearchEvent::Done { summary: summary? });
```
Details that matter:
- Ids are shared by all windows of the backend, so each page starts counting at a random point.
- A cancel that arrives **before** the command began is remembered for 120 seconds, and the request then starts out cancelled (`crates/ide-host/src/requests.rs`).
- Long work checks the `CancellationToken` and returns `ErrorKind::Cancelled` (or a partial result) promptly.
- Tasks the backend starts itself (a language server's work-done progress) are reported as `IdeEvent::Task` with a `TaskId` and cancelled with `cancel_task`, not `cancel_request`.
## Events
`IdeEvent` (`crates/ide-core/src/event.rs`) is one enum of everything the backend tells the UI unasked, serialized as `{ type: "<camelCase name>", ... }`. Feature crates emit through an `EventSink`:
```rust
pub trait EventSink: Send + Sync + 'static {
fn emit(&self, event: IdeEvent);
}
project.sink.emit(IdeEvent::ProjectTrustChanged { root: project.root.clone().into(), trusted });
```
`src-tauri/src/events.rs` implements it as `TauriSink`, which forwards to one window (plus its auxiliary windows) or all windows as the single Tauri event `ide://event`. Crates take a `SharedSink` and never depend on Tauri; tests use `NullSink`.
The UI subscribes by type, and the handler gets the narrowed variant:
```ts src/features/project-trust/index.tsx
onIdeEvent("projectTrustChanged", (e) => {
const project = useProject.getState().project;
if (project?.root === e.root) useProject.setState({ project: { ...project, trusted: e.trusted } });
});
```
`onIdeEvent` returns an unsubscribe function; return it from your feature's disposer. Listening is window-scoped.
## Threads
Long work never blocks the UI thread or a Tauri command thread. Make the command `async` and move CPU-bound or blocking work onto `spawn_blocking` or rayon; honour the `CancellationToken`. Synchronous commands run on the main thread, so keep them trivial.
## Remote windows
You don't write anything special for remote development. In a remote window, `src/platform/remote.ts` routes the commands the server answers through `remote_invoke`; each `Channel` argument is replaced by `{ "$channel": id }` and its values come back tagged with the id. Events arrive as `event` notifications and are re-emitted locally. What you must do is keep the implementation in `ide-host::ops` and register it with the server's `Dispatcher` (the `d.add("search_start", ...)` lists in `crates/ide-host/src/rpc.rs` and the `ops` modules) so the server can run it. See [Remote development](/en/contribute/architecture/remote).
## Checklist: adding a command
1. Put the logic in the owning crate; convert errors to `IdeError` at its edge; use IPC coordinates.
2. Add the shared implementation to `ide-host::ops::<domain>` if it touches a project; register it with the remote dispatcher if remote windows need it.
3. Add the `#[tauri::command] #[specta::specta]` adapter in `src-tauri/src/commands/<domain>.rs` and list it in `collect_commands!`.
4. If it executes anything, leave it out of the restricted-mode allowlist (it is denied in untrusted projects by default).
5. `pnpm gen:bindings`, then call it with `call(commands.yourCommand(...))`.
@@ -0,0 +1,102 @@
---
title: "Architecture overview"
description: "The processes and layers of OpenIDEA: the Tauri shell, the webview frontend, the UI-less Rust host, feature crates, external servers, plugins and the remote server."
---
OpenIDEA is a Tauri 2 desktop app. The UI is a React + CodeMirror 6 frontend in the system webview; everything that touches the disk, processes or the network runs in Rust. The Rust side is built so that the same backend runs inside the desktop app and, for remote development, on another machine as `openidea-server`.
## The big picture
```mermaid
flowchart TB
subgraph Desktop["Desktop app (one process)"]
direction TB
subgraph Webview["Webview: src/"]
WB["workbench + features<br/>actions, keymaps, contributions"]
ED["editor: CodeMirror 6"]
PL["platform/ipc<br/>call, onIdeEvent, Channel"]
WB --> PL
ED --> PL
end
subgraph Shell["src-tauri: glue"]
CMD["commands/*.rs<br/>typed IPC adapters"]
EV["events.rs: TauriSink<br/>ide://event"]
RD["remote_dev.rs<br/>remote_invoke"]
end
subgraph Host["ide-host: Host (no UI toolkit)"]
OPS["ops/: shared command implementations"]
PRJ["Project per window<br/>documents, LSP, index, VCS"]
PH["plugins: PluginHost"]
end
CRATES["feature crates<br/>ide-lsp, ide-dap, ide-index, ide-vcs,<br/>ide-search, ide-run, ide-terminal..."]
CORE["ide-core: shared types"]
PL -- "invoke (JSON)" --> CMD
EV -- "IdeEvent" --> PL
CMD --> OPS
OPS --> PRJ
PRJ --> CRATES
PH --> WASM["ide-plugin: wasmtime<br/>one actor thread per plugin"]
CRATES --> CORE
end
LS["language servers"]
DA["debug adapters"]
TOOLS["git, build tools, PTYs, ACP agents"]
MCP["external AI agents"]
CRATES -- "LSP / JSON-RPC over stdio" --> LS
CRATES -- "DAP" --> DA
CRATES --> TOOLS
MCP -- "MCP over HTTP 127.0.0.1" --> Host
subgraph Remote["Remote machine"]
SRV["openidea-server --stdio<br/>ide-host: Host + rpc"]
end
RD -- "JSON-RPC over SSH / wsl.exe / docker exec" --> SRV
```
## Layers
| Layer | Where | Role |
| ------------------ | ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Frontend | `src/` | Rendering, interaction and editor state. Features register actions, tool windows and editor extensions; they reach the backend only through the generated bindings. See [Frontend](/en/contribute/architecture/frontend). |
| Generated bindings | `src/bindings.ts` | Typed TypeScript for every command and IPC type, generated by tauri-specta. Never edited by hand. See [IPC](/en/contribute/architecture/ipc). |
| Desktop shell | `src-tauri/` | Glue only: `AppState`, one `commands/<domain>.rs` per domain, the event sink, windows, menus, the remote-development client. No domain logic. |
| Host | `crates/ide-host` | The IDE backend without a UI toolkit: projects per window, settings, terminals, processes, trust, plugins, MCP, and `ops/` — the implementations that both the desktop commands and `openidea-server` call. |
| Feature crates | `crates/ide-*` | One domain each (LSP, DAP, index, git, search, run, database…). They depend on `ide-core` and never on each other's internals. See [Crates](/en/contribute/architecture/crates). |
| Shared vocabulary | `crates/ide-core` | Ids, text coordinates, `IdeError`, `IdeEvent`, settings types — anything that crosses a crate or the IPC boundary. |
## Processes
- **The app process** hosts the webview and the whole Rust backend (Tokio runtime plus blocking pools). There is one backend for all windows; each project window owns one `Project` in the host.
- **Language servers and debug adapters** are child processes, started lazily per language and workspace root from `languages/<id>.toml`. The backend speaks LSP / DAP to them (`ide-lsp`, `ide-dap` over `ide-jsonrpc`). See [Code intelligence](/en/contribute/architecture/code-intelligence).
- **Tools** are run through their CLIs where that keeps behaviour identical to the command line: `git` for writes (reads use gitoxide in-process), `docker`, `adb`, `gh`, Maven/Gradle, coding agents over ACP.
- **Plugins** run in-process as WebAssembly components, each on its own thread with its own sandboxed store. See [Plugin host](/en/contribute/architecture/plugin-host).
- **`openidea-server`** is the same `Host` with a JSON-RPC front instead of Tauri. The desktop starts it over SSH, WSL or `docker exec` and routes a remote window's commands to it. See [Remote development](/en/contribute/architecture/remote).
- **Local servers** for outside clients: the MCP server for AI agents (`ide-mcp`) and the built-in static web server (`ide-web`), both on `127.0.0.1`.
## How a request flows
Taking Find in Files as an example:
1. The search feature allocates a `RequestId`, creates a `Channel`, and calls `commands.searchStart(request, query, channel)` from `src/bindings.ts`.
2. Tauri dispatches to `search_start` in `src-tauri/src/commands/search.rs`, after the invoke handler has checked project trust.
3. The command forwards to `ide_host::ops::search::start`, which registers a `CancellationToken` for the request and runs the `ide-search` engine on a blocking thread.
4. Each file's matches are sent through the channel as they are found; the command resolves after sending `Done`.
5. If the user cancels, the UI calls `cancel_request(request)` and the token stops the walk.
The same `ops::search::start` serves a remote window: the frontend's `invoke` is routed to `remote_invoke`, the server runs the identical function, and channel values come back tagged with their channel id.
## Where to go next
<CardGroup cols={2}>
<Card title="Rust or UI?" href="/en/contribute/architecture/boundaries">
Which side a feature belongs on, with examples.
</Card>
<Card title="IPC" href="/en/contribute/architecture/ipc">
Commands, errors, streaming, cancellation and events.
</Card>
<Card title="Crates" href="/en/contribute/architecture/crates">
Every backend crate and what it owns.
</Card>
<Card title="Frontend" href="/en/contribute/architecture/frontend">
Actions, keymaps, context keys and contributions.
</Card>
</CardGroup>
@@ -0,0 +1,119 @@
---
title: "Plugin host"
description: "Internals of the plugin system: the extension kernel, the wasmtime component runtime with one actor per plugin, the sandbox and limits, and how plugin contributions reach the UI."
---
This page is about the host side, for contributors changing it. Plugin authors should start with the [plugin docs](/en/plugins/index); the authoritative reference for the API is [`plugins/README.md`](https://github.com/l0ng-ai/openidea/blob/main/plugins/README.md).
## Three crates
| Crate | Role |
| ------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `ide-extension` | The kernel, independent of the Wasm runtime: the `plugin.toml` manifest, `Capability` and its `Risk`, the `ExtensionPoint` trait and `ExtensionRegistry`, UI contributions (`UiStore`, `UiPoint`), the grant ledger, packages, signatures and the marketplace index. |
| `ide-plugin` | The WebAssembly runtime: wasmtime with the component model and WASI preview 2, one `Store` per plugin, the `Actor` thread that owns it, limits, and the host side of the WIT interfaces. |
| `ide-host::plugins` | The `PluginHost` that assembles them: sources, lifecycle, consent, the extension points owned by the host, editor providers, views, remote plugins. |
Dependency direction: `ide-core` ← `ide-extension` ← feature crates (their extension points) and `ide-plugin` ← `ide-host` (assembly). `src-tauri` only adapts `PluginHost` to the `plugin_*` Tauri commands.
The plugin host runs **where the project is**: in the desktop app for local projects, in `openidea-server` for remote ones.
## The interface
The API is defined in WIT, in `wit/` (package `openidea:[email protected]`). A plugin is a component targeting the `plugin` world (or `plugin-syntax`). The guest SDK `sdk/rust/openidea-plugin` wraps it in a typed Rust API and carries a copy of `wit/`; `crates/ide-plugin/tests/api_compat.rs` checks the copies are identical.
Released API versions are frozen. wasmtime resolves interface names by semver, but function types must match exactly, so a released 1.x only grows: new interfaces and new optional exports, never changed records or variants. See _Evolving the plugin API_ in `plugins/README.md` before touching `wit/`.
## Package lifecycle
Sources of packages: installed plugins (`<plugins_dir>/<id>/`), project plugins (`<project>/.openidea/plugins/*/`, loaded only for trusted projects), and folders linked with `openidea-plugin link` (`links.json`).
<Steps>
<Step title="Prepare">
The manifest is parsed and every declaration is validated by the extension point that owns it
(`ExtensionRegistry::prepare`). No side effects.
</Step>
<Step title="Consent">
The grant ledger decides whether the package may load. New plugins and project plugins wait for the user's consent;
an update that asks for a new capability must be confirmed again.
</Step>
<Step title="Commit">
Contributions take effect all or nothing (`ExtensionRegistry::commit`) and can be withdrawn per plugin.
</Step>
<Step title="Activate">
Code is instantiated only when one of its activation events fires: `on-command`, `on-language`, `on-file`,
`on-view`, `on-schedule`, `on-event`, or `on-startup-finished`.
</Step>
</Steps>
Declarative contributions (menus, key bindings, status items, settings, themes, languages…) are live after commit, before any plugin code runs.
### Extension points
An extension point is implemented by the crate that owns the feature, so the kernel doesn't grow with every contribution type. For example `ide-language` implements the languages point (`plugin_point.rs`), and `ide-host::plugins` implements providers, run detectors, themes and navigation indexers. UI points (`ui.rs` in `ide-extension`, `ui_points.rs` in the host) validate menus, keymaps, status bar items, settings, views and custom editors completely in Rust.
## The runtime
### One actor per plugin
Each active plugin's runtime lives on its own `Actor` thread (`crates/ide-plugin/src/actor.rs`). Callers queue jobs and wait on a reply channel instead of holding a lock across the call, which gives three properties:
- an instance is entered by one job at a time, never concurrently;
- an edit-path caller (completion, line markers) can stop waiting at its soft deadline while the call runs on, and is interrupted at the hard deadline;
- any thread can cancel a call.
A parked thread per active plugin costs little, and idle plugins are reclaimed (after 300 s by default), ending the thread.
### Sandbox
Each plugin gets its own wasmtime `Store`: no preopened directories, no environment, no network, no inherited stdio — only clocks and randomness from WASI, plus the host functions. Every host function that reaches files, processes, the network or VCS writes checks a capability the user granted; a missing grant returns `denied` and the plugin keeps running. Path grants are resolved to real paths before comparison, and `process:` grants pin the program's absolute path and hash.
### Limits
Defaults from `Limits` in `crates/ide-plugin/src/types.rs`:
| Limit | Default |
| ------------------------------------------ | ----------------------------------------------------------------- |
| Fuel per call | 5·10⁹ (≈ wasm instructions) |
| Commands, events, activation, deactivation | 5 s wall clock |
| A call that began a task (`tasks.begin`) | 60 s |
| Editor providers | 50 ms soft (a later result is dropped), 500 ms hard (interrupted) |
| Linear memory | 256 MiB |
| Restart backoff | 1 s, doubling per crash in a row |
| Crashes before _Failed_ | 3 in a row |
| Idle reclaim | 300 s |
Deadlines and cancellation are enforced by wasmtime epoch interruption: the store's epoch callback checks the per-call deadline and the cancel flag. A trap (panic, fuel or memory exhausted, timeout, permission violation, cancellation) poisons the instance; the actor drops it and the host's supervisor starts a fresh one the next time it is needed. A call that returns normally resets the crash count. Settings › Plugins shows activations, p95 call latency, crashes, late editor results and reclaims.
## How contributions reach the UI
```mermaid
flowchart LR
M["plugin.toml"] --> P["extension points<br/>validate in Rust"]
P --> S["UiStore<br/>JSON snapshot + assets"]
S -- "plugin_list / plugin_subscribe" --> B["features/plugins/bridge.ts"]
B --> J["projection.ts<br/>defineUiPoint"]
J --> R["registerAction, registerToolWindow,<br/>registerStatusItem, menus..."]
C["plugin code"] -- "set-status-item, notify, tasks" --> E["PluginEvent channel"]
E --> B
```
- **Declarative UI** is validated completely in Rust and committed as JSON into the `UiStore`. The client receives the store's snapshot and projects each item onto an existing workbench `register*` function (`src/features/plugins/projection.ts`). A projection must not fail; if it throws, that item is logged and skipped, nothing is half-registered. Large payloads such as icon SVGs go into the store's assets and are fetched on demand.
- **Runtime UI** from running code (status item text, notifications, tasks, event subscriptions) arrives as `PluginEvent`s on the `plugin_subscribe` channel and is handled by `runtimeUi.tsx` and `bridge.ts`.
- **Views and custom editors** whose content plugin code renders are served by the `plugin_view_render` / `plugin_view_event` commands.
- **Web views** are pages shipped in the package. The client loads them in a sandboxed frame (an opaque origin with no network and no IDE API) from the `openidea-plugin` URL scheme (`src-tauri/src/plugin_web.rs`), with a CSP of their own; the page talks to the IDE only through the frame bridge.
Adding a UI contribution type means one Rust extension point plus one `defineUiPoint` in the client; the kernel (projection, store, bridge) isn't touched.
## Remote projects
When a window opens a remote project, the desktop sends its enabled code plugins and their grants to `openidea-server`, which runs them next to the files: file reads, process starts, fetches and diagnostics act on the remote machine, and `process:` programs are resolved and pinned there. Changes on the desktop (install, disable, grants) follow within a second. Secrets stay in the desktop's keychain; the server asks for one with the `plugin_secret` request. Project plugins don't run in remote projects yet.
## Testing the host
```sh
plugins/build.sh # rebuild the checked-in components
plugins/build.sh --check # what CI runs: nothing stale, everything builds
cargo test -p ide-host --test plugins
```
The host's integration tests (`crates/ide-host/tests/plugins.rs` and the other `plugin_*.rs` files) load the checked-in components of the official plugins and the fixtures in `crates/ide-plugin/tests/fixtures/`. Rebuild them after changing a plugin, the SDK or `wit/`. `pnpm perf:rust` includes plugin runtime benchmarks (`plugins/load_50`, `plugins/cold_activation`, `plugins/syntax_highlights_2k_lines`).
+109
View File
@@ -0,0 +1,109 @@
---
title: "Remote development"
description: "How a desktop window works on a project on another machine: openidea-server, its JSON-RPC protocol over SSH, WSL or docker exec, and command routing in the frontend."
---
In remote development a window's project lives on another machine (an SSH host, a WSL distribution or a dev container). The UI stays on the desktop; the backend for that project — the same `ide-host` `Host` the desktop uses — runs on the remote machine as `openidea-server`. For using it, see [Remote development over SSH](/en/guide/remote/ssh).
<Note>
Two crates have "remote" in their name but are different things. **Remote development** is `ide-host` (`rpc.rs`, the
`openidea-server` binary) plus the client in `src-tauri/src/remote_dev.rs`. **`ide-remote`** is SFTP deployment and
run targets (running a configuration over SSH or in Docker from a local project).
</Note>
## Pieces
```mermaid
flowchart LR
subgraph Desktop
UI["frontend<br/>platform/remote.ts"] -- "remote_invoke" --> RD["src-tauri remote_dev.rs<br/>Connection"]
end
RD -- "JSON-RPC, Content-Length framing" --> T{"transport"}
T -- "SSH exec channel" --> S1["openidea-server --stdio"]
T -- "wsl.exe --exec" --> S2["openidea-server --stdio"]
T -- "docker exec -i" --> S3["openidea-server --stdio"]
S1 --> H["ide-host: Host<br/>projects, LSP, index, git,<br/>terminals, runs, debugger, plugins"]
```
| Piece | Where |
| --------------------------------- | ----------------------------------------------------------------------------- |
| Server binary | `crates/ide-host/src/bin/server.rs` (`openidea-server`) |
| Protocol and dispatcher | `crates/ide-host/src/rpc.rs` |
| Client: connect, install, forward | `src-tauri/src/remote_dev.rs` |
| WSL and dev container transports | `src-tauri/src/remote_wsl.rs`, `src-tauri/src/remote_container.rs` |
| Port relay inside a container | `crates/ide-host/src/relay.rs` (`openidea-server --connect`) |
| Frontend routing | `src/platform/remote.ts`, hooked into `invoke` by `src/platform/tauriCore.ts` |
| UI (File › Remote Development…) | `src/features/remote-dev/` |
## The server
```text
openidea-server --stdio | --connect <host> <port> | --version
```
- `--stdio` serves one desktop client over stdin/stdout. stdout carries the protocol, so logs go to stderr (filter with `OPENIDEA_LOG`).
- Its state lives under `OPENIDEA_SERVER_HOME` (default `~/.openidea`), in `config/` and `data/`. Language definitions can be added in `config/languages/`.
- An SSH exec channel gets a minimal environment, so on every start the server takes the login shell's environment and adds the IDE-managed tools directory to `PATH`.
- `--version` prints the app version and the protocol version.
### Installing it on the remote machine
The desktop picks the server binary for the remote OS and architecture (from `uname -sm`): `OPENIDEA_SERVER_BINARY` if set, then `<data dir>/server/<os>-<arch>/openidea-server`, then the one next to the app when the platforms match; otherwise it downloads the release build for that platform once into its data dir. It uploads the binary into `~/.openidea/server/` on the remote host, one folder per version, keyed by a content hash, and removes other builds of that version. CI keeps a static Linux (musl) build working.
## The protocol
JSON-RPC 2.0 with `Content-Length` framing (`ide-jsonrpc`), over the server's stdio:
| Message | Direction | Payload |
| ----------------------- | --------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- |
| request `initialize` | client → server | `{ version }` → `ServerInfo { version, protocol, os, arch, home }` |
| request `commands` | client → server | the names of the commands the server answers |
| request `invoke` | client → server | `{ window, cmd, args }` → the command's result. `args` as the desktop command takes them (camelCase); a streaming argument is `{ "$channel": <id> }` |
| notification `event` | server → client | `{ window, event }`: an `IdeEvent` for that window, or for every window when `window` is null |
| notification `channel` | server → client | `{ id, data }`: one value of a streaming argument |
| notification `plugin` | server → client | `{ window, event }` from code plugins of the remote project |
| request `plugin_secret` | server → client | `{ plugin, name }` → `string` or `null`: a secret from the desktop's keychain |
The protocol version is `PROTOCOL_VERSION` in `rpc.rs`. Errors are `IdeError`s carried as JSON in the JSON-RPC error message, so the frontend sees the same `kind` / `message` as for a local command.
## Routing commands
The frontend doesn't know whether a window is remote. Features call `commands.*` as usual; the routing happens below the generated bindings:
1. `src/platform/tauriCore.ts` is what `@tauri-apps/api/core` resolves to (a Vite alias). Its `invoke` asks the installed route first.
2. In a remote window, `src/platform/remote.ts` installs a route that sends the commands listed in the session's `commands` to `remote_invoke`, and runs everything else locally.
3. `remote_invoke` replaces each `Channel` argument with `{ "$channel": id }`, forwards the call as an `invoke` request, and delivers `channel` notifications back to the right `Channel`. `event` notifications are re-emitted to the window through the normal event sink.
A few commands are always local even if the server knows them: `open_project` and `close_project` (opening a local folder leaves the remote one; closing drops the connection), system font listing, and client UI settings. Client settings (keymap, appearance, editor fonts and colours…) are listed in `src/platform/clientSettings.ts`; a global tooling setting is applied on both sides.
### What runs where
| On the remote machine | On the desktop |
| -------------------------------------------------------- | ------------------------------------ |
| The project: files, documents, settings | Windows, menus, the webview |
| Language servers, index, search | Client UI settings, keymaps, themes |
| Git, terminals, runs, debug adapters | The SSH connection and port forwards |
| Code plugins of the project, their processes and fetches | Plugin secrets (keychain) |
## Port forwarding
Forwarded ports go through the SSH connection. In a dev container, each connection to a forwarded port runs `openidea-server --connect localhost <port>` inside the container (via `docker exec`) and pipes the connection through its stdin/stdout, so the container's network is reachable the same way on every Docker setup.
## Trust
Restricted mode works the same in remote windows: before forwarding a command that isn't on the restricted-mode allowlist, the desktop asks the server whether the remote project is trusted, and the server authorizes every `invoke` again with `Host::authorize_command`. For a dev container of a folder the user already trusts, the desktop starts the server with that project folder marked trusted (`OPENIDEA_TRUSTED_ROOT`, never persisted).
The WSL and container transports run programs with arguments directly (`wsl.exe --exec`, `docker exec`); only fixed scripts reach `sh -c`, and every variable value is passed as a positional argument.
## Making a command work remotely
1. Implement it in `ide-host::ops::<domain>`, taking `&Host` and the window label, not Tauri types.
2. Register it with the server's `Dispatcher` (`d.add("name", |c| async move { ... })` in `rpc.rs` or the `ops` module), reading arguments with `c.arg("…")`.
3. Keep the desktop command a thin adapter over the same function.
If a command is not registered with the dispatcher, a remote window runs it locally — which is wrong for anything that touches the project.
## Testing
- `crates/ide-host/tests/rpc.rs`, `remote_env.rs`, `remote_plugins.rs`: the protocol and server behaviour.
- `smoke/server.ts` starts a real `openidea-server --stdio` and speaks the protocol to it; `smoke/desktop.ts` gives the frontend's modules a Tauri runtime whose commands go to that server through the remote-development route. The run and debug smoke tests use it (see [Smoke tests](/en/contribute/testing/smoke)).
@@ -0,0 +1,72 @@
---
title: "Code style"
description: "Formatting, linting and dead-code checks for Rust and TypeScript, plus the conventions for comments, errors and generated files."
---
Formatting and lint rules are enforced by tools, locally by the pre-commit hook and again in CI. Configure your editor to use the same tools and you rarely have to think about them.
## Rust
| Tool | Command | Config |
| ---------- | ------------------------------------------------------- | ------------------------------------------------------------------------------- |
| rustfmt | `cargo fmt --all` (CI: `--check`) | `rustfmt.toml`: `max_width = 120`, `use_small_heuristics = "Max"`; edition 2024 |
| clippy | `cargo clippy --workspace --all-targets -- -D warnings` | warnings are errors |
| cargo-deny | `cargo deny check` | `deny.toml`: advisories, licences, bans, sources |
While iterating, run clippy for the crate you changed only (`cargo clippy -p <crate> --all-targets -- -D warnings`), once, before committing. The plugin SDK and official plugins are separate workspaces; CI also lints them for `wasm32-wasip2`.
Conventions:
- Each crate's `lib.rs` (and each significant module) starts with a `//!` doc comment stating its contract. Keep it current when the contract changes.
- Crates keep their own error types internally (`thiserror`, `anyhow`) and convert to `IdeError` at their public edge.
- Doc comments on Tauri commands and IPC types become JSDoc in `src/bindings.ts`; write them for the frontend reader.
- Log with `tracing`, not `println!`. In `openidea-server`, stdout carries the protocol.
- Blocking or long work goes to `spawn_blocking` / rayon and honours a `CancellationToken`.
## TypeScript and React
| Tool | Command | Config |
| ---------- | --------------------------------------- | --------------------------------------------------------------------------------------- |
| oxfmt | `pnpm format` (CI: `pnpm format:check`) | `.oxfmtrc.json`: print width 120, double quotes, semicolons, trailing commas everywhere |
| oxlint | `pnpm lint` | `.oxlintrc.json`: TypeScript and React plugins |
| Type check | `pnpm typecheck` | `tsconfig.json` and the configs of `perf/`, `scripts/`, `smoke/` |
| knip | `pnpm knip` | `knip.ts`: unused files, exports and dependencies |
`pnpm knip` fails on an export nobody imports, a file nobody reaches or a dependency nobody uses. Delete dead code rather than suppressing it; exports used only inside their own file are allowed.
Conventions:
- Import app modules through the `@/` alias (`@/platform/ipc`, `@/workbench/contributions`).
- State lives in zustand stores; features expose behaviour through actions and contributions, not exported singletons other features import.
- Styling and UI rules: see [UI guidelines](/en/contribute/guidelines/ui). Text: see [Localization](/en/contribute/guidelines/i18n).
## Pre-commit hook
`pnpm install` installs a pre-commit hook (`simple-git-hooks` + `lint-staged`) that runs on staged files:
- `oxlint --fix` on JavaScript and TypeScript files;
- `oxfmt` on all files (TOML and generated files are ignored by its config);
- `rustfmt --edition 2024` on Rust files.
## Comments
Keep comments sparse and purposeful, and match the surrounding style:
- Explain **why**, a constraint, or a non-obvious contract; don't narrate what the next line does.
- Reference the issue number when a line exists because of a specific bug report (`(#1878)`), as existing code does.
- Don't keep change logs or commented-out code in comments; history is in git.
## Generated files
Never edit these by hand; regenerate them:
| File | Command |
| -------------------------------------------------- | ------------------------------------------------------------------ |
| `src/bindings.ts` | `pnpm gen:bindings` |
| `src/editor/core/cm/{angular,jsp,astro}/parser.ts` | `pnpm gen:angular-grammar`, `gen:jsp-grammar`, `gen:astro-grammar` |
| Token reference in `plugins/themes.md` | `pnpm gen:theme-docs` |
| `plugins/official/*/dist/plugin.wasm` | `plugins/build.sh` |
## Commit and PR titles
Use [Conventional Commits](https://www.conventionalcommits.org/) with a scope: `fix(editor): keep the caret visible after folding`, `feat(vcs): …`, `perf(index): …`. The repository-wide formatting commit is listed in `.git-blame-ignore-revs`; use `git blame --ignore-revs-file .git-blame-ignore-revs` to skip it.
+79
View File
@@ -0,0 +1,79 @@
---
title: "Localization"
description: "How user-visible text is translated: the source-text catalog, keyed message catalogs, the required English and Chinese entries, and the tests that enforce them."
---
Every user-visible string goes through a catalog. English and Simplified Chinese are required for every new string; other locales (`es`, `fr`, `ko`, `pl`, `pt`) may lack entries, and their UI shows the English text until a translator adds it.
## Two catalogs
| Catalog | Files | Keys | Used through |
| ---------------------------------------- | ----------------------------------------- | ----------------------------------------------- | ------------------------------------------- |
| **Source text** (most UI text) | `src/platform/locales/text/<locale>.json` | The English text itself | `tx("…")`, `useTx()`, and the UI primitives |
| **Messages** (menus, app-level messages) | `src/platform/locales/<locale>.json` | Namespaced keys: `menu.*`, `app.*`, `common.*`… | `t("key", params)`, `useT()` |
### Source text
Most text is written in English at the call site. The UI primitives (labels, buttons, menus, dialogs, tooltips) pass the strings they are given through the source-text catalog, so this is translated automatically:
```tsx
<Button>Retry Loading Conversation</Button>;
registerAction({ id: "project.trust", title: "Trust Project", category: "Project", run });
notifications().notify({ title: "Plugin update available", body: names });
```
The text catalog test treats a string as translatable when it reaches the translation layer directly: the argument of `tx` / `txTemplate`, the text children of `T`, `Button` and `Dialog`, and props or object keys such as `label`, `title`, `description`, `tooltip`, `placeholder`, `message`, `hint`, `aria-label`.
Text built at runtime uses the `tx` tag, which looks up a template with named placeholders:
```ts
tx`Cannot open ${path}`; // looks up "Cannot open {path}"
```
```json src/platform/locales/text/zh.json
"Cannot open {path}": "无法打开 {path}"
```
Action titles and categories are translated the same way (an `action.<id>` message key, if present, takes precedence). The English title stays searchable in Find Action.
### Messages
Keyed messages use an ICU-lite syntax:
```ts
t("app.unsaved.body", { count: 3, project: "openidea" });
```
- `{name}` parameters (numbers are locale-formatted);
- `{n, plural, =0 {none} one {# file} other {# files}}`;
- `{g, select, a {…} other {…}}`;
- `{n, number}`.
English (`en.json`) is bundled and is the fallback for missing keys; other locales load lazily.
## Rules
- **Add every new string to both `en` and `zh`.** For source text, that is `text/en.json` (the English text mapped to itself) and `text/zh.json`. For a message key, `en.json` and `zh.json`.
- **Rewording a string drops its translation.** Source-text keys are the English text, so if you change the wording at the call site, update the catalog entries too.
- **Remove entries you orphan.** The test fails on catalog entries whose text no longer appears anywhere in `src/`, the Rust crates or `languages/`.
- **Translate whole sentences.** Don't splice English words into translated text (`${kind.toLowerCase()}`, `${n === 1 ? "" : "s"}`); write one text per variant, or use a plural message.
- **No untagged template literals** in an exact lookup: `` `Cannot open ${path}` `` can never be translated. Use the `tx` tag.
- **Backend text** (error messages and the like) counts as used as long as the literal appears in the source; add its translation like any other text.
- **Chinese terminology:** follow the existing entries in `text/zh.json` and `zh.json` for the same concepts, so one English term doesn't get two translations.
## Tests
| Test | Fails when |
| ------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `src/platform/locales/textCatalog.test.ts` | A literal UI text has no Chinese entry (`zh` is a complete locale); a catalog has entries whose text no longer appears in the source; two template entries differ only in placeholder names; a template literal reaches an exact lookup; built text splices words; a literal key passed to `t` is missing from `en.json`. |
| `src/platform/locales/actionTitles.test.ts` | A literal action title, including arguments of action factory functions, has no Chinese entry. |
Run them on their own while iterating:
```sh
pnpm vitest run src/platform/locales
```
## Plugins
Plugins ship their own translations in the package; that mechanism is documented for plugin authors in `plugins/README.md` (_Translations_). The rules on this page apply to the IDE's own UI.
+52
View File
@@ -0,0 +1,52 @@
---
title: "UI guidelines"
description: "Rules for frontend UI code: design tokens, CSS Modules, icons, the UI primitives, accessibility and WebKit compatibility."
---
The visual style is dark, low-contrast greys and one cool accent. Consistency comes from a small set of rules that apply to every component.
## Design tokens
[`src/styles/tokens.css`](https://github.com/l0ng-ai/openidea/blob/main/src/styles/tokens.css) is the source of truth for colours and metrics (spacing, sizes, radii, font weights). `:root` holds the dark theme; `[data-theme="light"]` and `[data-theme="dark-hc"]` (high contrast) override it. Themes from plugins and colour-scheme settings set the same variables at runtime.
- **Use only tokens in components.** No hex, `rgb()` or named colour literals in a component's CSS or inline styles: `color: var(--fg-muted)`, not `color: #8c8c8c`.
- **Need a new colour or metric?** Add a token to `tokens.css` for every theme variant, with a name that says what it is for, not what it looks like.
- **Tokens are part of the theme API.** The token reference in `plugins/themes.md` is generated from `tokens.css` (`pnpm gen:theme-docs`), and `scripts/theme/theme.test.ts` fails when it is stale. Run the generator after adding or renaming a token.
## CSS Modules
Style components with CSS Modules: `Thing.module.css` next to `Thing.tsx`, imported as `styles` and combined with `clsx`. `src/styles/global.css` is only for document-level rules. Don't add global class names from a feature.
## Icons
- Icons are inline SVG from [`src/ui/icons.tsx`](https://github.com/l0ng-ai/openidea/blob/main/src/ui/icons.tsx): `<Icon name="folder" />`, or the `icon` prop of `Button` and actions.
- **Never use emoji** as icons or decoration, in the UI or in user-visible text.
- An `Icon` without `label` is decorative and rendered `aria-hidden`. Give it a `label` when the icon alone carries meaning.
- The SVGs live in `src/ui/expui/<group>/` (vendored new-UI icons, with `_dark` and `@20x20` variants) and `src/ui/openidea/<group>/` (the project's own, drawn in the same style), and are registered by name in `icons.tsx`. Names are the contract used by `Action.icon` and contributions. Add new icons there; don't inline ad-hoc SVG in a feature. Monochrome icons are drawn in `currentColor`, so they follow the theme.
## UI primitives
Build from the primitives in `src/ui/` before writing your own: `Button`, `BarButton`, `Input`, `Checkbox`, `Select`, `Dialog`, `Popup`, `Menu`, `Tooltip`, `List`, `Tree`, `Tabs`, `Splitter`, `Scrollbar`, `Spinner`, `Kbd`, and `T` for translated text. They already handle focus, keyboard navigation, theming and translation of their labels. Workbench-level pieces (tool windows, status items, overlays) come from [contributions](/en/contribute/architecture/frontend#contributions), not from custom layout.
## Accessibility
- Use real `<button>` and `<input>` elements (or the primitives that render them), never clickable `<div>`s.
- Every icon-only button has an `aria-label`. `aria-label` is one of the props the text catalog translates, so write it in English like any other label.
- Everything a user can do is an [action](/en/contribute/architecture/frontend#actions) with a stable id, so it is reachable from the keyboard and Find Action, not only by mouse.
- Keep focus visible and predictable: dialogs and popups return focus to where it came from.
## Text
User-visible text is written in English at the call site and goes through the text catalog; Chinese is required for every string. See [Localization](/en/contribute/guidelines/i18n).
## WebKit compatibility
On macOS and Linux the app runs in WebKit, and the supported floor is Safari 16 (macOS 11 can update to it). `pnpm build` runs `scripts/check-webkit.ts` on the bundle, and `src/platform/webkitCompatibility.test.ts` checks application regexes. Avoid:
- regex lookbehind (`(?<=…)`, `(?<!…)`), which needs Safari 16.4, and the regex `v` flag;
- import attributes;
- newer runtime APIs in startup code, such as `Promise.withResolvers`, `Object.groupBy`, `Map.groupBy`, `Array.fromAsync`, `String.prototype.isWellFormed`.
## Verifying UI changes
Run the app (`pnpm tauri dev`) and check the change in both the dark and light themes. PRs that change what users see need before/after screenshots, and multi-step interactions a recording; see [Contribution workflow](/en/contribute/workflow).
+66
View File
@@ -0,0 +1,66 @@
---
title: "Security"
description: "How to report a vulnerability, and the security model contributors must preserve: project trust, the plugin sandbox and local servers."
---
## Reporting a vulnerability
Report vulnerabilities privately through GitHub's private vulnerability reporting, which is enabled for the repository:
<Card title="Report a vulnerability" icon="shield" href="https://github.com/l0ng-ai/openidea/security/advisories/new">
Opens a private security advisory draft visible only to you and the maintainers.
</Card>
Please don't open a public issue, discussion or PR for a vulnerability before it is fixed. Include the version (or commit), the platform, steps to reproduce and what an attacker gains.
<Note>
The repository has no `SECURITY.md` and no stated response times or supported-version policy. Fixes ship in regular
releases.
</Note>
For a malicious plugin listed in the marketplace, use the same private channel.
## Security model
When you change the areas below, keep these properties intact. Reviewers check them.
### Project trust and restricted mode
Opening a project folder can run code that the project controls: language servers that execute build scripts, run configurations, build tools, git hooks, project plugins. So execution needs the user's trust.
- When a project that hasn't been decided opens, the **Trust and Open Project?** dialog offers **Trust Project**, **Preview in Restricted Mode** and **Don't Open**. Decisions and trusted locations are managed on Settings' **Project Trust** page (group **Security**), and **Revoke Trust** is an action.
- Trust is user-owned state (`crates/ide-host/src/trust.rs`). **Project settings never grant trust.**
- Enforcement is in the backend, not the UI: every IPC command passes `Host::authorize_command` in the Tauri invoke handler (`src-tauri/src/lib.rs`) before it runs. In an untrusted project only commands on an explicit allowlist (`restricted_command_allowed`) run: reading and editing files and documents, syntax, search, navigation, local history, spelling, analysis, refactoring, settings and similar.
- The allowlist is a default-deny list: **a new command that executes anything is denied in restricted mode until you deliberately classify it there.** Don't add an execution command to the allowlist to make a feature "work" in restricted mode.
- Below the command layer, language servers don't start (`LspManager` execution switch), and git runs with execution disabled and without the project's `git` executable setting until the project is trusted. Project plugins (`.openidea/plugins/`) load only for trusted projects.
- In a remote window the desktop checks the remote project's trust before forwarding a command, and `openidea-server` authorizes every `invoke` again.
Tests: `crates/ide-host/tests/trust.rs`, `trust_git_binary.rs`.
### Plugin sandbox
Plugins are WebAssembly components run by wasmtime (`crates/ide-plugin`). See [Plugin host](/en/contribute/architecture/plugin-host) for the internals.
- Each plugin gets its own `Store`: no preopened directories, no environment, no network, no inherited stdio — only WASI clocks and randomness plus the host functions.
- Every host function that reaches files, processes, the network, VCS writes and so on is gated by a **capability** declared in `plugin.toml` and granted by the user at install (or when an update asks for more). Each capability has a risk level shown in the consent dialog. A missing grant returns `denied`; the plugin keeps running.
- `process:<program>` grants pin the program's absolute path and content hash; a replaced file is refused until approved again. `fs.read:` / `fs.write:` paths are resolved to their real location before comparison, so symlinks can't escape a grant.
- Calls are bounded by fuel, wall-clock deadlines and a memory cap. A trap drops the instance; it never takes the IDE down.
- Plugin web views run in a sandboxed frame with an opaque origin, no network and no IDE API except the frame bridge; their files are served with a restrictive CSP.
- Packages are signed (Ed25519). The marketplace index is the trust root for publishers and carries revocations; an installed plugin updates silently only to a package signed with the same key or a key it endorsed.
The capability table and limits are documented for plugin authors in [Capabilities](/en/plugins/capabilities).
### Local servers
- **MCP server** (`crates/ide-mcp`): listens on `127.0.0.1` on a random port, requires a random per-run bearer token, and refuses requests whose `Origin` or `Host` doesn't match (DNS rebinding).
- **Built-in web server** (`crates/ide-web`): serves project files read-only on `127.0.0.1`; every URL starts with a random per-run token and requests with a foreign `Host` are refused.
- **Tauri MCP bridge** (`tauri-plugin-mcp-bridge`): compiled into debug builds only, bound to `127.0.0.1`.
### Remote development and child processes
- `openidea-server` is started over SSH, `wsl.exe --exec` or `docker exec`. The WSL and dev container transports pass every variable value (paths, versions, ids) as separate arguments; only fixed scripts reach `sh -c`. Keep it that way: never splice values into a shell string.
- Secrets used by remote plugins stay in the desktop's keychain; the server asks for one when a request needs it.
### Dependencies
CI runs `cargo deny check` (`deny.toml`) on every Rust change: security advisories, licences, banned crates and allowed sources.
+101
View File
@@ -0,0 +1,101 @@
---
title: "Development setup"
description: "Install the toolchain, build OpenIDEA from source, run it from a worktree and avoid the usual first-build pitfalls."
---
OpenIDEA is a Tauri 2 app: a Rust backend (`crates/`, `src-tauri/`) and a React + TypeScript frontend (`src/`). You need both toolchains to run it.
## Prerequisites
| Tool | Version | Notes |
| ------------------------- | --------------------- | --------------------------------------------------------------------------------------------------------- |
| Rust | stable, at least 1.96 | `rust-version` in the root `Cargo.toml`. Install `clippy` and `rustfmt` too. |
| Node.js | 22.18 or newer | The repo's scripts (`scripts/*.ts`, `perf/*.ts`) are run directly by Node as TypeScript. CI uses Node 22. |
| pnpm | 11 | The version CI uses. |
| Tauri system dependencies | — | Per OS: see [Tauri prerequisites](https://v2.tauri.app/start/prerequisites/). |
On Linux (Ubuntu 22.04, as in CI) the system packages are:
```sh
sudo apt-get install -y libwebkit2gtk-4.1-dev libayatana-appindicator3-dev librsvg2-dev libxdo-dev libssl-dev patchelf
```
Optional, only for the parts you work on:
- `rustup target add wasm32-wasip2` — building plugins and the plugin SDK (`plugins/build.sh`).
- `cargo install cargo-nextest` — the test runner CI uses (`cargo test` also works).
- The language servers, debuggers and toolchains of a language, for its [smoke tests](/en/contribute/testing/smoke).
You don't need a JDK to build: the Java extension bundles in `java/` are checked in as jars and embedded at build time.
## Clone and run
<Steps>
<Step title="Clone">```sh git clone https://github.com/l0ng-ai/openidea.git cd openidea ```</Step>
<Step title="Install frontend dependencies">
```sh pnpm install ``` This also installs the pre-commit hook (`simple-git-hooks` + `lint-staged`), which runs
`oxlint --fix`, `oxfmt` and `rustfmt` on staged files.
</Step>
<Step title="Run the app">
```sh pnpm tauri dev ``` The first run is a full debug build of the workspace and takes several minutes. Later runs
are incremental; frontend changes reload through Vite without a rebuild.
</Step>
</Steps>
`pnpm dev:app` is the same as `pnpm tauri dev`, plus the global Tauri API (`window.__TAURI__`, from `src-tauri/tauri.dev.conf.json`), which is useful when you drive the app from scripts.
Other everyday commands:
| Command | What it does |
| ------------------- | -------------------------------------------------------------------------------------------------------------- |
| `pnpm build` | Typecheck and bundle the frontend, then check the bundle against the WebKit floor (`scripts/check-webkit.ts`). |
| `pnpm gen:bindings` | Regenerate `src/bindings.ts` from the Rust command signatures. |
| `pnpm dev` | Vite alone: the UI in a plain browser with in-memory backends (no Rust). |
## Worktrees
Several worktrees can be developed and run side by side. Create one per task:
```sh
git worktree add ../my-task -b my-branch origin/main
cd ../my-task
pnpm install
pnpm tauri dev
```
Always launch through `pnpm tauri dev` / `pnpm dev:app`, not the raw `pnpm exec tauri dev`. The wrapper (`scripts/tauri.ts`):
- starts Vite on a port derived from the worktree path (moving on if it is taken) and passes the actual URL to Tauri;
- refuses to start a second launcher for the same worktree and prints the running session instead;
- gives each worktree's debug build its own app identity (`ai.l0ng.openidea.dev.w<hash>`), so data, settings, logs and the single-instance lock are separate per worktree. Different worktrees and an installed release can run at the same time.
Launching the same debug binary again focuses its running window and forwards its path argument. `--release` uses the normal release identity and bypasses the wrapper.
When you are done with a worktree: stop its app and dev session, delete its build output (`target/`), then `git worktree remove ../my-task`.
<Warning>
Keep each worktree's Cargo output separate. Don't point `CARGO_TARGET_DIR` or a `target` symlink at a shared
directory. A worktree's `target/` grows to around 9 GB after one build, more with tests and clippy, so avoid running
several heavy builds on one machine at once.
</Warning>
## MCP bridge and ports
Debug builds load the Tauri MCP bridge plugin (`tauri-plugin-mcp-bridge`, bound to `127.0.0.1`), which lets tools and agents drive the running app. Release builds don't include it.
Bridge ports are dynamic, in the range 9223–9322. To find the one for your worktree:
```sh
pnpm dev:status
```
It prints the worktree, its app identifier and the bridges that belong to it (`port`, `appIdentifier`). Connect to that port and pass `appIdentifier` explicitly; don't assume the default MCP session belongs to your worktree. If no bridge is found, the app is still compiling or not running.
## Common pitfalls
- **Running a subset of frontend tests:** use `pnpm vitest run <path>`. `pnpm test -- <path>` ignores the filter and runs the whole suite, which takes several minutes.
- **Clippy while iterating:** don't. `pnpm tauri dev` compiles anyway. Run `cargo fmt --all` and `cargo clippy -p <crate> --all-targets -- -D warnings` once, before committing. A passing clippy doesn't make the first launch faster: `pnpm tauri dev` still does a full debug build.
- **Long checks in one shell call:** a cold build cache takes around 5 minutes. Run long checks one at a time rather than chained.
- **`src/bindings.ts` changes on launch:** every debug launch re-exports the bindings (only rewriting the file when it differs). If you changed IPC types, commit the regenerated file; CI fails when it is stale.
- **Stopping processes:** stop only what you started for your worktree. Never use broad kills such as `pkill openidea`; they hit other worktrees and the installed app.
- **Raw Tauri CLI:** `pnpm exec tauri dev` skips the worktree isolation above, so it can collide with another worktree's Vite port or app identity.
+95
View File
@@ -0,0 +1,95 @@
---
title: "Performance tests"
description: "Measuring UI interaction latency, frames and leaks with pnpm perf, backend benchmarks with pnpm perf:rust, the budgets in perf/budgets.json, and the external IDE comparison in perf/compare."
---
There are three performance tools, all run locally (CI doesn't run them):
| Tool | Measures | Checked against |
| ----------------------- | ----------------------------------------------------------------------------------------------- | ----------------------------------------------------- |
| `pnpm perf` | UI interaction latency, dropped frames, main-thread time, heap and DOM size, leaks, bundle size | `perf/budgets.json` › `ui`, and your local baseline |
| `pnpm perf:rust` | Criterion benchmarks of the crates on the interactive path | `perf/budgets.json` › `rust`, and your local baseline |
| `perf/compare/bench.py` | Startup, readiness, memory and CPU of whole IDEs, from the outside | Nothing: a report comparing the apps |
When you change something on the interactive path (typing, completion, scrolling, opening files, indexing, search), run them before and after the change and include the numbers in the PR's verification section.
## UI: `pnpm perf`
`perf/run.ts` builds the workbench with `vite build --mode perf` (the production bundle plus the sample project and a `__openideaPerf` probe), serves it, drives interaction scenarios in Chromium with Playwright, and checks the medians of the measured runs.
```sh
pnpm perf # all scenarios, 3 measured runs each
pnpm perf -- --list # list the scenarios
pnpm perf -- --only typing,scroll --runs 5
pnpm perf -- --cpu 4 # 4x CPU slowdown (reported, budgets not enforced)
pnpm perf -- --save-baseline # store this run as target/perf/baseline-<cpu>x.json
pnpm perf -- --only typing --trace # Chrome trace of the last run in target/perf/traces/
pnpm perf -- --only typing --profile --unminified # CPU profile and top functions
```
Other options: `--warmup <n>` (default 1), `--headed`, `--no-build` (reuse the last build), `--channel <browser>` (default `chromium`, or `OPENIDEA_PERF_CHANNEL`), `--fail-on-regression`.
Scenarios (`perf/scenarios.ts`): `typing`, `typingLarge`, `typingAngular`, `completion`, `consoleOutput`, `terminalTui`, `keyRepeat`, `scroll`, `gitLog`, `gitLogScrollbar`, `resizePanels`, `startup`, `tabs`, `agentTabs`, `popups`, `toolWindows`, `projectTree`, `leaks`. Bundle size (`bundle.initialJsKb`, `bundle.initialCssKb`) is checked on every run.
Latencies are measured from input to the start of the first frame that shows the result, so about 16.7 ms means "next frame".
<Note>
This runs in Chromium, while the desktop app runs in the system webview (WebKit on macOS and Linux, WebView2 on
Windows). It catches regressions in the frontend's own work reliably; absolute numbers in the app can differ.
</Note>
### Adding a scenario
A scenario in `perf/scenarios.ts` has an `id`, a `title` and a `run(h)` that drives the page through the harness (`perf/lib/harness.ts`: open files, set the cursor, type and measure, read editor state) and returns metrics. Add a budget for each metric you want enforced as `"<scenario>.<metric>"` in `budgets.json`.
## Backend: `pnpm perf:rust`
`perf/rust.ts` runs `cargo bench` for `ide-text`, `ide-language`, `ide-index`, `ide-search` and `ide-host`, reads the Criterion results from `target/criterion`, and checks each bench's mean (in µs).
```sh
pnpm perf:rust # run the benches, then check
pnpm perf:rust -- --no-run # only check the latest Criterion results
pnpm perf:rust -- --package ide-text --save-baseline
```
The benches live in `crates/<crate>/benches/`:
| Bench group | Crate | Covers |
| -------------- | -------------- | -------------------------------------------------------------------------------------------------- |
| `text/…` | `ide-text` | opening 1 MB, applying keystrokes and change batches, diffs, coordinate conversion, save with CRLF |
| `syntax_2mb/…` | `ide-language` | selection, outline and folding on a cached tree and after an incremental keystroke |
| `index_100k/…` | `ide-index` | file, directory and symbol queries on a 100k-file index |
| `search_5k/…` | `ide-search` | literal, regex and dense-match search over 5k files |
| `plugins/…` | `ide-host` | loading 50 plugins, cold activation, syntax highlights of a 2k-line file |
## Budgets and baselines
[`perf/budgets.json`](https://github.com/l0ng-ai/openidea/blob/main/perf/budgets.json) holds upper bounds (lower is better):
- `ui`: `<scenario>.<metric>` → bound, compared with the median of the measured runs; enforced at CPU 1x only;
- `rust`: Criterion bench id → mean in microseconds;
- `regressionTolerance` (0.2) and `regressionFloor` (1): how much worse than the baseline a metric may get, relative and absolute, before it counts as regressed.
Each metric gets a verdict:
| Verdict | When | Fails the run |
| ----------- | --------------------------------------------------------------- | -------------------------------- |
| over budget | above its budget | yes |
| regressed | more than 20% and more than the floor above your saved baseline | only with `--fail-on-regression` |
| improved | more than 20% and more than the floor below the baseline | no |
Baselines are local (`target/perf/`), so save one on your machine before you start changing things, and compare on the same machine.
## Comparing with other IDEs: `perf/compare`
`pnpm perf` measures OpenIDEA from inside its webview. `perf/compare/bench.py` measures any IDE from the outside, the same way for each, so OpenIDEA can be compared with the other apps listed in `perf/compare/apps.toml`:
```sh
pip install -r perf/compare/requirements.txt
python perf/compare/bench.py ~/src/some-project --runs 10
python perf/compare/bench.py ~/src/some-project --runs 5 --wipe-caches # cold index
```
Each run launches the app on the project, waits until it is ready (the process tree's CPU stays low after the window and the log markers appear), idles, closes it, and records time to window and to ready, peak and idle memory summed over every process the app started (webview, language servers, JVMs), CPU time, exit time and cache size. Apps run interleaved after warmup runs; output goes to `results/<time>-<project>/` (`report.md`, `results.json`).
Use release builds only; the script refuses to run while another instance of the same executable is running. See [`perf/compare/README.md`](https://github.com/l0ng-ai/openidea/blob/main/perf/compare/README.md) for fair-run rules and platform notes.
+77
View File
@@ -0,0 +1,77 @@
---
title: "Smoke tests"
description: "Language smoke tests with real language servers, and run and debug smoke tests through a real openidea-server, on the fixtures projects."
---
Smoke tests check each language end to end with the real tools the IDE uses: language servers, build tools, test runners and debuggers. They run on the sample projects in [`fixtures/`](https://github.com/l0ng-ai/openidea/tree/main/fixtures), which each have a few sources, passing tests, an opt-in failing test and an intentional warning.
They are not part of `cargo nextest run` or `pnpm test`, because they need the tools installed. CI runs them in the **Language smoke** workflow with pinned versions.
| Suite | What it checks | Languages |
| ----------------------------------------- | ------------------------------------------------------------------------------ | ------------------------------------------------------- |
| `crates/ide-host/tests/language_smoke.rs` | Code intelligence through the `ops` the app's commands call | typescript, python, rust, go, java, kotlin, php |
| `crates/ide-host/tests/project_smoke.rs` | Opening a fixture with its build tool and SDK | `maven_sync`, `jdk`, `gradle_sync`, `python_venv` |
| `smoke/run.test.ts` | Run and test configurations through the run store and a real `openidea-server` | typescript, python, rust, go, java, kotlin, php |
| `smoke/debug.test.ts` | Debugging the entry point with the debugger the IDE installs | typescript, python, rust, go, java, kotlin, c, cpp, php |
## Language smoke
For each language, the everyday checklist on its fixture against the language server the app starts by default: completion, go to definition, Find Usages, hover, diagnostics with a quick fix, an intention, rename, formatting, parameter info, the Structure view, auto-import, Optimize Imports and moving a file. Every check names what it looks for, so a failure says which operation broke.
```sh
cargo test -p ide-host --test language_smoke -- --ignored --exact rust
```
The tests are `#[ignore]`d with the server they need (`#[ignore = "language smoke: needs vtsls"]`). Install that server first; the CI workflow shows how for each language.
The project smoke tests open fixtures the way the app does (source and test roots from the build file, the index, Go to Class / Symbol, the Maven / Gradle model). The ignored ones run Maven, Gradle, python3 and a JDK 21:
```sh
cargo test -p ide-host --test project_smoke -- --ignored --exact gradle_sync
```
## Run and debug smoke
These are Vitest suites (`pnpm smoke`, config in `smoke/vitest.config.ts`) that drive the **frontend's own modules** — the run store, test reporting, the debugger state — against a real backend. `smoke/server.ts` starts `openidea-server --stdio`, and `smoke/desktop.ts` provides an in-process Tauri runtime whose commands go to that server through the same route remote development uses.
Build the server first, then run one language:
```sh
cargo build -p ide-host --bin openidea-server
pnpm smoke smoke/run.test.ts -t '^rust$'
pnpm smoke smoke/debug.test.ts -t '^rust$'
```
`-t` is a pattern on the test name; anchor it (`'^c$'`) so `c` doesn't also select `cpp`.
- **run** detects the fixture's configurations, starts them, parses the output into the test tree, switches on the intentionally failing test, and reruns the failed tests.
- **debug** sets a breakpoint on the entry point's "Set a breakpoint here" line, starts Debug from the file's context (or a saved configuration when the debugger is an explicit choice), then checks the paused frame, its variables, evaluation, Step Over and Resume. C and C++ use GDB's own DAP interpreter (GDB 14+); the other languages use the debugger the IDE installs.
Each test file starts its own server and toolchains, so files run one at a time with a 10-minute test timeout.
<Note>
On macOS, CodeLLDB and Delve may need Developer Mode: `sudo DevToolsSecurity -enable`. The IDE downloads some
debuggers from GitHub releases; set `GITHUB_TOKEN` if you hit rate limits (CI does).
</Note>
## In CI
`.github/workflows/language-smoke.yml` runs one job per language (typescript, python, rust, go, java, kotlin, php, c, cpp) on pushes to `main` and on PRs that touch `crates/`, `languages/`, `fixtures/`, `Cargo.toml`/`Cargo.lock`, `src/features/run/`, `src/features/debugger/`, `src/platform/`, `smoke/`, `package.json`/`pnpm-lock.yaml` or the workflow itself. Each job runs the language smoke test, the relevant project smoke tests, then the run and debug smoke tests.
Server versions are pinned in the workflow's `env` so a server release can't fail an unrelated PR; bump them deliberately:
| Variable | Pinned to |
| -------------- | ------------------- |
| `VTSLS` | 0.3.0 |
| `TYPESCRIPT` | 6.0.3 |
| `BASEDPYRIGHT` | 1.40.2 |
| `RUFF` | 0.17.3 |
| `GOPLS` | v0.24.0 |
| `JDTLS` | 1.63.0-202610191322 |
| `KOTLIN_LSP` | 263.7084.0 |
| `GRADLE` | 8.14.3 |
| `INTELEPHENSE` | 1.18.5 |
## When to run them
When you change a fixture, a language definition in `languages/`, `ide-lsp`, `ide-dap`, the run or debugger features, or how a language's tools are installed, run the smoke tests for the affected languages and put the commands and results in the PR's **How it was verified**.
+69
View File
@@ -0,0 +1,69 @@
---
title: "Unit and E2E tests"
description: "Running and writing Rust tests with cargo nextest, frontend tests with Vitest, and UI flows with Playwright."
---
| Layer | Runner | Command |
| -------------------------------------------- | ----------------------------------- | ----------------------------------------------- |
| Rust crates | cargo-nextest (or `cargo test`) | `cargo nextest run --workspace` |
| Frontend modules and components | Vitest + jsdom + Testing Library | `pnpm test`, or `pnpm vitest run <path>` |
| UI flows | Playwright (Chromium) | `pnpm e2e` |
| Real language servers, toolchains, debuggers | ignored Rust tests and `pnpm smoke` | see [Smoke tests](/en/contribute/testing/smoke) |
## Rust
```sh
cargo nextest run --workspace # everything, as CI does
cargo nextest run -p ide-text # one crate
cargo nextest run -p ide-host --test vcs_flow # one integration test file
```
- Unit tests sit next to the code (`#[cfg(test)]` modules or a `tests.rs`); integration tests in `crates/<crate>/tests/`. `ide-host` has the most: opening projects, trust, VCS flows, run configurations, plugins, the remote protocol.
- Tests that need real external tools (language servers, Docker, build tools) are `#[ignore]`d with a reason and run explicitly with `-- --ignored`.
- Use `NullSink` when a crate wants an `EventSink` and the test doesn't care about events.
- CI runs the suite on macOS, Ubuntu 22.04 and Windows with `--no-fail-fast`. Test fixtures write LF files, so CI sets `git config --global core.autocrlf false`; do the same on Windows if fixture comparisons fail.
- The CI test run also regenerates `src/bindings.ts` (the `export_bindings` test) and fails if it differs from the committed file.
Plugins have their own workspaces: see [Plugin host › Testing the host](/en/contribute/architecture/plugin-host#testing-the-host).
## Frontend unit tests
`pnpm test` runs `vitest run` over `src/**/*.test.{ts,tsx}`, `perf/**/*.test.ts` and `scripts/**/*.test.ts` in jsdom. The full suite takes several minutes, so while iterating run a subset:
```sh
pnpm vitest run src/platform/keymap.test.ts
pnpm vitest run src/features/search
pnpm test:watch # watch mode
```
<Warning>
`pnpm test -- <path>` ignores the path and runs the whole suite. Use `pnpm vitest run <path>`.
</Warning>
What to know when writing them:
- Tests live next to the module (`foo.test.ts`) or in a feature's `__tests__/` folder.
- `src/test/setup.ts` makes jsdom look like macOS (so ⌘ keymaps apply; opt into Windows explicitly) and gives elements a size so virtualized lists render rows.
- Outside Tauri, `isTauri()` is false and features use their in-memory backends. To simulate a backend event, use `emitLocalIdeEvent` from `@/platform/ipc`.
- Some tests are repository checks rather than unit tests: the text catalogs (`src/platform/locales/*.test.ts`), WebKit compatibility (`src/platform/webkitCompatibility.test.ts`), the theme token reference (`scripts/theme/theme.test.ts`). They run in `pnpm test` too.
## End-to-end tests
`pnpm e2e` runs Playwright against the UI in **plain-browser mode**: Vite serves the frontend (port 1450, or `OPENIDEA_E2E_PORT`), the shell injects a sample project, and features fall back to in-memory backends — no Tauri, no Rust build, no network.
```sh
pnpm exec playwright install chromium # once
pnpm e2e # all specs
pnpm e2e e2e/editor.spec.ts # one file
pnpm e2e -g "opens a file" # by test name
```
- Specs are `e2e/*.spec.ts`. Import `test` and `expect` from `e2e/fixtures.ts`; its `workbench` fixture opens the sample project (`acme-api`) and helpers like `openFromTree` and `doubleShift` cover common steps.
- Specs use the macOS keymap (`Meta+…`) on every OS; the fixture forces it.
- To exercise code paths that need a desktop backend, `stubDesktop(page)` switches the page to desktop mode over a stub: you register handlers per command name (`window.__ipc.handlers.lsp_goto = …`), push values into a `Channel` with `send`, and emit `IdeEvent`s with `emit`. Unhandled commands fail as `unavailable`. `ipcCalls(page, cmd)` returns the calls made.
- Standalone pages for focused component tests are `e2e/*.html` + `e2e/*.fixture.ts`.
- In CI, E2E runs in the `mcr.microsoft.com/playwright` container matching the `@playwright/test` version, with one retry; on failure the HTML report and traces (`trace: "retain-on-failure"`) are uploaded as an artifact.
## Verifying in the real app
Unit and E2E tests don't run the Rust backend inside the webview. For UI or runtime behaviour, run `pnpm tauri dev` and check the change in the app. Debug builds include the Tauri MCP bridge, so the check can be scripted; find the port with `pnpm dev:status` (see [Development setup](/en/contribute/setup#mcp-bridge-and-ports)). The repository's agent skill `.agents/skills/verify` describes driving the app that way and capturing screenshots.
+102
View File
@@ -0,0 +1,102 @@
---
title: "Contribution workflow"
description: "How issues are triaged, what a pull request needs, how verification is checked, and how changes get merged."
---
These rules come from [`CONTRIBUTING.md`](https://github.com/l0ng-ai/openidea/blob/main/CONTRIBUTING.md) and apply to everyone, including coding agents working on someone's behalf. Coding rules, commands and checks are in [`AGENTS.md`](https://github.com/l0ng-ai/openidea/blob/main/AGENTS.md).
## Issues
- One problem or request per issue. Use the issue forms ([Bug report](https://github.com/l0ng-ai/openidea/issues/new?template=bug_report.yml) or [Feature request](https://github.com/l0ng-ai/openidea/issues/new?template=feature_request.yml)); blank issues are disabled.
- **Bugs:** steps that reproduce the problem from a fresh start, on the [latest release](https://github.com/l0ng-ai/openidea/releases/latest). A minimal sample project is the fastest way to a fix.
- **Features:** the problem and the acceptance criteria. A feature is ready to build once a maintainer labels it `planned`.
Labels track status:
| Label | Meaning |
| ------------------ | --------------------------------------------------------------------------- |
| `needs-info` | Waiting on the reporter (or, on a PR, on a filled-in verification section). |
| `needs-decision` | Waiting on a maintainer's product decision. |
| `planned` | Accepted; ready to build. |
| `needs-acceptance` | A fix exists and is waiting to be checked. |
## Pull requests
<Steps>
<Step title="Pick one problem">
One problem per PR. If it resolves an issue, link it with `Closes #123`. Unrelated refactors go in their own PR.
</Step>
<Step title="Reproduce first">Reproduce the problem, or write a test that fails before the fix.</Step>
<Step title="Fix the cause">
Fix the root cause, then check whether the same cause breaks anything nearby. Keep the diff focused.
</Step>
<Step title="Run the checks">
Run what CI runs for the parts you touched (below), and verify UI or runtime changes in the running app.
</Step>
<Step title="Open the PR">
Title in [Conventional Commits](https://www.conventionalcommits.org/) form, e.g. `fix(editor): keep the caret
visible after folding`. Fill in the template.
</Step>
</Steps>
Keep the description short and in plain words. If it doesn't fit in a few sentences, the PR is probably too big.
### The template
[`.github/pull_request_template.md`](https://github.com/l0ng-ai/openidea/blob/main/.github/pull_request_template.md) has three sections:
| Section | What goes in it |
| ----------------------- | ----------------------------------------------------------------------------------------------------------------------- |
| **What and why** | 2–4 sentences: the change a user notices, and the root cause. |
| **How it was verified** | Required. The commands you ran and their results; what else the change could affect and how you checked it still works. |
| **Review focus** | Optional. Anything reviewers should look at closely. |
### How it was verified
This section is required. Include:
- the commands you actually ran and their results (tests, clippy, `pnpm e2e`, a smoke test…);
- what else the change could affect, and how you checked it;
- **visible UI changes:** before/after screenshots; **multi-step interactions:** a recording;
- **backend changes:** logs or command output.
Screenshots are not needed for UI that goes away with a removed feature, copy-only changes or non-visual changes; say what you checked instead.
A workflow (`.github/workflows/pr-check.yml`) reads each PR's description when it is opened or edited. If **How it was verified** is missing or empty, it comments and adds `needs-info`; once you fill it in, it removes its comment and the label. It only reads the title and description, never your code.
### Checks to run
| You touched | Run |
| ----------------------- | ------------------------------------------------------------------------------------------------------------------- |
| Rust | `cargo fmt --all --check`, `cargo clippy --workspace --all-targets -- -D warnings`, `cargo nextest run --workspace` |
| IPC types | `pnpm gen:bindings`, and commit `src/bindings.ts` |
| Frontend | `pnpm typecheck`, `pnpm lint`, `pnpm format:check`, `pnpm knip`, `pnpm test`, `pnpm build`; `pnpm e2e` for UI flows |
| Plugins, SDK or `wit/` | `plugins/build.sh`, `cargo test -p ide-host --test plugins` |
| UI or runtime behaviour | Verify in the running app (`pnpm tauri dev`) |
See [Unit and E2E tests](/en/contribute/testing/unit-e2e) for details.
### What CI runs
`.github/workflows/ci.yml` first classifies the changed files, so a docs-only or frontend-only PR skips the jobs it can't affect:
- **Rust** on macOS, Ubuntu 22.04 and Windows: `cargo fmt --check` (Linux), clippy with `-D warnings`, `cargo nextest run --workspace`, and a check that `src/bindings.ts` is up to date.
- **Remote server**: a static Linux (musl) build of `openidea-server`.
- **Plugins**: format, clippy (host and `wasm32-wasip2`) and tests of the SDK, the official plugins and the test fixtures; plugin web pages typechecked and tested; `plugins/build.sh --check`.
- **Cargo deny**: `cargo deny check` (licences, advisories, sources).
- **Frontend**: typecheck, lint, format check, knip, tests, build.
- **E2E**: `pnpm e2e` in the Playwright container.
`.github/workflows/language-smoke.yml` runs the [language smoke tests](/en/contribute/testing/smoke) when crates, `languages/`, `fixtures/`, the run/debugger features, `src/platform/` or `smoke/` change.
## Product decisions
If a change needs a product decision you can't make (behaviour users would notice, a trade-off between options, scope), ask in the issue and stop rather than guessing. The issue gets `needs-decision` until a maintainer answers.
## Plugins
PRs to this repository add or change the official plugins (`plugins/official/`) and the plugin API. Community plugins live in their authors' repositories and are listed through [l0ng-ai/openidea-plugins](https://github.com/l0ng-ai/openidea-plugins). See [Publishing](/en/plugins/publishing/marketplace).
## Review and merge
Maintainers review the PR against the rules above: one problem, root cause fixed, focused diff, verification that covers what the change affects. Expect requests to split a PR that does several things. On `main`, a merged PR's commit carries its Conventional Commits title and number, e.g. `fix(maven): recognize POMs with non-UTF-8 declared encodings (#2495)`, so write the title as the commit message you want in the history.
+142
View File
@@ -0,0 +1,142 @@
---
title: "Agents"
description: "Run coding agents such as Claude Code, Codex and Gemini CLI inside the IDE, each task in its own Git worktree or in your checkout."
---
OpenIDEA runs command-line coding agents and gives them a chat interface in the IDE. Each conversation is a **task**. A task can work in a new Git worktree, away from your own changes, or directly in the project. You review what it changed as diffs, answer its permission requests, and keep several tasks running side by side.
OpenIDEA does not include a model or an account of its own: it starts the agent you have installed and signed in to, and talks to it over the [Agent Client Protocol](https://agentclientprotocol.com) (ACP).
## Supported agents
These agents are built in. Install the agent's own CLI and sign in once in a terminal; OpenIDEA finds it on `PATH`.
| Agent | Command OpenIDEA starts | Sign in with |
| ----------------- | ----------------------------------------------------- | --------------------- |
| Claude Code | `claude` | `claude` |
| Codex | `codex app-server` | `codex login` |
| Claude Code (ACP) | `npx -y @agentclientprotocol/[email protected]` | `claude` |
| Codex (ACP) | `npx -y @agentclientprotocol/[email protected]` | `codex login` |
| Gemini CLI | `gemini --experimental-acp` | `gemini` |
| GitHub Copilot | `copilot --acp` | `copilot` |
| Cursor | `cursor-agent acp` | `cursor-agent login` |
| OpenCode | `opencode acp` | `opencode auth login` |
| Qwen Code | `qwen --experimental-acp` | `qwen` |
| Kimi CLI | `kimi acp` | `kimi` |
| Goose | `goose acp` | `goose configure` |
| Auggie | `auggie --acp` | `auggie login` |
| Kiro | `kiro-cli acp` | `kiro-cli login` |
| Mistral Vibe | `vibe-acp` | `vibe` |
**Claude Code** and **Codex** are driven through their own protocols (Claude Code's stream-json mode and `codex app-server`); the "(ACP)" variants use the official ACP adapters through `npx` instead. If an agent needs you to sign in, the task tells you which command to run.
Agents are unavailable in a project you have not marked as trusted.
## Start a task
| Action | macOS | Windows / Linux |
| ------------------ | ----- | ------------------ |
| **New Agent Task** | `⇧⌘I` | `Ctrl+Alt+Shift+I` |
<Steps>
<Step title="Open the New Agent Task page">
Press the shortcut, or click **New Agent Task** in the **Agents** tool window (on the right).
</Step>
<Step title="Describe the task">
Type what the agent should do. Type `@` to reference a file, `/` for the agent's slash commands, and paste or attach images (**Attach Image…**) when the agent accepts them.
</Step>
<Step title="Choose where it works">
- **New worktree**: the agent works in a new Git worktree on a branch named `agent/<task>`, created from the base branch you pick. Your checkout is not touched. Worktrees are created next to the project, in a folder named `<project>.worktrees`.
- **Local checkout**: the agent edits your files directly.
</Step>
<Step title="Pick the agent and its settings">
Choose the agent, and its model, mode and other options when it offers them. Models and modes appear after the agent's first task.
</Step>
<Step title="Send">
The task opens as an editor tab and starts.
</Step>
</Steps>
## Work with a task
The task tab shows the conversation: your messages, the agent's replies, its reasoning, plans, the tools it runs and the subagents it starts. While the agent works:
- Messages you send are queued and delivered when the turn ends (**Queued messages**). **Send now** stops the current turn and sends that message next; queued messages can also be edited or removed.
- **Stop** ends the current turn.
- `⌘F` / `Ctrl+F` finds text in the conversation.
On your own messages you can **Edit** (the messages after it are replaced; files already changed stay as they are) or **Retract Message**. On replies you can **Copy Markdown**, **Quote Reply**, **Regenerate Response** and **Branch Conversation from Here**, which starts a new task from that point.
### Permissions
When an agent wants to run a command or call a tool that needs approval, the task shows the request and is listed as **Needs Input**. Choose one of the options the agent offers, such as **Allow Once**, **Allow for This Session**, **Always Allow**, **Decline**, **Always Decline** or **Decline and Stop**. Many agents also have a mode picker that approves edits or everything automatically; use it with care.
### Review the changes
Click **Show changes** in the task header to list the files the task changed. Click a file to open a diff between the task's starting point and now. For a worktree task, the changes live on its `agent/…` branch: open a terminal there with **Open in Terminal**, and merge or cherry-pick the branch with the Git tools (see [Version control](/en/guide/vcs)) when you are happy with it.
### Context from the editor
- **Send to Agent** in the editor and Project view context menus adds the selection or the selected files as `@` references to the message you typed last. It has no default shortcut; assign one in **Settings › Keymap**.
- Referenced files up to 256 KB are embedded in the prompt when the agent accepts embedded files.
- When the [built-in MCP server](/en/guide/mcp) is on, every task is given OpenIDEA's tools as an MCP server, so the agent can use the IDE's search, symbol lookup, problems, run configurations and more.
## The Agents tool window
The **Agents** tool window lists the project's tasks by what they need from you: **Needs Input**, **Failed**, usage limit reached, **Running**, **Ready to Review** and **Archived**. Its badge counts tasks that need input or have unread replies.
Tasks open in editor tabs by default. **Chat in Tool Window** and **Chat in Editor** switch where conversations open; you can also drag a task into the editor area. A task's context menu has **Open**, **Rename…**, **Open in Terminal**, **Copy to a New Task**, **Archive** / **Unarchive** and **Delete…**. Deleting a worktree task also removes its worktree.
Tasks are saved per project and survive restarts. When the agent supports resuming sessions, the conversation continues where it left off on the next message; such an agent is also stopped after five idle minutes and started again when you send the next message.
## Manage agents
**Settings › Tools › Agents** (also **More Agents…** in the agent picker) has two tabs:
- **Built-in**: for each built-in agent, whether its executable was found on `PATH`, a field to point at a **Custom executable**, **Environment variables** for the agent process (written as `NAME=value; OTHER=value`) and how to sign in. Changes apply to new sessions.
- **ACP Registry**: agents published in the [ACP Registry](https://agentclientprotocol.com). Search, **Install**, **Update** and **Remove**. Installed agents are kept in OpenIDEA's data directory; downloads are checked against the registry's SHA-256 when it provides one.
**Settings › Tools › Agents › Chat** chooses what the task header shows: **Context window usage**, **Time spent on the task**, **Usage limits** (when the agent reports them, such as Claude Code) and **Session cost**.
## Add a custom agent
Any agent that speaks ACP over stdio can be added with an agent file, `agents/<id>.toml` in OpenIDEA's configuration directory:
| OS | Agent files |
| ------- | -------------------------------------------------------- |
| macOS | `~/Library/Application Support/ai.l0ng.openidea/agents/` |
| Windows | `%APPDATA%\ai.l0ng.openidea\agents\` |
| Linux | `~/.config/ai.l0ng.openidea/agents/` |
```toml
# agents/my-agent.toml
name = "My Agent"
command = "/usr/local/bin/my-agent"
args = ["--acp"]
[env]
MY_AGENT_LOG = "info"
```
<ParamField path="name" type="string">
Name shown in the agent picker. Defaults to the file name.
</ParamField>
<ParamField path="command" type="string" required>
The executable to start, a path or a name on `PATH`.
</ParamField>
<ParamField path="args" type="string[]">
Arguments that start the agent in ACP mode.
</ParamField>
<ParamField path="env" type="table">
Extra environment variables for the agent process.
</ParamField>
<ParamField path="driver" type="string" default="acp">
`acp` for an ACP agent. `claude` and `codex` use the Claude Code and Codex protocols instead.
</ParamField>
The file name (without `.toml`) is the agent's id. A file named after a built-in agent (`gemini.toml`, `claude.toml`…) replaces that agent's command; this is what the **Built-in** tab writes when you set a custom executable or environment. Files that fail to parse are skipped and logged.
## Limits
- Agents run on your machine with your credentials and your subscriptions or API keys; OpenIDEA does not proxy or meter them.
- A worktree task needs the project to be a Git repository with at least one commit.
+69
View File
@@ -0,0 +1,69 @@
---
title: "Android and Logcat"
description: "Watch the log of a connected Android device or emulator in the Logcat tool window."
---
OpenIDEA's Android support is the **Logcat** tool window: it streams the log of a connected device or emulator through `adb`, with level and text filters. It does not build, install or run Android apps, manage emulators or edit Android resources.
## Set up adb
Logcat drives the `adb` command from the Android SDK platform-tools. OpenIDEA does not bundle it. It looks for `adb` in this order:
1. The `OPENIDEA_ADB` environment variable (full path to the executable).
2. `platform-tools/` under `ANDROID_HOME` or `ANDROID_SDK_ROOT`.
3. The usual SDK locations: `~/Library/Android/sdk` (macOS), `~/Android/Sdk` (Linux), `%LOCALAPPDATA%\Android\Sdk` (Windows).
4. `adb` on `PATH`, then a few common install paths such as `/opt/homebrew/bin/adb` or `C:\platform-tools\adb.exe`.
<Note>
On macOS, an app started from the Dock does not see variables exported in your shell profile. If you rely on
`ANDROID_HOME`, install the SDK in the default location or start OpenIDEA from a terminal.
</Note>
## Open Logcat
Logcat is not shown on the tool window bar until you first open it. Open it from **More Tool Windows** (the `…` button on the tool window bar), from **View › Tool Windows › Logcat**, or with Find Action (`⌘⇧A` / `Ctrl+Shift+A`). It works without a project open.
Connect a device over USB or start an emulator. The device list shows each device as `model (serial)`; devices that are offline or unauthorized are listed but cannot be selected. Click **Refresh devices** after connecting a new one.
When you pick a device, Logcat shows the last 200 lines of its buffer and then follows new output. Switching to another device starts with an empty view.
## Toolbar
| Control | What it does |
| ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- |
| **Device** | The device to watch. |
| **Minimum level** | Show lines at this level or above: Verbose, Debug, Info, Warnings, Errors, Fatal. Lines that don't follow the logcat format are always shown. |
| Filter box | A filter expression (see below). |
| **Filter history** | Recent filters. **Clear History** empties the list. |
| **Pause** / **Resume** | Stop updating the view. Lines that arrive while paused are added when you resume. |
| Scroll to end | Follow the newest line. Scrolling up stops following; click the button to follow again. |
| **Clear** | Empty the view. The device's own log buffer is not cleared. |
| **Refresh devices** | Ask `adb` for the device list again. |
The status line shows whether the stream is running, `Paused` while paused, the number of lines (`shown / total` when filtered), and an error if the filter is invalid.
## Filter expressions
The filter box takes expressions in the same shape as Android Studio's Logcat:
| Expression | Matches |
| -------------------------------------------- | --------------------------------------------------- |
| `tag:glide` | Tag contains `glide` |
| `message:boom` (or `msg:`) | Message contains `boom` |
| `pid:4242` | Process or thread id is 4242 |
| `level:error`, `level:warn,error`, `level:e` | Level is one of those (names or letters) |
| `-tag:chatty` | Excludes matching lines (`!` works too) |
| `a b` | Both (`&` works too) |
| `a\|b` | Either |
| `(a\|b) -c` | Grouping |
| `message~:^Tile.*` | Case-insensitive regular expression |
| `message:"two words"` | Quoted value |
| `glide` | A bare word matches the tag, the message, or `#pid` |
Text matching is case-insensitive. Press `Enter` to save the filter to the history, and `↑` / `↓` in the filter box to step through earlier filters. The history keeps the last 20 filters.
## Limits
- The view keeps the most recent 20,000 lines.
- One device at a time.
- No package or process picker, no saved filter presets and no export of the log.
+130
View File
@@ -0,0 +1,130 @@
---
title: "Build tools"
description: "Build the project from the Build menu, work with Maven and Gradle in their tool windows, and manage .NET, CMake, Cargo, Go and npm projects."
---
OpenIDEA does not have its own build system. It reads your build files, runs the build tool you already use, and shows the results in the IDE.
## Build menu
The **Build** menu works with every supported build system. OpenIDEA picks the build system of the project, or of the module that holds the current file.
| Action | macOS | Windows / Linux |
| --------------------------------------------------- | ------ | --------------- |
| **Build Project**: compile sources and tests | `⌘F9` | `Ctrl+F9` |
| **Build Module**: compile the current file's module | `⌘⇧F9` | `Ctrl+Shift+F9` |
| **Rebuild Project**: clean, then build | — | — |
| **Build Artifacts**: package the outputs | — | — |
What each action runs:
| Build system | Detected by | Build | Rebuild | Build Artifacts |
| ------------ | --------------------------------------------- | ----------------------------------------------------- | --------------------------- | ----------------------------------------------------- |
| Maven | `pom.xml` | `mvn test-compile` (a module: `-pl <module> -am`) | `clean test-compile` | `package -DskipTests` |
| Gradle | `build.gradle(.kts)`, `settings.gradle(.kts)` | `classes testClasses` | `clean classes testClasses` | `assemble` |
| Cargo | `Cargo.toml` | `cargo build --all-targets` | `cargo clean`, then build | `cargo build --release` |
| Go | `go.mod`, `go.work` | `go build ./...` (every module of a `go.work`) | `go build -a ./...` | — |
| TypeScript | `tsconfig.json`, `package.json` | `tsc --noEmit` (the project's own `tsc` if installed) | — | the `build` script with the project's package manager |
| CMake | `CMakeLists.txt` | configure if needed, then `cmake --build` | `--clean-first` | — |
Maven and Gradle use the wrapper or installation configured in Settings (see below). A TypeScript project without a `tsconfig.json` but with a `build` script runs that script.
Output streams into the **Build** tool window (bottom). Compiler errors and warnings are grouped by file, appear in the Problems view and are underlined in the editor. **Stop Build** cancels a running build.
## Maven
When the project has a `pom.xml`, a **Maven** tool window appears on the right. On first open, OpenIDEA imports the project with Maven in the background (effective POMs and dependency trees).
The tree shows **Profiles**, then each project with **Lifecycle**, **Plugins** (expand to see goals), **Dependencies**, **Run Configurations** and its modules. Double-click a goal to run it, or select several and run them together. Right-click a goal for **Run '…'**, **Debug '…'** and **Create Run Configuration…**. Running a goal from the tree creates a temporary run configuration.
Toolbar:
- **Reload All Maven Projects** re-reads the POMs and dependency trees.
- **Add Maven Projects** adds a `pom.xml` the import did not find. Right-click a project to unlink it.
- **Run Maven Build** runs the selected goal.
- **Execute Maven Goal…** opens a prompt with goal completion (`Tab` to complete, `Enter` to run). It also lists recent commands.
- **Toggle Offline Mode** (`-o`) and **Toggle 'Skip Tests' Mode** (`-DskipTests`) apply to every Maven run.
- **Analyze Dependencies…** opens the Dependency Analyzer.
- **Maven Settings…** opens **Settings › Build, Execution, Deployment › Maven**.
Profile checkboxes in the tree apply to every Maven run as well.
### Reloading after changes
When you edit a POM in the editor, the tool window shows **Build files changed since the last reload** with a **Load Maven Changes** button. When build files change outside the editor (a `git pull`, another tool), the Maven project reloads on its own.
### Editor support in `pom.xml`
`⌘`-click / `Ctrl`-click (or **Navigate › Declaration or Usages**, `⌘B` / `Ctrl+B`) on a `${property}` opens the property that defines it, in this POM or a parent POM. On a dependency or plugin without a version, it opens the `<dependencyManagement>` or `<pluginManagement>` entry that supplies it.
### Maven settings
**Settings › Build, Execution, Deployment › Maven**:
| Setting | What it does |
| --------------------------- | ------------------------------------------------------------------------------------------------------------------------- |
| **JRE** | JDK for Maven sync, builds, goals and tests |
| **Maven home path** | A Maven installation to use instead of the project's `mvnw` |
| **User settings file** | Passed as `-s` |
| **Toolchains file** | Passed as `-t` |
| **Local repository** | Passed as `-Dmaven.repo.local` |
| **Active profiles** | Comma-separated profiles (`-P`); `!id` excludes one |
| **Thread count** | Passed as `-T` (for example `4` or `2C`) |
| **Use Maven Daemon (mvnd)** | Runs builds, sync and run configurations through `mvnd`; falls back to `mvn` if it is missing or the JDK is older than 17 |
## Gradle
When the project has Gradle build files, a **Gradle** tool window appears on the right. On first open, OpenIDEA reads the task list with Gradle in the background.
The tree shows the root project with its **Tasks** (grouped), **Run Configurations** and subprojects. Double-click a task to run it. The toolbar has **Reload All Gradle Projects**, **Link Gradle Projects** (add a build the project did not find), **Run Gradle Task**, **Execute Gradle Task…**, **Toggle Offline Mode** (`--offline`) and **Analyze Dependencies…**.
When a build script changes, the tool window shows **Build files changed since the last reload** with a **Load Gradle Changes** button.
In build scripts, **Navigate › Declaration or Usages** on a version catalog alias (`libs.spring.boot.starter`) opens its entry in `gradle/libs.versions.toml`, and hover shows the declaration. In the catalog, `version.ref` and bundle members link to their entries.
### Gradle settings
**Settings › Build, Execution, Deployment › Gradle**:
| Setting | What it does |
| ----------------------- | -------------------------------------------------------------------------------------------------------------------------------- |
| **Build and run using** | **Gradle** runs a main class as a `JavaExec` task; **OpenIDEA** launches it on the Java language server's classpath |
| **Gradle JVM** | JDK for Gradle sync, tasks, run configurations and tests |
| **Distribution** | **Gradle wrapper** (or a `mise` / `asdf` pinned Gradle, then Gradle on `PATH`), or **Local installation** with a **Gradle home** |
| **Gradle user home** | Sets `GRADLE_USER_HOME` |
## Dependency Analyzer
**Analyze Dependencies…** (Maven or Gradle tool window, or the context menu of a `pom.xml` / build script) opens the Dependency Analyzer in an editor tab. It shows:
- a Maven module's dependency tree (read with `dependency:tree -Dverbose`, so omitted duplicates are listed too), or a Gradle project's resolved dependencies per configuration;
- version conflicts (**Show conflicts only**), as a tree or a list;
- every path that brings a library in, with **Jump to Source** to the declaration.
For Maven, **Exclude** adds an `<exclusion>` to the POM and reloads.
## .NET
The **.NET** tool window (right side; open it with **.NET Workspace** in Find Action) lists the solution's projects. Select a project to see its target frameworks, project references, NuGet packages and tests:
- search NuGet, install, update and remove packages;
- **Discover .NET Tests** and **Run all tests** / **Run test**;
- **Install TRX reporting** adds the reporting extension that Microsoft.Testing.Platform projects need to show results in the test tree.
**Reload .NET Workspace** re-reads the projects. .NET run and test configurations are detected automatically (see [Run configurations](/en/guide/run)).
## CMake
**Settings › Build, Execution, Deployment › CMake** holds CMake profiles: build type, generator, CMake arguments, build directory, build arguments and environment variables. **Build Project** uses the first profile. Without a profile it uses the first directory that already has a `CMakeCache.txt`, else `build`. CMake targets show up as run configurations.
## Cargo, Go, npm and others
These build systems have no tool window of their own:
- **Cargo**: run configurations for each binary, `cargo test` and `cargo build`, plus the Build menu. In `Cargo.toml`, version completion lists published crate versions, and the **Newer dependency version available** inspection offers a quick fix to update a requirement.
- **Go**: run and test configurations per module (`go.work` workspaces included) and the Build menu. In `go.mod`, version completion lists module versions.
- **npm, pnpm, yarn, bun, Deno**: one run configuration per `package.json` script or Deno task, and ▶ icons on the `scripts` entries in `package.json`. The package manager follows the lock file.
- **Make** and **just**: one run configuration per target or recipe.
- **Composer**: when `vendor/` is missing or out of date with `composer.lock`, a banner offers to run `composer install`.
Dependencies of these projects (crates, Go modules, `node_modules` packages, Maven and Gradle jars, the JDK, the Python interpreter's libraries) appear under **External Libraries** in the Project view and open read-only.
+129
View File
@@ -0,0 +1,129 @@
---
title: "Code intelligence"
description: "Completion, navigation, find usages, documentation, hierarchies and structure, and where the answers come from."
---
## Where the answers come from
Code intelligence has two sources:
- **Language servers.** Each language is configured in `languages/<id>.toml` with one or more [LSP](https://microsoft.github.io/language-server-protocol/) servers (for example basedpyright and Ruff for Python, rust-analyzer for Rust, JDT LS for Java). A server starts when you open a file of its language and answers completion, navigation, usages, documentation, diagnostics, formatting and refactorings. Several servers can serve one language; their results are merged.
- **The IDE's own index and syntax trees.** OpenIDEA parses files with bundled tree-sitter grammars and indexes the project's files and symbols in the background. This powers Go to File / Class / Symbol, the Structure view, folding, Extend Selection and syntax highlighting, and it is the fallback when no server is running: navigation, Find Usages, Rename of local variables and Introduce Variable then work from the syntax tree for what is certain, and Find Usages reports name matches as text occurrences.
When a language server is missing, a notification offers to install it. Installed servers, their versions and settings are in **Settings › Languages & Frameworks › Language Servers**. The **Language Services** widget in the status bar shows which servers run for the project and lets you restart one. See [Languages](/en/languages/index) for what each language uses.
Plugins can add completion items, quick fixes and other providers on top of these; see the Plugins tab.
## Completion
| Action | macOS | Windows / Linux |
| ---------------------------------- | -------- | --------------- |
| **Code › Code Completion › Basic** | `⌃Space` | `Ctrl+Space` |
| **Cyclic Expand Word** | `⌥/` | `Alt+/` |
| **Cyclic Expand Word (Backward)** | `⌥⇧/` | `Alt+Shift+/` |
| **Parameter Info** | `⌘P` | `Ctrl+P` |
Suggestions also appear as you type. `↵` / `Enter` inserts the selected one; `⇥` / `Tab` replaces the rest of the identifier. Documentation for the selected item opens beside the list. Live and postfix templates appear in the list too (see [Live templates](/en/guide/templates)).
Parameter info opens automatically after `(` and `,`; `⇥` / `⇧⇥` move between arguments.
**Cyclic Expand Word** completes the word at the caret from other words in the file and in open editors, without a language server. Press it again to cycle.
Completion behaviour (case matching, auto-insert of a single suggestion, sorting, parentheses, when documentation shows) is in **Settings › Editor › General › Code Completion**. **Settings › Editor › General › Auto Import** controls imports on paste and adding unambiguous imports on the fly.
### Inline completion
Language servers that support LSP inline completion show a grey suggestion at the caret as you type. Turn it off, delay it until typing pauses, or limit it to some languages in **Settings › Editor › General › Inline Completion**.
| Action | macOS | Windows / Linux |
| -------------------------------------------- | ----------- | ----------------- |
| **Call Inline Completion** | `⌥\` | `Alt+\` |
| **Insert Inline Proposal** | `⇥` | `Tab` |
| **Insert Inline Proposal: Next Word** | `⌘→` | `Ctrl+Right` |
| **Show Next Inline Proposal** / **Previous** | `⌥]` / `⌥[` | `Alt+]` / `Alt+[` |
| **Hide Inline Proposal** | `Esc` | `Esc` |
## Navigation
| Action | macOS | Windows / Linux |
| ------------------------------------- | ------------------- | ---------------------------------- |
| **Go to Declaration or Usages** | `⌘B` or `⌘`-click | `Ctrl+B` or `Ctrl`-click |
| **Go to Implementation(s)** | `⌥⌘B` or `⌥⌘`-click | `Ctrl+Alt+B` or `Ctrl+Alt`-click |
| **Go to Type Declaration** | `⇧⌘B` | `Ctrl+Shift+B` |
| **Super Method** | `⌘U` | `Ctrl+U` |
| **Navigate › Test** | `⇧⌘T` | `Ctrl+Shift+T` |
| **Back** / **Forward** | `⌘[` / `⌘]` | `Ctrl+Alt+Left` / `Ctrl+Alt+Right` |
| **Next Method** / **Previous Method** | `⌃⇧↓` / `⌃⇧↑` | `Alt+Down` / `Alt+Up` |
- **Go to Declaration or Usages** on a declaration shows its usages instead.
- Hold `⌘` / `Ctrl` and hover over a name to see it underlined as a link, with brief information about it.
- Gutter icons mark types and methods that are implemented or overridden (↓) and methods that implement or override another (↑). Click one to go there.
- **Navigate › Test** jumps between a class and its tests by naming convention and offers **Create New Test…** when there is none.
Going to files, classes and symbols by name is covered in [Search and navigation](/en/guide/search).
## Find usages
| Action | macOS | Windows / Linux |
| ----------------------------------------- | ------------- | ------------------------------- |
| **Find Usages** | `⌥F7` | `Alt+F7` |
| **Show Usages** | `⌥⌘F7` | `Ctrl+Alt+F7` |
| **Highlight Usages in File** | `⇧⌘F7` | `Ctrl+Shift+F7` |
| **Next Highlighted Usage** / **Previous** | `⌃⌥↓` / `⌃⌥↑` | `Ctrl+Alt+Down` / `Ctrl+Alt+Up` |
**Find Usages** lists the results in the **Find Usages** tool window, grouped by file, with a preview. **Show Usages** shows them in a popup at the caret. The occurrences of the symbol under the caret are highlighted in the editor as you move.
Above declarations, a **N usages** hint and the last author of the code (from Git blame) are shown. Turn them off under **Code Vision** in **Settings › Editor › Inlay Hints**.
## Documentation and information
| Action | macOS | Windows / Linux |
| ------------------------ | -------- | --------------- |
| **Quick Documentation** | `F1` | `Ctrl+Q` |
| **Quick Definition** | `⌥Space` | `Ctrl+Shift+I` |
| **Expression Type Info** | `⌃⇧P` | `Ctrl+Shift+P` |
| **Error Description** | `⌘F1` | `Ctrl+F1` |
| **Context Info** | `⌃⇧Q` | `Alt+Q` |
Hovering over code shows the documentation and any problem under the pointer after a short delay. In the **Quick Documentation** popup, links open other symbols' documentation in place, `←` / `→` walk back and forward, and `F4` jumps to the source. Pressing `F1` again while the popup has focus moves it into the **Documentation** tool window; while that window is open, `F1` shows documentation there instead of in a popup.
**Quick Definition** shows the source of the definition in a popup without leaving the file. **Context Info** shows the declaration lines around the caret (class, method and block headers), which helps when they have scrolled out of view.
## Inlay hints
Inline hints for parameter names, inferred types and other details (such as chained call types) come from the language server. Turn them on or off per kind and per language in **Settings › Editor › Inlay Hints**, or right-click a hint to disable that kind. `⌘`-click / `Ctrl`-click a hint part that names a declaration to go there.
## Structure and hierarchies
| Action | macOS | Windows / Linux |
| ------------------------- | ------ | --------------- |
| **File Structure** | `⌘F12` | `Ctrl+F12` |
| **Structure** tool window | `⌘7` | `Alt+7` |
| **Call Hierarchy** | `⌃⌥H` | `Ctrl+Alt+H` |
| **Type Hierarchy** | `⌃H` | `Ctrl+H` |
**File Structure** opens a popup of the file's declarations; type to filter and press `↵` / `Enter` to jump. The **Structure** tool window shows the same outline; its toolbar can make it follow the caret. Breadcrumbs at the top or bottom of the editor show where the caret is; set their position and languages in **Settings › Editor › General › Breadcrumbs**.
**Call Hierarchy** and **Type Hierarchy** open in the **Hierarchy** tool window. Levels load as you expand them. They need a language server that supports the LSP call and type hierarchy requests.
## Generating code
| Action | macOS | Windows / Linux |
| ---------------------- | ----- | --------------- |
| **Generate…** | `⌘N` | `Alt+Insert` |
| **Implement Methods…** | `⌃I` | `Ctrl+I` |
| **Override Methods…** | `⌃O` | `Ctrl+O` |
These list what the language server can generate at the caret (constructors, getters and setters, missing members and so on), so the choices depend on the language. Quick fixes and intentions are covered in [Inspections](/en/guide/inspections), refactorings in [Refactoring](/en/guide/refactoring).
## Semantic highlighting
Besides syntax highlighting, colours from the language server's semantic tokens are applied. **Semantic Highlighting** (each local variable and parameter name gets its own colour throughout the file) and **Rainbow Brackets** are toggle actions you can run from Find Action; both are also in **Settings › Editor › Color Scheme**.
## When something doesn't work
- Check the **Language Services** status bar widget: the server may still be starting, may have failed, or may not be installed.
- A file outside the source folders of its module (for example a Java file outside the module's source roots) gets only syntax checks from some servers. A banner at the top of the editor says so.
- **File › Power Save Mode** turns off automatic completion and parameter info popups, documentation on hover, usage highlighting, usage hints and inline completion.
- Indexing runs in the background after a project opens. Searches answer from what is already indexed and complete when indexing finishes.
- [Troubleshooting](/en/reference/troubleshooting) lists more checks and where logs are kept.
+73
View File
@@ -0,0 +1,73 @@
---
title: "Coverage"
description: "Run a configuration with coverage and see covered, partly covered and uncovered lines in the editor, the project view and the Coverage tool window."
---
**Run with Coverage** runs a [run configuration](/en/guide/run) under its runtime's coverage tool, then reads the report and shows it in three places:
- **Editor gutter**: a stripe per line. Green is covered, yellow is partly covered (not every branch or block ran), red is not covered. Hover a stripe for the hit count and branch count.
- **Project view**: a percentage of covered lines next to each file and folder.
- **Coverage tool window**: a tree of folders and files with **Line, %** and **Branch, %** columns. Click a column header to sort; open a file from the tree.
## Run with coverage
<Steps>
<Step title="Pick a configuration">
Select a test or program configuration in the run widget, or put the caret in a test or `main` function.
</Step>
<Step title="Start the run">
Use **Run › Run with Coverage**, or choose **Run '…' with Coverage** from the gutter run icon menu or the run
widget's **More Actions** menu. **Run Context Configuration with Coverage** (in **Find Action…**, `⌘⇧A` /
`Ctrl+Shift+A`) covers the test or program at the caret.
</Step>
<Step title="Read the results">
When the run finishes, the Coverage tool window opens and a notification shows the share of covered lines.
</Step>
</Steps>
If the coverage tool is missing, a notification names it and offers **Install**, which runs the install command in the Run tool window. Configurations the IDE does not know how to cover get a notification that explains why.
## Supported runtimes
| Runtime | Configurations | Coverage tool |
| ----------------------- | -------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------- |
| Rust | `cargo test`, `cargo run`, `cargo nextest` | `cargo llvm-cov` (install `cargo-llvm-cov` and the `llvm-tools-preview` rustup component) |
| Go | `go test` | `go test -coverprofile` |
| Java / Kotlin (Maven) | Maven configurations that run tests (`test`, `verify`) | JaCoCo Maven plugin goals |
| Java / Kotlin (Gradle) | Gradle configurations that run tests (`test`, `check`) | JaCoCo, applied through an init script |
| JavaScript / TypeScript | `package.json` test scripts that run vitest or jest (npm, pnpm, yarn, bun) | The runner's own `--coverage` (vitest needs `@vitest/coverage-v8` or `@vitest/coverage-istanbul`) |
| Node.js | `node`, `npx`, `tsx`, `ts-node` programs | `c8` |
| Python | scripts, modules, pytest | coverage.py (`coverage run --branch`) in the project's interpreter |
| Ruby | rspec, minitest, rake, rails and plain scripts | SimpleCov (in the Gemfile for Bundler projects) |
Not supported:
- Plain `java` applications: run a Maven or Gradle test configuration with coverage instead.
- `go run`: Go coverage is collected by `go test`.
- Deno tasks.
<Note>
The IDE does not add anything to your project files. For Gradle it passes a temporary init script; for Ruby projects
without their own SimpleCov setup it preloads SimpleCov through the environment. Installing a missing tool (for
example `bundle add simplecov --group test` or `uv add --dev coverage`) does change the project, so the IDE only runs
it when you click **Install**.
</Note>
## Import a report from CI
**Import a Coverage Report…** (in **Find Action…**, or the link in the empty Coverage tool window) shows a report collected elsewhere. The format is detected from the content:
- lcov tracefile (`lcov.info`)
- Go cover profile
- JaCoCo XML report
- SimpleCov `.resultset.json`
File paths recorded on another machine are mapped into the project by their longest matching suffix.
## Manage suites
Each coverage run or imported report is a suite. The last five runs stay available as tabs in the Coverage tool window header.
**Manage Coverage Reports…** (`⌘⌥F6` / `Ctrl+Alt+F6`) opens the **Coverage Reports** dialog: choose a suite and click **Show Selected**, **Delete** a suite, or **Import…** more reports. Imported reports are remembered per project.
To remove the highlighting, use **Run › Hide Coverage** or the button in the Coverage tool window. **Show Coverage** brings the selected suite back. Editor stripes follow your edits until the next run.
+175
View File
@@ -0,0 +1,175 @@
---
title: "Databases"
description: "Connect to SQL databases, browse schemas, run queries in a console, and view, edit, import and export data."
---
The **Database** tool window connects to SQLite, PostgreSQL, MySQL/MariaDB, Microsoft SQL Server, Oracle and ClickHouse, plus databases that speak one of their protocols. Drivers are built into the IDE; there are no JDBC drivers to download.
Open it from **Tools › Database** or **View › Tool Windows › Database**. The **Tools** menu also has **New Data Source…** and **Open Query Console**.
## Supported databases
**New › Data Source** lists these presets:
| Engine | Presets |
| ---------- | -------------------------------------------------------------------------------------------- |
| PostgreSQL | PostgreSQL, Amazon Redshift, CockroachDB, Greenplum, Neon, Supabase, TimescaleDB, YugabyteDB |
| MySQL | MySQL, MariaDB, Amazon Aurora MySQL, OceanBase (MySQL mode), SingleStore, StarRocks, TiDB |
| SQL Server | Microsoft SQL Server |
| Oracle | Oracle (needs Oracle Instant Client, see below) |
| ClickHouse | ClickHouse (over HTTP) |
| SQLite | SQLite |
Presets set the default port and, where needed, SSL (for example Redshift, Neon and Supabase require SSL).
## Add a data source
Click **+** in the Database tool window (or press `⌘N` / `Alt+Insert` there) and choose:
- **Data Source ›** a preset from the list above.
- **Data Source from File…** to open a SQLite file.
- **Data Source from URL…** to paste a URL such as `postgresql://user@localhost:5432/db`. Schemes like `mysql://`, `mariadb://`, `sqlserver://`, `oracle://`, `clickhouse://`, `sqlite:path` and `jdbc:` URLs are accepted.
- **Import from Clipboard** to read a connection URL from the clipboard.
The data source dialog has a name, an optional **Color:** (tints the tree and console tabs), **Global** (make it available in all projects) and a comment, then these tabs:
<AccordionGroup>
<Accordion title="General">
**Driver:**, **Host:**, **Port:**, **User:**, **Password:** with **Save password**, **Database:** and **URL:**. Editing the URL fills in the fields, and the other way round. Set **Connection type:** to **URL only** to connect with the URL alone. For SQLite there is a **File:** field instead; the file must exist.
**Test Connection** shows the server version or the error.
</Accordion>
<Accordion title="Options">
- **Read-only**: blocks writes at the session level where the database supports it (PostgreSQL, MySQL, SQLite, Oracle, ClickHouse) and disables grid editing.
- **Transaction control:** **Auto** or **Manual** (ClickHouse is always Auto).
- **Default schema:** (PostgreSQL `search_path`) or **Current schema:** (Oracle).
- **Instant Client:** (Oracle).
- **Time zone:**, a keep-alive query interval, and **Auto-disconnect after** a number of idle seconds.
- **Single database mode** (PostgreSQL): show only the configured database.
- **Startup script:** runs on every new connection.
</Accordion>
<Accordion title="SSH/SSL">
**Use SSH tunnel** with a saved SSH configuration or custom fields (host or `~/.ssh/config` alias, port, user, and authentication by **OpenSSH config and authentication agent**, **Password** or **Key pair**).
SSL **Mode:** **Disable**, **Prefer**, **Require**, **Verify CA** or **Verify Full**, with optional **CA file:**, **Client certificate file:** and **Client key file:**. Not available for SQLite.
</Accordion>
<Accordion title="Schemas">
Choose which databases (PostgreSQL) and schemas appear in the tree: all, a picked list, or name patterns with `*` and `?`. **Load Names from Server** fetches the list. **Object filter:** limits tables, views, routines and sequences by name. **Show internal system schemas** and **Show template databases** (PostgreSQL) are off by default.
</Accordion>
<Accordion title="Advanced">
Driver properties as name/value pairs, for example `statement_timeout` (PostgreSQL), `sql_mode` (MySQL, set as session variables), `journal_mode` (SQLite pragmas), `NLS_DATE_FORMAT` (Oracle) or `max_threads` (ClickHouse). `connect_timeout` (seconds) works for every driver.
</Accordion>
</AccordionGroup>
### Where settings and passwords are stored
- Project data sources: `.openidea/data-sources.toml` in the project.
- Global data sources: `data-sources.toml` in the IDE's data directory (see [Data directories](/en/reference/data-directories)).
- Passwords are never written to these files. Saved passwords go to the OS credential store (Keychain, Windows Credential Manager or Secret Service). Unsaved ones are kept in memory for the session; OpenIDEA asks for them when it connects.
## Browse the database
The tree shows data source › database (PostgreSQL) › schema › `tables`, `views`, `materialized views`, `routines` and `sequences`. Tables contain `columns`, `keys`, `foreign keys`, `indexes` and `triggers`. Nodes load when you expand them. Turn on **Separate Procedures and Functions** in the tool window's options menu to split `routines`.
Double-click a table to edit its data, a data source to open its console, and a routine, sequence or trigger to see its DDL. Type in **Filter objects** to filter the tree.
| Action | macOS | Windows/Linux |
| ------------------------------------------ | ---------------- | ------------------ |
| New… (data source, console, table, column) | `⌘N` | `Alt+Insert` |
| Data Source Properties… | `⇧Enter` | `Shift+Enter` |
| Refresh | `⌘R` | `Ctrl+F5` |
| Deactivate (disconnect) | `⌘F2` | `Ctrl+F2` |
| Edit Data | `F4` | `F4` |
| Go to DDL | `⌘B` | `Ctrl+B` |
| Quick Documentation | `F1` | `Ctrl+Q` |
| Jump to Query Console… | `⌘⇧F10` | `Ctrl+Shift+F10` |
| Rename… | `⇧F6` | `Shift+F6` |
| Modify Table… | `⌘F6` | `Ctrl+F6` |
| Drop… | `Delete` or `⌘⌫` | `Delete` |
| Copy Reference | `⌘⌥⇧C` | `Ctrl+Alt+Shift+C` |
| Duplicate (data source) | `⌘D` | `Ctrl+D` |
| Generate DDL to Clipboard | `⌘⌥⇧G` | `Ctrl+Alt+Shift+G` |
| Generate DDL to Query Console | `⌘⌥⇧B` | `Ctrl+Alt+Shift+B` |
These keys work while the Database tree has focus. The context menu also has **Truncate…**, **Run SQL Script…** (runs every statement of a `.sql` file), **Import Data from File…**, **Go to Referenced Table** (on foreign keys), SQL templates (select, insert, update, delete) and **Remove Data Source…**.
**Modify Table…** and **New › Table** open a dialog to add, drop or change columns (name, data type, not null, primary key, default) with a live SQL preview. SQLite can only rename or drop existing columns. ClickHouse tables can't be modified this way.
## Query console
Each data source has a query console, a `.sql` file kept in the IDE's data directory. Open it with **Query Console** (`⌘⇧L` / `Ctrl+Shift+Q`), from the tree, or with **Tools › Open Query Console**. Any writable `.sql` file in the project gets the same toolbar, so you can bind it to a data source and run it.
The console toolbar has:
- **Execute** (`⌘Enter` / `Ctrl+Enter`): runs the selected statements, or the statement at the caret.
- **Explain Plan**: shows the plan in a **Query Plan** tab.
- **Cancel Running Statement**. Cancelling inside a manual transaction rolls it back.
- **Browse Query History**: the last 500 statements per data source, with search and **Paste**.
- A row limit (100 to 50,000; default 500).
- **Tx: Auto** / **Tx: Manual**. In manual mode **Commit** and **Roll Back** appear.
- A schema picker (PostgreSQL, MySQL, SQL Server, Oracle) and the data source picker.
Statements are split on `;`, respecting strings, comments and PostgreSQL dollar quoting. Oracle PL/SQL blocks are kept whole, and a line with only `/` ends a statement. Parameters such as `:id`, `?`, `$1` or `@name` open a **Parameters** dialog; values are inserted as SQL text, so quote strings.
SQL completion knows the connected schema: tables after `FROM`/`JOIN` (with join conditions from foreign keys), columns in scope, and each dialect's keywords and functions. Unresolved tables and columns are flagged, and Go to Declaration, Quick Documentation and Reformat Code work in the console. See [SQL](/en/languages/sql) for SQL files in general.
## Results and data editing
Results appear in the **Services** tool window under the **Database** tab: an **Output** tab logs every statement, and each result set gets a **Result N** tab (up to 12).
- **Table** or **Chart** view (bar or line, with X/Y columns and an aggregation).
- Paging with page sizes 10 to 1,000; click the row count to count all rows.
- **Search in results** and a per-column local filter on the loaded rows.
- A side panel with **Record**, **Value** and **Aggregates** (count, distinct, nulls, sum, average, median, min, max).
- JSON values get a JSON editor with **Reformat** and **Open in Window**.
### Edit data
**Edit Data** (`F4`) opens a table in an editor tab with server-side paging (100 to 5,000 rows), a `WHERE` filter and an `ORDER BY` field (both with completion). Click a column header to sort; `Shift`-click to add more sort columns.
- Double-click, `Enter` or `F2` edits a cell; `Shift+Enter` opens a larger editor. Pasting tab-separated text fills several cells.
- **Add Row**, **Delete Rows**, **Set NULL** (`⌘⌫` / `Ctrl+Backspace`), **Set DEFAULT** (`⌘⇧⌫` / `Ctrl+Shift+Backspace`). `Space` toggles booleans.
- **Submit** (`⌘Enter` / `Ctrl+Enter`) sends all pending changes in one transaction; **Revert** (`⌘⌥Z` / `Ctrl+Alt+Z`) discards them.
Console results are editable the same way when the query is a plain select from one table that includes the primary key. Tables without a primary key, joins, aggregates, `UNION` queries, read-only data sources and ClickHouse results are read-only.
## Export and import
The export button offers **Export Data…** and quick copies: **Copy as CSV**, **Copy as TSV**, **Copy as JSON**, **Copy as SQL Inserts**, **Copy as Markdown** and **Copy as One-Row**. The **Export Data** dialog adds **Transpose**, column and row headers, a preview, **Copy to Clipboard** and **Export to File**. Export covers the rows that are loaded (the current page).
**Import Data from File…** loads CSV, TSV or TXT into a table: the separator is detected, the first row can be a header matched to columns by name, and empty values can become `NULL`. The import runs in one transaction. It is not available for ClickHouse.
## Oracle Instant Client
Oracle support uses the Oracle client libraries, which OpenIDEA does not bundle.
<Steps>
<Step title="Install Instant Client">
Download the Basic (or Basic Light) package of Oracle Instant Client 19 or later for your OS and architecture, and
unpack it in a stable place.
</Step>
<Step title="Point the data source at it">
In the data source's **Options** tab, set **Instant Client:** to that directory. If you leave it empty,
`ORACLE_HOME` or the system library path is used.
</Step>
<Step title="Connect">
On **General**, set host, port (1521) and the service name (for example `ORCLPDB1`) in **Database:**. A TNS
descriptor or alias in **Database:** is passed through as is. URLs such as
`oracle://[email protected]:1521/ORCLPDB1` and `jdbc:oracle:thin:@//db.example.com:1521/ORCLPDB1` also work.
</Step>
</Steps>
If the libraries can't be loaded, Test Connection fails with `DPI-1047` and a hint to set the Instant Client directory. On macOS, remove the quarantine flag if the libraries are blocked: `xattr -dr com.apple.quarantine /path/to/instantclient`.
Oracle schemas are database users; system accounts appear only with **Show internal system schemas**.
## Not supported
- MongoDB, Redis, Snowflake, BigQuery and other non-listed databases; JDBC drivers.
- SQL Server `GO` batch separators.
- A "run the whole console" action (use a selection, or **Run SQL Script…**).
- Exporting more than the loaded page.
- Date pickers and foreign key lookups in the grid.
- Session-level read-only mode on SQL Server (only grid editing is blocked).
+192
View File
@@ -0,0 +1,192 @@
---
title: "Debugging"
description: "Set breakpoints, step through code, inspect variables and evaluate expressions with the built-in Debug Adapter Protocol client."
---
OpenIDEA debugs programs through the [Debug Adapter Protocol](https://microsoft.github.io/debug-adapter-protocol/) (DAP). Each language names its debug adapter in its definition (`languages/<id>.toml`, `[debugger]` table), and the IDE starts that adapter when you debug a run configuration. Commands and shortcuts follow IntelliJ IDEA, so most of what you know from IDEA works the same way.
## Start a debug session
Any [run configuration](/en/guide/run) with a debug adapter can be debugged:
- Click the gutter run icon next to a `main` function or test and choose **Debug**.
- Pick a configuration in the run widget on the toolbar and click the debug button.
- Use **Run › Debug** to debug the selected configuration, **Run › Debug…** to choose one, or **Run › Debug Context Configuration** to debug the program or test at the caret.
The **Debug adapter** field in **Run › Edit Configurations…** decides which debugger a configuration uses. Set it to **None** to turn debugging off for that configuration.
Before the session starts, OpenIDEA saves all open files. When the adapter is missing, the IDE says so and, for debuggers it can install, offers **Install and Debug**.
The Debug tool window (`⌘5` / `Alt+5`) opens with one tab per session. It has two views:
- **Threads & Variables**: the threads, the stack frames of the selected thread, and the variables of the selected frame. Library frames are folded; use **Show All Frames** to expand them.
- **Console**: the program's output, plus an input line that evaluates expressions in the selected frame. Browse earlier expressions with **Browse Console History** (`⌘⌥E` / `Ctrl+Alt+E`).
## Supported debuggers
| Language | Debug adapter | How to get it |
| -------------------------------- | ----------------------------------------- | --------------------------------------------------------------------------------------- |
| Java | Microsoft java-debug (runs inside JDT LS) | One-click install |
| Kotlin | kotlin-debug-adapter | One-click install |
| C, C++, Objective-C, Swift | `lldb-dap` | Ships with LLVM, Xcode (`xcrun -f lldb-dap`) and Swift 6+ toolchains |
| Rust, Zig | CodeLLDB | One-click install |
| Go | Delve (`dlv`) | One-click install (runs `go install`, so Go must be installed) |
| Python | debugpy | One-click install of a private copy used by every interpreter, or `pip install debugpy` |
| JavaScript, TypeScript (Node.js) | vscode-js-debug | One-click install |
| C#, F# | netcoredbg | One-click install |
| PHP | PHP Debug (Xdebug) | One-click install of the adapter; PHP itself needs the Xdebug 3 extension |
| Shell scripts | Bash Debug | One-click install |
| Ruby | rdbg | `gem install debug` |
| Elixir | ElixirLS debugger | One-click install |
| Dart, Flutter | `dart debug_adapter` | Ships with the Dart / Flutter SDK |
| OCaml | earlybird | `opam install earlybird` |
| GDScript | Godot editor | Godot 4 |
A configuration can also use GDB 14 or newer (`gdb -i dap`) for C and C++: choose **GDB (C, C++)** in its **Debug adapter** field. GDB is never installed by the IDE (`apt install gdb`, `dnf install gdb`, `brew install gdb`).
One-click installs live in **Settings › Languages & Frameworks › Language Servers**, which lists debuggers next to language servers. You can install, update or point the IDE at your own copy there.
<Warning>
On macOS, CodeLLDB and Delve may fail to start or attach until Developer Mode is enabled. Run `sudo DevToolsSecurity
-enable` once, then start the session again.
</Warning>
## Breakpoints
Click a line number to add or remove a line breakpoint, or use **Toggle Line Breakpoint** (`⌘F8` / `Ctrl+F8`). In the gutter:
- `⌥`-click (`Alt`-click) adds a temporary breakpoint, which is removed after it is hit once.
- `⇧`-click (`Shift`-click) adds a logging breakpoint that does not suspend the program, and opens its properties.
- Middle-click enables or disables an existing breakpoint.
- Right-click a breakpoint to edit its properties.
Blank and comment-only lines take no breakpoint. By default line breakpoints can only go into files of languages that have a debugger; turn on **Allow placing breakpoints in any file** in **Settings › Build, Execution, Deployment › Debugger** to lift that.
### Breakpoint properties
| Option | What it does |
| ---------------------------- | -------------------------------------------------------------------------------------------- |
| **Enabled** | Turns the breakpoint on or off without removing it. |
| **Suspend** | Off: the program keeps running when the breakpoint is hit (useful for logging). |
| **Condition** | Stops only when the expression is true. |
| **Hit count** | Stops only when the hit count condition matches (adapter syntax, for example `5` or `>= 5`). |
| **"Breakpoint hit" message** | Logs a message to the console each time the breakpoint is hit. |
| **Evaluate and log** | Logs the value of an expression each time the breakpoint is hit. |
In the Breakpoints dialog the same options are available, plus **Remove once hit**, which makes the breakpoint temporary. Which of these work depends on the adapter: conditions, hit counts and log messages are DAP features that most, but not all, adapters support.
### Other breakpoint kinds
| Kind | How to add it |
| ------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Temporary line breakpoint | **Toggle Temporary Line Breakpoint** (`⌘⌥⇧F8` / `Ctrl+Alt+Shift+F8`) or `⌥`-click in the gutter |
| Method breakpoint | **Toggle Method Breakpoint** on a method declaration, or **Add Method Breakpoint** in the Breakpoints dialog |
| Field watchpoint | **Toggle Field Watchpoint** on a field declaration, or **Add Field Watchpoint** on a value in the Variables view. Choose whether it stops on **Field access**, **Field modification** or both. |
| Exception breakpoints | The **Exception Breakpoints** section of the Breakpoints dialog lists the exception filters the adapter offers (for example caught and uncaught exceptions). |
Method breakpoints and field watchpoints need an adapter that supports DAP function and data breakpoints. Filters for debugpy, java-debug, js-debug and CodeLLDB are listed right away; other adapters' filters appear after you have debugged with them once.
### The Breakpoints dialog
**View Breakpoints…** (`⌘⇧F8` / `Ctrl+Shift+F8`) lists every breakpoint in the project. From there you can enable, edit, remove and jump to breakpoints, toggle **Mute breakpoints** (skip all of them without removing them), and set exception filters. **Remove All Breakpoints** clears the list.
Breakpoints are saved per project and kept across restarts. A breakpoint the adapter could not place is shown as unverified.
## Step through code
These commands are in the **Run** menu and on the Debug tool window toolbar.
| Command | macOS | Windows / Linux |
| -------------------------------- | ------------- | --------------- |
| Resume Program | `⌘⌥R` or `F9` | `F9` |
| Pause Program | — | — |
| Step Over | `F8` | `F8` |
| Step Into | `F7` | `F7` |
| Smart Step Into | `⇧F7` | `Shift+F7` |
| Step Out | `⇧F8` | `Shift+F8` |
| Force Step Over | `⌥⇧F8` | `Alt+Shift+F8` |
| Force Step Into | `⌥⇧F7` | `Alt+Shift+F7` |
| Run to Cursor | `⌥F9` | `Alt+F9` |
| Force Run to Cursor | `⌘⌥F9` | `Ctrl+Alt+F9` |
| Show Execution Point | `⌥F10` | `Alt+F10` |
| Reset Frame | — | — |
| Stop | `⌘F2` | `Ctrl+F2` |
| Rerun (in the Debug tool window) | `⌘R` | `Ctrl+F5` |
- **Smart Step Into** lets you pick which call on the current line to step into, when the line has several.
- The force variants ignore breakpoints on the way.
- **Run to Cursor** is also offered as an inline button on a line while the program is suspended (**Show inlay Run to Cursor popup** in the Debugger settings).
- **Reset Frame** returns to the start of the selected frame's method, when the adapter supports it.
## Inspect and change values
While the program is suspended:
- The **Variables** view shows the variables of the selected frame. Right-click a value for **Set Value…** (`F2`), **Copy Value**, **Copy Name**, **Jump to Source** (`F4`) and **Add to Watches**.
- **Watches** appear at the top of the Variables view and are re-evaluated on every stop and frame change. Add one with **Add to Watches**; remove it with **Remove Watch**.
- Hovering a variable in the editor shows its value. `⌥`-click (`Alt`-click) or **Quick Evaluate Expression** (`⌘⌥F8` / `Ctrl+Alt+F8`) evaluates the expression under the mouse or the selection.
- Values of local variables are shown inline after the lines that use them.
- **Evaluate Expression…** (`⌥F8` / `Alt+F8`) opens a dialog that evaluates any expression in the selected frame, keeps a history, and can add the expression to watches.
Expressions use the language of the program and are evaluated by the debug adapter, so what you can write depends on the adapter.
## Attach to a running process
**Run › Attach to Process…** (`⌥⇧F5` / `Ctrl+Alt+F5`) lists your processes that a debugger can attach to, grouped by debugger:
| Group | Processes |
| ---------------- | -------------------------------------------------------------------------- |
| Java (JDWP) | JVMs started with a JDWP agent |
| Node.js | Node processes started with `--inspect` |
| Python (debugpy) | Python processes (debugpy is injected, or attached to its `--listen` port) |
| Go (Delve) | Go binaries |
| Native (LLDB) | Anything else, through CodeLLDB when installed, otherwise `lldb-dap` |
Type to filter by name, PID or command line.
### Remote debug configurations
To debug a process started elsewhere (another machine, a container), add one of these configurations in **Run › Edit Configurations…**. They only support Debug; they do not start a process.
| Configuration | What it does |
| --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Remote JVM Debug** | Attaches to a JVM started with a JDWP agent, or listens for the JVM to connect (**Debugger mode**: **Attach to remote JVM** / **Listen to remote JVM**). The form shows the agent arguments to start the remote JVM with. |
| **Go Remote** | Attaches to a Delve server started with `dlv --headless`. |
| **Python Remote Debug (debugpy)** | Attaches to a Python process listening with debugpy. |
| **Attach to Node.js** | Attaches to a Node.js process started with `--inspect`. |
| **PHP Remote Debug (Xdebug)** | Listens for Xdebug connections, for PHP served by php-fpm or a container. |
| **Attach to Godot** | Debugs the game the Godot editor is running. |
Each form has **Host** and **Port** fields and shows the command to start the remote side with. **Local root** and **Remote root** map source paths when the remote code lives in a different folder.
## Language-specific features
- **Java HotSwap**: **Run › Debugging Actions › Reload Changed Classes** compiles your changes and reloads the changed classes into the running Java debug session. Active frames keep running the old code until they return. When JDT LS finishes a build after a save, the IDE offers the reload.
- **Maven and Gradle**: debugging a Maven or Gradle configuration starts the JVM with a JDWP agent and attaches java-debug to it.
- **Dart / Flutter**: **Hot Reload** and **Hot Restart** are available while a Dart session is running.
- **PHP**: if the PHP interpreter does not load Xdebug, breakpoints are skipped; the IDE warns about this when the session starts.
- **Python**: **Settings › Build, Execution, Deployment › Python Debugger** has **Attach to subprocess automatically while debugging** and **Gevent compatible**.
## Settings
**Settings › Build, Execution, Deployment › Debugger**:
- **Show debug window on breakpoint hit**, **Focus application on breakpoint**, **Hide debug window on process termination**.
- **Breakpoints**: **Allow placing breakpoints in any file**, **Show inlay Run to Cursor popup**.
- **Data Views**: **Show values inline**, **Sort values alphabetically**, **Show value tooltip on mouse hover**, **Show value tooltip on code selection**, **Value tooltip delay (ms)**.
- **Stepping**: **Do not step into library scripts** (Python: only your code; JavaScript: skips `node_modules` and Node.js internals).
## Shortcuts
| Action | macOS | Windows / Linux |
| ----------------------------- | ------ | --------------- |
| Debug | `⌃D` | `Shift+F9` |
| Debug… (choose configuration) | `⌃⌥D` | `Alt+Shift+F9` |
| Debug Context Configuration | `⌃⇧D` | — |
| Toggle Line Breakpoint | `⌘F8` | `Ctrl+F8` |
| View Breakpoints… | `⌘⇧F8` | `Ctrl+Shift+F8` |
| Evaluate Expression… | `⌥F8` | `Alt+F8` |
| Attach to Process… | `⌥⇧F5` | `Ctrl+Alt+F5` |
| Debug tool window | `⌘5` | `Alt+5` |
All shortcuts are from the default keymap and can be changed in [Keymap](/en/guide/keymap) settings.
+143
View File
@@ -0,0 +1,143 @@
---
title: "Editor basics"
description: "Carets and selections, line editing, the clipboard history, folding, formatting, editor tabs and split editors."
---
The editor follows IntelliJ IDEA's behaviour and default keymap. Shortcuts below are the defaults of the **macOS** keymap on macOS and the **Windows** keymap on Windows and Linux; you can change any of them in **Settings › Keymap** (see [Keymaps](/en/guide/keymap)). Every command is also available from **Find Action** (`⇧⌘A` / `Ctrl+Shift+A`).
## Carets and selections
| Action | macOS | Windows / Linux |
| ---------------------------------------- | ---------- | ------------------ |
| **Add or Remove Caret** | `⌥⇧`-click | `Alt+Shift`-click |
| **Clone Caret Above** | `⌥⌥↑` | `Ctrl Ctrl+Up` |
| **Clone Caret Below** | `⌥⌥↓` | `Ctrl Ctrl+Down` |
| **Add Selection for Next Occurrence** | `⌃G` | `Alt+J` |
| **Unselect Occurrence** | `⌃⇧G` | `Alt+Shift+J` |
| **Select All Occurrences** | `⌃⌘G` | `Ctrl+Alt+Shift+J` |
| **Add Carets to Ends of Selected Lines** | `⌥⇧G` | `Alt+Shift+G` |
| **Column Selection Mode** | `⇧⌘8` | `Alt+Shift+Insert` |
| **Extend Selection** | `⌥↑` | `Ctrl+W` |
| **Shrink Selection** | `⌥↓` | `Ctrl+Shift+W` |
- **Clone Caret Above / Below**: press the modifier twice and hold it, then press the arrow key.
- **Add Selection for Next Occurrence** selects the word at the caret first, then adds the next match on every press. When nothing more is found, a hint appears; press again to continue from the top of the file.
- Drag with `⌥` / `Alt` held (or with the middle mouse button on macOS and Windows) to make a rectangular selection without switching modes.
- **Extend Selection** grows the selection to the enclosing word, expression, statement and block, using the syntax tree.
## Editing lines and code
| Action | macOS | Windows / Linux |
| ----------------------------------------------- | --------------- | ---------------------------------------------- |
| **Duplicate Line or Selection** | `⌘D` | `Ctrl+D` |
| **Delete Line** | `⌘⌫` | `Ctrl+Y` |
| **Move Line Up** / **Move Line Down** | `⌥⇧↑` / `⌥⇧↓` | `Alt+Shift+Up` / `Alt+Shift+Down` |
| **Move Statement Up** / **Move Statement Down** | `⇧⌘↑` / `⇧⌘↓` | `Ctrl+Shift+Up` / `Ctrl+Shift+Down` |
| **Move Element Left** / **Move Element Right** | `⌥⇧⌘←` / `⌥⇧⌘→` | `Ctrl+Alt+Shift+Left` / `Ctrl+Alt+Shift+Right` |
| **Comment with Line Comment** | `⌘/` | `Ctrl+/` |
| **Comment with Block Comment** | `⌥⌘/` | `Ctrl+Shift+/` |
| **Join Lines** | `⌃⇧J` | `Ctrl+Shift+J` |
| **Toggle Case** | `⇧⌘U` | `Ctrl+Shift+U` |
| **Start New Line** | `⇧↵` | `Shift+Enter` |
| **Start New Line Before Current** | `⌥⌘↵` | `Ctrl+Alt+Enter` |
| **Split Line** | `⌘↵` | `Ctrl+Enter` |
| **Complete Current Statement** | `⇧⌘↵` | `Ctrl+Shift+Enter` |
| **Unwrap/Remove…** | `⇧⌘⌦` | `Ctrl+Shift+Delete` |
| **Move Caret to Matching Brace** | `⌃M` | `Ctrl+Shift+M` |
| **Move Caret to Code Block Start** / **End** | `⌥⌘[` / `⌥⌘]` | `Ctrl+[` / `Ctrl+]` |
| **Toggle Insert/Overwrite** | `Insert` | `Insert` |
**Edit › Sort Lines** and **Edit › Reverse Lines** reorder the selected lines; they have no default shortcut.
Brackets and quotes are paired as you type, and typing a quote or bracket over a selection surrounds it. These and other typing aids (smart Home, Tab out of a closing bracket, smart indent on Enter, documentation comment stubs, what Backspace does with indentation, reformat on paste) are under **Settings › Editor › General › Smart Keys**.
## Clipboard
| Action | macOS | Windows / Linux |
| ------------------------ | ------ | ------------------ |
| **Paste from History…** | `⇧⌘V` | `Ctrl+Shift+V` |
| **Paste as Plain Text** | `⌥⇧⌘V` | `Ctrl+Alt+Shift+V` |
| **Copy Reference** | `⌥⇧⌘C` | `Ctrl+Alt+Shift+C` |
| **Copy Path/Reference…** | `⇧⌘C` | `Ctrl+Shift+C` |
**Paste from History…** lists the last 30 pieces of text you copied or cut in the window, newest first. Pick one with `↵` / `Enter`; `⌫` / `Delete` removes the selected entries. The history lives in memory and is cleared when the window closes.
Copying from the editor puts rich text (with syntax colours) on the clipboard as well; turn this off in **Settings › Editor › General › Rich-Text Copy**.
## Folding
| Action | macOS | Windows / Linux |
| ------------------------------------------------- | ------------------- | ------------------------------- |
| **Collapse** / **Expand** | `⌘-` / `⌘=` | `Ctrl+-` / `Ctrl+=` |
| **Collapse All** / **Expand All** | `⇧⌘-` / `⇧⌘=` | `Ctrl+Shift+-` / `Ctrl+Shift+=` |
| **Collapse Recursively** / **Expand Recursively** | `⌥⌘-` / `⌥⌘=` | `Ctrl+Alt+-` / `Ctrl+Alt+=` |
| **Expand to Level** 1–5 | `⌘*`, then `1`–`5` | `Ctrl+*`, then `1`–`5` |
| **Expand All to Level** 1–5 | `⇧⌘*`, then `1`–`5` | `Ctrl+Shift+*`, then `1`–`5` |
| **Fold Code Block** | `⇧⌘.` | `Ctrl+Shift+.` |
| **Fold Selection / Remove Region** | `⌘.` | `Ctrl+.` |
| **Custom Folding…** | `⌥⌘.` | `Ctrl+Alt+.` |
All folding commands are also in **Code › Folding**. Fold regions come from the language's syntax and from region comments; **Custom Folding…** jumps to one of the file's custom regions. Choose what is folded when a file opens (file header, imports, documentation comments) in **Settings › Editor › General › Code Folding**.
## Formatting
| Action | macOS | Windows / Linux |
| --------------------- | ------ | ------------------ |
| **Reformat Code** | `⌥⌘L` | `Ctrl+Alt+L` |
| **Reformat File…** | `⌥⇧⌘L` | `Ctrl+Alt+Shift+L` |
| **Auto-Indent Lines** | `⌃⌥I` | `Ctrl+Alt+I` |
| **Optimize Imports** | `⌃⌥O` | `Ctrl+Alt+O` |
**Reformat Code** formats the selection, or the whole file when nothing is selected, as one undo step. The formatting comes from the file's language server or from the formatter the language is configured with (for example Ruff for Python, Prettier or oxfmt for web languages; find their commands as **Reformat with …** in Find Action). **Reformat File…** also lets you choose the scope and optimize imports.
Indentation and other style options are under **Settings › Editor › Code Style**, with a page per language. `.editorconfig` files in the project are read too.
To format on every save, enable **Reformat code** (and optionally **Optimize imports**) in **Settings › Tools › Actions on Save**. Trailing spaces and the final line break on save are set in **Settings › Editor › General**, section **Save Files**.
## Tabs
| Action | macOS | Windows / Linux |
| ----------------------- | ----- | --------------- |
| **Close Tab** | `⌘W` | `Ctrl+F4` |
| **Select Next Tab** | `⌃→` | `Alt+Right` |
| **Select Previous Tab** | `⌃←` | `Alt+Left` |
| **Switcher** | `⌃⇥` | `Ctrl+Tab` |
**Window › Editor Tabs** also has **Pin Tab**, **Close Other Tabs**, **Close All Tabs** and **Reopen Closed Tab**, all without default shortcuts. **Window › Move Tab to New Window** detaches the current tab into its own window.
Tab placement, the tab limit and what happens when it is reached, the preview tab, sorting, and which tab is activated after one closes are set in **Settings › Editor › General › Editor Tabs**.
## Split editors
| Action | macOS | Windows / Linux |
| --------------------------- | ----- | --------------- |
| **Split Right** | `⌘\` | — |
| **Go to Next Splitter** | `⌥⇥` | `Alt+Tab` |
| **Go to Previous Splitter** | `⌥⇧⇥` | `Alt+Shift+Tab` |
**Window › Editor Tabs** also offers **Split Down**, **Unsplit**, **Unsplit All**, **Move to Opposite Group**, **Open in Opposite Group** and **Change Splitter Orientation**. You can also drag a tab to an edge of the editor area to split it.
## View options
| Action | macOS | Windows / Linux |
| ---------------------- | ----- | --------------- |
| **Increase Font Size** | `⌥⇧.` | `Alt+Shift+.` |
| **Decrease Font Size** | `⌥⇧,` | `Alt+Shift+,` |
| **Scroll to Center** | `⌃L` | `Ctrl+M` |
- **View › Active Editor › Soft-Wrap** wraps long lines in the current editor. To wrap some file types by default, use **Settings › Editor › General**, section **Soft Wraps**.
- **View › Reset Font Size in All Editors** undoes font zoom. Changing the font size with `⌘` / `Ctrl` + mouse wheel can be turned on in **Settings › Editor › General**, section **Mouse Control**.
- **View › Appearance › Enter Distraction Free Mode** hides tool windows, tabs, the status bar and the gutter and centres the text. Run the same command again to leave.
- Line numbers, whitespace, indent guides, caret style and the intention bulb are in **Settings › Editor › General › Appearance**. Breadcrumbs and sticky lines (the enclosing declarations pinned at the top while you scroll) have their own pages under **Settings › Editor › General**.
## Saving
As in IntelliJ IDEA, there is no per-file save: files are saved when you switch to another application or to the built-in terminal, and **File › Save All** (`⌘S` / `Ctrl+S`) saves everything at once. **Settings › Appearance & Behavior › System Settings**, section **Autosave**, can also save after the IDE has been idle for a while.
Changes made outside the IDE are picked up when you switch back to its window. **Reload from Disk** (`⌥⌘Y` / `Ctrl+Alt+Y`) reloads the current file now.
## Related
- [Code intelligence](/en/guide/code-intelligence): completion, navigation and documentation.
- [Live templates and macros](/en/guide/templates)
- [Vim mode](/en/guide/vim)
+146
View File
@@ -0,0 +1,146 @@
---
title: "GitHub"
description: "Review, create, check out and merge GitHub pull requests, and clone your repositories, through the GitHub CLI."
---
OpenIDEA works with GitHub through the [GitHub CLI](https://cli.github.com) (`gh`). It uses gh's login and calls the GitHub API through `gh api`. It has no account manager or token field of its own and never stores your credentials.
## Set up
<Steps>
<Step title="Install gh">
Install the GitHub CLI. OpenIDEA looks for it in `OPENIDEA_GH` (a full path), then on `PATH`, then in
`/opt/homebrew/bin`, `/usr/local/bin`, `/usr/bin`, `/snap/bin` (macOS/Linux) or `C:\Program Files\GitHub CLI\gh.exe`
(Windows).
</Step>
<Step title="Sign in">
Run `gh auth login` in a terminal. If you open the **Pull Requests** tool window while signed out, it shows **Not
logged in to GitHub** with a **Run gh auth login** button that runs the command in the built-in terminal. Click
**Retry** when you are done.
</Step>
<Step title="Open a project with a GitHub remote">
The project needs a Git remote on `github.com`. With one GitHub remote, OpenIDEA connects to it automatically. With
several, the tool window asks you to pick a **Repository**; the choice is remembered per project. To switch later,
use **Change Repository or Account…** in the tool window's `⋮` menu.
</Step>
</Steps>
<Note>Only `github.com` is supported. GitHub Enterprise hosts, GitLab and Bitbucket are not.</Note>
## Menu and shortcuts
The **Git › GitHub** menu (also in the editor context menu under **Git**) has:
- **View Pull Requests**: opens the Pull Requests tool window.
- **Create Pull Request…**
- **Checkout Pull Request…**: checks out the selected pull request, or asks for a number.
- **Open on GitHub**: opens the selected pull request, or the repository, in the browser.
| Action | macOS | Windows/Linux |
| ------------------------------------------------------- | ----- | -------------- |
| Create Pull Request… (in the Pull Requests tool window) | `⌘N` | `Alt+Insert` |
| Refresh the list, a pull request or a timeline | `⌘R` | `Ctrl+F5` |
| Toggle viewed/not viewed state | `⌘⇧S` | `Ctrl+Shift+S` |
## Browse pull requests
The **Pull Requests** tool window opens on the left. The first tab lists the repository's pull requests; each pull request you open gets its own `#N` tab.
- **Search**: type text or GitHub search qualifiers and press `Enter`. The history button (or `⌥↓` / `Alt+Down`) shows your last 10 searches.
- **Quick Filters** (funnel button): **Open**, **Your pull requests**, **Assigned to you**, **Review requests**.
- **Filter chips**: **State**, **Author**, **Label**, **Assignee**, **Review** and **Sort**. Press `Delete` or `Backspace` on a chip to reset it.
- Rows show unread markers, labels, state, merge conflicts, assignee and reviewer avatars, and the count of unresolved comments.
Right-click a pull request for **View Pull Request**, **Checkout Branch**, **Open in Worktree**, **View Pull Request in Browser**, **Copy Pull Request URL** and **Refresh List**.
## Pull request details
Double-click a pull request (or press `Enter`) to open its details tab:
- The title, state and a **View Timeline** link.
- A commit picker: review all commits together or one commit at a time.
- The source branch, with **Checkout**, **Open in Worktree**, **Show in Git Log** and **Copy Branch Name**.
- The changed files. Double-click a file to open its diff. Mark files with **Mark as Viewed** / **Mark as Not Viewed** (or the checkbox in the all-commits view).
- Status: check runs (with a **Details…** list that opens each job on GitHub), merge conflicts (with a **Resolve** link that checks out the branch and merges the base into it locally), required reviews and reviewer states.
The buttons below depend on your role and the pull request state:
| You are | Buttons |
| ----------------------------- | ------------------------------------------------------------------------------------------------ |
| The author | **Request Review…**, the merge button once approved, **Close Pull Request** (under More Actions) |
| A reviewer | **Submit Review…**, and merge once approved |
| Anyone else | **Set Myself as Reviewer** |
| Any, on a draft | **Post Pull Request** (marks it ready for review) |
| Any, on a closed pull request | **Reopen Pull Request** |
The merge button offers the methods the repository allows: **Merge…**, **Squash and Merge…** (both let you edit the commit message) and **Rebase**.
## Review code
### Comment in the diff
Pull request diffs compare the merge base with the head. OpenIDEA fetches the pull request ref and base branch first if they are missing locally.
- Hover a line and click **+** in the gutter to comment. Drag to select several lines.
- Choose **Start Review** (comments stay pending until you submit) or **Add Single Comment**. OpenIDEA remembers which one you used last.
- Existing threads show inline under their lines. Set which ones under **Show Review Threads** in the diff settings: **All**, **Unresolved Only** (default) or **Do Not Show**.
- The diff toolbar has **Refresh Review Data**, **Submit Review…**, **Previous Comment** and **Next Comment**. Past the last change, `F7` moves to the next file of the pull request.
To mark each file as viewed when you open it, turn on **Automatically mark opened files as viewed** in **Settings › Version Control › GitHub**.
### Submit a review
**Submit Review…** opens a small popup. Write an optional summary, then choose **Approve**, **Request Changes** or **Comment**. The trash button discards all pending comments. Authors can't approve or request changes on their own pull requests.
### Timeline
**View Timeline** opens the pull request's conversation in an editor tab: the description, commits, reviews, review threads and events, newest at the bottom. You can:
- Add a comment (`⌘Enter` / `Ctrl+Enter` to send), with `@` mention completion.
- Edit or delete your comments, and add reactions.
- Reply to review threads and **Resolve** or unresolve them.
- Apply a suggested change with **Apply Locally**, or apply and commit it in one go with **Commit…**. Suggestions can only be applied while the pull request's branch is checked out.
- Delete the head branch from the banner after the pull request is merged.
### Review mode
When the checked-out branch is the head of an open pull request, the branch widget shows `#N on branch` and review mode turns on:
- Review threads appear inline in the regular editor, with the gutter **+** to add comments and change markers against the pull request base.
- The editor's corner toolbar has **Previous Comment**, **Next Comment** and a **Review Mode** menu (**Disable Review Mode**, **Show Unresolved Comments Only**, **Show All Comments**).
- The editor context menu adds **Show Review Diff**.
- If your local branch is behind the remote, the branch widget offers **Update to Enable Review Mode…**.
Review mode is on by default and remembered per project.
## Create a pull request
**Create Pull Request…** opens a tab titled "New Pull Request at owner/repo":
1. Pick the **Base branch** and the **Head branch** (local or remote, and the repository when you have forks).
2. Check the commits and changed files.
3. Fill in the title and description. The title defaults to the only commit's subject or the branch name; the description is pre-filled from the repository's pull request template.
4. Optionally set **Reviewers:**, **Assignees:** and **Labels:**.
5. Click **Create Pull Request**, or use the arrow for **Create Draft Pull Request**.
If the head branch exists only locally, OpenIDEA asks for a remote branch name and pushes it first. If a pull request already exists for the branches, it offers to open it instead.
## Check out a pull request
- **Checkout Branch** (or **Checkout Pull Request…**) runs `gh pr checkout`, which also works for pull requests from forks.
- **Open in Worktree** creates a Git worktree next to the repository (named `<repo>_PR_<n>`) on the pull request head and opens it in a new window.
## Clone from GitHub
**Git › Clone…** opens **Clone Repository**. Choose **GitHub** in the sidebar to list the repositories you own, collaborate on or can access through an organization, most recently pushed first. Search with **Search repositories**, then double-click a repository (or press `Enter`) to clone it. The URL uses HTTPS, or SSH when gh's `git_protocol` for github.com is `ssh`.
## Commit checks in the Git log
The Git log can show the GitHub check status of each commit. Turn on the **GitHub Commit Checks** column in the log's **View Options › Columns**, or in **Settings › Version Control › Log**. Click a commit's icon to list its check runs; click a run to open it on GitHub.
## Not supported
- GitHub Enterprise, GitLab, Bitbucket.
- Issues, gists, notifications and creating or sharing repositories.
- Opening a file or line on GitHub, or copying a permalink.
- Managing GitHub Actions (re-running jobs, reading logs).
+138
View File
@@ -0,0 +1,138 @@
---
title: "HTTP Client"
description: "Write HTTP requests in .http files, run them from the editor, and inspect responses."
---
The HTTP client runs requests written in `.http` and `.rest` files, in the same format as the IntelliJ IDEA HTTP client. Responses show in the **Services** tool window under the **HTTP Client** tab.
## Create a request file
- **Tools › New HTTP Request** creates a scratch file `requests.http` (then `requests_2.http`, and so on) in the IDE's scratch folder.
- In the Project tool window, **New › HTTP Request** creates a `.http` file in the selected folder.
- Any file ending in `.http` or `.rest` works.
## Syntax
```http
@host = localhost:8080
### Get a user
GET http://{{host}}/users/1
Accept: application/json
### Create a user
# @name createUser
POST http://{{host}}/users
Content-Type: application/json
{ "name": "Ada", "id": "{{$uuid}}" }
### Upload from a file
POST http://{{host}}/import
Content-Type: application/json
< ./payload.json
```
| Element | Rules |
| ------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- |
| Separator | A line starting with `###`. Text after it names the request. |
| Name | `# @name createUser` or `// @name createUser` (overrides the separator text). |
| Comments | Lines starting with `#` or `//` before the request line and among the headers. Lines in the body are sent as written. |
| Request line | `METHOD URL [HTTP/version]`, or just a URL for `GET`. Methods: `GET`, `POST`, `PUT`, `DELETE`, `PATCH`, `HEAD`, `OPTIONS`, `TRACE`, `CONNECT`. |
| Long URLs | Indented lines starting with `?`, `&` or `/` continue the URL. |
| Relative URLs | `GET /path` needs a `Host:` header. A URL without a scheme gets `http://`. |
| Headers | `Name: value` lines up to the first blank line. |
| Body | Everything after the blank line. `< ./file` inserts a file, relative to the `.http` file. |
| Multipart | With a `multipart/*` `Content-Type`, body lines are joined with CRLF. Write the boundaries yourself. |
## Variables
Write `{{name}}` anywhere in the URL, headers or body. A name is looked up in this order:
1. File variables: `@name = value` lines. They apply to the whole file; a later definition wins.
2. The selected environment in `http-client.private.env.json`.
3. The selected environment in `http-client.env.json`.
4. `$shared` in the private file, then `$shared` in the public file.
Variables can refer to other variables. A request with an unresolved variable is not sent; the error lists the missing names. Variables inside a file inserted with `<` are not substituted.
Dynamic variables:
| Variable | Value |
| --------------------------------------- | ---------------------------------------- |
| `{{$uuid}}`, `{{$random.uuid}}` | A random UUID |
| `{{$timestamp}}` | Current Unix time in seconds |
| `{{$isoTimestamp}}` | Current UTC time, `YYYY-MM-DDTHH:MM:SSZ` |
| `{{$randomInt}}`, `{{$random.integer}}` | A random integer from 0 to 999 |
### Environments
Put environments in `http-client.env.json`, and secrets in `http-client.private.env.json` (keep that one out of version control):
```json http-client.env.json
{
"$shared": { "version": "v1" },
"dev": { "host": "localhost:8080" },
"prod": { "host": "api.example.com" }
}
```
OpenIDEA looks for each file in the request file's folder, then in each parent folder up to the project root, and uses the closest one. Files at different levels are not merged. Only string, number and boolean values are used.
Pick the environment in the editor toolbar under **Run with:** (**No Environment** or one of the names). The choice is remembered per file. Click **Reload Environments** after editing the files.
## Run requests
| Action | macOS | Windows/Linux |
| ----------------------------- | -------- | ------------- |
| Run HTTP Request at Caret | `⌘Enter` | `Ctrl+Enter` |
| Run All HTTP Requests in File | none | none |
You can also click the run icon in the gutter next to a request line. The editor toolbar has **Run All Requests in File**, **Stop** and **Run with:**.
- The request at the caret is the block containing the caret, or the nearest one above it.
- Unsaved edits are included.
- Starting a new run cancels the one in progress.
- **Run All** runs requests in order. It stops if a request can't be prepared (for example, an unresolved variable); network errors are recorded and the next request runs.
## Responses
The **HTTP Client** tab in the Services tool window lists past exchanges on the left, newest first, with status, method, name and time. For the selected one it shows:
- The status, HTTP version, total time (hover for the time to headers) and size.
- **Response**: the body. JSON, XML and HTML are pretty-printed; switch with **Show Raw** / **Show Formatted**. Binary bodies show their type and size only. Bodies over 16 MiB are cut off.
- **Headers**: the response headers.
- **Request**: the request as it was sent, after variable substitution.
The toolbar has **Stop Request**, **Reload History** and **Clear History**. The history keeps the last 100 exchanges per project.
## Connection behaviour
- HTTP/1.1 and HTTP/2. gzip, deflate and brotli responses are decoded.
- TLS certificates are verified against the system trust store.
- Redirects are followed.
- Cookies are not kept between requests.
- Connections time out after 30 seconds; there is no overall request timeout.
- The proxy comes from **Settings › Appearance & Behavior › System Settings › HTTP Proxy**.
There is no separate HTTP client settings page.
## Endpoints and Spring
OpenIDEA indexes Spring Web endpoints (`@RestController` / `@Controller` classes with `@RequestMapping`, `@GetMapping` and similar) in Java and Kotlin:
- A gutter icon on each mapping and controller. **Generate Request in HTTP Client** and **Generate Requests for Controller** (also in the editor context menu and as intentions) add requests to a scratch file `generated-requests.http` and open it.
- Generated requests use `http://localhost:<server.port>` plus the context path from `application.properties` or `application.yml` (port 8080 by default). Path variables become `{{var}}`, and a JSON skeleton of the `@RequestBody` type is filled in.
- Search Everywhere has an **Endpoints** tab.
- In a `.http` file, URL completion after the method suggests known endpoints.
Plugins can contribute endpoints of other frameworks; they get an **Open in HTTP Client** gutter icon.
## Not supported
- JavaScript response handlers (`> {% ... %}`, `> handler.js`) and pre-request scripts. Handler lines are recognized and skipped.
- Saving responses with `>>` / `>>!`, and `<>` references to previous responses (skipped).
- WebSocket, gRPC and GraphQL requests in `.http` files. GraphQL has its own support in `.graphql` files.
- Per-request SSL options, client certificates, turning off redirects, and cookie jars.
- Importing from or exporting to cURL.
+85
View File
@@ -0,0 +1,85 @@
---
title: "Inspections"
description: "Problems in the editor and the Problems tool window, quick fixes and intentions, inspection profiles, batch analysis and spell checking."
---
OpenIDEA treats every source of problems as an inspection: each language server (and, where the server reports codes, each of its diagnostic codes) plus a few built-in checks such as syntax errors, unresolved file references, unresolved merge conflict markers, build problems and spelling. All of them are shown the same way and can be configured in one place.
## Problems in the editor
Problems are underlined in the editor and marked in the scrollbar. The widget at the top-right of the editor shows the counts per severity (or a check mark when the file is clean); click it to open the **Problems** tool window. Hover over an underlined range to read the message.
| Action | macOS | Windows / Linux |
| ------------------------------ | ----- | --------------- |
| **Next Highlighted Error** | `F2` | `F2` |
| **Previous Highlighted Error** | `⇧F2` | `Shift+F2` |
| **Error Description** | `⌘F1` | `Ctrl+F1` |
| **Problems** tool window | `⌘6` | `Alt+6` |
The **Problems** tool window has two tabs: **File** (the current editor) and **Project Errors**. Its options menu sorts by severity or name, shows a preview and can autoscroll to the source.
## Quick fixes and intentions
| Action | macOS | Windows / Linux |
| ------------------------ | ----- | ----------------- |
| **Show Context Actions** | `⌥↵` | `Alt+Enter` |
| **Apply Quick Fix** | `⌥⇧↵` | `Alt+Shift+Enter` |
A light bulb appears on the caret line when something can be done there. **Show Context Actions** lists the quick fixes for problems at the caret first (the preferred one in bold), then intentions and refactorings the language server offers, and those contributed by plugins. Most entries show a preview of the change.
**Apply Quick Fix** applies the preferred (or first) fix of the most severe problem at the caret or on the caret line, without opening the list.
From the submenu of a problem's entry (`→`) you can also open the inspection's settings, disable the inspection, or change its severity. These changes are written to the inspection profile in effect.
## Inspection profiles
**Settings › Editor › Inspections** lists every inspection, grouped by language or area, with a checkbox to turn it off and a severity: **Error**, **Warning**, **Weak Warning** and others. Language servers appear here once they have reported something, and their diagnostic codes appear as separate entries as they are seen.
- **Default** is stored with the IDE settings and applies to every project.
- **Project Default** is stored in the project.
- From the gear menu you can duplicate a profile, copy it to the IDE or project level, rename it or delete it.
The selected profile filters what the editor, the Problems tool window and Inspect Code show.
## Analyze a whole scope
| Action | macOS | Windows / Linux |
| ------------------------ | ----- | --------------- |
| **Inspect Current File** | `⌥⇧I` | `Alt+Shift+I` |
These commands are in the **Code** menu:
- **Code › Inspect Code…** runs the language servers' diagnostics over a scope and lists the results in the **Inspection Results** tool window, grouped by severity, inspection and file. The scope can be the whole project, a module or directory, the current file, the uncommitted files or a custom scope. Files whose language has no server are skipped.
- **Code › Analyze Code › Inspect Current File** does the same for the open file.
- **Code › Code Cleanup…** applies the language servers' automatic fixes, optimizes imports and reformats over a scope, file by file, and saves the files. Above 100 files it asks first. It can be cancelled from the background tasks popup.
- **Code › Analyze Code › Locate Duplicates…** finds repeated code with a token-based clone detector. Set the minimum size in tokens and whether identifiers and literals are anonymized (so that code differing only in names still matches). Results open in the **Duplicates** tool window.
## Structural search and replace
**Edit › Find › Search Structurally…** and **Edit › Find › Replace Structurally…** match code by its syntax tree rather than its text. Choose the language and write a template using placeholders:
- `$name$` or `$NAME` matches one syntax node;
- `$$$NAME` matches any number of nodes (arguments, statements);
- a placeholder used twice must match equal code.
For example, in Rust the search template `$e$.unwrap()` with the replace template `$e$.expect("todo")` rewrites every `unwrap()` call and keeps its receiver. Search results open in the **Find Usages** tool window; replacements are previewed and applied as one change. Matching uses ast-grep's matcher over the bundled tree-sitter grammars.
## Spell checking
The **Proofreading › Typo** inspection checks words in comments, string literals and declared names against bundled English (US and UK) dictionaries and a list of programming terms. `camelCase` and `snake_case` names are split into words.
On a typo, `⌥↵` / `Alt+Enter` offers:
- **Typo: Change to…** (comments, strings, text) or **Typo: Rename to…** (a declared name, done through Rename so references follow);
- saving the word to the project-level or application-level dictionary.
Manage the project and application dictionaries, and add your own dictionary files, in **Settings › Editor › Natural Languages › Spelling**. Typo's severity and options are in **Settings › Editor › Inspections** like any other inspection.
## Before committing
The commit dialog can run **Analyze code** and **Check TODO** on the changed files before committing; see [Version control](/en/guide/vcs).
## Limits
- Inspections come from language servers and a few built-in checks. There is no large set of IDE-side inspections like IntelliJ IDEA's; what you see depends on the servers configured for the language.
- Inspect Code and Code Cleanup skip files with no language server.
+102
View File
@@ -0,0 +1,102 @@
---
title: "Install"
description: "Download and install OpenIDEA on macOS, Windows or Linux, and keep it up to date."
---
Every release on the [GitHub releases page](https://github.com/l0ng-ai/openidea/releases/latest) has builds for macOS, Windows and Linux, each for x64 and arm64. Releases are also mirrored to [CNB](https://cnb.cool/l0ng-ai/openidea/-/releases), which is usually faster from mainland China.
In the file names below, `<version>` is the release version, for example `0.1.40`.
## macOS
macOS 11 or later.
| Mac | File |
| ------------- | -------------------------------- |
| Apple silicon | `OpenIDEA_<version>_aarch64.dmg` |
| Intel | `OpenIDEA_<version>_x64.dmg` |
<Steps>
<Step title="Open the disk image">Double-click the `.dmg` file.</Step>
<Step title="Copy the app">Drag **OpenIDEA** into **Applications**.</Step>
<Step title="Start OpenIDEA">
Open it from Applications or Launchpad. Release builds are signed and notarized, so macOS opens them without extra
steps.
</Step>
</Steps>
<Tip>
Move OpenIDEA to Applications before you install the `openidea` shell command: the command links into the app bundle,
so it must stay where it is. See [Command line](/en/reference/cli).
</Tip>
## Windows
Three packages per architecture (`x64` or `arm64`):
| Package | File | Notes |
| ------------- | --------------------------------------------- | --------------------------------------------------------------------------------------- |
| Setup program | `OpenIDEA_<version>_x64-setup.exe` | Installs for the current user or for all users. |
| MSI | `OpenIDEA_<version>_x64_en-US.msi` | For managed or scripted installs. |
| Portable zip | `OpenIDEA_<version>_windows_x64_portable.zip` | No installer: unpack and run `openidea.exe`. A `.sha256` checksum file sits next to it. |
OpenIDEA uses the Microsoft Edge WebView2 runtime, which Windows 10 and 11 include. The setup program installs it if it is missing.
<Warning>
The Windows packages are not code-signed yet, so Microsoft Defender SmartScreen may warn about an unknown publisher
the first time you run one. Choose **More info**, then **Run anyway**.
</Warning>
## Linux
Three packages per architecture:
| Package | x64 | arm64 |
| ------------------------ | ----------------------------------- | ------------------------------------- |
| Debian / Ubuntu | `OpenIDEA_<version>_amd64.deb` | `OpenIDEA_<version>_arm64.deb` |
| Fedora / RHEL / openSUSE | `OpenIDEA-<version>-1.x86_64.rpm` | `OpenIDEA-<version>-1.aarch64.rpm` |
| AppImage | `OpenIDEA_<version>_amd64.AppImage` | `OpenIDEA_<version>_aarch64.AppImage` |
<CodeGroup>
```sh Debian / Ubuntu
sudo apt install ./OpenIDEA_<version>_amd64.deb
```
```sh Fedora / RHEL
sudo dnf install ./OpenIDEA-<version>-1.x86_64.rpm
```
```sh AppImage
chmod +x OpenIDEA_<version>_amd64.AppImage
./OpenIDEA_<version>_amd64.AppImage
```
</CodeGroup>
The app needs WebKitGTK 4.1; the `.deb` and `.rpm` packages pull it in as a dependency.
<Note>
Keep an AppImage in a permanent location. The `openidea` shell command points at the AppImage file, so after moving
it, install the command again.
</Note>
## Updates
OpenIDEA checks for a new version at startup and every 4 hours while it runs, and shows a notification when one is available. Choose **Install and Restart** in the notification: OpenIDEA downloads the update in the background, installs it and restarts. If a window has unsaved changes and cancels the restart, the update applies the next time you start OpenIDEA. You can also check by hand:
- macOS: **OpenIDEA › Check for Updates…**
- Windows and Linux: **Help › Check for Updates…**
Configure updates in **Settings › Appearance & Behavior › System Settings › Updates**:
- **Check for updates automatically**: turn off the background checks. **Check Now** still works.
- **Update source**: **GitHub**, **CNB (China mirror)**, or **Automatic**, which uses CNB when the IDE language is Chinese and GitHub otherwise. When the chosen source can't be reached, the other one is tried.
To update a portable Windows copy, download the newer zip and replace `openidea.exe`.
Updates go through the proxy set in **Settings › Appearance & Behavior › System Settings › HTTP Proxy**.
## Build from source
To build and run OpenIDEA from a checkout, see [Development setup](/en/contribute/setup).
+91
View File
@@ -0,0 +1,91 @@
---
title: "Keymaps"
description: "Choose a keymap, change, add and remove shortcuts, create your own keymap, and edit bindings in settings.toml."
---
A keymap maps actions to keyboard and mouse shortcuts. Every command in OpenIDEA is an action with a stable id, so any of them can get a shortcut.
## Bundled keymaps
| Keymap | Id | Notes |
| --------------------- | ------------------ | ------------------------------------------------------------------------------------- |
| **macOS** | `intellij-macos` | IntelliJ IDEA's macOS keymap. Default on macOS. |
| **Windows** | `intellij-windows` | IntelliJ IDEA's Windows keymap. Default on Windows and Linux. |
| **VS Code (macOS)** | `vscode-macos` | Visual Studio Code's defaults. |
| **VS Code (Windows)** | `vscode-windows` | The macOS VS Code keymap with `⌘` read as `Ctrl`, plus VS Code's Windows differences. |
| **Eclipse (macOS)** | `eclipse-macos` | IntelliJ IDEA's Eclipse keymap for macOS. |
| **Eclipse** | `eclipse-windows` | IntelliJ IDEA's Eclipse keymap. |
The default shortcuts are listed in the [shortcut reference](/en/reference/shortcuts). Inside the IDE, **Help › Keymap Reference** lists every bound action of the active keymap and filters by action name, action id or shortcut.
## Switch keymaps
Open **Settings › Keymap** and pick one in **Keymap:**. You can also switch from the Welcome screen (**Customize** › **Keymap**) or with **View › Quick Switch Scheme…** › **Keymap**.
If you have changed shortcuts on a bundled keymap, switching to another bundled keymap carries your changes over.
## Change a shortcut
In **Settings › Keymap**, find the action:
- Type in **Search actions** to filter by name, or
- Click **Find Actions by Shortcut** and press a shortcut to see which actions use it.
Actions are grouped as **Main Menu** (File to Help), **Editor Actions**, **Tool Windows**, **Other** and **Plugins**.
Right-click an action, or select it and press Enter or double-click, to open its menu:
- **Add Keyboard Shortcut**: press the keys in the dialog. Check **Second stroke:** for a two-stroke chord such as `⌘K ⌘C`. The dialog lists conflicts; when you confirm, choose **Remove** to take the shortcut away from the other actions, or **Leave** to keep both.
- **Add Mouse Shortcut**: click with Ctrl, Alt or Command held, or press a mouse side button.
- **Remove** a single shortcut of the action.
- **Reset Shortcuts** to go back to the keymap's defaults for that action.
Changes are saved with **Apply** or **OK**.
<Tip>
Faster: open **Find Action** (`⇧⌘A` / `Ctrl+Shift+A`), highlight an action and press `⌥↵` / `Alt+Enter` to assign a
shortcut to it directly.
</Tip>
## Your own keymaps
The gear button next to **Keymap:** has:
- **Duplicate…**: copy the selected keymap under a new name. Your keymap keeps the bundled one as its parent and stores only its differences.
- **Rename…** and **Delete** for keymaps you created.
- **Restore Defaults**: drop all your changes to the selected keymap.
## Keymaps in settings.toml
Keymap settings are global and live in `settings.toml` (see [Settings reference](/en/reference/settings)):
```toml
keymap = "intellij-macos"
[keymapOverrides]
"navigate.gotoFile" = ["Meta+P"]
"code.reformat" = ["Meta+Alt+L", "Meta+Shift+F"]
"vcs.push" = []
```
- `keymap` is a bundled keymap id or the id of one of your keymaps.
- `keymapOverrides` maps an action id to its shortcuts on top of a bundled keymap. An empty list unbinds the action.
- `customKeymaps.<id>` holds a keymap you created: `name`, `parent` (a bundled keymap id) and its own `overrides`.
Shortcut syntax:
| Syntax | Meaning |
| ------------------------ | ----------------------------------------------------------------------------------------------- |
| `Meta+Shift+O` | Modifiers `Meta`, `Ctrl`, `Alt`, `Shift` joined with `+` to a key. `Meta` is `⌘` on macOS. |
| `Meta+K Meta+C` | A chord: strokes separated by a space. |
| `Shift Shift` | A modifier pressed twice. |
| `Alt Alt+Up` | A modifier pressed twice and held, then a key. |
| `Button4`, `Alt+Button1` | Mouse buttons, 1-based: `Button1` is the left button, `Button4` and `Button5` the side buttons. |
Keys are matched by their physical position, so `⌥` combinations work on macOS layouts that type symbols with Option. To find an action's id, look it up in the bundled keymap files, for example [`intellij-macos.ts`](https://github.com/l0ng-ai/openidea/blob/main/src/platform/keymaps/intellij-macos.ts).
## Shortcuts that don't reach the IDE
Some keys are taken by the operating system or another app before OpenIDEA sees them, for example macOS's `⌃⌘D` (Look Up in Dictionary) or a GPU overlay's hotkeys on Windows. Change the conflicting system or app shortcut, or assign another shortcut in OpenIDEA.
Vim mode can also claim keys in the editor; see [Vim mode](/en/guide/vim).
+45
View File
@@ -0,0 +1,45 @@
---
title: "Display language"
description: "Change the language of OpenIDEA's menus and dialogs, and the date format it uses."
---
## Change the language
Open **Settings › Appearance & Behavior › System Settings › Language and Region** and choose a **Language**:
| Language | Setting value |
| ------------------------------ | ------------- |
| English (default) | `en` |
| 简体中文 (Chinese, Simplified) | `zh` |
| 한국어 (Korean) | `ko` |
| Français (French) | `fr` |
| Polski (Polish) | `pl` |
| Português (Portuguese) | `pt` |
| Español (Spanish) | `es` |
The change applies as soon as you click **Apply** or **OK**, including the native menu bar; no restart needed. Text that hasn't been translated yet stays in English. English and Simplified Chinese are complete; the other languages may have gaps.
In `settings.toml` the option is `locale` under `[appearance]`:
```toml
[appearance]
locale = "zh"
```
Plugins are told the display language and can show their own text in it when they ship a translation.
## Search in your language
The settings search, Find Action and Search Everywhere match both the English names and the translated ones, so you can search in either.
## Date and time format
By default, dates in the Git log, Local History and blame annotations follow your operating system's locale. To choose a format yourself, open **Settings › Appearance & Behavior › System Settings › Date Formats** and turn on **Override system date and time format**. The pattern uses `dd`, `MM`, `MMM`, `yy` and `yyyy`.
## Update source
When **Update source** is **Automatic** (the default), OpenIDEA downloads updates from the CNB mirror if the display language is Chinese, and from GitHub otherwise. See [Install](/en/guide/install#updates).
## Help translate
Translations live in the repository. See [Text and localization](/en/contribute/guidelines/i18n) to add or fix strings.
+113
View File
@@ -0,0 +1,113 @@
---
title: "MCP server"
description: "Let Claude Code, Codex and other MCP clients use OpenIDEA's search, code insight, editing, run and Git tools on your open projects."
---
OpenIDEA has a built-in [Model Context Protocol](https://modelcontextprotocol.io) server. With it on, AI agents running outside the IDE, such as Claude Code in a terminal, Codex or Cursor, can call the IDE's tools on the projects you have open: search with the project index, look up symbols through the language servers, read the Problems view, rename, reformat, build, run configurations and read Git status.
Agent tasks started inside OpenIDEA (see [Agents](/en/guide/agents)) get the same tools automatically while the server is on.
## Turn it on
<Steps>
<Step title="Open the settings page">Go to **Settings › Tools › MCP Server**.</Step>
<Step title="Enable it">Turn on **Enable MCP server** and apply. It is off by default.</Step>
<Step title="Check the status">
**Status** shows the port it is running on, or why it failed to start. **Endpoint** shows the URL for the current
window's project.
</Step>
</Steps>
The server listens on `127.0.0.1` only, on a random port, and every request must carry a token that changes each time OpenIDEA starts. Requests from another origin or host are refused.
## Connect a client
The settings page has two buttons under **Client configuration**. Both copy an `mcpServers` entry in the JSON format most clients accept.
- **Copy Stdio Config** (recommended): the client starts OpenIDEA's executable with `--mcp-stdio`, which relays to the running IDE. It holds no token and keeps working after OpenIDEA restarts.
- **Copy HTTP Config**: the client connects to `http://127.0.0.1:<port>/mcp` with `Authorization: Bearer <token>`. It stops working when OpenIDEA restarts, because the port and token change.
The stdio configuration looks like this (the path is the one on your machine):
```json
{
"mcpServers": {
"openidea": {
"type": "stdio",
"command": "/Applications/OpenIDEA.app/Contents/MacOS/openidea",
"args": ["--mcp-stdio", "--project", "/path/to/project"]
}
}
}
```
`--project` names the project the client works on. Leave it out and the bridge uses the directory the client starts it in, so one configuration works for every project you open in OpenIDEA.
<Tabs>
<Tab title="Claude Code">
Add the server for the current project from a terminal in the project directory:
```sh
claude mcp add openidea -- /Applications/OpenIDEA.app/Contents/MacOS/openidea --mcp-stdio
```
Or paste the copied JSON into a `.mcp.json` file at the project root to share it with your team.
</Tab>
<Tab title="Codex">
Add the server to `~/.codex/config.toml`:
```toml
[mcp_servers.openidea]
command = "/Applications/OpenIDEA.app/Contents/MacOS/openidea"
args = ["--mcp-stdio"]
```
Codex starts it in the directory you run `codex` from.
</Tab>
<Tab title="Other clients">
Paste the copied configuration into the client's MCP settings (for example `mcp.json` in Cursor). Clients that only speak HTTP can use **Copy HTTP Config**, but must be updated after every restart of OpenIDEA.
</Tab>
</Tabs>
Use the executable path from **Copy Stdio Config**; the macOS path above is only an example. On Windows it is `openidea.exe` in the installation (or portable) folder.
OpenIDEA must be running with the project open for the tools to work. A client may name any folder inside an open project; the server picks the open project that contains it. HTTP clients name it with the `X-OpenIDEA-Project` header or the `?project=` query.
While the server runs, OpenIDEA writes its port and token to `mcp/server.json` in its application data directory (readable only by you); the stdio bridge reads it from there.
## Tools
| Area | Tools |
| ------------- | --------------------------------------------------------------------------- |
| Files | `read_file`, `list_directory_tree`, `create_new_file`, `apply_patch` |
| Search | `search_text`, `search_regex`, `search_symbol`, `search_file` |
| Editors | `get_all_open_file_paths`, `get_current_file`, `open_file_in_editor` |
| Code | `get_symbol_info`, `find_references`, `rename_refactoring`, `reformat_file` |
| Problems | `get_file_problems`, `get_project_problems` |
| Run and build | `get_run_configurations`, `execute_run_configuration`, `build_project` |
| Git | `get_repositories`, `git_status`, `git_diff` |
Notes on what they do:
- `read_file` also reads sources of dependencies and files inside jar and zip archives.
- `search_symbol` and `find_references` use the IDE's index and the language servers; `get_symbol_info` returns what Quick Documentation shows.
- `get_file_problems` and `get_project_problems` return what the Problems tool window shows, after your inspection profile is applied, rather than a fresh analysis.
- `rename_refactoring` renames through the language server, so references that a text search would miss are updated too. It can preview without writing.
- `apply_patch` accepts the Codex `apply_patch` format and unified diffs. Paths must stay inside the project.
- `git_diff` compares files on disk, so unsaved editor text is not included.
Plugins can add more tools; they appear in the same list. A plugin tool marked as destructive asks for confirmation the same way.
## Safety
- **Untrusted projects** expose only the read-only tools. Tools that write files, rename, reformat, build or run need a project you have trusted.
- **Running and building ask first.** `execute_run_configuration` and `build_project` show a dialog in OpenIDEA naming the client, the project, the tool and what will run. Choose **Allow** or **Deny**; several requests queue up.
- Tools that write refuse paths outside the project.
## Limits
- Remote projects (opened over SSH, on a server or in a container) are not supported by the MCP server yet.
- The server serves only projects open in OpenIDEA. Closing the project's window makes its tools unavailable.
- Responses are plain JSON; the server does not stream over SSE.
+96
View File
@@ -0,0 +1,96 @@
---
title: "Migrate from another IDE"
description: "Pick a familiar keymap, bring over settings, themes and run configurations, and map concepts from IntelliJ IDEA, VS Code and Eclipse."
---
OpenIDEA follows IntelliJ IDEA's model: a folder is a project, features live in tool windows, and almost everything is reachable from Search Everywhere or Find Action. This page covers what you can bring with you and where familiar things live.
## Choose a keymap
OpenIDEA ships six keymaps:
| Keymap | Based on |
| ------------------------------------------ | --------------------------------------------------------------------------------------------- |
| **macOS** | IntelliJ IDEA's macOS keymap. The default on macOS. |
| **Windows** | IntelliJ IDEA's Windows keymap. The default on Windows and Linux. |
| **VS Code (macOS)**, **VS Code (Windows)** | Visual Studio Code's default shortcuts. Actions VS Code has no shortcut for are left unbound. |
| **Eclipse (macOS)**, **Eclipse** | IntelliJ IDEA's Eclipse keymaps. |
Switch keymaps in any of these places:
- Welcome screen › **Customize** › **Keymap**
- **Settings › Keymap**
- **View › Quick Switch Scheme…** (`` ⌃` `` / `` Ctrl+` ``) › **Keymap**
You can change single shortcuts on top of any keymap, or duplicate one into your own. See [Keymaps](/en/guide/keymap).
## From IntelliJ IDEA and other JetBrains IDEs
The defaults already match IDEA: the keymap, tool window numbers (`⌘1` / `Alt+1` for Project, `⌘9` / `Alt+9` for Git), `⇧⇧` / `Shift Shift` for Search Everywhere and `⌥↵` / `Alt+Enter` for context actions.
### Import settings
Use **File › Manage IDE Settings › Import Settings from JetBrains IDE…** and pick one of the JetBrains IDEs found on this computer. To use a `settings.zip` exported from a JetBrains IDE instead, choose **File › Manage IDE Settings › Import Settings…** and select the zip.
A confirmation lists what will be imported before anything changes:
- Color schemes (the `.icls` files) and the color scheme in use
- Editor font: family, size, line spacing (as line height) and ligatures
- Trailing-space stripping, final newline on save, sticky lines, whitespace display, soft wraps
- **Confirm before exiting the IDE**
Keymaps, code styles, live templates, inspection profiles and plugins are not imported; the confirmation lists the ones it skipped. Imported values go to the global settings.
### Import a theme
With a project open, go to **Settings › Plugins**, open the gear menu and choose:
- **Import Theme from JetBrains IDE…** to pick a theme plugin installed in your JetBrains IDEs, or
- **Import IntelliJ Theme from File…** for a theme plugin `.jar`, a Marketplace `.zip`, a plugin folder or a `.theme.json` file.
The theme is converted into an OpenIDEA theme plugin and installed. Select it in **Settings › Appearance & Behavior › Appearance › Theme**. See [Themes and fonts](/en/guide/themes).
### Import run configurations
Run **Import Run Configurations from JetBrains (.run, .idea)** from Find Action, or use the import link in **Run › Edit Configurations…**. OpenIDEA reads the project's `.run` and `.idea` run configurations it can map; a notification says how many came in and why others were left out.
### IdeaVim
Vim mode reads `~/.ideavimrc` (or `~/.openideavimrc`). Turn it on with **Tools › Vim**. See [Vim mode](/en/guide/vim).
## From VS Code
Pick **VS Code (macOS)** or **VS Code (Windows)** as the keymap; then `⇧⌘P` / `Ctrl+Shift+P` opens Find Action and `⌘P` / `Ctrl+P` opens Go to File as you expect.
| In VS Code | In OpenIDEA |
| -------------------- | -------------------------------------------------------------------------------------------------------------- |
| Command Palette | **Find Action** (`⇧⌘A` / `Ctrl+Shift+A` in the default keymap) |
| Quick Open | **Go to File** (`⇧⌘O` / `Ctrl+Shift+N`), or **Search Everywhere** (`⇧⇧` / `Shift Shift`) |
| Explorer | **Project** tool window (`⌘1` / `Alt+1`) |
| Problems panel | **Problems** tool window (`⌘6` / `Alt+6`) |
| Integrated terminal | **Terminal** tool window (`⌥F12` / `Alt+F12`) |
| Source Control | **Commit** tool window (`⌘0` / `Alt+0`) and **Git** tool window (`⌘9` / `Alt+9`) |
| Extensions | **Settings › Plugins** |
| `settings.json` | **Settings** (`⌘,` / `Ctrl+Alt+S`), stored in `settings.toml`; per-project values in `.openidea/settings.toml` |
| `launch.json` | Run configurations: **Import Run Configurations from .vscode/launch.json** |
| Multi-root workspace | **File › New Workspace…** and **Attach Project…** |
`.editorconfig` files are applied. VS Code settings and extensions are not imported.
## From Eclipse
Pick **Eclipse (macOS)** or **Eclipse** as the keymap.
- **Formatter.** To keep using an Eclipse formatter profile for Java, open **Settings › Editor › Code Style › Java**, set **Formatting profile** to **Custom Eclipse formatter XML** and choose the exported XML file.
- **Workspaces and projects.** Eclipse opens many projects in one workspace. OpenIDEA opens one project per window; to work on several folders together, create a workspace with **File › New Workspace…** or add folders with **Attach Project…**.
- **Perspectives.** OpenIDEA has no perspectives. Arrange tool windows the way you like and save the arrangement with **Window › Tool Window Layouts › Save Current Layout as…**.
## Concepts at a glance
| Concept | In OpenIDEA |
| ----------------- | ----------------------------------------------------------------------------------------------------------- |
| Project | A folder you open. Project settings and shared run configurations live in its `.openidea/` folder. |
| Tool window | A panel at the window edge: Project, Git, Run, Debug, Terminal and others. Toggle with its number shortcut. |
| Action | Every command has an action you can run from **Find Action** and bind in **Settings › Keymap**. |
| Run configuration | What to run or debug and how; see [Run configurations](/en/guide/run). |
| Language server | The process that provides code insight for a language; see [Languages](/en/languages/index). |
+86
View File
@@ -0,0 +1,86 @@
---
title: "Notebooks"
description: "Open, edit and run Jupyter notebooks (.ipynb) with a local Python kernel."
---
OpenIDEA opens `.ipynb` files in a notebook editor. You can edit code and Markdown cells, run them on a Python kernel that the IDE starts for you, and save the result back to the same file.
## Requirements
- A Python interpreter with **ipykernel** installed (`pip install ipykernel`). OpenIDEA never installs packages for you. If ipykernel is missing, the editor shows an error with the hint _Install ipykernel in the selected Python environment, then restart the kernel_.
- A trusted project. Running cells is blocked while the project is in Restricted Mode.
- Notebooks in nbformat 4. Other versions are refused and the file is left untouched.
## Open a notebook
Open any `.ipynb` file from the Project tool window, Go to File or the command line. It opens in the notebook editor instead of as raw JSON.
The toolbar at the top of the editor has:
| Control | What it does |
| ------------------------------- | -------------------------------------------------------------------------------------- |
| **Run Cell** | Runs the selected code cell. |
| **Run All** | Runs every code cell from top to bottom. Execution stops at the first cell that fails. |
| **Interrupt Kernel** | Interrupts the cell that is running. |
| **Restart Kernel** | Restarts the kernel and clears its state. |
| Interpreter list | The Python interpreter for this notebook (**Select Python interpreter**). |
| **Refresh Python interpreters** | Detects Python environments again. |
| Kernel status | `starting`, `idle`, `busy` or `dead`. |
| **Undo** / **Redo** / **Save** | Notebook-level undo, redo and save. |
| **Reload Notebook from Disk** | Discards unsaved changes, stops execution and reloads the file. |
## Choose the Python interpreter
The interpreter list shows the Python environments OpenIDEA detects, plus the project interpreter. If you don't pick one, the notebook uses the project's Python interpreter, or the first detected environment.
Your choice is remembered per notebook. Change it while no cell is running.
<Tip>
For a virtual environment, select its own `python` (for example `.venv/bin/python`). OpenIDEA does not resolve the
symlink to the base interpreter, so the kernel runs inside the environment.
</Tip>
Each open notebook gets its own kernel process. Closing the notebook shuts its kernel down.
## Edit cells
Every cell has a small toolbar:
- **Cell type** list: **Code**, **Markdown** or **Raw**.
- **Run Cell** (code cells), or **Edit Markdown** / **Preview Markdown** (Markdown cells). Double-click a rendered Markdown cell to edit it.
- **Move Cell Up**, **Move Cell Down**, **Insert Code Cell Below**, **Delete Cell**.
Use **Add Code Cell** and **Add Markdown Cell** at the end of the notebook to append cells.
Code cells show the execution count (`[3]`) next to the cell toolbar. Raw cells and cell types OpenIDEA doesn't know are shown read-only; their content is kept as it is.
## Keyboard
| Action | Keys |
| -------------------------- | --------------------------------------------------------------------------- |
| Run the selected code cell | `⇧Enter` or `⌃Enter` (macOS), `Shift+Enter` or `Ctrl+Enter` (Windows/Linux) |
| Save | `⌘S` / `Ctrl+S` |
| Undo | `⌘Z` / `Ctrl+Z` |
| Redo | `⌘⇧Z` / `Ctrl+Shift+Z` or `Ctrl+Y` |
These keys are handled by the notebook editor itself and are not part of the keymap settings.
## Outputs
Outputs are written into the notebook file when you save, the same way Jupyter does.
| Output | How it is shown |
| ------------------------------------------ | ----------------------------------------------------------------------- |
| `stdout` / `stderr` streams | Text with ANSI colours; `stderr` is highlighted. |
| Errors | The traceback. |
| `image/png`, `image/jpeg`, `image/svg+xml` | Inline image. |
| `text/html` | Static HTML in a sandbox: no scripts, no network, only embedded images. |
| `text/plain` | Text. |
| `application/javascript` | Shown as source, not run. |
| Other MIME types | A notice; the data is kept in the file. |
## Limits
- Only Python kernels are supported. Kernels for other languages and remote Jupyter servers are not.
- Interactive widgets (ipywidgets) and JavaScript outputs do not run.
- Undo and redo are not available while cells are running.
+46
View File
@@ -0,0 +1,46 @@
---
title: "Profiler"
description: "Profile a run configuration with a sampling profiler and explore the result as a flame graph, call tree or method list."
---
**Profile** runs a [run configuration](/en/guide/run) under an external sampling profiler. When the program exits (or you stop it), the IDE reads the samples and opens them in the Profiler tool window.
## Profile a run
- Use **Run › Profile** to profile the selected configuration.
- Choose **Profile '…'** from the gutter run icon menu or from the run widget's **More Actions** menu.
- **Profile Context Configuration** (in **Find Action…**, `⌘⇧A` / `Ctrl+Shift+A`) profiles the program or test at the caret.
The program runs in the Run tool window as usual. If the profiler is missing, a notification names it and offers **Install** (when there is an install command) or **Download…** (the profiler's download page).
## Supported runtimes
| Runtime | Configurations | Profiler | Platforms |
| --------------- | ------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------- | ------------ |
| Node.js | `node`, `npx`, `tsx`, `ts-node` programs and npm, pnpm or yarn scripts | `node --cpu-prof` (set through `NODE_OPTIONS`, so every Node.js process of the run is profiled) | All |
| Python | scripts, modules, pytest | `py-spy record` (subprocesses included) | All |
| JVM | configurations that start `java`, such as Application and JAR Application | async-profiler agent (set through `JAVA_TOOL_OPTIONS`) | macOS, Linux |
| Rust | `cargo run` | `perf record -g` | Linux |
| Native programs | native configurations | `perf record -g` | Linux |
Setting up the profilers:
- **py-spy**: the IDE looks next to the project's interpreter first, then on the search path. Without a package manager it offers `python -m pip install py-spy`.
- **async-profiler**: unpack a release from [async-profiler releases](https://github.com/async-profiler/async-profiler/releases) and put its `bin/asprof` on the `PATH`, or set `ASYNC_PROFILER_HOME` to the unpacked folder. The IDE cannot install it for you.
- **perf**: install your distribution's `linux-tools` / `linux-perf` package.
Not supported: Bun and Deno programs, JVM programs on Windows, native programs and `cargo run` on macOS and Windows, and Maven or Gradle configurations.
## Read the results
Each profiled run becomes a snapshot. The last five snapshots stay available as tabs in the Profiler tool window header. Each snapshot has three views:
| View | What it shows |
| --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Flame Graph** | Call stacks stacked from the root down; wider frames took more time. Click a frame to zoom into it, use **Reset Zoom** to go back. Hover a frame for its total and self weight. |
| **Call Tree** | The same stacks as an expandable tree, heaviest calls first. |
| **Method List** | One row per function with its self and total weight. Click the **Self**, **Total** or **Method** column header to sort. |
Double-click a frame or a row to open its source location. Type in **Search frames** in the header to highlight matching frames in the flame graph and filter the method list.
Snapshots are kept only for the current session; they are cleared when you switch projects or close the window.
+74
View File
@@ -0,0 +1,74 @@
---
title: "Quickstart"
description: "Open a project, let it index, find your way around, then run and debug it."
---
This page takes you from a fresh install to running and debugging a project. Shortcuts are for the default keymap: macOS first, then Windows and Linux.
## Open a project
OpenIDEA opens a folder as a project. Any of these works:
- On the Welcome screen, click **Open** and choose the folder, or drop the folder onto the window.
- Use **File › Open…** (`⌥⇧⌘O` on macOS).
- From a terminal, run `openidea .` in the folder. See [Command line](/en/reference/cli).
- Clone a repository with **Clone Repository** on the Welcome screen or **Git › Clone…**.
A single file opened outside any project opens on its own, without a project. Use **Open as Project** in its window to open the containing folder as a project.
## Trust the project
The first time you open a folder, OpenIDEA asks **Trust and Open Project?** Opening, building or running a project can execute code from its build scripts, so only trust sources you know.
- **Trust Project** opens it with everything enabled. Check **Trust projects in** to trust every project in the parent folder too.
- **Preview in Restricted Mode** opens it read-only for tools: you can browse, edit and search files, but language servers, builds, run configurations and other project commands don't start. A banner shows **Restricted Mode**; click **Trust Project** there when you're ready.
- **Don't Open** cancels.
Manage trusted projects and trusted folders later in **Settings › Security › Project Trust**.
## Let it index
After the project opens, the status bar shows an **Indexing** task with the number of files and symbols. Navigation by symbol and code insight need the index, so it can't be cancelled; on most projects it takes a few seconds.
When a language needs a language server that isn't installed, a notification names it and offers **Install**. Manage servers in **Settings › Languages & Frameworks › Language Servers**. See [Languages](/en/languages/index) for what each language uses.
## Find things
| To | macOS | Windows / Linux |
| ------------------------------------------------------------- | ----- | ------------------ |
| Search everything: files, classes, symbols, actions, settings | `⇧⇧` | `Shift Shift` |
| Run any action by name | `⇧⌘A` | `Ctrl+Shift+A` |
| Go to a class | `⌘O` | `Ctrl+N` |
| Go to a file | `⇧⌘O` | `Ctrl+Shift+N` |
| Go to a symbol | `⌥⌘O` | `Ctrl+Alt+Shift+N` |
| Find text in the project | `⇧⌘F` | `Ctrl+Shift+F` |
| Reopen a recent file | `⌘E` | `Ctrl+E` |
| Go to the declaration under the caret | `⌘B` | `Ctrl+B` |
| Show the project tree | `⌘1` | `Alt+1` |
`⇧⇧` means pressing Shift twice. **Find Action** is the fastest way to discover a feature: type a few words of what you want. For more, see [Search and navigation](/en/guide/search) and the full [shortcut reference](/en/reference/shortcuts).
## Run
OpenIDEA detects what can run in your project and shows a run icon in the gutter next to entry points and tests.
- Click the gutter icon and choose **Run '…'**, or put the caret in the code and press `⌃⇧R` / `Ctrl+Shift+F10` (**Run Context Configuration**).
- Choose a configuration in the toolbar and press `⌃R` / `Shift+F10` to run it again.
- Press `⌃⌃` / `Ctrl Ctrl` (**Run Anything…**) to run a command, script or task by name.
- **Run › Edit Configurations…** creates and edits run configurations.
Output appears in the **Run** tool window (`⌘4` / `Alt+4`); `⌘F2` / `Ctrl+F2` stops the process. See [Run configurations](/en/guide/run).
## Debug
1. Click in the gutter next to a line, or press `⌘F8` / `Ctrl+F8`, to set a breakpoint.
2. Start the configuration with **Debug** (`⌃D` / `Shift+F9`) or the gutter icon's **Debug '…'**.
3. When the breakpoint hits, the **Debug** tool window shows frames and variables. Step with `F8` (Step Over), `F7` (Step Into) and `⇧F8` / `Shift+F8` (Step Out); resume with `F9` (on macOS also `⌥⌘R`).
4. Evaluate an expression with `⌥F8` / `Alt+F8`.
If the language's debugger isn't installed yet, OpenIDEA offers to install it and then starts the session. See [Debugging](/en/guide/debugging).
## Next steps
- Coming from another IDE: [Migrate](/en/guide/migrate).
- Change the theme, fonts or keymap: [Settings](/en/guide/settings), [Themes and fonts](/en/guide/themes), [Keymaps](/en/guide/keymap).
+93
View File
@@ -0,0 +1,93 @@
---
title: "Refactoring"
description: "Rename, introduce and extract, inline, move, change signature and safe delete, and which ones depend on the language server."
---
The **Refactor** menu has IntelliJ IDEA's refactorings with their usual shortcuts. Most of them are carried out by the file's language server, so what is available depends on the language: Rename works almost everywhere, while Extract Method, Inline or Change Signature appear only where the server offers them. When a refactoring cannot run at the caret, a hint says why (for example "Cannot perform refactoring. Select the code to extract").
## Refactor This
| Action | macOS | Windows / Linux |
| ------------------ | ----- | ------------------ |
| **Refactor This…** | `⌃T` | `Ctrl+Alt+Shift+T` |
**Refactor This…** lists only the refactorings that apply at the caret or selection: first the ones below, then any other refactorings the language server offers there (for example "Convert to arrow function" in TypeScript).
## Available refactorings
| Refactoring | macOS | Windows / Linux | Works through |
| ------------------------ | ----- | --------------- | ------------------------------------------------------ |
| **Rename…** | `⇧F6` | `Shift+F6` | Language server; local variables also without one |
| **Change Signature…** | `⌘F6` | `Ctrl+F6` | Language server |
| **Introduce Variable…** | `⌥⌘V` | `Ctrl+Alt+V` | Language server; simple cases also without one |
| **Introduce Constant…** | `⌥⌘C` | `Ctrl+Alt+C` | Language server; simple cases also without one |
| **Introduce Field…** | `⌥⌘F` | `Ctrl+Alt+F` | Language server |
| **Introduce Parameter…** | `⌥⌘P` | `Ctrl+Alt+P` | Language server |
| **Extract Method…** | `⌥⌘M` | `Ctrl+Alt+M` | Language server |
| **Inline…** | `⌥⌘N` | `Ctrl+Alt+N` | Language server |
| **Move…** | `F6` | `F6` | Language server (symbols); the IDE (files and folders) |
| **Safe Delete…** | `⌘⌦` | `Alt+Delete` | The IDE, using the server's references |
| **Pull Members Up…** | — | — | Java only |
| **Push Members Down…** | — | — | Java only |
## Rename
<Steps>
<Step title="Start the rename">
Put the caret on a name and press `⇧F6` / `Shift+F6`. The name becomes editable in place, and its other occurrences
in the file change as you type.
</Step>
<Step title="Finish or cancel">
Press `↵` / `Enter` or `⇥` / `Tab` to apply the rename across the project. `Esc` restores the old name.
</Step>
<Step title="Use the dialog for more options">
Press `⇧F6` / `Shift+F6` a second time to open the rename dialog instead.
</Step>
</Steps>
When the change touches more than one file, or renames or moves files, a preview lists the affected files before anything is written; confirm with **Do Refactor**. If the server may have missed usages (for example while it is still loading the project), the preview says so and the button reads **Rename Anyway**.
Without a running language server, only local variables are renamed, from the syntax tree. Members, types and other cross-file symbols wait for the server.
## Introduce and extract
**Introduce Variable**, **Introduce Constant**, **Introduce Field**, **Introduce Parameter** and **Extract Method** work like this:
1. Select the expression or statements. With only a caret, a chooser lists the expressions around it; each is highlighted while you move through the list.
2. If the expression occurs several times, choose whether to replace this occurrence or all of them (**Multiple occurrences found**).
3. The new name is then edited in place. `↑` / `↓` cycle through suggested names; `↵` / `Enter` accepts.
When the language server cannot help, Introduce Variable and Introduce Constant still work from the syntax tree in clear-cut cases, and refuse with a hint otherwise.
## Move, copy and rename files
In the Project view, **Rename…** (`⇧F6` / `Shift+F6`), **Move…** (`F6`, drag and drop, or cut and paste) and **Copy…** (`F5`, copy and paste, or `⌥`-drag / `Alt`-drag) open a dialog. With **Search for references** on, references to the file are updated through the language servers that support it (for example imports in TypeScript); **Search in comments and strings** also looks there. The changes are shown in a preview first.
In the editor, **Move…** on a symbol runs the server's move refactoring, where the language has one.
## Safe Delete
<Steps>
<Step title="Start">
Put the caret on a declaration and press `⌘⌦` / `Alt+Delete`, or use **Safe Delete…** on a file in the Project view.
</Step>
<Step title="Confirm">Confirm the deletion. You can also search comments and strings.</Step>
<Step title="Review usages">
If the symbol is still used, **Usages Detected** offers **View Usages**, **Delete Anyway** or **Cancel**. Otherwise
the declaration is removed together with its doc comment and annotations, as one undo step.
</Step>
</Steps>
## Pull Members Up and Push Members Down
These move fields and methods between a Java class and its superclasses or subclasses. A dialog lets you pick the members and the destination; problems the move would cause are listed in **Problems Detected** before you continue. They are provided by the Java extension bundled with OpenIDEA, so they are not available for other languages.
## Undo
A refactoring is one undo step, even when it changed several files. After a multi-file refactoring, a notification offers **Undo** (and then **Redo**).
## Limits
- Refactorings other than Rename, Safe Delete and the syntax-based fallbacks need a running language server that offers them. Check the **Language Services** widget in the status bar if one is missing.
- Change Signature appears only when the server offers a signature rewrite as a code action.
- There is no structural search-and-replace refactoring in this menu; see **Edit › Find › Replace Structurally…** in [Inspections](/en/guide/inspections).
+99
View File
@@ -0,0 +1,99 @@
---
title: "Containers"
description: "Reopen a local project in its dev container, and run configurations in Docker or Docker Compose containers."
---
OpenIDEA works with containers in two ways:
- **Dev containers**: the whole project opens inside the container described by its `devcontainer.json`, with the IDE
backend running there. This is remote development, like [SSH](/en/guide/remote/ssh).
- **Docker and Docker Compose run targets**: the project stays local and only a run configuration runs in a
container.
To list, start, stop and inspect containers, images and Compose services, use the [Services](/en/guide/services)
tool window.
## Dev containers
### Requirements
- Docker installed on your machine, with the daemon of the current Docker context running locally. The project folder
is bind-mounted into the container, so a daemon on another machine doesn't work.
- The Dev Containers CLI (`devcontainer`). Install **Dev Containers CLI** from
**Settings › Languages & Frameworks › Language Servers** (it needs Node.js), or put `devcontainer` on your `PATH`.
- A `.devcontainer/devcontainer.json` or `.devcontainer.json` in the project folder.
- The project must be trusted: building the container runs the project's commands (`initializeCommand`, the
Dockerfile build).
- A Linux container on x86_64 or aarch64.
### Reopen a project in its container
<Steps>
<Step title="Open the project locally">Open the folder that contains the `devcontainer.json` and trust it.</Step>
<Step title="Reopen in the container">
Choose **File › Reopen in Container**, then **This Window** or **New Window** if asked.
</Step>
<Step title="Wait for the container">
OpenIDEA runs `devcontainer up` for the folder, which builds or starts the container. The first build can take a
while. Then it installs and starts [`openidea-server`](/en/guide/remote/server) in the container and opens the
project folder there.
</Step>
</Steps>
The project then opens at the container's `remoteWorkspaceFolder`, as its `remoteUser`. The status bar shows
**Dev Container: `<folder name>`**, and the project appears in the recent projects list on the Welcome screen.
If something fails (no `devcontainer.json`, Docker not installed, the build failed), a notification
**Could not reopen the project in a container** shows the reason, including the end of the CLI's log.
### What applies from `devcontainer.json`
- Everything `devcontainer up` handles: image, Dockerfile, features, mounts and the lifecycle commands. They run once,
through the CLI; the server doesn't run `postStartCommand` again.
- `remoteEnv`: applied to terminals, runs, language servers and tools in the container. Change it and choose
**File › Reload Environment** to apply it to new processes.
- `forwardPorts`: forwarded to your machine automatically. Forward more in the **Ports** tool window, as in an
[SSH window](/en/guide/remote/ssh#forward-ports).
Language servers and debuggers that OpenIDEA installs go into the container, under the container user's
`~/.openidea/data`.
### Limits
- Only the default configuration is used (`.devcontainer/devcontainer.json`, then `.devcontainer.json`). There is no
picker for several configurations in subfolders of `.devcontainer/`.
- **Reopen in Container** is available for local projects only, not from an SSH or WSL window.
- There is no command to attach to an arbitrary running container; the container always comes from the project's
`devcontainer.json`.
## Docker and Docker Compose run targets
A run target runs a run configuration somewhere other than your machine. Container targets rewrite the configuration
into a local `docker run --rm` (or `docker compose run --rm`) with the project bind-mounted, so the console, input,
**Stop** and test results work as for a local run.
<Steps>
<Step title="Add a target">
Open **Settings › Build, Execution, Deployment › Run Targets**, click **+**, and set **Type** to **Docker** or
**Docker Compose**.
</Step>
<Step title="Describe the container">
For **Docker**, enter the **Image** (for example `python:3.12`). For **Docker Compose**, list the **Compose files**
(empty uses the project's default file) and the **Service**.
</Step>
<Step title="Use it">
Pick the target under **Run on** in a run configuration, or make it the **Default run target**.
</Step>
</Steps>
Container target options:
| Option | Meaning |
| ------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Docker connection** | Which Docker daemon to use; **Docker CLI default** uses the Docker CLI's own default. Only daemons on this machine can mount the project. |
| **Container folder** | Where the project is mounted. Paths and the working directory are mapped into it. For Compose, empty means where the service mounts the project. |
| **Published ports** | Ports to publish, for example `8000:8000`. |
| **Run options** | Extra `docker run` options, for example `--network host`. |
| **Run as the image's user, not the project folder's owner (Linux)** | On Linux the container runs as the owner of the project folder by default, so the files it writes stay yours. Tick this to keep the image's user. |
See [Run configurations](/en/guide/run) for run targets in general, including SSH targets.
+106
View File
@@ -0,0 +1,106 @@
---
title: "openidea-server"
description: "The IDE backend that runs on the remote host: how the desktop app installs and starts it, where it keeps its files, and how to install it offline."
---
`openidea-server` is the OpenIDEA backend without a UI. In a remote window it runs where the project is (an SSH host,
a WSL distribution or a dev container) and does the work the desktop app would do locally: file system, indexing,
language servers, terminals, runs, debugging, build tools and Git. The desktop window only shows the UI and sends
commands to it.
You don't start it yourself. When you open a remote project ([SSH](/en/guide/remote/ssh),
[Containers](/en/guide/remote/containers)), the desktop app installs the server on the host if needed, starts it as
`openidea-server --stdio` over the SSH connection (or `wsl.exe` / `docker exec`) and talks to it over stdin and
stdout. The server serves one desktop window and exits when that window disconnects.
## Supported hosts
Each release publishes a server build for:
| OS | Architectures |
| ----- | ------------------------------------------------------------------------- |
| Linux | `x86_64`, `aarch64` (static musl builds, independent of the host's glibc) |
| macOS | `aarch64`, `x86_64` |
The desktop app detects the host with `uname -sm`. Other platforms, including Windows hosts, are not supported.
Dev containers must be Linux.
## How it is installed
On every connection the desktop app checks whether the host already has the server for its own version. If not:
1. It looks for a server binary for the host's platform on your machine (see [Offline installation](#offline-installation)).
2. Otherwise it downloads the release asset `openidea-server-<os>-<arch>.gz` for its version from
[GitHub releases](https://github.com/l0ng-ai/openidea/releases), verifies its SHA-256 checksum, and caches it in the
desktop's local data directory under `server/<version>/<os>-<arch>/`.
3. It uploads the binary to the host and removes older builds of the same version.
The desktop and the server always run the same version. After you update OpenIDEA, the next connection installs the
new server next to the old one.
## Files on the host
| Path | Contents |
| ------------------------------------------------------ | -------------------------------------------------------------------------------------------- |
| `~/.openidea/server/<version>/<build>/openidea-server` | The installed server binaries, one folder per version and build. |
| `~/.openidea/config/` | The host's settings, including custom language definitions in `languages/`. |
| `~/.openidea/data/` | Tools OpenIDEA installs on the host (language servers, debuggers, JDKs), caches and indexes. |
To free space, delete old version folders under `~/.openidea/server/`. After deleting `~/.openidea/data/`, OpenIDEA
installs its tools again when they are needed.
## Environment
The server reads your login shell's environment when it starts, because an SSH command otherwise gets a minimal one.
So `PATH` and other variables from your shell profile apply to terminals, runs and language servers. After changing
them, use **File › Reload Environment** in the remote window instead of reconnecting.
| Variable | Effect |
| ---------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `OPENIDEA_SERVER_HOME` | Use this folder instead of `~/.openidea` for `config/` and `data/`. It must be set in the environment the server process starts with. The installed binaries stay under `~/.openidea/server/`. |
| `OPENIDEA_LOG` | Log filter for the server's log on stderr, for example `debug` or `ide_lsp=debug`. Default: `info`. |
## Offline installation
When the desktop machine can't reach GitHub, give it the server binary yourself. The desktop app looks for one in
this order:
1. The file named by the `OPENIDEA_SERVER_BINARY` environment variable, if it is set when OpenIDEA starts.
2. `server/<os>-<arch>/openidea-server` in the desktop's [local data directory](/en/reference/data-directories), for
example `server/linux-x86_64/openidea-server`.
3. An `openidea-server` next to the OpenIDEA executable, when the host has the same platform as the desktop.
Download `openidea-server-<os>-<arch>.gz` from the release that matches your OpenIDEA version, unpack it with
`gunzip`, and put it in one of these places. Use the same version as the desktop app; the desktop refuses a server
that speaks a different protocol.
If the host already has a build of the current version installed, the desktop uses it when nothing can be downloaded.
### Build from source
```bash
cargo build --release -p ide-host --bin openidea-server
```
Build on, or cross-compile for, the host's platform, then point `OPENIDEA_SERVER_BINARY` at
`target/release/openidea-server` (or the target-specific folder when cross-compiling).
## Command line
The flags are meant for the desktop app, but `--version` is handy for checking an installed build.
| Flag | Meaning |
| ------------------------- | -------------------------------------------------------------------------------------------------------------- |
| `--version` | Print the server version and protocol version, then exit. |
| `--stdio` | Serve one desktop client over stdin / stdout. Logs go to stderr. |
| `--connect <host> <port>` | Relay one TCP connection to `<host>:<port>` over stdin / stdout. Used for port forwarding into dev containers. |
```bash
ls ~/.openidea/server/*/*/openidea-server
~/.openidea/server/<version>/<build>/openidea-server --version
```
## Plugins
The desktop's code plugins and the languages they contribute are sent to the server when the window connects, and
the plugins run there, next to the project.
+141
View File
@@ -0,0 +1,141 @@
---
title: "SSH"
description: "Open a project that lives on an SSH host or in a WSL distribution, with the IDE backend running next to the code."
---
Remote development opens a project folder on another machine in a normal OpenIDEA window. The UI stays on your
desktop; the IDE backend, [`openidea-server`](/en/guide/remote/server), runs on the remote host. Indexing, language
servers, terminals, run configurations, debuggers, build tools and Git all run there, next to the code.
OpenIDEA connects over SSH, or on Windows directly into a WSL distribution. For dev containers, see
[Containers](/en/guide/remote/containers).
## Requirements
- An SSH host running Linux (x86_64 or aarch64) or macOS (Apple silicon or Intel). Windows hosts are not supported.
- A POSIX shell with `uname`, `gzip`, `mkdir`, `chmod`, `mv` and `find` on the host, and the SFTP subsystem enabled
(used to install the server and by **Browse** in the connect dialog).
- The first connection downloads the matching `openidea-server` release build to your desktop and uploads it to the
host. Without internet access on the desktop, see [Offline installation](/en/guide/remote/server#offline-installation).
## Add an SSH configuration
Remote development, run targets, data source tunnels, deployment servers and **Start SSH Session…** all connect
through the app-wide SSH configurations.
<Steps>
<Step title="Open the SSH settings">
Choose **Tools › SSH Configurations…**, or open **Settings › Tools › SSH Configurations**.
</Step>
<Step title="Add a configuration">
Click **+** (Add) and fill in **Name**, **Host**, **Port:** and **User name**.
</Step>
<Step title="Pick the authentication type">Choose one of the options under **Authentication type** (see below).</Step>
<Step title="Test the connection">
Click **Test Connection**. If the host key is not in `~/.ssh/known_hosts` yet, OpenIDEA shows the key and offers
**Trust Host and Retry**, which adds it there.
</Step>
</Steps>
| Authentication type | What it uses |
| ------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Password** | A password. Tick **Save password** to keep it in the OS credential store. |
| **Key pair (OpenSSH or PuTTY)** | A **Private key file** (for example `~/.ssh/id_ed25519`) and its **Passphrase**, which **Save passphrase** keeps in the credential store. |
| **OpenSSH config and authentication agent** | `HostName`, `User`, `Port`, `IdentityFile` and `ProxyJump` from `~/.ssh/config`, and the keys of the SSH agent. **Host** can be a `Host` alias from `~/.ssh/config`. |
Saved passwords and passphrases go to the macOS Keychain, Windows Credential Manager or the Secret Service on Linux,
never to a settings file. The **+**, **−** and copy buttons above the list add, remove and duplicate configurations.
### `~/.ssh/config` support
OpenIDEA has its own SSH client and reads `~/.ssh/config` itself. It understands `Host`, `Match`, `Include`,
`HostName`, `User`, `Port`, `IdentityFile`, `ProxyJump`, `ProxyCommand`, `StrictHostKeyChecking` and
`UserKnownHostsFile`. Jump hosts authenticate with the agent and identity files; the password or passphrase of the
configuration is only used for the target host.
Host keys are checked against `known_hosts`. An unknown key has to be trusted once; a changed key is refused unless
host key checking is turned off for that host.
## Open a remote project
<Steps>
<Step title="Open the connect dialog">
Choose **File › Remote Development…**, or click **Remote Development** on the Welcome screen.
</Step>
<Step title="Choose the host">
Pick an entry under **SSH configuration:**. **Configure…** opens the SSH settings if you need to add one.
</Step>
<Step title="Choose the folder">
Type a path under **Project directory:** (`~` and `~/…` are relative to the remote home), or click **Browse** to
walk the remote folders.
</Step>
<Step title="Open">Click **Open**. If a project is already open, choose **This Window** or **New Window**.</Step>
</Steps>
While connecting, the dialog shows each stage: connecting to the host, checking the IDE backend on the host,
downloading or installing it when needed, starting it, and opening the project. The first connection to a host takes
longer because of the upload; later ones reuse the installed server.
After the project opens you are asked whether to trust it, as for a local folder.
<Tip>
Run **Test Connection** in the SSH settings before the first connection. That is where you trust a new host key and
enter the password or passphrase.
</Tip>
## Working in a remote window
- The window title ends with the host, and the status bar shows **SSH: `<host>`**. Clicking it opens the
**Remote Development** dialog.
- Remote projects appear in the recent projects list on the Welcome screen, so you can reopen them from there.
- Language servers, debuggers and other tools that OpenIDEA installs go to the remote host, under
`~/.openidea/data`.
- Your keymap, appearance, editor font and colour scheme stay on the desktop. Project and tool settings live on the
host.
- Your code plugins are synced to the server and run next to the project.
- **File › Disconnect from Remote Host** closes the project and ends the connection.
### Forward ports
A remote window has a **Ports** tool window. Enter a port under **Remote port, e.g. 3000** and click
**Forward Port**. `localhost:<port>` on the remote host is then reachable on your machine, on the same port when it is
free or on another free one. Each row can be opened in the browser, copied, or stopped.
Forwarded ports are remembered per host and folder, and forwarded again the next time you connect.
### Reload the environment
The server takes its environment from your login shell on the host when it starts. After changing a shell profile,
choose **File › Reload Environment** so new terminals, runs and tools get the new values. Processes that are already
running keep the old environment. When variables changed, the notification offers **Restart Language Servers**.
If the remote project has a `.devcontainer/devcontainer.json` or `.devcontainer.json`, its `remoteEnv` is applied too,
and its `postStartCommand` runs once each time the server starts.
## Start an SSH terminal
**Tools › Start SSH Session…** (or **SSH Session…** in the Terminal tool window's new-tab menu) picks an SSH
configuration and opens a terminal tab that runs `ssh` for it. This uses the OpenSSH client on your machine, so `ssh`
must be on your `PATH`. It does not open a remote project.
## Connect to WSL (Windows)
On Windows, when at least one WSL distribution is installed, the connect dialog shows **Connect through:** with
**SSH** and **WSL**. Choose **WSL**, pick the **WSL distribution:** and enter the **Project directory:** inside the
distribution. OpenIDEA installs and starts `openidea-server` inside the distribution with `wsl.exe`; no SSH server is
needed. The status bar then shows **WSL: `<distribution>`**.
Opening a folder under `\\wsl.localhost\<distribution>\` also opens it through the distribution.
**Browse** is not available for WSL.
## Other uses of SSH configurations
- **Run targets** (**Settings › Build, Execution, Deployment › Run Targets**) run a configuration on an SSH host. See
[Run configurations](/en/guide/run).
- **Data sources** can connect through an SSH tunnel. See [Database](/en/guide/database).
- **Tools › Deployment** uploads, downloads and syncs project files with a server over SFTP.
## Limits
- One project per remote window: a remote project can't be attached to another window's projects.
- The SSH host must run Linux or macOS on x86_64 or aarch64.
+196
View File
@@ -0,0 +1,196 @@
---
title: "Run configurations"
description: "Create, edit and launch run configurations, run code from the gutter, and read the output in the Run tool window."
---
A run configuration describes how to start something: a command line, a main class, a test suite, an npm script, a Docker container. OpenIDEA keeps three kinds:
- **Detected** configurations, read from the project's build files every time they change.
- **User** configurations you create or save in **Run/Debug Configurations**.
- **Temporary** configurations, created when you run the code at the caret or a file. The five most recent are kept.
The selected configuration is shown in the run widget in the title bar, with Run, Debug and Stop buttons next to it.
## Detected configurations
When a project opens, OpenIDEA scans its build files (also in nested projects of a folder that holds several) and lists what it can run:
| Build file | Configurations |
| ------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `package.json` | One per `scripts` entry, run with the package manager of the lock file (npm, pnpm, yarn or bun). `test`, `test:*`, `jest` and `vitest` scripts are marked as tests. |
| `deno.json` / `deno.jsonc` | Deno tasks |
| `Cargo.toml` | `cargo run` for each binary, `cargo test`, `cargo build`; workspace members are included |
| `build.gradle(.kts)` / `pom.xml` | Gradle and Maven run configurations |
| `go.mod` / `go.work` | Go programs and tests |
| `pyproject.toml`, `setup.py`, `requirements.txt`, `pytest.ini`, `tox.ini`, `manage.py`, a `.venv` | Python programs, pytest, Django |
| `composer.json`, `phpunit.xml` | PHPUnit |
| Ruby projects (`Gemfile`, `Rakefile`, …) | Rake tasks, RSpec, Minitest |
| `CMakeLists.txt` | CMake build and run targets |
| `.csproj` / `.sln` | .NET projects and tests |
| `Makefile`, `justfile` | Make and just targets |
| `project.godot` | Godot |
Plugins can contribute more detectors. Detected configurations reload when a build file changes; run **Reload Run Configurations** from Find Action to force it.
If you edit a detected configuration, the change is saved as a user configuration that overrides it. Removing a detected configuration hides it for this workspace.
## Run code from the editor
<Steps>
<Step title="Click the gutter icon">
A ▶ icon appears next to `main` functions, tests and test classes in Rust, Go, Java, Kotlin, Scala, Groovy,
JavaScript / TypeScript, Python, PHP, Ruby, C# and C / C++. It also appears on `scripts` entries in `package.json`.
Click it to choose **Run '…'**, **Debug '…'**, other executors (such as coverage or profiling) or **Modify Run
Configuration…**.
</Step>
<Step title="Or run the context">
**Run › Run Context Configuration** runs the test or program at the caret, or the current file. The same items
appear in the editor, editor tab and Project view context menus as **Run '…'**, **Debug '…'** and **More
Run/Debug**. On a directory in the Project view, it runs the tests in that directory.
</Step>
<Step title="Keep it">
A context run creates a temporary configuration. Open the configuration chooser and pick **Save '…' Configuration**
to keep it as a user configuration.
</Step>
</Steps>
After a test run, the gutter icon turns green or red to show the last result of that test.
## Launch a configuration
| Action | macOS | Windows / Linux |
| ------------------------------------------------ | --------- | ---------------- |
| **Run** (selected configuration) | `⌃R` | `Shift+F10` |
| **Debug** (selected configuration) | `⌃D` | `Shift+F9` |
| **Run Context Configuration** | `⌃⇧R` | `Ctrl+Shift+F10` |
| **Debug Context Configuration** | `⌃⇧D` | — |
| **Run…** (choose a configuration and run it) | `⌃⌥R` | `Alt+Shift+F10` |
| **Debug…** (choose a configuration and debug it) | `⌃⌥D` | `Alt+Shift+F9` |
| **Edit Configurations…** | `⌃⌥⇧R` | — |
| **Stop** | `⌘F2` | `Ctrl+F2` |
| **Rerun** (in the Run tool window) | `⌘R` | `Ctrl+F5` |
| **Run Anything…** | `⌃` twice | `Ctrl` twice |
In the configuration chooser, hold `Shift` to debug the configuration instead of running it. The first entry, **Current File**, runs whatever the active editor holds.
**Stop** stops the process of the focused Run tab. Elsewhere it stops the only running process, or lists them with **Stop All** when several run. Press Stop again to kill a process that does not exit.
**Run Anything…** runs a shell command in the project directory, a run configuration or a build tool goal. Lines that start with `mvn` or `gradle` get goal and task completion.
## Edit configurations
Open **Run › Edit Configurations…** (also in the run widget menu). The **Run/Debug Configurations** dialog lists configurations on the left, grouped by folder.
- **Add New Configuration** (`⌘N` / `Ctrl+N`, or `Alt+Insert`) opens the list of configuration types.
- **Copy Configuration**, **Create New Folder**, **Move Up** / **Move Down** organize the list. `Delete` removes the selection.
- **Edit configuration templates…** sets the starting values for new configurations of each type.
- **Store as project file** decides where the configuration is saved (see [Where configurations are stored](#where-configurations-are-stored)).
- **Modify options** (`⌥M` / `Alt+M`) adds optional fields to the form.
### Configuration types
| Type | Runs |
| ------------------------------------------------------- | ------------------------------------------------------------------------------ |
| **Shell Script** | A script file, script text or a command line |
| **Program** | An executable with arguments |
| **npm** | A `package.json` script with npm, pnpm, yarn or bun |
| **Gradle** / **Maven** | Gradle tasks or Maven goals, optionally through the wrapper |
| **Application** / **JAR Application** / **Spring Boot** | A Java main class, an executable JAR, a Spring Boot application |
| **Tomcat Server** | A WAR application in a local Tomcat |
| **pytest** / **Django tests** | pytest tests, `manage.py test` |
| **Ruby** / **Rake** / **RSpec** | A Ruby script, a Rake task, RSpec examples |
| **PHPUnit** | PHPUnit tests |
| **Go Build** / **Go Test** | A Go application, Go tests |
| **.NET Project** / **dotnet test** | A .NET project with a launch profile, .NET tests |
| **Godot** | A Godot project's main scene or a chosen scene |
| **Dockerfile** / **Docker Image** / **Docker Compose** | A container built from a Dockerfile or started from an image, Compose services |
| **Compound** | Several configurations together |
The debugger adds attach-only types such as **Remote JVM Debug**, **Go Remote**, **Python Remote Debug (debugpy)**, **Attach to Node.js** and **PHP Remote Debug (Xdebug)**; see [Debugging](/en/guide/debugging). Plugins can add their own types.
### Common fields
- **Run on**: the local machine, or an SSH host defined in **Settings › Build, Execution, Deployment › Run Targets**. Debugging is not available on run targets. See [SSH](/en/guide/remote/ssh).
- **Working directory**: relative to the project root; empty means the project root.
- **Environment variables**: `VAR=value` pairs separated by `;`. The list button opens the **Environment Variables** dialog, where **Include system environment variables** can be turned off.
- **Debug adapter**: which debugger **Debug** uses for this configuration (for example `debugpy`, `js-debug`, `codelldb`, `delve`).
### Environment variables and `.env` files
Turn on **Modify options › Paths to ".env" files** to load one or more `.env` files (relative to the project root, separated by `;`). They support `KEY=value`, `export` prefixes, `#` comments, quoted values and `${VAR}` references. Variables set in the configuration override the ones from `.env` files.
With **Include system environment variables** on, the process also gets the IDE's environment and the variables of your login shell.
### Modify options
| Group | Option |
| ---------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Operating System | **Allow multiple instances**, **Redirect input**, **Paths to ".env" files**, **Emulate terminal in output console** |
| Logs | **Save console output to file**, **Show console when a message is printed to stdout**, **Show console when a message is printed to stderr** |
| Test | **Show results in the test runner** |
| Before Launch | **Build** (Java, Maven and Gradle), **Add before launch task**, **Open run/debug tool window when started**, **Focus run/debug tool window when started** |
**Emulate terminal in output console** runs the process in a terminal, for programs that need a TTY. It cannot be combined with **Redirect input**.
### Before launch tasks
Add tasks under **Modify options › Add before launch task**. They run in order before the process starts:
- **Run Another Configuration**
- **Build Project**, **Build Module**, **Build Artifacts**, **Build .NET project** (for .NET configurations)
- **Run External Tool**
- **Run Shell Command**
- **Activate Tool Window**
For Java, Maven and Gradle configurations, the **Build** option picks how the project is built first: **Automatic (incremental)**, **Build (Make)**, **Rebuild**, **No build** or **Build, no error check**.
### Compound configurations
A **Compound** configuration starts several configurations at once. Run it to start all of them, or Debug it to debug each of them. Stop from the run widget stops them all.
## The Run tool window
Each launch opens a tab in the **Run** tool window (bottom). The toolbar has **Rerun**, **Debug**, **Rerun Failed Tests** (for test runs), **Update Application**, **Find…**, **Scroll to End**, **Soft-Wrap**, **Clear All** and **Edit Configurations…**.
- File paths and stack trace frames in the output are links.
- Repeated lines can be folded; configure the patterns in **Settings › Build, Execution, Deployment › Console**, along with the buffer size and the console encoding.
- For test runs the tab shows a test tree next to the output. See [Testing](/en/guide/testing).
**Settings › Build, Execution, Deployment › Run/Debug** controls whether the tool window opens when a process starts, whether rerun and closing a running tab ask first, and the time limit for a process to exit after Stop.
## Import configurations
Two actions in Find Action (also linked from the dialog) import existing configurations:
- **Import Run Configurations from .vscode/launch.json**: Python and Node.js launches and attaches, plus compounds. Fixed variables such as `${workspaceFolder}` and `${env:NAME}` are replaced; entries that use `${file}`, `${input:…}` or `${command:…}` are skipped.
- **Import Run Configurations from JetBrains (.run, .idea)**: reads `.run/*.run.xml`, `.idea/runConfigurations/*.xml` and `.idea/workspace.xml`. Python, Django, pytest, npm, Jest, Vitest, Node.js, Shell Script, Application, JAR Application, Spring Boot, Go and compound configurations are converted; other types are skipped.
A notification says how many were imported and why others were left out.
## Where configurations are stored
| File | Contains |
| -------------------------- | ----------------------------------------------------------------------------------------------------------- |
| `.openidea/run.toml` | Configurations with **Store as project file** on, and templates. Commit it to share them. |
| `.openidea/run.local.toml` | Your other configurations and the detected ones you removed. Added to `.openidea/.gitignore` automatically. |
Both files are plain TOML and can be edited by hand:
```toml
[[configurations]]
id = "user:server"
name = "Server"
kind = "shell"
command = "cargo"
args = ["run", "--release"]
cwd = "server"
env = { RUST_LOG = "debug" }
env-files = [".env"]
before-launch = [{ kind = "build" }, { kind = "run", target = "user:migrate" }]
[[templates]]
kind = "npm"
env = { NODE_ENV = "development" }
```
The file is reloaded when it changes on disk.
+63
View File
@@ -0,0 +1,63 @@
---
title: "Scratch files and TODO"
description: "Keep throwaway files outside the project, and collect TODO and FIXME comments in the TODO tool window."
---
## Scratch files
A scratch file is a throwaway file in any language: a place to try a snippet, keep notes or paste a response, without adding anything to the project.
| Action | macOS | Windows / Linux |
| --------------------- | ----- | ----------------------- |
| **New Scratch File…** | `⇧⌘N` | `Ctrl+Alt+Shift+Insert` |
<Steps>
<Step title="Create one">
Choose **File › New Scratch File…** (or **Scratch File** at the top of the Project view's **New** menu).
</Step>
<Step title="Pick a language">
Type to filter the language list and press `↵` / `Enter`.
</Step>
<Step title="Edit">
The file opens in a normal editor tab. It is named `scratch.<ext>`, then `scratch_1.<ext>`, `scratch_2.<ext>` and so on. For C, C++, Go, Java, Kotlin and Rust the file starts with an empty `main` function.
</Step>
</Steps>
Scratch files are saved in a `scratches` folder in OpenIDEA's application data directory, not in the project, so they are shared by all projects and are not seen by Git:
| OS | Folder |
| ------- | ---------------------------------------------------------- |
| macOS | `~/Library/Application Support/ai.l0ng.openidea/scratches` |
| Windows | `%APPDATA%\ai.l0ng.openidea\scratches` |
| Linux | `~/.local/share/ai.l0ng.openidea/scratches` |
They are listed under **Scratches** in the Project view, where you can open, rename and delete them like other files.
Because a scratch file is outside every project, some language servers give it only syntax checks; completion and navigation may be limited. A banner at the top of the editor says so when the server reports it.
## TODO
OpenIDEA collects comments that contain `TODO`, `FIXME`, `HACK` or `XXX` in the **TODO** tool window and highlights them in the editor, each pattern in its own colour.
### The TODO tool window
Open it from **View › Tool Windows › TODO**, or with **Show TODO Items in Current File** in Find Action. Its tabs are:
- **Project**: every TODO item in the project.
- **Current File**: the items in the active editor, updated as you type.
- **Scope Based**: the items in a scope you choose, including custom scopes (**Edit Scopes…**).
- **Changes**: the items in files with uncommitted changes, optionally for one changelist.
Items are grouped by file. Click one to jump to it. The toolbar has a pattern filter (**Filter TODO Items**), **Match Case** and **Refresh**; the options menu turns on a preview and autoscroll to the source. The list updates when files change on disk and when you save; **Refresh TODO Items** rescans the project. At most 10 000 matches are shown.
### Patterns
**Settings › Editor › TODO** controls what counts as a TODO item:
- **Patterns**: add, remove, enable or recolour keywords. **Restore Defaults** brings back `TODO`, `FIXME`, `HACK` and `XXX`.
- **Case sensitive**: on by default, so `todo` does not match `TODO`.
- **Only in comments**: on by default; a match counts only when it follows a comment marker on its line.
### Before committing
The commit dialog's **Check TODO** option warns about `TODO` and `FIXME` comments in the lines you added or changed. See [Version control](/en/guide/vcs).
+104
View File
@@ -0,0 +1,104 @@
---
title: "Search and navigation"
description: "Search Everywhere, Go to File, Class and Symbol, Find and Replace in Files, recent files and locations, bookmarks and Run Anything."
---
## Search Everywhere
| Action | macOS | Windows / Linux |
| -------------------------- | ------ | ------------------ |
| **Search Everywhere** | `⇧⇧` | `Shift Shift` |
| **Go to Class…** | `⌘O` | `Ctrl+N` |
| **Go to File…** | `⇧⌘O` | `Ctrl+Shift+N` |
| **Go to Symbol…** | `⌥⌘O` | `Ctrl+Alt+Shift+N` |
| **Find Action…** | `⇧⌘A` | `Ctrl+Shift+A` |
| **Search Text Everywhere** | `⌥⇧⌘E` | `Ctrl+Alt+Shift+E` |
Press `Shift` twice to open Search Everywhere. All the commands above open the same popup on a different tab: **All**, **Classes**, **Files**, **Symbols**, **Actions** and **Text**. Press `⇥` / `Tab` to move to the next tab.
- **All** shows the best matches of every kind in sections, plus **Endpoints**: HTTP routes of web frameworks, found by request path (`/api/users`, optionally prefixed with a method such as `POST`) or by controller and method name. With an empty query it lists recent files and recent actions.
- Matching is fuzzy: `usrctl` finds `UserController`. For files you can add a line and column, as in `Main.kt:42` or `Main.kt:42:5`, and you can paste an absolute path or a `file://` URL.
- **Classes**, **Files** and **Symbols** search the project by default. Check **Include non-project items** (or press the tab's shortcut again) to include libraries and other files outside the project. When nothing matches in the project, the search widens by itself.
- The scope menu narrows results to **All Places**, **Project Files**, **Project Production Files** or **Project Test Files**.
- **Actions** finds every command by name, including settings pages and toggles; **Include disabled actions** also lists commands that don't apply right now. Running an action from here is the same as using its menu item or shortcut.
- `↵` / `Enter` opens the selected item; `⇧↵` / `Shift+Enter` opens it in a split to the side. Right-click a result to copy its path or name, or to search in its directory.
Classes and symbols come from the IDE's own index (available as soon as a file is indexed) merged with the language servers' workspace symbols.
## Go to line and recent places
| Action | macOS | Windows / Linux |
| -------------------------- | ----------- | ---------------------------------- |
| **Go to Line:Column…** | `⌘L` | `Ctrl+G` |
| **Recent Files** | `⌘E` | `Ctrl+E` |
| **Recent Locations** | `⇧⌘E` | `Ctrl+Shift+E` |
| **Last Edit Location** | `⇧⌘⌫` | `Ctrl+Shift+Backspace` |
| **Switcher** | `⌃⇥` | `Ctrl+Tab` |
| **Back** / **Forward** | `⌘[` / `⌘]` | `Ctrl+Alt+Left` / `Ctrl+Alt+Right` |
| **Jump to Navigation Bar** | `⌘↑` | `Alt+Home` |
| **Select In…** | `⌥F1` | `Alt+F1` |
- **Recent Files** lists recently opened files and the tool windows; type to filter.
- **Recent Locations** shows the places you visited or edited with a few lines of code around each. Press the shortcut again (or check **Show changed only**) to see only edited places.
- **Switcher** works like an application switcher: hold `⌃` / `Ctrl`, press `⇥` / `Tab` to move through open files and tool windows, and release to switch.
- **Select In…** reveals the current file in the Project view or another tool window.
## Find and replace in a file
| Action | macOS | Windows / Linux |
| ------------------------------- | ----- | --------------- |
| **Find** | `⌘F` | `Ctrl+F` |
| **Replace** | `⌘R` | `Ctrl+R` |
| **Find Next Occurrence** | `⌘G` | `F3` |
| **Find Previous Occurrence** | `⇧⌘G` | `Shift+F3` |
| **Open Results in Find Window** | `⌥F7` | `Alt+F7` |
The find bar at the top of the editor has match case, whole words and regex options. Matches are highlighted as you type. **Add Selection for Next Occurrence** (`⌃G` / `Alt+J`) and **Select All Occurrences** (`⌃⌘G` / `Ctrl+Alt+Shift+J`) turn matches into carets for multi-caret editing.
## Find and replace in files
| Action | macOS | Windows / Linux |
| --------------------- | ----- | --------------- |
| **Find in Files…** | `⇧⌘F` | `Ctrl+Shift+F` |
| **Replace in Files…** | `⇧⌘R` | `Ctrl+Shift+R` |
| **Find** tool window | `⌘3` | `Alt+3` |
The **Find in Files** popup searches as you type and previews the selected match below the list.
- **Match Case** (`⌥C` / `Alt+C`), **Words** (`⌥W` / `Alt+W`) and **Regex** (`⌥X` / `Alt+X`) control matching. When the text looks like a regular expression, the popup offers **Search with Regex**.
- **File mask** (`⌥K` / `Alt+K`) limits the search to file names such as `*.ts, *.tsx`.
- The search context menu limits matches to **In Comments**, **In String Literals**, or everything **Except Comments and String Literals** (and the other combinations).
- The scope is **In Project**, **Module**, **Directory** or **Scope**. Scope offers **Project Files**, **Project Production Files**, **Project Test Files**, **Open Files**, **Recently Viewed Files** and your custom scopes (**Edit Scopes…**).
- `⌥↓` / `Alt+Down` in the search field shows recent searches. **Pin Window** keeps the popup open while you work in the editor.
- **Open in Find Window** moves the results to the **Find** tool window, grouped by file (optionally by directory), where you can rerun the search later.
For replacing, type the replacement in the second field. **Preserve Case** keeps the case pattern of each match. **Replace** replaces the selected match; **Replace All** replaces every match and asks first when they span more than one file. Excluded results are skipped.
The search uses ripgrep's engine. It skips folders that `.gitignore` excludes, `.git` and binary files, and it searches the unsaved text of open editors rather than the version on disk.
## Bookmarks
| Action | macOS | Windows / Linux |
| --------------------------------- | ----------- | ----------------------------- |
| **Toggle Bookmark** | `F3` | `F11` |
| **Toggle Bookmark with Mnemonic** | `⌥F3` | `Ctrl+F11` |
| **Show Bookmarks** | `⌘F3` | `Shift+F11` |
| **Bookmarks** tool window | `⌘2` | `Alt+2` |
| Toggle numbered bookmark 0–9 | `⌃⇧0`–`⌃⇧9` | `Ctrl+Shift+0`–`Ctrl+Shift+9` |
| Go to numbered bookmark 0–9 | `⌃0`–`⌃9` | `Ctrl+0`–`Ctrl+9` |
A bookmark marks a line, a file or a folder (from the Project view or an editor tab context menu: **Add Bookmark**). A line bookmark can have a mnemonic, a digit or a letter, shown in the gutter. **Show Bookmarks** opens a popup with a preview; the **Bookmarks** tool window organizes bookmarks into lists and lets you add descriptions. **Edit › Bookmarks** has **Next Bookmark** and **Previous Bookmark** too.
## Run Anything
| Action | macOS | Windows / Linux |
| ----------------- | ----- | --------------- |
| **Run Anything…** | `⌃⌃` | `Ctrl Ctrl` |
Press `Ctrl` twice to open Run Anything, a command line for the project:
- Type a shell command and press `↵` / `Enter` to run it in the project directory. Its output appears in the **Run** tool window.
- Matching run configurations and build tool goals (for example Maven and Gradle tasks) are listed as you type; `⇥` / `Tab` completes.
- With an empty line it shows your recent commands; **Clear History** empties the list.
Run Anything is also in **Run › Run Anything…**. See [Run configurations](/en/guide/run) for the configurations it lists.
+164
View File
@@ -0,0 +1,164 @@
---
title: "Docker and services"
description: "Manage run configurations, Docker containers, images and Compose projects in the Services tool window, and deploy files over SFTP."
---
The **Services** tool window (`⌘8` / `Alt+8`) gathers things that run: run configurations, Docker connections and registries. The HTTP client and database results also show up there as extra tabs once you use them (see [HTTP Client](/en/guide/http-client) and [Databases](/en/guide/database)).
## The Services tool window
The left side is a tree; the right side shows details of the selected item (a console, a log, a dashboard). The toolbar has **+** (**Add Service**, `⌘N` / `Alt+Insert` while the tree has focus), **Expand All**, **Collapse All**, **View Options**, and the actions of the selection. Select several rows to run an action on all of them; only actions they have in common are offered.
**Add Service** (the **+** button, or right-click empty space) offers:
- **Run Configuration Type**: adds a group for that configuration type.
- **Docker Registry…**
- **Docker Connection…**
### Run configurations
Spring Boot configurations appear by default. Other types appear when you add them with **Add Service › Run Configuration Type**, or once a configuration of that type has run. Each type is a group.
- Group actions: **Run All**, **Restart All**, **Stop All**, **Open in Browser**.
- Configuration actions: **Run** (or **Rerun**), **Debug**, **Stop**, **Open in Browser**, **Update Application** (for configurations that support it).
- Context menu: **Show in Run Tool Window**, **Show in Debug Tool Window**, **Edit Configuration…**, **Remove from Services**.
A configuration that ran shows its console in the detail pane. Reorder configurations within a group by dragging them or with **Move Up** / **Move Down**; the order is saved per project.
## Docker
OpenIDEA drives Docker through the `docker` CLI, so anything that provides a working `docker` command works: Docker Desktop, OrbStack, Colima, Rancher Desktop or a plain Docker Engine. It looks for `docker` in `OPENIDEA_DOCKER`, on `PATH`, and in the usual install folders of those tools.
### Connections
With nothing configured, Services shows one connection named **Docker** that uses the Docker CLI default (`DOCKER_HOST` or the current context). To add another, choose **Add Service › Docker Connection…**:
| Field | Options |
| ---------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Name:** | Shown in the tree. |
| **Connect to Docker daemon with:** | **Docker CLI default (DOCKER_HOST / current context)**, **Unix socket** (a socket path or an Engine API URL such as `npipe://`), **TCP / SSH host** (`tcp://host:2376` or `ssh://user@host`, with an optional TLS certificates folder holding `ca.pem`, `cert.pem` and `key.pem`), or **Docker context** (a context name such as `desktop-linux`, `orbstack` or `colima`). |
Right-click a connection for **Edit Connection…** and **Remove Connection**. Connections are app-wide, not per project. The tree updates live from `docker events`; when the daemon is not running, Services retries every 10 seconds.
### Registries
**Add Service › Docker Registry…** saves credentials for a registry (**Address:** such as `docker.io`, `ghcr.io` or `registry.example.com:5000`, **Username:**, **Password:**). Use **Test Connection** in the dialog or **Test Login** on the registry node. When you pull an image from that registry, OpenIDEA logs in with these credentials in a temporary Docker config, so your own `~/.docker/config.json` is not changed.
### What the tree shows
Under each connection:
- Compose projects first (`Docker-compose: <project>`, with how many services are running), then **Dev Containers**, **Containers**, **Images**, **Networks** and **Volumes**.
- **View Options** hides or shows **Stopped Containers** and **Untagged Images** (both shown by default).
| Item | Context menu |
| --------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Container | **Start**, **Stop**, **Restart**, **Pause** / **Unpause**, **Dashboard**, **Show Log**, **Open Log in Editor**, **Show Processes**, **Inspect**, **Create Terminal**, **Copy Container ID**, **Copy Image Name**, **Delete…** |
| Compose project | **Start**, **Stop**, **Restart**, **Compose Down** |
| Image | **Dashboard**, **Show Layers**, **Inspect**, **Pull Again**, **Copy Image ID**, **Delete…** |
| Volume, network | **Dashboard**, **Inspect**, **Delete…** |
| Connection | **Refresh**, **Pull Image…**, **View Options**, **Prune** |
**Create Terminal** opens a [terminal](/en/guide/terminal) tab inside a running container (`bash` if present, otherwise `sh`).
**Prune** removes, after confirmation, **Stopped Containers**, **Dangling Images**, **Unused Images**, **Unused Volumes**, **Unused Networks** or **Everything Unused (System Prune)**. Each group node also has its own prune entry.
Long operations (pull, prune, Compose) show progress at the top with a **Cancel** button. One Docker operation runs at a time.
### Details
Selecting an item shows tabs in the detail pane:
- **Container**: **Dashboard** (overview, live resource usage, port bindings that open in the browser, mounts, networks, environment variables, labels), **Log** (last 1,000 lines, following while the container runs, with **Timestamps**), **Processes** (`docker top`) and **Inspect** (the raw JSON).
- **Image**: **Dashboard** (tags, containers using it, environment, labels), **Layers** and **Inspect**.
- **Volume** and **network**: **Dashboard** and **Inspect**.
**Open Log in Editor** opens the log in an editor tab with **Timestamps**, **Soft-wrap**, **Find in Logs** and **Clear**.
## Dockerfiles and Compose files
OpenIDEA recognizes `Dockerfile`, `Containerfile`, `Dockerfile.<name>`, `*.dockerfile` and `*.containerfile`, and Compose files (YAML whose name contains `compose`, or whose content looks like a Compose file). For a file detected by content, a banner lets you choose **Not a Compose file** or **Keep as Docker Compose**.
The gutter shows run icons:
- On a Dockerfile `FROM` line: **Run '…'** (build, then run) and **Build Image '…'** for the final stage, or **Build Stage '…'** for a named intermediate stage.
- In a Compose file: **Compose Up (All Services)** on `services:`, and **Compose Up '…'** on each service.
- **Modify Run Configuration…** opens the generated configuration so you can save and change it.
The image tag is derived from the folder name (`My App` becomes `my-app:latest`). Gutter runs use the first Docker connection. The **Compose Up** action does the same from the caret.
## Docker run configurations
**Run › Edit Configurations…** has a **Docker** group with three types. Each has a **Server** field that picks the Docker connection. Output goes to the Run tool window.
<AccordionGroup>
<Accordion title="Dockerfile">
Builds an image and, with **Run built image** checked, runs a container from it.
**Dockerfile** (relative to the project root), **Context folder**, **Image tag**, **Build stage**, **Build args** (`KEY=value; OTHER=value`), **Build options** (for example `--no-cache --platform linux/amd64`), **Run built image**, **Command**, plus the container fields below.
</Accordion>
<Accordion title="Docker Image">
Runs a container from an image: **Image ID or name**, **Pull the image before running**, **Command**, plus the container fields below.
</Accordion>
<Accordion title="Docker Compose">
Starts Compose services: **Compose files** (separated by `;`), **Services** (empty for all), **Project name**, **Run in the background (-d)**, **Build images before starting (--build)**, **Up options**. Stopping an attached run runs `docker compose down`.
</Accordion>
</AccordionGroup>
Container fields: **Container name**, **Bind ports** (`8080:80, 5005:5005`), **Bind mounts** (`./data:/data; cache:/root/.cache`), **Container environment**, **Run options** (for example `--rm --network host`). Before each run, a previous container with the same name is removed; **Stop** runs `docker stop`.
## Deployment (SFTP)
Deployment copies project files to a server, or to a local or mounted folder. It is set up under **Settings › Build, Execution, Deployment › Deployment**, and its actions are under **Tools › Deployment** and the **Deployment** submenu of the Project view, editor and editor tab context menus.
### Configure a server
<Steps>
<Step title="Create an SSH configuration">
SFTP servers connect through an SSH configuration from **Settings › Tools › SSH Configurations** (host, port, user,
and password, key pair, or OpenSSH config and agent). See [SSH](/en/guide/remote/ssh).
</Step>
<Step title="Add the server">
In **Deployment**, click **Add**. On the **Connection** tab, set **Name**, **Type** (**SFTP** or **Local or mounted
folder**), **SSH configuration**, **Root path** and optionally **Web server URL**. Click **Test Connection**.
</Step>
<Step title="Map paths">
On **Mappings**, pair a **Local path** (relative to the project root; empty means the whole project) with a
**Deployment path** (relative to the root path) and an optional **Web path**. On **Excluded Paths**, list remote
paths that are never touched.
</Step>
<Step title="Set a default">
Select the server and click **Use as Default**. The "to default server" actions and automatic upload use it.
</Step>
</Steps>
The **Options** page under Deployment has:
| Option | Default |
| ------------------------------------------------------------ | ----------------------------------------------------------- |
| **Exclude items by name** | `.git;.svn;.hg;.idea;.openidea;.DS_Store;*.pyc;__pycache__` |
| **Upload changed files automatically to the default server** | **Never** (or **On explicit save action**, **Always**) |
| **Delete remote files when local are deleted** | off |
| **Preserve files timestamps** | on |
Automatic upload runs only in trusted projects. Failed automatic transfers are reported; successful ones are silent.
### Transfer files
The actions work on the selection in the Project view, or on the file in the active editor:
- **Upload to Default Server**, **Upload to…**, **Download from Default Server**, **Download from…**
- **Sync with Deployed to…**: opens a comparison window listing files that are only local, only remote, newer on one side, different or identical. Compare by **Size and Timestamp** or **Binary Content**, pick an operation per file (upload, download, delete, skip; `Space` cycles it), preview and edit the diff, then **Synchronize Selected** or **Synchronize All**.
- **Delete from Server**
- **Automatic Upload**: turns automatic upload on (Always) or off.
- **Open in Browser (Deployed)**: opens the file's URL on the web server.
- **Browse Remote Host**: opens the **Remote Host** tool window, a tree of the server's files with **Open** (edit a copy; saving uploads it back), **Download to Project**, **New Folder…**, **Rename…**, **Delete…**, **Refresh** and **Copy Path**.
Unknown host keys are confirmed before connecting, and OpenIDEA asks for a password or passphrase when the SSH configuration doesn't save one.
## Not supported
- Kubernetes, and Podman-specific features.
- Creating volumes or networks, browsing files inside containers, pushing images.
- FTP, FTPS and WebDAV deployment servers (only SFTP and local folders).
+54
View File
@@ -0,0 +1,54 @@
---
title: "Settings"
description: "Open and search the Settings window, choose between global and project values, and export, import or carry settings to new projects."
---
## Open Settings
- macOS: **OpenIDEA › Settings…** or `⌘,`
- Windows and Linux: **File › Settings…** or `Ctrl+Alt+S`
Settings open in their own window. The tree on the left groups the pages as in IntelliJ IDEA: **Appearance & Behavior**, **Keymap**, **Editor**, **Plugins**, **Version Control**, **Build, Execution, Deployment**, **Languages & Frameworks**, **Tools** and **Security**. Installed plugins can add pages of their own.
## Find a setting
Type in the search field above the tree. The tree keeps only the pages that match, and matching option labels are highlighted on the page. Search works in English and in the current display language.
You don't need the Settings window open to search: **Search Everywhere** (`⇧⇧` / `Shift Shift`) and **Find Action** (`⇧⌘A` / `Ctrl+Shift+A`) also list settings, and choosing one opens its page.
## Apply changes
Changes are collected until you click **Apply** or **OK**. **Cancel** discards them. Most changes take effect right away in every open window.
## Global and project settings
Settings have two levels:
- **Global** values apply to every project.
- **Project** values apply to one project and override the global value.
Pages that can hold project values show a **Global** / **This project** switch at the top. With **This project** selected, your changes are saved to `.openidea/settings.toml` in the project and override the global value. Commit that file if your team should share the values.
Some settings are global only, such as the MCP server and HTTP proxy. Project files can never mark a project as trusted; trust decisions stay in your user data (see [Quickstart](/en/guide/quickstart#trust-the-project)).
## Settings for new projects
**File › New Projects Setup › Settings for New Projects…** opens Settings on the global level only, starting at the Java page. New projects inherit these values. Use it for defaults such as the JDK, Maven or language servers that every project should start with.
## Export and import
Under **File › Manage IDE Settings**:
- **Export Settings…** writes your global settings to a JSON file (`openidea-settings.json` by default), together with live templates, file templates, postfix templates, macros, tool window layouts and a few view options.
- **Import Settings…** replaces your global settings with those in an exported file. Restart when asked to apply the imported templates, macros, layouts and view options. Choosing a JetBrains `settings.zip` here imports from a JetBrains IDE instead.
- **Import Settings from JetBrains IDE…** imports from a JetBrains IDE installed on this computer. See [Migrate](/en/guide/migrate#import-settings).
## Settings files
Settings are stored as TOML: the global values in `settings.toml` in the configuration directory, the project values in `<project>/.openidea/settings.toml`. You can edit them by hand. See the [settings reference](/en/reference/settings) for the file format and keys, and [Data directories](/en/reference/data-directories) for where the global file lives.
## Related pages
- [Keymaps](/en/guide/keymap)
- [Themes and fonts](/en/guide/themes)
- [Display language](/en/guide/locale)
+94
View File
@@ -0,0 +1,94 @@
---
title: "Live templates and macros"
description: "Expand abbreviations, surround code, use postfix completion and Emmet, write your own templates, and record editor macros."
---
## Live templates
A live template is an abbreviation that expands into a code snippet with fields to fill in. Type the abbreviation and press `⇥` / `Tab`, or pick it from the completion list. For example, in Java `sout` becomes `System.out.println();` with the caret inside the parentheses; in Rust `fn` expands into a function skeleton.
| Action | macOS | Windows / Linux |
| -------------------------------- | ----- | --------------- |
| **Insert Live Template…** | `⌘J` | `Ctrl+J` |
| **Surround With…** | `⌥⌘T` | `Ctrl+Alt+T` |
| **Surround with Live Template…** | `⌥⌘J` | `Ctrl+Alt+J` |
After expansion, the caret is in the first field. `⇥` / `Tab` moves to the next field and fields that share a name change together. `Esc` leaves the template.
Built-in templates exist for Rust, JavaScript / TypeScript, Python, Go, Java, Kotlin, PHP and SQL, plus a few general ones (`todo`, `fixme`, `hack`, `date`). **Insert Live Template…** lists the templates that apply at the caret. By default templates expand only in code, not in comments or strings.
### Surround With
Select code and press `⌥⌘T` / `Ctrl+Alt+T`. The list starts with the language's wrappers (`if`, `while`, `try` and so on), followed by live templates that use `$SELECTION$`. **Surround with Live Template…** shows only the latter.
## Postfix completion
Postfix templates rewrite the expression before a dot. Type an expression, a dot and the template key, then press `⇥` / `Tab`:
```java
user.name.nn → if (user.name != null) { … }
items.for → for (var item : items) { … }
```
Postfix templates are available for JavaScript / TypeScript, Rust, Python, Go, Java and Kotlin. Common keys are `if`, `else`, `not`, `var`, `return`, `for`, `fori`, `nn` (not null), `null`, `par` (wrap in parentheses) and `try`; each language adds its own, such as `match`, `ok` and `some` in Rust, `sout` in Java and Kotlin, `when` in Kotlin, `await` in JavaScript / TypeScript and Python.
Enable or disable postfix completion, individual templates, `Tab` expansion and whether postfix templates appear in the completion list in **Settings › Editor › General › Postfix Completion**.
## Emmet
In HTML, Vue and Svelte files, and in CSS, SCSS, Less and Sass, `⇥` / `Tab` expands Emmet abbreviations, for example `ul>li*3` or `m10`. In markup files, the editor context menu also has **Update Tag with Emmet**, which rewrites the tag at the caret from an abbreviation.
## Write your own templates
Templates are edited in **Settings › Editor › Live Templates**.
<Steps>
<Step title="Add a template">
Click **Add Template** and enter the **Abbreviation**, **Description** and **Group**.
</Step>
<Step title="Write the body">
Use `$NAME$` for a field (repeated names are linked), `$END$` for the final caret position, `$SELECTION$` for the
text being surrounded and `$$` for a literal dollar sign. Leading tabs in the body are indent units.
</Step>
<Step title="Choose contexts">
Pick the languages where the template applies. Check **Also expand in comments and strings** if it should expand
there too.
</Step>
<Step title="Give fields values (optional)">
In the variables table, set an **Expression**, a **Default value**, and **Skip if defined** to jump over a field
whose expression produced a value.
</Step>
</Steps>
Expressions can be string literals, references to other variables or functions: `className()`, `methodName()`, `fileName()`, `fileNameWithoutExtension()`, `date("yyyy-MM-dd")`, `time()`, `user()`, `clipboard()`, `complete()`, `suggestVariableName()`, `commentStart()`, `capitalize(…)`, `camelCase(…)` and `snakeCase(…)`.
For example, this template logs a variable:
```text
Abbreviation: logv
Body: console.log("$EXPR$ =", $EXPR$);$END$
```
The page also has **Expand abbreviations with Tab** and **Show live templates in code completion**. A built-in template you changed can be reset with **Restore Default**. Templates contributed by plugins are shown read-only.
Live templates, postfix settings and macros are stored per user on this machine, not in the project.
For new-file templates (what **File › New…** creates), see **Settings › Editor › File and Code Templates**.
## Macros
A macro records the actions you run, the text you type and the editing keys you press in the editor, so you can replay them. The commands are in **Edit › Macros**:
<Steps>
<Step title="Record">Choose **Start Macro Recording**. The status bar shows that a macro is recording.</Step>
<Step title="Stop">
Choose **Stop Macro Recording** and enter a name, or leave the name empty to keep it only as the last macro.
</Step>
<Step title="Play back">
**Play Back Last Macro** replays the last recording. **Play Saved Macros…** picks a saved one.
</Step>
</Steps>
**Edit Macros…** renames or deletes saved macros. Every saved macro is also an action with the macro's name, so you can find it in Find Action and give it a shortcut in **Settings › Keymap**. None of the macro commands has a default shortcut.
Playback stops with a notification when a recorded step cannot run in the current context. Actions run with an argument that cannot be stored (such as a menu item that acts on a selected tree node) are not recorded.
+106
View File
@@ -0,0 +1,106 @@
---
title: "Terminal"
description: "Use the built-in terminal: tabs, splits, shells, links, find, and terminal settings."
---
The **Terminal** tool window runs your shell in the IDE. Each tab is a real pseudo-terminal session, rendered with xterm.js. In a remote project the shell runs on the remote machine.
## Open the terminal
| Action | macOS | Windows/Linux |
| ------------------------------------- | ------ | -------------- |
| Show or hide the Terminal tool window | `⌥F12` | `Alt+F12` |
| New Terminal Tab (terminal focused) | `⌘T` | `Ctrl+Shift+T` |
Showing an empty Terminal window opens a tab in the start directory (the project root by default). To open a terminal in a specific folder, right-click it in the Project tool window and choose **Open In › Terminal**, or run **Open in Terminal**.
## Tabs and splits
- **+** (**New Tab**) opens a tab with the default shell.
- The **▾** button (**More Terminal Options**) has **New Terminal Tab**, **New Predefined Session** (one entry per installed shell, plus **SSH Session…**), **Split Right**, **Split Down** and **Settings**.
- Right-click a tab for **Rename Session…**, **Close Tab**, **Close Other Tabs**, **Split Right**, **Split and Move Right**, **Split Down**, **Split and Move Down** and **Unsplit**. Double-clicking a tab also renames it.
- Drag tabs to reorder them, or drop a tab on the edge of another group to split.
A tab is named after your custom name, otherwise after the program running in it (when the shell reports it), otherwise `Local`, `Local (2)` and so on. A spinner shows while a command runs; **Exited** or **Failed** shows once the session ends.
When a session's process exits, the terminal prints the exit code; press `Enter` to restart it. With **Close session when it ends** on (the default), the tab closes instead. Closing a tab that still runs a program asks you to confirm.
### Across restarts
OpenIDEA remembers each project's terminal tabs: their directory, custom names, the shell used and the split layout (up to 32 tabs). After a restart each tab starts a **new** session in its directory. Scrollback and running commands are not restored.
## Keys in the terminal
| Action | macOS | Windows/Linux |
| ------------------- | ----------- | -------------------------------------------------- |
| Copy selection | `⌘C` | `Ctrl+Shift+C`, or `Ctrl+C` with a selection |
| Paste | `⌘V` | `Ctrl+Shift+V` |
| Find in terminal | `⌘F` | **Find in Terminal** action (no default key) |
| Clear buffer | `⌘K` | **Clear Terminal Buffer** action (no default key) |
| Close session | `⌘W` | **Close Terminal Session** action (no default key) |
| Start / end of line | `⌘←` / `⌘→` | shell default |
| Word left / right | `⌥←` / `⌥→` | shell default |
| Delete word / line | `⌥⌫` / `⌘⌫` | shell default |
Other `Ctrl` combinations go to the shell. `Shift+Enter` sends `Esc` + `Return`, which TUIs such as Claude Code treat as a newline. `Esc` clears a selection first, then goes to the shell.
With **Override IDE shortcuts** on (the default), the terminal keeps most keys for the shell. A short list of IDE shortcuts still works: tool window keys, Find Action, Go to File, Class and Symbol, Settings, Push, Recent Files, the switcher and the terminal actions. Use **Configure terminal keybindings** on the settings page to review them.
The pane's context menu has **Copy**, **Paste**, **Select All**, **Clear Buffer**, **Split Right**, **Split Down** and **Close Session** (plus **Restart Session** after the process exits).
## Find
`⌘F` (or **Find in Terminal**) opens the find bar over the scrollback (10,000 lines). Toggle **Match Case** (`Alt+C`), **Words** (`Alt+W`) and **Regex** (`Alt+X`); `Enter` and `Shift+Enter` move between matches, `Esc` closes the bar.
## Links, paths and drops
- `http`, `https` and `file` URLs open in your browser. OSC 8 hyperlinks from programs work too.
- File locations such as `src/main.rs:42:7` or `Main.java(42,7)` open in the editor at that line. Paths resolve against the shell's current directory, then by file name across the project.
- Pasting copied files pastes their paths, quoted for the shell in use (bash/zsh/fish, PowerShell or cmd). Pasting an image from the clipboard saves it to a file and pastes that file's path.
- Dropping files from your file manager onto the terminal types their quoted paths at the prompt. They are not run.
Turn off **Highlight hyperlinks** in the settings to stop detecting links.
## Shell and environment
The default shell is:
- **macOS/Linux**: your login shell (from the user database, then `$SHELL`), falling back to `/bin/zsh`, `/bin/bash`, `/bin/sh`. zsh, bash, sh, fish, ksh, dash, tcsh and csh start as login shells.
- **Windows**: `pwsh.exe`, then `powershell.exe`, then `%COMSPEC%`, then `cmd.exe`.
**New Predefined Session** also offers the other shells it finds: zsh, bash, fish, nu and pwsh on macOS/Linux; PowerShell, cmd, WSL and Git Bash on Windows.
Every session gets `TERM=xterm-256color`, `COLORTERM=truecolor`, `TERM_PROGRAM=OpenIDEA` and `TERM_PROGRAM_VERSION`, plus `LANG=en_US.UTF-8` if `LANG` is unset. Your own variables from the settings are added on top.
With **Shell integration** on, zsh, bash and fish report the current directory and command start/end to the IDE. That keeps links and new splits in the right folder and lets the tab title follow the running program.
## Settings
**Settings › Tools › Terminal**:
| Setting | Default | Notes |
| ---------------------------------------- | ------------ | --------------------------------------------------------------------------------------------------------------------- |
| **Start directory** | Project root | Relative paths resolve against the project root. **Open in Terminal** still opens the chosen folder. Project setting. |
| **Environment variables** | none | Added to new sessions. Project setting. |
| **Shell path** | login shell | A command line, e.g. `/bin/zsh --login`. **Installed Shells** lists what was found. |
| **Default Tab name** | `Local` | |
| **Audible bell** | on | |
| **Close session when it ends** | on | |
| **Mouse reporting** | on | Off: programs can't capture the mouse. |
| **Copy to clipboard on selection** | off | |
| **Paste on middle mouse button click** | on | |
| **Move focus to the Editor with Escape** | off | Otherwise `Esc` goes to the shell. |
| **Override IDE shortcuts** | on | See above. |
| **Shell integration** | on | |
| **Highlight hyperlinks** | on | |
| **Use Option as Meta key** | off | macOS only. |
| **Cursor shape** | Block | Block, Underline or Vertical. |
| **Blinking cursor** | on | |
| **Use editor font** | on | Off: set **Font**, **Size** (6–72), **Line height**, **Fallback font** and **Column width**. |
Changes apply to open sessions right away. Colours follow the IDE theme.
## Related
- **Tools › Start SSH Session…** opens an SSH connection from a saved SSH configuration in a terminal tab. See [SSH](/en/guide/remote/ssh).
- Docker containers can open a shell in a terminal tab with **Create Terminal**. See [Docker and services](/en/guide/services).
+77
View File
@@ -0,0 +1,77 @@
---
title: "Testing"
description: "Run tests from the gutter, read results in the test tree, rerun failed tests and jump between code and tests."
---
OpenIDEA runs tests with the project's own test tool (Cargo, Jest, pytest, Maven, Gradle, `go test`, `dotnet test`…) and reads its output into a test tree. There is nothing to install beyond what the project already uses.
## Run tests
<Steps>
<Step title="From the gutter">
Click the ▶ icon next to a test, a test class or a test module and choose **Run '…'** or **Debug '…'**. After a run,
the icon turns green or red to show that test's last result.
</Step>
<Step title="From the caret">
**Run › Run Context Configuration** (`⌃⇧R` / `Ctrl+Shift+F10`) runs the test at the caret, or all tests in the
current file.
</Step>
<Step title="A whole directory">
Right-click a test directory in the Project view and choose **Run '…'** to run the tests inside it.
</Step>
<Step title="Everything">
Run a detected test configuration such as `cargo test`, `go test ./...`, `pytest` or the `test` script of
`package.json` from the run widget.
</Step>
</Steps>
Running a single test creates a temporary run configuration based on the project's detected test configuration (for example the `test` script for Jest, or the Maven / Gradle build for JUnit). See [Run configurations](/en/guide/run) to save it or change its options.
## Supported test frameworks
| Language | Gutter icons on | Runs with |
| --------------------------- | --------------------------------------------------------------------------------------------- | -------------------------------------------------------------- |
| Rust | `#[test]` functions, `mod tests` | `cargo test`, `cargo nextest` |
| JavaScript / TypeScript | `test`, `it`, `describe`, `suite`, `context` (also `.each`, `.only`, `.skip`) | Jest or Vitest through the project's test script; Deno and Bun |
| Python | pytest `def test_*` and `class Test*`, unittest `TestCase` subclasses | pytest, Django `manage.py test` |
| Go | `func TestX(t *testing.T)`, benchmarks | `go test` |
| Java, Kotlin, Scala, Groovy | JUnit `@Test`, `@ParameterizedTest`, `@RepeatedTest`, `@TestFactory`, `@TestTemplate`; TestNG | Maven Surefire (`-Dtest=`) or Gradle (`--tests`) |
| PHP | PHPUnit test classes and methods, Pest `test` / `it` / `describe` | PHPUnit, Pest |
| Ruby | Minitest and Test::Unit classes, modules that hold them, `def test_*` | Minitest through `ruby` or `rake test` |
| C# | xUnit `[Fact]` / `[Theory]`, NUnit `[Test]` / `[TestCase]`, MSTest `[TestMethod]` | `dotnet test` (VSTest or Microsoft.Testing.Platform) |
RSpec has its own configuration type (**RSpec**) and a detected `rspec` configuration, but no gutter icons. Plugins can add test frameworks and report their results to the same test tree.
<Note>
For .NET projects that use Microsoft.Testing.Platform, run **Install TRX reporting** from the .NET tool window so
results show in the test tree. See [Build tools](/en/guide/build-tools).
</Note>
## The test results view
A test run opens a tab in the **Run** tool window with the test tree next to the console. Failed suites expand automatically.
- Click a test to see its own output in the console.
- Double-click, `Enter` or `F4` jumps to the test's source (**Jump to Source**).
- **Show Passed** and **Show Ignored** filter the tree. Passed tests are hidden by default; the filters are remembered.
- Right-click a test or a selection for **Run '…'**, **Debug '…'** and **Jump to Source**.
- For an assertion failure with expected and actual values, **Show Diff** opens them side by side.
Detected test configurations show results in the tree automatically. For your own configuration, turn on **Modify options › Show results in the test runner**.
## Rerun tests
| Action | macOS | Windows / Linux |
| ---------------------------------- | ----- | --------------- |
| **Rerun** (in the Run tool window) | `⌘R` | `Ctrl+F5` |
| **Rerun Failed Tests** | — | — |
**Rerun Failed Tests** (in the Run tool window toolbar and Find Action) runs only the tests that failed in the last run, using each tool's filter syntax. It works for Cargo, Jest / Vitest, pytest, Django, PHPUnit, Go, Maven / Gradle and .NET runs, and for plugin test frameworks.
## Navigate between code and tests
**Navigate › Test** (`⌘⇧T` / `Ctrl+Shift+T`) jumps from a class to its tests and back, using common naming conventions (`FooTest`, `test_foo.py`, `foo.test.ts`…) and the project's source and test roots. With several matches it shows a chooser. With none, **Create New Test…** creates one from the file templates.
## Coverage
To see which lines the tests execute, run them with coverage. See [Coverage](/en/guide/coverage).
+78
View File
@@ -0,0 +1,78 @@
---
title: "Themes and fonts"
description: "Choose a UI theme and editor color scheme, import IntelliJ themes and color schemes, and set the editor, UI and terminal fonts."
---
## Choose a theme
Open **Settings › Appearance & Behavior › Appearance** and pick a **Theme**. Built-in themes:
| Light | Dark |
| ----------------------- | -------------- |
| Islands Light (default) | Islands Dark |
| Solarized Light | Solarized Dark |
| One Light | One Dark |
| | High Contrast |
Themes from installed plugins appear in the same list, under the plugin's name.
- **Sync with OS** follows the system's light or dark mode. While it is on, the **Theme** list is disabled; pick the theme for each mode with **Preferred theme for light OS mode** and **Preferred theme for dark OS mode** in the options next to it.
- **View › Appearance › Toggle Light/Dark Theme** switches between Islands Light and Islands Dark.
- **View › Quick Switch Scheme…** (`` ⌃` `` / `` Ctrl+` ``) › **Theme** previews themes as you move through the list.
- The Welcome screen's **Customize** page has the same theme and accent choices.
The same page also has:
- **Accent colour**: the UI's accent colour.
- **Different tool window background**: set tool windows apart from the editor.
- **Accessibility**: **Use contrast scrollbars** and **Adjust colors for red-green vision deficiency** (for protanopia and deuteranopia).
- **Compact mode** under **UI Options**.
## Editor color scheme
The editor's syntax colours come from a color scheme. In **Settings › Appearance & Behavior › Appearance**, **Editor color scheme** is **Theme default** unless you change it: the scheme of the current theme. Choose another built-in scheme or one you imported to keep the same editor colours under any theme.
**Settings › Editor › Color Scheme** edits schemes:
- Select an element in the tree and change its foreground, background, bold, italic and effects. Changes are stored per scheme; **Restore Defaults** drops them.
- **Language Defaults** has **Semantic highlighting** (local variables and parameters get a colour derived from their name) and **Rainbow brackets** (brackets coloured by depth).
- **Import Scheme** reads an IntelliJ color scheme (`.icls`) exported from a JetBrains IDE. If a scheme with the same name exists, choose **Overwrite** or **Keep Both**. **Export Scheme** writes the selected scheme as `.icls`.
## IntelliJ themes
IntelliJ UI themes can be converted into OpenIDEA themes. With a project open, go to **Settings › Plugins**, open the gear menu and choose:
- **Import Theme from JetBrains IDE…** to pick a theme plugin installed in your JetBrains IDEs, or
- **Import IntelliJ Theme from File…** for a theme plugin `.jar`, a Marketplace `.zip`, a plugin folder or a `.theme.json` file.
OpenIDEA converts the theme (and the editor scheme it bundles, if any) into a theme plugin and installs it. The theme in use doesn't change; select the new one under **Theme**.
## Theme and icon plugins
**Settings › Plugins** also offers theme and file icon theme plugins from the marketplace. Once a file icon theme is installed, **File icon theme** appears on the Appearance page. Icon themes with options get a settings page of their own.
To create a theme or icon theme, see [Themes](/en/plugins/guides/themes) and [Icon themes](/en/plugins/guides/icon-themes) in the plugin guide.
## Fonts
### Editor font
**Settings › Editor › Font**:
- **Font**: the editor font family. **Show only monospaced fonts** narrows the list. The default is JetBrains Mono, bundled with OpenIDEA.
- **Size** and **Line height**.
- **Enable ligatures** for fonts with programming ligatures.
- **Typography Settings**: **Main font weight**, **Bold font weight**, and a **Fallback font** for characters the main font lacks.
To change the size temporarily, use **Increase Font Size** (`⌥⇧.` / `Alt+Shift+.`) and **Decrease Font Size** (`⌥⇧,` / `Alt+Shift+,`); **View › Reset Font Size in All Editors** goes back. On macOS, **Change font size with Command+Mouse Wheel** in **Settings › Editor › General** enables wheel zoom.
### UI font and zoom
On the Appearance page under **Accessibility**:
- **Use custom font:** sets the **UI font**, a **UI fallback font** and the size for menus, tool windows and dialogs. The default UI font is Inter.
- **Zoom** scales the whole IDE. **View › Appearance › Zoom In IDE**, **Zoom Out IDE** and **Reset IDE Zoom** do the same from the menu (`⌃⌥=`, `⌃⌥-`, `⌃⌥0` on macOS; `Alt+Shift+=`, `Alt+Shift+-`, `Alt+Shift+0` on Windows and Linux).
### Terminal font
The terminal uses the editor font by default. To give it its own font, turn off **Use editor font** in **Settings › Tools › Terminal**.
+249
View File
@@ -0,0 +1,249 @@
---
title: "Version control"
description: "Commit, branch, merge, rebase, stash and review history with the built-in Git integration."
---
OpenIDEA has built-in Git support. Git is the only version control system it supports.
It reads repository state (status, diffs, blame, log) in-process, and runs the `git` executable for everything that changes the repository, so hooks, credentials, signing and your Git config behave as on the command line. You need Git installed. OpenIDEA finds it through `OPENIDEA_GIT`, then `PATH`, then the usual install locations; set an explicit path in **Settings › Version Control › Git › Path to Git executable** and click **Test** to check it.
## Where to find things
| Place | What it has |
| ----------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Git** main menu | Commit, push, pull, fetch, merge, rebase, branches, tags, reset, worktrees, log, patches, stash and shelf, current-file actions, GitHub, remotes, clone. |
| **Commit** tool window (left) | Local changes, changelists, commit message, Shelf and Stash tabs. |
| **Git** tool window (bottom) | **Log**, history tabs, **Console** (the Git commands the IDE ran), **Worktrees**. |
| Branch widget (title bar) | The current branch; click it for the branches popup. |
| **VCS Operations…** popup | The most common Git actions in one list. |
| **Git** submenu in the Project view, editor and tab context menus | Actions for the selected files. |
| Action | macOS | Windows/Linux |
| ------------------------------ | --------------- | ---------------------------- |
| Commit tool window | `⌘0` | `Alt+0` |
| Git tool window | `⌘9` | `Alt+9` |
| VCS Operations… | `⌃V` | ``Alt+` `` |
| Branches… | none | ``Ctrl+Shift+` `` |
| Commit… | `⌘K` | `Ctrl+K` |
| Commit and Push… | `⌥⌘K` | `Ctrl+Alt+K` |
| Push… | `⇧⌘K` | `Ctrl+Shift+K` |
| Update Project… | `⌘T` | `Ctrl+T` |
| Rollback… | `⌥⌘Z` | `Ctrl+Alt+Z` |
| Show Diff | `⌘D` | `Ctrl+D` |
| Add to VCS | `⌥⌘A` | `Ctrl+Alt+A` |
| New Branch… | `⌥⌘N` | `Ctrl+Alt+N` |
| Next / previous change | `⌃⌥⇧↓` / `⌃⌥⇧↑` | `Ctrl+Alt+Shift+Down` / `Up` |
| Recent Changes (local history) | `⌥⇧C` | `Alt+Shift+C` |
**Annotate with Git Blame**, **Show Git Log**, **Show History**, **Pull…**, **Fetch**, **Merge…**, **Rebase…**, **Stash Changes…** and **Resolve Conflicts…** have no default shortcut; assign one in the [keymap](/en/guide/keymap) if you use them often.
## Get a repository
- **Git › Clone…** opens **Clone Repository**. Enter a **URL:** and **Directory:**, and optionally check **Shallow clone with a history truncated to** N commits. The **GitHub** source in the sidebar lists your GitHub repositories (see [GitHub](/en/guide/github)).
- When creating a project, check **Create Git repository** in the New Project dialog.
There is no action to run `git init` in a project that is already open; run it in the terminal and OpenIDEA picks the repository up.
OpenIDEA finds the repository that contains the project and nested repositories up to four levels deep (skipping folders such as `node_modules`, `target` and `build`). Status, commit, fetch and update cover all of them; branch and log actions work on the selected repository. Submodules show as nested repositories, without submodule commands.
## Commit changes
The **Commit** tool window lists your changes in groups: **Merge Conflicts**, **Changes** (one group per changelist) and **Unversioned Files**. With **View Options › Group By › Staging State**, the changes split into **Staged Changes** and **Unstaged Changes**.
<Steps>
<Step title="Choose what to commit">
Check the files to include. Unversioned files and conflicts are not included by default, and only the active
changelist is checked. `Space` toggles the selected item. A partially staged file includes only its staged hunks.
</Step>
<Step title="Write the message">
Type in the message box. The **Commit Message History** button (or `⌃M` / `Ctrl+M`) offers your last 20 messages.
During a merge, cherry-pick or revert the message is pre-filled from Git; otherwise from `commit.template`.
</Step>
<Step title="Commit">
Click **Commit**, or press `⌘Enter` / `Ctrl+Enter` in the message box. **Commit and Push…** (`⌥⌘Enter` /
`Ctrl+Alt+Enter` in the message box) commits and then pushes.
</Step>
</Steps>
Check **Amend** (`⌃⌥M` / `Alt+M`) to amend the last commit; the button changes to **Amend Commit**.
Double-click a file (or press `Enter`) to see its diff; **Preview Diff** shows it next to the tree. The file context menu has **Show Diff**, **Jump to Source**, **Rollback…**, **Add to VCS**, **Add to .gitignore**, **Stage**, **Unstage**, **Move to Another Changelist…**, **Shelve Changes…**, **Create Patch…**, **Copy as Patch to Clipboard**, **Show History**, **Show Local History** and **Annotate with Git Blame**.
### Partial commits
To commit only part of a file, open its diff from the Commit tool window. Each changed block has an **Include in commit** checkbox in the gutter; uncheck the blocks to leave out. The file's checkbox in the tree becomes partly checked. OpenIDEA commits the selected blocks through a temporary index, so your working tree and staging area stay as they are. Selection works per block, not per line.
The diff toolbar also has **Stage Change (git add hunk)** if you prefer the staging area.
### Changelists
Changelists group local changes without touching Git. The default list is called **Changes**; new changes go to the active list. Changelists are saved per project in `.openidea/changelists.json`.
- **New Changelist…**, **Edit Changelist…** (`F2` or `⇧F6` / `Shift+F6`), **Delete Changelist**, **Set Active Changelist**.
- **Move to Another Changelist…** (`⇧⌘M` / `Alt+Shift+M`), or drag files between lists.
- Right-click a changelist and choose **Commit…** to commit just that list.
### Commit options and checks
The gear button (**Commit Options**) next to **Commit** has:
- **Author:** to commit as someone else (`Name <email>`), and **Sign-off commit**.
- **Commit Checks**: **Reformat code**, **Optimize imports**, **Cleanup** (applies fix-all code actions) and **Run Git hooks** (off adds `--no-verify`).
- **Advanced Commit Checks**: **Analyze code** (errors and warnings from the language servers) and **Check TODO** (TODO and FIXME in added lines).
Before committing, OpenIDEA also warns about committing in detached HEAD or during a rebase, file names that cause problems on other systems, files larger than 50 MB, and CRLF line endings. A warning stops the commit once; click **Commit Anyway** to go ahead. There is no "run tests" check.
### Modal commit
With **Use non-modal commit interface** turned off (**Settings › Version Control › Commit**), there is no Commit tool window: **Local Changes**, **Shelf** and **Stash** become tabs of the Git tool window, and **Commit…** opens a **Commit Changes** dialog.
## Changes in the editor
The editor gutter marks added, modified and deleted lines compared with HEAD. Click a marker to open a popup with **Previous Change**, **Next Change**, **Rollback**, **Show Diff** and **Copy Original Text**. **Rollback…** with the caret on changed lines rolls back just those lines.
## Roll back, add and ignore
- **Rollback…** opens **Rollback Changes**: pick the files, optionally **Delete local copies of added files**.
- **Add to VCS** adds untracked files. When you create or delete files, OpenIDEA asks whether to add or remove them in Git; change this under **Settings › Version Control › Confirmation**.
- **Add to .gitignore** writes a rule to the nearest `.gitignore` or to `.git/info/exclude`.
## Diff viewer
Diffs open in an editor tab.
| Action | macOS | Windows/Linux |
| -------------------------- | ------------- | -------------------------- |
| Next / previous difference | `F7` / `⇧F7` | `F7` / `Shift+F7` |
| Next / previous file | `⌃⇧→` / `⌃⇧←` | `Alt+Shift+Right` / `Left` |
| Accept left side | `⌃⌘→` | `Ctrl+Alt+R` |
| Accept right side | `⌃⌘←` | `Ctrl+Alt+A` |
| Jump to source | `F4` or `⌘↓` | `F4` |
| Diff settings | `⇧⌘D` | `Ctrl+Shift+D` |
The toolbar switches between **Side-by-side Viewer** and **Unified Viewer**, and sets whitespace handling (**Do not ignore**, **Trim whitespaces**, **Ignore whitespaces**, **Ignore whitespaces and empty lines**), highlighting (words, lines or none), **Collapse Unchanged Fragments**, **Synchronize Scrolling** and **Align Changes**. When the right side is your working file you can edit it, use **Revert Change**, or **Stage Change (git add hunk)**. Gutter arrows apply a change from one side to the other.
Other ways to compare:
- **Compare with Branch or Revision…** and **Compare with Revision…** for the current file, or for folders (a list of changed files).
- **Compare With…** (`⌘D` / `Ctrl+D` in the Project view), **Compare Files**, **Compare Directories** (with copy left/right) and **View › Compare with Clipboard**.
## Branches
Click the branch widget in the title bar (or **Git › Branches…**) to open the branches popup. Type to search branches and actions. The popup shows **Recent**, **Local**, **Remote** and **Tags**, and at the top **Update Project…**, **Commit…**, **Push…**, **New Branch…** and **Checkout Tag or Revision…**. The gear button toggles grouping by prefix, recent branches and tags.
Select a branch (`→` or `Enter`) for its actions:
- **Checkout**, **New Branch from '…'…**, **Checkout and Rebase onto '…'**
- **Compare with '…'**, **Show Diff with Working Tree**, **Show History**
- **Rebase '…' onto '…'**, **Merge '…' into '…'**, and for remote branches **Pull into '…' Using Rebase** / **Using Merge**
- **Update**, **Push…**, **Rename…**, **Add to Favorites**, **Delete**
If local changes block a checkout, you can choose **Smart Checkout** (stash, check out, unstash), **Force Checkout** or **Don't Checkout**. A deleted branch can be brought back with **Restore** in the notification.
With several repositories, the popup shows branches common to all of them, plus a submenu per repository.
## Sync with remotes
- **Fetch** fetches all repositories. OpenIDEA also checks for incoming commits in the background (after 20 seconds, then every 15 minutes); see **Explicitly check for incoming commits on remotes** in the Git settings.
- **Update Project…** (`⌘T` / `Ctrl+T`) pulls all repositories, by merge or by rebase. Uncommitted changes are stashed and restored. The **Update Project** dialog only shows when **Show options before Update** is on.
- **Pull…** pulls a chosen remote branch into the current branch, with `--rebase`, `--ff-only`, `--no-ff`, `--squash` and other options.
- **Push…** (`⇧⌘K` / `Ctrl+Shift+K`) lists outgoing commits and their files. Edit the target branch, pick a remote, and choose **Force (with lease)**, **Set upstream** or push tags. Force push is not allowed to protected branches (`master; main` by default). If the push is rejected, OpenIDEA offers to merge or rebase and push again.
- **Manage Remotes…** adds, edits and removes remotes.
HTTPS passwords and SSH passphrases are asked for in a dialog and can be remembered; saved HTTPS credentials are listed under **Settings › Version Control › Git › Passwords**.
## Merge, rebase and conflicts
**Git › Merge…** and **Git › Rebase…** open dialogs with the common Git options (`--no-ff`, `--ff-only`, `--squash`, `--no-commit`; `--onto`, `--rebase-merges`, `--keep-empty`). While a merge, rebase, cherry-pick or revert is in progress, the title bar shows a widget with **Resolve Conflicts…** and **Continue** / **Abort**, and the Git menu enables **Abort Merge**, **Continue Rebase**, **Skip Commit**, **Abort Rebase** and the cherry-pick and revert equivalents. A merge is concluded by committing.
When conflicts appear, a notification and the **Conflicts** dialog list the files. For each file choose **Accept Yours**, **Accept Theirs** or **Merge…**.
**Merge…** opens a three-way merge editor: your side, the result and their side.
- Click the arrows in the gutter to accept a change, or the cross to ignore it. Hold the modifier key to resolve the whole conflict with one side.
- The toolbar applies all non-conflicting changes (from the left, the right or both), **Resolve Simple Conflicts**, compares any two of the versions, and shows how many changes and conflicts remain.
- Click **Apply** to save the result and mark the file resolved, or **Accept Yours** / **Accept Theirs** for the whole file.
Files with conflict markers show a banner with **Resolve conflicts…**, and the markers are reported as errors. **Accept Yours / Theirs / Both (Conflict at Caret)** resolve one conflict in the text editor.
## Git log
The **Log** tab of the Git tool window shows the commit graph.
- **Filters**: text or hash (with regex and match case), branch, user, date and paths; **Recent Filters** brings back earlier ones.
- **Go to Hash/Branch/Tag** (`⌘F` / `Ctrl+F` in the log).
- **Graph Options**: sort by date, topologically or incoming first; first parent only; no merges.
- **View Options**: reference display, details pane, and columns (**Author**, **Date**, **Hash**, **Signature**, **GitHub Commit Checks**).
- The branches pane on the left filters the log by branch and has branch actions.
- The details pane shows the changed files, the commit message, who committed it, which branches contain it and its signature.
Right-click a commit for **Copy Revision Number** (`⌥⇧⌘C` / `Ctrl+Alt+Shift+C`), **Create Patch…**, **Cherry-Pick**, **Checkout Revision**, **Browse Repository at Revision**, **Compare with Local**, **Reset Current Branch to Here…**, **Revert Commit**, **Undo Commit…**, **New Branch…**, **New Tag…**, **View in Browser** and more. With two commits selected, **Compare Versions** diffs them.
### Rewrite history
From the log you can **Edit Commit Message…**, **Fixup…**, **Squash Into…**, **Drop Commit**, **Squash Commits…** and **Interactively Rebase from Here…**. The interactive rebase dialog lets you pick, reword, edit, squash, fixup, drop and reorder commits. These actions are disabled for commits that are already pushed.
Limits: interactive rebase doesn't support ranges with merge commits, and **Undo Commit…** works only on the latest commit (its changes go back to a changelist).
### Tags, reset and worktrees
- **Git › New Tag…** creates a tag on a commit (annotated when you enter a message). **New Tag…** in the log creates a lightweight tag.
- **Git › Reset HEAD…** and **Reset Current Branch to Here…** reset with **Soft**, **Mixed** (default), **Hard** or **Keep**.
- **Git › New Worktree…** and **Worktrees…** create, list, remove and open worktrees in a new window.
## Stash, shelf and patches
**Stash** uses `git stash`:
- **Stash Changes…**: a message, **Keep index** and **Include untracked files**. Stashes always include all changes; there is no per-file stash.
- **Unstash Changes…**: apply or pop a stash, reinstate the index, or create a branch from it.
- The **Stash** tab of the Commit tool window lists stashes with apply, pop and drop.
**Shelf** is managed by the IDE and stored as patches in `.openidea/shelf`:
- **Shelve Changes…** lets you pick files and a name; the changes are then reverted.
- **Unshelve…** applies a shelf (or one file of it) with a three-way merge, into a changelist you choose.
- The **Shelf** tab shows shelves with diff, compare with local, patch export, rename and delete.
Turn on **Combine stashes and shelves in one tab** in the Git settings to show both in one tab.
**Git › Patch** has **Create Patch…**, **Apply Patch…** and **Apply Patch from Clipboard…**.
## Blame and history
- **Annotate with Git Blame** shows author and date per line in the gutter (also from the gutter's context menu). Click an entry to open the commit in the log. **Show inline blame for the current line** in the Git settings shows the author at the end of the caret line.
- **Show History** opens the file's history in a tab of the Git tool window.
- **Show History for Selection** shows the commits that changed the selected lines.
- **Show Current Revision** reveals the commit of the caret line in the log.
### Local history
Local history keeps snapshots of your files independent of Git, including deleted files and labelled states such as the one taken before a refactoring.
- **Git › Current File › Local History** (or **Local History › Show History** in the Project view) opens the history of a file or folder. Select a revision and click **Revert** (or **Restore** for a deleted file).
- **Put Label…** marks the current state.
- **Recent Changes** (`⌥⇧C` / `Alt+Shift+C`) lists recent edits across the project.
Retention is set in the **Local History** section of **Settings › Appearance & Behavior › System Settings**: 5 working days and 512 MB per project by default. Files over 2 MB are not snapshotted automatically.
## Settings
**Settings › Version Control** has these pages:
| Page | Main options |
| ---------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Commit** | Non-modal commit interface, clearing the message after commit, message inspections (blank line after subject, subject and body length, right margin), default commit checks. |
| **Confirmation** | What to do when files are created or deleted, **Show options before Update**, confirm before dropping commits, highlight modified folders in the Project view, history row limit. |
| **Directory Mappings** | The detected repositories (read-only). |
| **Issue Navigation** | Regex patterns that turn issue IDs in commit messages into links (presets for JIRA, YouTrack and GitHub). |
| **Log** | Reference and time display, visible columns. |
| **Shelf** | Remove applied files from the shelf. |
| **Git** | Git executable, inline blame, commit warnings, sign-off default, push options and protected branches, update method (merge or rebase), incoming-commit checks, saved passwords, combining stash and shelf. |
| **GitHub** | See [GitHub](/en/guide/github). |
## Not supported
- Mercurial, Subversion, Perforce and other systems.
- `git init` for an open project, submodule commands, editable directory mappings.
- Per-line partial commits (block level only) and per-file stashes.
- Git LFS beyond the large-file warning.
+79
View File
@@ -0,0 +1,79 @@
---
title: "Vim mode"
description: "Turn on Vim emulation, configure it with ~/.ideavimrc, map keys to IDE actions and resolve shortcut conflicts."
---
OpenIDEA has a built-in Vim emulation modelled on IntelliJ IDEA's IdeaVim plugin. The editing engine is [`@replit/codemirror-vim`](https://github.com/replit/codemirror-vim); OpenIDEA adds IDE integration on top: Ex commands that save and close tabs, `:action` to run IDE commands, IdeaVim-style rc files and a shortcut conflict table.
## Turn it on
Use any of these:
- **Tools › Vim**, which turns it on or off.
- The **Vim** widget in the status bar: click it and check **Enabled**.
- **Settings › Editor › Vim** › **Enable Vim emulation**.
The setting applies to every editor in every window right away and is remembered across restarts. While Vim mode is on, the status bar widget shows the current mode: `NORMAL`, `INSERT`, `VISUAL`, `V-LINE`, `V-BLOCK` or `REPLACE`.
## What works
The usual Vim editing model is available through codemirror-vim: Normal, Insert, Visual (characterwise, linewise and blockwise) and Replace modes, motions, operators and text objects with counts, registers, marks, `.` repeat, search with `/`, `?`, `*` and `#`, and its Ex commands such as `:s`, `:g` and `:sort`.
OpenIDEA adds these Ex commands:
| Command | Does |
| -------------- | ------------------------ |
| `:w`, `:wa` | **Save All** |
| `:q` | Close the editor tab |
| `:qa` | Close all editor tabs |
| `:wq`, `:x` | Save, then close the tab |
| `:action <id>` | Run an IDE action |
## The rc file
When Vim mode turns on (including at startup, if it was on), OpenIDEA reads `~/.openideavimrc`, or `~/.ideavimrc` when the first does not exist. Use `~/.openideavimrc` for settings that should apply only to OpenIDEA and not to IntelliJ IDEA.
After editing the file, run **Reload .ideavimrc** (from the status bar widget or Find Action), or click **Reload** in **Settings › Editor › Vim**. The settings page shows which file was read, how many mappings it has, and every line that was ignored with the reason.
Supported lines:
| Line | Notes |
| --------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `map`, `noremap` and their `n`, `v`, `x`, `i`, `o` variants (`nnoremap`, `vmap`…) | `<silent>`, `<unique>`, `<nowait>`, `<buffer>`, `<expr>` and `<script>` flags are accepted and ignored. Key names such as `<esc>` and `<CR>` are case-insensitive. |
| `let mapleader = "…"` | Also `g:mapleader`. `"\<Space>"` and `" "` both mean Space. The default leader is `\`. |
| `set …` | Only the options codemirror-vim implements: `textwidth` (`tw`), `pcre`, `insertModeEscKeysTimeout` and `langmap` (`lmap`). Other options are reported as ignored. |
| `source <file>` | Reads another file by the same rules. `~` is your home directory. |
| `" comment` | Ignored. |
Anything else (functions, `if`, `autocmd`, plugin commands) is reported as ignored and never executed.
### Map keys to IDE actions
The right-hand side of a mapping can run an IDE action, written the IdeaVim way:
```vim
let mapleader = " "
nnoremap <leader>r :action RenameElement<CR>
nnoremap <leader>f <Action>(ReformatCode)
nnoremap <leader>e :action SearchEverywhere<CR>
nnoremap gi <Action>(GotoImplementation)
```
IdeaVim action ids such as `GotoDeclaration`, `GotoImplementation`, `FindUsages`, `ReformatCode`, `RenameElement`, `ShowIntentionActions`, `SearchEverywhere`, `RecentFiles`, `Run`, `Debug`, `ToggleLineBreakpoint`, `SplitVertically` and many others are translated to the matching OpenIDEA action. OpenIDEA's own action ids work too, for example `:action code.reformat`. To find an action's id, see the [shortcuts reference](/en/reference/shortcuts) or **Settings › Keymap**.
When a leader mapping starts with `Space`, `,`, `;`, `Backspace` or `Delete`, the built-in Vim command on that key is disabled so the mapping can complete.
## Shortcut conflicts
Some keys are both Vim commands and IDE shortcuts, for example `Ctrl+R` (redo in Vim) or `Ctrl+E`. **Settings › Editor › Vim** › **Shortcut conflicts** lists every key Vim uses with the IDE action bound to it, and lets you choose the **Handler** for each:
- **Vim**: the key goes to the editor while Vim mode is on and an editor has focus.
- **IDE**: the IDE action runs first; Vim still gets the key when no IDE action is enabled.
The defaults follow IdeaVim: `Esc`, `Ctrl+C`, `Ctrl+N`, `Ctrl+P`, `Ctrl+Q` and `Ctrl+T` go to the IDE, the others to Vim.
## Limits
- Only the rc file subset above is understood. IdeaVim's own options (for example `ideajoin`) and IdeaVim extensions such as `surround`, `commentary` or `easymotion` are not available.
- Vim mode is an IDE-wide setting; it cannot be turned on for one project or one file type only.
- `:q` and `:wq` close the editor tab, not the window.
+58
View File
@@ -0,0 +1,58 @@
---
title: "Introduction"
description: "OpenIDEA is an open-source IDE with the IntelliJ IDEA way of working, built as a native app with a Rust core."
---
OpenIDEA brings the IntelliJ IDEA way of working (its keymap, its tool windows, its search-everything habits) to a native desktop app with a Rust core. It runs on macOS, Windows and Linux and is licensed under Apache 2.0.
If you have used IntelliJ IDEA, PyCharm or another JetBrains IDE, most of what you know carries over: pressing <kbd>Shift</kbd> twice opens Search Everywhere, `⌥↵` / `Alt+Enter` shows context actions, and the default keymap matches IDEA's. Coming from VS Code or Eclipse? Their keymaps are built in too.
## Highlights
- **One IDE for many languages.** Editor and code insight for 60+ languages, and a one-click install of the language servers a project needs. See [Languages](/en/languages/index).
- **The tools you expect.** Debugger, Git, terminal, database client, HTTP client, test runner, coverage and profiler, each in its own tool window.
- **Search everything.** Search Everywhere, Find Action, Go to Class / File / Symbol and Find in Files across the indexed project.
- **Refactorings and inspections.** Rename, extract, inline, change signature, safe delete, quick fixes and Inspect Code.
- **Remote development.** Work on a machine over SSH or in a dev container with the `openidea-server` backend.
- **AI agents.** Run coding agents in a tool window, and give them the IDE's own tools through the built-in MCP server.
- **Plugins.** Plugins add actions, completion, quick fixes, tool windows, custom editors, themes, file icons and languages. They run as sandboxed WebAssembly components; themes, icon themes and languages need no code. The plugin API is still a preview.
## Benchmark
Opening [freqtrade](https://github.com/freqtrade/freqtrade) (Python, about 770 files) with warm caches, median of 5 runs:
| | OpenIDEA | IntelliJ IDEA 2026.2.3 |
| --------------------------- | ----------------- | ---------------------- |
| Window with the project | **0.9 s** | 3.1 s |
| Ready (CPU settled) | **1.1 s** | 13.2 s |
| Memory (USS, all processes) | **169 MB** | 2011 MB |
| CPU time until ready | **2.4 s** | 47.9 s |
| Idle CPU | **4 %** of a core | 7 % of a core |
| Quit | **0.3 s** | 5.9 s |
Measured with a release build of OpenIDEA from `main` and IDEA with default settings and plugins, on Windows 11, 32-core AMD, 62 GB RAM. IDEA analyses Python more deeply, so "ready" doesn't mean the same work on both sides. Both are measured from the outside, the same way: see [`perf/compare`](https://github.com/l0ng-ai/openidea/blob/main/perf/compare) to run the comparison on your own machine and projects.
## Where to go next
<CardGroup cols={2}>
<Card title="Install" icon="download" href="/en/guide/install">
Download OpenIDEA for macOS, Windows or Linux.
</Card>
<Card title="Quickstart" icon="rocket" href="/en/guide/quickstart">
Open a project, find your way around, run and debug.
</Card>
<Card title="Coming from another IDE" icon="right-left" href="/en/guide/migrate">
Keymaps, settings import and concepts for IntelliJ IDEA, VS Code and Eclipse users.
</Card>
<Card title="Languages" icon="code" href="/en/languages/index">
What each language supports and what it needs installed.
</Card>
<Card title="Plugins" icon="puzzle-piece" href="/en/plugins/index">
Write themes, languages and WebAssembly plugins.
</Card>
<Card title="Contribute" icon="code-branch" href="/en/contribute/setup">
Build OpenIDEA from source and send a pull request.
</Card>
</CardGroup>
<Note>OpenIDEA is not affiliated with JetBrains. IntelliJ IDEA is a trademark of JetBrains s.r.o.</Note>
+165
View File
@@ -0,0 +1,165 @@
---
title: "Add a language"
description: "The language definition format: file detection, comments, highlighting, language servers, debugger and formatter."
---
Every language OpenIDEA knows is a TOML file in the format described here. You can use it in three ways:
- **Change a built-in language for yourself**: put a file with the same `id` in the `languages` folder of the configuration directory. It replaces the built-in definition as a whole (fields are not merged), so start from a copy of the built-in file.
- **Add a language for yourself**: put a new file with a new `id` in the same folder.
- **Ship a language to others**: add it to a plugin (see [Contributing a language from a plugin](#contributing-a-language-from-a-plugin)), or to the repository's `languages/` folder.
The configuration directory is:
| OS | Folder |
| ------- | ----------------------------------------------------------- |
| macOS | `~/Library/Application Support/ai.l0ng.openidea/languages/` |
| Windows | `%APPDATA%\ai.l0ng.openidea\languages\` |
| Linux | `~/.config/ai.l0ng.openidea/languages/` |
The folder is read once at startup, so restart the IDE after changing a file. A file that does not parse is skipped and the reason is written to the log.
## Example
A complete definition for a language with a tree-sitter grammar, a language server and a debugger:
```toml languages/zig.toml
id = "zig"
name = "Zig"
extensions = ["zig", "zon"]
line-comment = "//"
indent = { tab-size = 4, insert-spaces = true }
tree-sitter = "zig"
[[language-servers]]
name = "zls"
command = "zls"
root-markers = ["build.zig", "build.zig.zon"]
install-hint = "Install ZLS matching your Zig version (https://github.com/zigtools/zls/releases)"
[debugger]
adapter = "codelldb"
command = "codelldb"
args = ["--port", "${port}"]
transport = "tcp"
install-hint = "Install CodeLLDB in Settings › Languages & Frameworks › Language Servers"
```
All keys are kebab-case. Only `id` and `name` are required; a definition with nothing else still gives the files a name in the UI.
## Language fields
| Field | Type | Description |
| -------------------- | --------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `id` | string | Stable language id, e.g. `python`. Use letters, digits, `-`, `_`, `.` or `+` (at most 64 characters). |
| `name` | string | Name shown in the UI. |
| `extensions` | string array | Extensions without the dot, matched case-insensitively. Compound extensions work: `html.erb`, `tftest.hcl`. |
| `filenames` | string array | Exact file names (`Dockerfile`, `go.mod`) or patterns with `*` and `?` (`Dockerfile.*`). |
| `first-line` | string | Regular expression tried against the first line when name and extension don't match, e.g. `^#!.*\\bpython[0-9.]*\\b`. |
| `line-comment` | string | Line comment token, e.g. `//`. Used by **Comment with Line Comment**. |
| `block-comment` | `[open, close]` | Block comment tokens, e.g. `["/*", "*/"]`. |
| `indent` | table | `{ tab-size = 4, insert-spaces = true }`. Both keys are required when the table is present. |
| `ui-mode` | string | CodeMirror mode used for instant highlighting. The name of a mode from `@codemirror/language-data` (`Rust`, `Python`, `C++`, `Properties files`, ...) or one of the IDE's own modes (such as `CMake`, `Vue`, `Astro`, `ERB`, `JSP`, `Log`, `GoMod`). |
| `framework-ui-modes` | array of tables | Modes that replace `ui-mode` in a framework's projects; see below. |
| `tree-sitter` | string | Bundled tree-sitter grammar for outline, folding and project symbols. When there is no `ui-mode`, it also highlights. See the list below. |
| `language-servers` | array of tables | Language servers, in order; see below. |
| `debugger` | table | Debug adapter; see below. |
| `formatter` | table | External formatter; see below. |
| `scratch-template` | string | Content of a new scratch file of this language. `$END$` marks where the caret goes. |
Bundled tree-sitter grammars: `bash`, `c`, `cmake`, `cpp`, `csharp`, `css`, `dart`, `elixir`, `gdscript`, `go`, `graphql`, `haskell`, `hcl`, `html`, `java`, `javascript`, `json`, `kotlin`, `lua`, `make`, `markdown`, `nix`, `ocaml`, `ocaml_interface`, `php`, `proto`, `python`, `ruby`, `rust`, `scala`, `swift`, `toml`, `tsx`, `typescript`, `xml`, `yaml`, `zig`. A new grammar has to be compiled into the IDE; a plugin can provide highlighting and structure for a language without a grammar instead (see [Language features](/en/plugins/guides/language-features)).
### `[[framework-ui-modes]]`
| Field | Type | Description |
| -------------- | ------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------- |
| `ui-mode` | string | The mode to use instead of `ui-mode`. |
| `root-markers` | string array | The mode applies to a file when one of these exists in a folder above it, up to the project folder. Matched like a language server's `root-markers`. |
The first entry whose marker is found wins. HTML uses this for Angular templates:
```toml
[[framework-ui-modes]]
ui-mode = "Angular Template"
root-markers = ["angular.json", "node_modules/@angular/core"]
```
## `[[language-servers]]`
Each entry starts one LSP server. Servers are listed in order; all regular servers run side by side, and a `fallback` server runs only while every server listed before it is missing.
| Field | Type | Description |
| ------------------------- | ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `name` | string | Stable name, e.g. `rust-analyzer`. Users' settings refer to it (`languageServers.<name>`), and it selects the IDE's installer when one exists. |
| `command` | string | Executable to start. Looked up in the IDE's tools folder, then on `PATH`. |
| `args` | string array | Arguments, e.g. `["--stdio"]`. |
| `env` | table | Extra environment variables. |
| `root-markers` | string array | Files that mark a workspace root. The outermost folder above the file (within the project) that contains one becomes the server's root. A marker can be a relative path, a pattern with one `*` (`*.csproj`), or `file#table` to require a TOML table (`pyproject.toml#tool.ruff`). |
| `require-root-marker` | bool | Serve only files below a root marker, not the whole project. Use it for a framework's server next to the language's own one. |
| `initialization-options` | table | Sent as `initializationOptions` when the server starts. |
| `settings` | table | Answers the server's `workspace/configuration` requests. Without it, the requested sections are looked up in `initialization-options`. |
| `install-hint` | string | Shown when the server is missing and the IDE cannot install it. |
| `fallback` | bool | An alternative to the servers listed before it; used only while none of them is installed. |
| `features` | string array | Use the server only for these features; empty means everything. Values: `completion`, `inline-completion`, `hover`, `signature-help`, `definition`, `declaration`, `type-definition`, `implementation`, `references`, `document-highlight`, `document-symbol`, `workspace-symbol`, `code-action`, `code-lens`, `rename`, `formatting`, `inlay-hint`, `semantic-tokens`, `document-link`, `call-hierarchy`, `type-hierarchy`, `folding-range`, `selection-range`, `diagnostics`. |
| `code-action-kinds` | string array | Keep only these code action kinds, matched by prefix (`refactor` keeps `refactor.extract`); empty keeps all. |
| `connect` | string | `host:port` of a server that speaks LSP over TCP. See [TCP servers](#tcp-servers). |
| `max-java` | integer | Newest Java release the server runs on. A newer `JAVA_HOME` is replaced by the newest installed JDK that fits. |
| `tsserver-project` | bool | For TypeScript servers: register source files with tsserver when the folder has no `tsconfig.json` / `jsconfig.json`. |
| `resync-on-configuration` | bool | Send the open documents again once `workspace/configuration` has been answered, for servers that apply some settings only when they parse a document. |
Users can override most of these per server in **Settings › Languages & Frameworks › Language Servers** (executable, arguments, environment, initialization options, workspace configuration), without editing the definition.
### TCP servers
Some servers only listen on a TCP port, such as the one inside the Godot editor. With `connect`, the IDE connects to that address when something listens there. Otherwise it starts `command` (when installed) and connects once it accepts connections: to `127.0.0.1:${port}` when the arguments contain `${port}` (the IDE picks a free port), else to the `connect` address. `${root}` in the arguments is the workspace root. A server the IDE reached without starting it is never shut down by the IDE.
```toml
[[language-servers]]
name = "godot"
command = "godot"
args = ["--headless", "--editor", "--path", "${root}", "--lsp-port", "${port}"]
connect = "127.0.0.1:6005"
root-markers = ["project.godot"]
```
## `[debugger]`
| Field | Type | Description |
| -------------- | ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `adapter` | string | Debug adapter name, e.g. `codelldb`, `debugpy`, `js-debug`, `delve`. It also selects the IDE's installer when one exists. |
| `command` | string | Executable that starts the adapter. |
| `args` | string array | Arguments. With `tcp`, `${port}` is replaced by a free port. |
| `transport` | string | `stdio` (default) or `tcp`. |
| `connect` | string | `tcp` only: `host:port` of an adapter that runs inside another program (the Godot editor). Used when it accepts connections; otherwise `command` is started. |
| `install-hint` | string | Shown when the adapter is missing. |
A language with a `[debugger]` lets you set breakpoints in its files.
## `formatter`
An external formatter, used when no language server can format the file, or when selected in settings:
```toml
formatter = { tool = "prettier", parser = "typescript", range = true }
```
| Field | Type | Description |
| -------- | ------ | -------------------------------------------------------------------------------------------- |
| `tool` | string | `prettier`, `oxfmt` or `ruff`. |
| `parser` | string | The tool's parser for this language (Prettier's `--parser`, e.g. `css`, `markdown`, `yaml`). |
| `range` | bool | The tool can format a selection. Prettier and Ruff only; oxfmt always formats whole files. |
## Contributing a language from a plugin
A plugin ships the same file and declares it in its manifest:
```toml plugin.toml
[[contributes.languages]]
path = "languages/solidity.toml"
```
Every program the definition starts (`language-servers.command`, `debugger.command`) needs a `process:<program>` capability; a server whose program is not granted is left out, while the language itself still works. Built-in and user definitions win: a plugin cannot reuse their ids, extensions or file names. See [Language features](/en/plugins/guides/language-features) for the plugin side, and the Solidity example in [`plugins/official/solidity`](https://github.com/l0ng-ai/openidea/blob/main/plugins/official/solidity).
## Adding a built-in language
To add a language to OpenIDEA itself, add `languages/<id>.toml` and register it in the `BUILTIN` table in [`crates/ide-language/src/registry.rs`](https://github.com/l0ng-ai/openidea/blob/main/crates/ide-language/src/registry.rs), where the files are embedded at build time. One-click installation of its server needs an entry in [`crates/ide-tools/src/registry.rs`](https://github.com/l0ng-ai/openidea/blob/main/crates/ide-tools/src/registry.rs) under the same `name`.
+121
View File
@@ -0,0 +1,121 @@
---
title: "C and C++"
description: "Code intelligence with clangd, CMake and Makefile run configurations, single-file runs and native debugging with lldb-dap, CodeLLDB or GDB."
---
OpenIDEA supports C, C++, Objective-C and Objective-C++ through [clangd](https://clangd.llvm.org). Highlighting, folding and the outline work without any setup. Completion, navigation, diagnostics and refactorings come from clangd, and how good they are depends on clangd knowing your compile flags. For the other languages, see the [support matrix](/en/languages/index).
| Language | Extensions | Language server | Debugger (default) |
| ------------- | ----------------------------------------------------------------------------------------------------- | --------------- | ------------------ |
| C | `.c`, `.h` | clangd | lldb-dap |
| C++ | `.cpp`, `.cc`, `.cxx`, `.c++`, `.hpp`, `.hh`, `.hxx`, `.h++`, `.ipp`, `.tpp`, `.inl`, `.ixx`, `.cppm` | clangd | lldb-dap |
| Objective-C | `.m` | clangd | lldb-dap |
| Objective-C++ | `.mm` | clangd | lldb-dap |
`.h` files open as C. To treat headers as C++, change the association in **Settings › Editor › File Types**.
## Language server
clangd starts with `--background-index --header-insertion=never` for C and C++, so it indexes the whole project in the background and never adds `#include` lines on its own.
clangd finds the workspace root by looking up from the file for one of these files: `compile_commands.json`, `compile_flags.txt`, `.clangd`, `CMakeLists.txt`, `meson.build` or `Makefile`. Objective-C and Objective-C++ only use `compile_commands.json`, `compile_flags.txt` and `.clangd`.
### Compile flags
clangd needs the flags each file is compiled with. Without them it guesses, and you see false errors about missing headers or unknown macros. Provide one of:
- **`compile_commands.json`**: a compilation database. CMake writes it into the build directory when `CMAKE_EXPORT_COMPILE_COMMANDS` is on, and the CMake configurations OpenIDEA creates always pass `-DCMAKE_EXPORT_COMPILE_COMMANDS=ON` (see [CMake](#cmake)). For Make-based projects, tools such as Bear can generate one.
- **`compile_flags.txt`**: one flag per line, applied to every file. Good for small projects.
- **`.clangd`**: clangd's own configuration file, for example to point `CompilationDatabase` at a build directory.
<Tip>
clangd looks for `compile_commands.json` in the file's parent directories and in a `build/` subdirectory of each. If your build directory has another name (for example `cmake-build-debug` from a CMake profile), add a `.clangd` file with `CompileFlags: { CompilationDatabase: cmake-build-debug }` or copy or link the file to the project root.
</Tip>
### Apple's clangd
The `clangd` that ships with Xcode and the Command Line Tools does not run clang-tidy. When a project has a `.clang-tidy` file and the IDE is using Apple's clangd, it shows a warning with an **Install** button that installs the LLVM build instead.
## Setup
When clangd is missing, opening a C or C++ file shows a "clangd is not installed" notification with **Install**. You can also install it from **Settings › Languages & Frameworks › Language Servers**.
| Tool | How it is installed | Notes |
| -------- | ------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------ |
| clangd | Downloaded from the `clangd/clangd` GitHub releases into the IDE's tools folder | Prebuilt for macOS, Linux x64 and Windows x64. On other platforms, install clangd yourself (for example `apt install clangd`). |
| CodeLLDB | Downloaded from the `vadimcn/codelldb` GitHub releases | Optional, see [Debugging](#debugging). |
| lldb-dap | Not installed by the IDE | Comes with LLVM (`brew install llvm`). On macOS the IDE also finds Xcode's copy through `xcrun -f lldb-dap`. |
| GDB | Not installed by the IDE | Needs GDB 14 or newer. |
A clangd or debugger already on your `PATH` is used as is. To use a specific binary, select it on the tool's page in **Settings › Languages & Frameworks › Language Servers**.
The compiler is not managed by the IDE. Single-file runs look for `clang`, `gcc` or `cc` (C) and `clang++`, `g++` or `c++` (C++) on `PATH`.
## Build, run and test
### CMake
For every `CMakeLists.txt` project, OpenIDEA creates run configurations:
- **CMake Configure**: `cmake -S . -B <build dir> -DCMAKE_EXPORT_COMPILE_COMMANDS=ON` (plus `-DCMAKE_BUILD_TYPE=Debug` for a new build directory).
- **CMake Build**: `cmake --build <build dir>`. It runs the configure step first when the build directory is not configured yet.
- **CTest**: `ctest --test-dir <build dir> --output-on-failure`, after a build.
- **Build &lt;target&gt;** and one run configuration per executable target. Running a target builds it first.
The executable targets come from CMake's File API, so they appear after the first configure. Nested CMake projects (for example in a folder that holds several repositories) get their own set of configurations.
Without profiles, the IDE uses every subdirectory of the project that contains a `CMakeCache.txt`, or `build/` when there is none.
#### CMake profiles
To control the build directories yourself, add profiles in **Settings › Build, Execution, Deployment › CMake**. Each profile has:
| Field | Meaning |
| --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- |
| Name | Shown next to the configuration names. |
| Build type | `Debug`, `Release`, `RelWithDebInfo` or `MinSizeRel` (passed as `CMAKE_BUILD_TYPE`). |
| Generator | Optional `-G` value, for example `Ninja`, `Unix Makefiles`, `Ninja Multi-Config`, `Visual Studio 17 2022` or `Xcode`. Empty uses CMake's default. |
| CMake arguments | Extra arguments for the configure step. |
| Build directory | Empty means `cmake-build-<build type>`, for example `cmake-build-debug`. |
| Build arguments | Extra arguments for `cmake --build`. |
| Environment variables | Set for configure, build, test and run. |
Once profiles exist, only they are used. Changing the generator of an existing build directory clears its CMake cache on the next configure.
### Makefiles
A `Makefile`, `makefile` or `GNUmakefile` gives one `make <target>` configuration per target. `test` and `check` targets are marked as tests.
### Single files
A C or C++ file with a `main` function can be run without a build system: use **Run Context Configuration** (`⌃⇧R` / `Ctrl+Shift+F10`) or **Debug Context Configuration** (`⌃⇧D` on macOS). The IDE compiles the file with debug info (`-g`, and `-std=c++17` for C++) into a temporary folder and runs it.
See [Run configurations](/en/guide/run) and [Testing](/en/guide/testing) for the general workflow.
## Debugging
Debugging uses a DAP adapter. C and C++ configurations default to **lldb-dap**. To use another one, open the run configuration and change **Debug adapter**:
- **lldb-dap (C, C++, Swift)**: LLVM's adapter, the default. The IDE does not install it.
- **CodeLLDB (Rust, C, C++)**: the IDE can install it; if it is missing when you start debugging, a notification offers to install it.
- **GDB (C, C++)**: GDB's own DAP mode (`gdb -i dap`). It needs GDB 14 or newer; older versions are refused with a message.
**Attach to Process…** lists native processes too. It attaches with CodeLLDB when it is installed, otherwise with lldb-dap.
See [Debugging](/en/guide/debugging) for breakpoints, stepping and the variables view.
## Arduino
`.ino` sketches use the Arduino Language Server, which drives clangd with Arduino's generated compile context. The IDE does not install it: install Arduino IDE 2 (it includes the Arduino Language Server, Arduino CLI and clangd) and the board's core. OpenIDEA finds those tools on `PATH` or inside Arduino IDE 2.
To override the paths, set them in **Settings › Languages & Frameworks › Language Servers**, or set `languages.arduino.cli`, `languages.arduino.clangd`, `languages.arduino.languageServer` and `languages.arduino.cliConfig` in `settings.toml`. Set the board FQBN in `sketch.yaml` or `languages.arduino.fqbn`. Plain `.cpp` files next to a sketch are still served by clangd.
There are no upload or serial monitor actions for Arduino.
## Limitations
- C and C++ code intelligence is only as good as clangd's compile flags. Without a `compile_commands.json` or `compile_flags.txt`, expect false errors.
- The IDE does not generate a compilation database for Make, Meson or other build systems.
- CTest runs as a single configuration and shows its console output; individual tests are not listed in a test tree.
- There are no gutter run icons for C and C++ test frameworks (GoogleTest, Catch2 and so on).
- lldb-dap and GDB are not installed by the IDE.
+83
View File
@@ -0,0 +1,83 @@
---
title: "C# and .NET"
description: "C# with csharp-ls, F# with FsAutoComplete, the .NET tool window for builds, NuGet packages and tests, and debugging with netcoredbg."
---
OpenIDEA supports C# (`.cs`, `.csx`) through [csharp-ls](https://github.com/razzmatazz/csharp-language-server) and F# (`.fs`, `.fsi`, `.fsx`) through [FsAutoComplete](https://github.com/ionide/FsAutoComplete). Both are installed as .NET tools, and both debug with [netcoredbg](https://github.com/Samsung/netcoredbg). Builds, NuGet packages and tests go through the `dotnet` CLI. For other languages, see the [support matrix](/en/languages/index).
## Language server
| Language | Server | Workspace root markers |
| -------- | -------------- | ----------------------------------------------------------------------------------------------------------- |
| C# | csharp-ls | `*.sln`, `*.slnx`, `*.csproj`, `global.json`, `Directory.Build.props`, `ProjectSettings/ProjectVersion.txt` |
| F# | fsautocomplete | `*.sln`, `*.fsproj`, `global.json` |
The server loads the solution or project it finds from these markers. FsAutoComplete starts with `AutomaticWorkspaceInit` on, so it loads the projects without waiting for the editor to ask.
When you go to the definition of a library type, csharp-ls returns decompiled source. It opens as a read-only document, and so does source-generated code.
`ProjectSettings/ProjectVersion.txt` is there so that Unity projects work too. See [Unity](/en/languages/unity) for using OpenIDEA as Unity's external editor.
## Setup
You need the [.NET SDK](https://dotnet.microsoft.com/download) with `dotnet` on your `PATH`. The IDE runs every build, test and package command through it, and installs the language servers with it.
When a server is missing, opening a C# or F# file shows a "csharp-ls is not installed" (or FsAutoComplete) notification with **Install**. You can also install the tools from **Settings › Languages & Frameworks › Language Servers**.
| Tool | How it is installed |
| -------------- | ------------------------------------------------------------------------------------------------------------------------- |
| csharp-ls | `dotnet tool install --tool-path` into the IDE's tools folder (not a global tool) |
| FsAutoComplete | `dotnet tool install --tool-path` into the IDE's tools folder |
| netcoredbg | Downloaded from the `Samsung/netcoredbg` GitHub releases. Prebuilt for macOS arm64, Linux x64 and arm64, and Windows x64. |
A `csharp-ls`, `fsautocomplete` or `netcoredbg` already on your `PATH` (for example a global `dotnet tool install --global csharp-ls`) is used as is.
## The .NET tool window
The **.NET** tool window (on the right) works with the `.csproj`, `.fsproj` and `.vbproj` projects in the project folder, including the projects listed in `.sln` and `.slnx` solutions and their project references. Open it with the **.NET Workspace** action from **Find Action…** (`⌘⇧A` / `Ctrl+Shift+A`). **Reload .NET Workspace** scans the folder again.
For the selected project you can:
- Pick the **Configuration** (`Debug` by default) and **Target framework** (for multi-targeted projects).
- **Build**, **Restore** and **Clean** it (also available as the **Build .NET Project**, **Restore .NET Project** and **Clean .NET Project** actions). Output appears in the **Output** section.
- Browse its **NuGet packages**, including transitive ones, search your package sources with **Search NuGet packages** (`dotnet package search`), and add, update or remove packages (`dotnet add package` / `dotnet remove package`).
- See its **Project references**.
- Discover and run tests (see below).
Project evaluation uses MSBuild, so the target frameworks, output paths and packages you see are the ones MSBuild computes, including imported properties.
## Run
OpenIDEA creates run configurations for every `.csproj` it finds (also through `.sln` / `.slnx`) whose output type is `Exe` or `WinExe`, or that uses `Microsoft.NET.Sdk.Web`:
- **&lt;Project&gt; (.NET Project)**: `dotnet run --project <path> --no-launch-profile`.
- **&lt;Project&gt;: &lt;profile&gt;**: one per profile in `Properties/launchSettings.json`, run with `--launch-profile`. The profile's environment variables, working directory, command-line arguments and `applicationUrl` (as `ASPNETCORE_URLS`) are applied.
The **.NET Project** configuration type has fields for the project, **Target framework**, **Launch profile** and **Program arguments**. The project is built before it runs.
See [Run configurations](/en/guide/run) for editing and creating configurations.
## Tests
Projects that reference `Microsoft.NET.Test.Sdk`, MSTest, NUnit or xUnit, or set `IsTestProject`, are test projects. Each gets a `dotnet test (<Project>)` configuration.
- In the editor, test methods marked `[Fact]`, `[Theory]`, `[Test]`, `[TestCase]`, `[TestCaseSource]`, `[TestMethod]` or `[DataTestMethod]` get gutter run icons.
- In the .NET tool window, **Discover .NET Tests** lists the tests (`dotnet test --list-tests`), and you can run all of them or one.
Results appear in the test tree of the Run tool window. The IDE asks VSTest for a TRX report and reads it. Projects on Microsoft.Testing.Platform need the `Microsoft.Testing.Extensions.TrxReport` package for that; **Install TRX reporting** adds it in the version matching the project's testing platform.
See [Testing](/en/guide/testing) for the test tree and rerunning failed tests.
## Debugging
Running a .NET Project configuration with **Debug** builds the project, asks MSBuild for its output assembly (`TargetPath`, in the Debug configuration) and launches it under netcoredbg. If netcoredbg is missing, a notification offers to install it.
See [Debugging](/en/guide/debugging).
## Limitations
- Automatic run configurations are created only for C# projects (`.csproj`). F# and VB projects can be built and tested from the .NET tool window, but you create their run configurations yourself.
- Test configurations run without a debugger; there is no Debug action for .NET tests.
- There is no Visual Basic language server.
- netcoredbg has no prebuilt download for Windows on Arm.
- There is no designer for WinForms, WPF or XAML.
+74
View File
@@ -0,0 +1,74 @@
---
title: "Go"
description: "Go code intelligence with gopls, GOROOT and build tag settings, go run and go test configurations, and debugging with Delve."
---
OpenIDEA uses gopls for Go code intelligence, runs and tests modules with the `go` command, and debugs with Delve. `go.mod` and `go.work` files are highlighted and served by gopls too. For other languages, see the [support matrix](/en/languages/index).
## Language server
gopls serves `.go`, `go.mod` and `go.work` files. A workspace starts at the nearest `go.work`, else the nearest `go.mod`. Semantic highlighting from gopls is turned on.
If gopls is missing, OpenIDEA shows a **gopls is not installed** notification with an **Install** button. You can also install it in **Settings › Languages & Frameworks › Language Servers**, or select your own `gopls` there. The IDE installs it with `go install golang.org/x/tools/gopls@latest` into its own tools folder, so Go must be on `PATH`. A `gopls` already on `PATH` is used as is.
## Setup
### Go SDK
**Settings › Languages & Frameworks › Go** has two settings:
- **GOROOT**: the Go SDK used by gopls and the go run and test configurations. **Default** uses `go` on `PATH`; if the project pins a Go version for mise or asdf, that install is used instead.
- **Project GOPATH**: empty uses `go env GOPATH`.
The GOROOT list shows the SDKs the IDE finds: the `go` on `PATH`, `/usr/local/go`, Homebrew and `/usr/lib/go` installs, SDKs in `~/sdk` (installed with `golang.org/dl`), toolchains Go downloaded itself (`~/go/pkg/mod/golang.org/toolchain@…`), and mise and asdf installs.
### Build tags
**Settings › Languages & Frameworks › Build Tags** sets the build constraints for the project: **Operating System**, **Architecture**, **Go Version**, **Compiler** (`gc` or `gccgo`), **CGO**, **Custom Tags** and **Experiments**. They are sent to gopls (as build flags and environment) and apply to the go run and test configurations and to debugging. Go configurations can turn on **Use all custom build tags**.
### Go modules
**Settings › Languages & Frameworks › Go Modules** has **Enable vendoring support automatically** and fields for `GOPROXY`, `GOPRIVATE`, `GOFLAGS`, `GONOPROXY`, `GONOSUMDB`, `GOSUMDB`, `GOMODCACHE`, `GOTOOLCHAIN` and `GOWORK`. Empty fields inherit the environment. The values are passed to gopls and to `go` commands.
Right-click a `go.mod` (in the Project view, an editor tab or the editor) for **Go Mod Tidy** and **Download Go Modules**; right-click a `go.work` for **Sync Go Workspace** and **Add Directory to Go Workspace…**. A `.go` file offers **Go Generate File** and **Go Generate Package**.
GOROOT and the module cache appear under **External Libraries** in the Project view.
## Run and test
OpenIDEA detects run configurations for each module (every `go.mod` in the project, or the modules listed in `go.work`):
| Configuration | When |
| --------------------- | ----------------------------------- |
| `go run .` | The module root is a `main` package |
| `go run ./cmd/<name>` | `cmd/<name>` is a `main` package |
| `go test ./...` | Always |
In the editor, gutter icons run or debug `func main` of a `main` package, `Test…`, `Benchmark…`, `Example…` and `Fuzz…` functions, and subtests declared with `t.Run` or `b.Run`. A `//go:generate` line gets its own icon. Test output (plain and `-v`) appears in the test tree, grouped by package; **Rerun Failed Tests** runs only the failures.
The **Go Build** and **Go Test** configuration types in **Run › Edit Configurations…** let you set the **Run kind** (directory, file or package), the target, a test **Pattern**, **Go tool arguments**, **Program arguments**, and for builds an **Output directory** and **Run after build**. See [Run configurations](/en/guide/run) and [Testing](/en/guide/testing).
| Action | macOS | Windows / Linux |
| --------------------------- | ----- | ---------------- |
| Run Context Configuration | `⌃⇧R` | `Ctrl+Shift+F10` |
| Debug Context Configuration | `⌃⇧D` | none |
| Build Project | `⌘F9` | `Ctrl+F9` |
**Build Project** runs `go build ./...` in each module (each module of a `go.work`); **Rebuild Project** adds `-a`. Compiler errors go to the Build tool window and the editor. **Run with Coverage** on `go test` uses Go's own `-coverprofile`; see [Coverage](/en/guide/coverage).
## Debugging
Go debugging uses Delve (`dlv dap`). If it is missing when you start a debug session, OpenIDEA offers to install it with `go install github.com/go-delve/delve/cmd/dlv@latest` into its tools folder. The detected `go run` and `go test` configurations and the gutter icons can all be debugged.
- **Attach to Process…** (`⌥⇧F5` / `Ctrl+Alt+F5`) lists local Go processes under **Go (Delve)**.
- The **Go Remote** configuration attaches to a Delve server (`dlv --headless`, Delve 1.7.3 or later) on this or another machine, with local and remote root mapping. The form shows the command to start the remote side.
<Tip>On macOS, Delve may need Developer Mode before it can debug: run `sudo DevToolsSecurity -enable` once.</Tip>
See [Debugging](/en/guide/debugging) for breakpoints and stepping.
## Limitations
- gopls and Delve are installed with `go install`, which needs Go on `PATH`. Without Go, install them yourself and select them in **Settings › Languages & Frameworks › Language Servers**.
- The profiler does not support Go programs.
- Gutter icons come from a lightweight scan of the file, not from gopls; tests built in unusual ways may not get an icon, but still run with `go test`.
+84
View File
@@ -0,0 +1,84 @@
---
title: "Godot"
description: "Edit GDScript with the Godot editor's language server, run and debug scenes, and open scripts from Godot in OpenIDEA."
---
OpenIDEA works with Godot 4 projects (folders with a `project.godot` file). GDScript code intelligence and debugging come from the Godot editor itself: its language server and debug adapter run inside the editor, and OpenIDEA connects to them. Nothing has to be installed besides Godot.
## Supported files
| Files | Language | What you get |
| ------------------------------------------------------------------------------------------------- | -------------- | -------------------------------------------------------------------------------------------------------------------------- |
| `.gd` | GDScript | Highlighting, outline and folding (tree-sitter), code intelligence from Godot, breakpoints. Indents with tabs, like Godot. |
| `.gdshader`, `.gdshaderinc` | Godot Shader | Highlighting (C-like) and comments. No language server. |
| `.tscn`, `.tres`, `.godot`, `.import`, `export_presets.cfg`, `override.cfg`, `extension_list.cfg` | Godot Resource | INI-style highlighting. Scenes (`.tscn`) can be run directly. |
C# scripts in a Godot .NET project are handled like any other C# code; see [C# and .NET](/en/languages/csharp).
## Find the Godot executable
OpenIDEA looks for Godot in this order:
1. The executable chosen in **Settings › Languages & Frameworks › Language Servers**, entry **godot** (stored as `tools.godot` in settings).
2. The search path, under the names `godot`, `godot4`, `godot-4`, `Godot`, `godot-mono` and `godot4-mono`.
3. Usual install locations:
- macOS: `Godot.app`, `Godot_mono.app` or `Godot 4.app` in `/Applications` or `~/Applications`, and Steam.
- Windows: Scoop, WinGet, `%LOCALAPPDATA%\Programs\Godot`, `Program Files\Godot`, and Steam.
- Linux: Flatpak (`org.godotengine.Godot`), Snap (`godot-4`, `godot`), and Steam.
If Godot is somewhere else, set the executable on that settings page. When it cannot be found, running a Godot configuration reports "Godot was not found".
## Code intelligence
The GDScript language server lives in the Godot editor and speaks LSP over TCP. When you open a `.gd` file, OpenIDEA:
1. connects to `127.0.0.1:6005`, Godot's default language server port, if an editor is listening there; otherwise
2. starts a headless editor for the project (`godot --headless --editor --path <project> --lsp-port <free port>`) and connects once it is ready. The first start imports the project and can take a while; OpenIDEA waits up to two minutes.
A Godot editor you opened yourself is never shut down by OpenIDEA. To keep the editor and the IDE talking to the same server, open the project in Godot before you open it in OpenIDEA. If you changed the language server port in Godot (**Editor Settings › Network › Language Server**), the IDE will not find the running editor and starts its own headless one instead.
What you get depends on what Godot's server provides: completion, hover, go to definition, symbols and diagnostics, among others. Features the server lacks, such as inlay hints, are simply absent.
## Run
For each `project.godot` at the project root or in a subfolder, OpenIDEA creates a **Godot Project** run configuration (named `Godot Project (<folder>)` for a subfolder). It runs `godot --path .` in that folder, which starts the project's main scene.
To run one scene, right-click a `.tscn` file and run it, or open it and use **Run › Run Context Configuration** (`⌃⇧R` / `Ctrl+Shift+F10`). This runs `godot --path . res://<scene>`.
You can also add a **Godot** configuration in **Run › Edit Configurations…**. Its fields:
| Field | Meaning |
| --------------------- | ----------------------------------------------------------------------- |
| **Project directory** | The folder with `project.godot`. |
| **Scene** | A scene such as `res://levels/level_1.tscn`. Empty runs the main scene. |
| **Arguments** | Extra Godot command-line options, e.g. `--debug-collisions`. |
## Debug
Debugging GDScript goes through the Godot editor's debug adapter (DAP over TCP).
- **Debug a Godot configuration.** OpenIDEA connects to an editor listening on `127.0.0.1:6006`, Godot's default debug adapter port. If none is running, it starts a headless editor for the project on a free port. The editor then plays the scene with its own run settings. A headless editor started this way ends with the debug session.
- **Attach to Godot.** This configuration type (debug only, defaults `localhost` and port `6006`) debugs the game the Godot editor is already running. Open the project in the Godot editor, play a scene (F5 / F6), then start the configuration. The port is set in Godot under **Editor Settings › Network › Debug Adapter**.
Breakpoints, the call stack, variables and stepping work as with other languages; see [Debugging](/en/guide/debugging).
<Note>
Godot matches scripts by their real path. OpenIDEA resolves symlinks in the project path before it sends breakpoints,
so a project opened through a symlinked folder (such as `/tmp` on macOS) still stops at breakpoints.
</Note>
## Open scripts from Godot
To make Godot open scripts in OpenIDEA, use Godot's external editor setting (**Editor Settings › Text Editor › External**): turn on **Use External Editor**, set **Exec Path** to the OpenIDEA executable (on macOS, the executable inside `OpenIDEA.app/Contents/MacOS/`), and set **Exec Flags** to:
```text
{project} --file {file} --line {line} --column {col}
```
OpenIDEA then opens the project and puts the caret at the given position. The arguments are the same as for Unity; see [Unity](/en/languages/unity) for how they work.
## Limitations
- GDScript code intelligence needs a Godot 4 editor, either running or startable headless. There is no built-in GDScript analysis without it.
- Godot Shader and resource files have highlighting only.
- Debugging works through the editor's adapter only; there is no debugging of exported builds.
+207
View File
@@ -0,0 +1,207 @@
---
title: "Language support"
description: "Which languages OpenIDEA supports, which language servers and debuggers it uses, and how they get installed."
---
OpenIDEA describes every language in a small definition file, `languages/<id>.toml`. The definition says which files belong to the language, how to highlight them, and which language server and debug adapter provide code intelligence and debugging. Everything a language server offers (completion, navigation, diagnostics, refactorings) then works through the same editor features for every language.
## How language support works
Each open file gets its language from the definitions, checked in this order:
1. A per-file **Override File Type** (in the context menu of a file in the project tree or of an editor tab).
2. An exact file name (`Dockerfile`, `go.mod`) or a file name pattern.
3. The extension, longest match first (`.html.erb` before `.erb`).
4. The first line: a shebang such as `#!/usr/bin/env python3`, or a pattern such as `<?php`.
5. Otherwise the file is plain text.
You can add or remove file name patterns per language in **Settings › Editor › File Types** (**Recognized File Types**).
For a recognized file, support comes in layers:
- **Highlighting** is instant: it comes from a CodeMirror mode in the editor, or from the bundled tree-sitter grammar when the language has no mode.
- **Structure** (outline, folding and the symbols in the project index) comes from a bundled tree-sitter grammar and needs no setup.
- **Code intelligence** comes from one or more language servers (LSP). The IDE starts them when you open a file, using the nearest folder that contains one of the server's root markers (`Cargo.toml`, `package.json`, `pom.xml`, ...) as the workspace root.
- **Debugging** comes from a debug adapter (DAP).
<Note>
Language servers and debug adapters run programs from your machine, so they start only in projects you trust.
</Note>
### Several servers for one language
A language can list more than one server:
- **Alternatives** (shown as `a → b` in the table below): only the first one that is installed runs. Python, for example, uses basedpyright, and falls back to pyright, ty or pyrefly only when basedpyright is missing. In **Settings › Languages & Frameworks › Language Servers** you can pick one of them or set the choice to **Automatic** (the first one available).
- **Additions** (shown as `+ b`): run next to the main server, usually only in projects that use them. Ruff joins basedpyright in projects with a Ruff configuration; oxlint joins vtsls in projects with an oxlint configuration; the Angular and Laravel servers join in Angular and Laravel projects.
## Installing language servers and debuggers
OpenIDEA does not bundle language servers. When you open a file whose server is missing, a notification says that the server is not installed and offers **Install**, which installs it and restarts code insight for that language. You can also manage every tool on one page:
<Steps>
<Step title="Open the Language Servers page">
Open **Settings › Languages & Frameworks › Language Servers** (`⌘,` / `Ctrl+Alt+S`, then search for "Language
Servers"), or run the **Language Servers…** action from **Find Action…** (`⌘⇧A` / `Ctrl+Shift+A`).
</Step>
<Step title="Find the tool">
Filter by tool or language. Each entry shows whether the tool is installed by the IDE, found on your `PATH`, set to
a custom executable, or missing.
</Step>
<Step title="Install or point to your own copy">
Click **Install** (or **Reinstall**) to let the IDE install it, or set **Executable** to a binary you installed
yourself.
</Step>
</Steps>
The IDE never needs `sudo`. It installs tools into its own tools folder in the local data directory (see [Data directories and logs](/en/reference/data-directories)), using the method that fits each tool:
| Method | Used for |
| ----------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| npm | Node-based servers such as vtsls, Pyright, Intelephense, the Vue, Svelte, Astro, Angular, CSS, HTML, JSON, YAML, Bash and Dockerfile servers. Needs Node.js. |
| pip into a private virtualenv | basedpyright, ty, pyrefly, Ruff (uses `uv` when available). |
| pip into a private folder | debugpy. |
| `go install` | gopls, Delve, sqls. Needs Go. |
| `rustup component add` | rust-analyzer (falls back to a download from Open VSX). |
| `gem install` | Solargraph into its own gem home; Ruby LSP into the project's Ruby. |
| `dotnet tool install` | csharp-ls, FsAutoComplete. Needs the .NET SDK. |
| Release download | clangd, CodeLLDB, vscode-js-debug, JDT LS, java-debug, Kotlin LSP, Lua, Marksman, Taplo, ZLS, terraform-ls and others, from GitHub, Open VSX, Maven Central, the Eclipse download site or HashiCorp. |
| Bundled | The Lombok agent for JDT LS ships with the app. |
When the IDE looks for a server, it checks the executable you set in settings first, then its own tools folder, then your `PATH`. Servers that the IDE cannot install (for example sourcekit-lsp, which comes with the Swift toolchain, or the Godot editor's own server) show an install hint instead.
<Tip>
GitHub release downloads use `GITHUB_TOKEN`, `GH_TOKEN` or your `gh` login when one is available, which avoids
anonymous API rate limits.
</Tip>
### Configuring a server
Selecting a language server on the Language Servers page shows its settings, which are stored under `languageServers.<name>` in your settings (globally or per project):
- turn the server off, or choose which of several alternatives runs;
- **Executable**: the program to start;
- **Startup**: **Arguments**, **Environment variables** and **Initialization options** (changing these restarts the server);
- **Workspace Configuration**: the answer to the server's `workspace/configuration` requests, applied without a restart.
**Add Language Server…** registers a server that no definition knows about: give its command, the languages it serves and its root markers.
## Support matrix
The table lists every built-in language definition. Server and debugger names in **bold** can be installed from the IDE; the others you install yourself (the install hint in the IDE says how). "Highlighting" and "Tree-sitter" mark instant highlighting and the bundled grammar for outline and folding.
| Language | ID | Files | Highlighting | Tree-sitter | Language servers | Debugger |
| ------------------- | ------------------ | ----------------------------------------------------------- | ------------ | ----------- | ------------------------------------------------------------------------------------- | ------------------------ |
| .env | `dotenv` | `.env` | Yes | — | — | — |
| Arduino | `arduino` | `.ino` | Yes | Yes | arduino-language-server | — |
| AsciiDoc | `asciidoc` | `.adoc`, `.asciidoc` | Yes | — | — | — |
| Astro | `astro` | `.astro` | Yes | — | **astro-ls** + **oxlint** | — |
| Batch | `bat` | `.bat`, `.cmd` | Yes | — | — | — |
| C | `c` | `.c`, `.h` | Yes | Yes | **clangd** | lldb-dap |
| C# | `csharp` | `.cs`, `.csx` | Yes | Yes | **csharp-ls** | **netcoredbg** |
| C++ | `cpp` | `.cpp`, `.cc`, `.cxx`, `.c++`, `.hpp`, … | Yes | Yes | **clangd** | lldb-dap |
| Clojure | `clojure` | `.clj`, `.cljs`, `.cljc`, `.cljd`, `.edn`, … | Yes | — | **clojure-lsp** | — |
| CMake | `cmake` | `.cmake` | Yes | Yes | **neocmakelsp** → cmake-language-server | — |
| CSS | `css` | `.css`, `.pcss`, `.postcss` | Yes | Yes | **vscode-css-language-server** | — |
| D | `d` | `.d`, `.di` | Yes | — | serve-d | — |
| Dart | `dart` | `.dart` | Yes | Yes | dart | dart |
| Diff | `diff` | `.diff`, `.patch`, `.rej` | Yes | — | — | — |
| Dockerfile | `dockerfile` | `.dockerfile`, `.containerfile` | Yes | — | **dockerfile-language-server** | — |
| Elixir | `elixir` | `.ex`, `.exs` | Yes | Yes | **elixir-ls** | **elixir-ls-debugger** |
| ERB | `erb` | `.erb`, `.html.erb`, `.rhtml`, `.js.erb`, `.text.erb`, … | Yes | — | — | — |
| Erlang | `erlang` | `.erl`, `.hrl`, `.escript`, `.app.src` | Yes | — | elp | — |
| F# | `fsharp` | `.fs`, `.fsi`, `.fsx` | Yes | — | **fsautocomplete** | **netcoredbg** |
| GDScript | `gdscript` | `.gd` | Yes | Yes | godot | godot |
| Git Attributes | `gitattributes` | `.gitattributes` | — | — | — | — |
| Go | `go` | `.go` | Yes | Yes | **gopls** | **delve** |
| Go Module | `go.mod` | `go.mod` | Yes | — | **gopls** | — |
| Go Workspace | `go.work` | `go.work` | Yes | — | **gopls** | — |
| Godot Resource | `godot-resource` | `.tscn`, `.tres`, `.godot`, `.import` | Yes | — | — | — |
| Godot Shader | `gdshader` | `.gdshader`, `.gdshaderinc` | Yes | — | — | — |
| GraphQL | `graphql` | `.graphql`, `.graphqls`, `.gql` | Yes | Yes | **graphql-lsp** | — |
| Groovy | `groovy` | `.groovy`, `.gradle`, `.gvy`, `.gy`, `.gsh` | Yes | — | groovy-language-server | — |
| Haskell | `haskell` | `.hs`, `.hs-boot`, `.hsig` | Yes | Yes | haskell-language-server | — |
| HCL | `hcl` | `.hcl`, `.nomad`, `.pkr.hcl` | Yes | Yes | — | — |
| HTML | `html` | `.html`, `.htm`, `.xhtml`, `.shtml`, `.html.tmpl` | Yes | Yes | **vscode-html-language-server** + **angular-language-server** | — |
| HTTP Request | `http` | `.http`, `.rest` | Yes | — | — | — |
| Ignore File | `ignore` | `.gitignore`, `.dockerignore` | — | — | — | — |
| INI | `ini` | `.ini`, `.cfg`, `.conf`, `.cnf`, `.inf`, … | Yes | — | — | — |
| Java | `java` | `.java` | Yes | Yes | **jdtls** → **kmp-lsp** | **java-debug** |
| JavaScript | `javascript` | `.js`, `.mjs`, `.cjs` | Yes | Yes | **vtsls** → **typescript-language-server** + **oxlint** | **js-debug** |
| JavaScript JSX | `javascriptreact` | `.jsx` | Yes | Yes | **vtsls** → **typescript-language-server** + **oxlint** | **js-debug** |
| JSON | `json` | `.json`, `.geojson`, `.topojson`, `.webmanifest`, `.har`, … | Yes | Yes | **vscode-json-language-server** | — |
| JSON with Comments | `jsonc` | `.jsonc`, `.code-workspace`, `.code-snippets` | Yes | Yes | **vscode-json-language-server** | — |
| JSP | `jsp` | `.jsp`, `.jspx`, `.jspf`, `.jsph`, `.tag`, … | Yes | — | — | — |
| Julia | `julia` | `.jl` | Yes | — | julia-languageserver | — |
| Kotlin | `kotlin` | `.kt`, `.kts` | Yes | Yes | **kotlin-lsp** → **kotlin-language-server** | **kotlin-debug-adapter** |
| LaTeX | `latex` | `.tex`, `.ltx`, `.sty`, `.cls`, `.dtx`, … | Yes | — | **texlab** | — |
| Less | `less` | `.less` | Yes | — | **vscode-css-language-server** | — |
| Log | `log` | `.log` | Yes | — | — | — |
| Lua | `lua` | `.lua`, `.luau`, `.rockspec` | Yes | Yes | **lua-language-server** | — |
| Makefile | `makefile` | `.mk`, `.mak`, `.make` | Yes | Yes | — | — |
| Markdown | `markdown` | `.md`, `.markdown`, `.mdown`, `.mkd`, `.mkdn`, … | Yes | Yes | **marksman** | — |
| Nix | `nix` | `.nix` | Yes | Yes | nil → nixd | — |
| Objective-C | `objective-c` | `.m` | Yes | — | **clangd** | lldb-dap |
| Objective-C++ | `objective-cpp` | `.mm` | Yes | — | **clangd** | lldb-dap |
| OCaml | `ocaml` | `.ml`, `.mll`, `.mly` | Yes | Yes | ocamllsp | ocamlearlybird |
| OCaml Interface | `ocaml.interface` | `.mli` | Yes | Yes | ocamllsp | — |
| Perl | `perl` | `.pl`, `.pm`, `.pod`, `.psgi`, `.cgi` | Yes | — | **perlnavigator** | — |
| PHP | `php` | `.php`, `.phtml`, `.php3`, `.php4`, `.php5`, … | Yes | Yes | **intelephense** → phpactor + **laravel-lsp** | **php-debug** |
| Plain Text | `plaintext` | `.txt`, `.text` | — | — | — | — |
| PowerShell | `powershell` | `.ps1`, `.psm1`, `.psd1`, `.pssc`, `.psrc` | Yes | — | powershell-editor-services | — |
| Properties | `properties` | `.properties`, `.prefs` | Yes | — | — | — |
| Protocol Buffers | `proto` | `.proto` | Yes | Yes | **buf** | — |
| Python | `python` | `.py`, `.pyi`, `.pyw` | Yes | Yes | **basedpyright** → **pyright** → **ty** → **pyrefly** + **ruff** | **debugpy** |
| R | `r` | `.r`, `.rprofile` | Yes | — | r-languageserver | — |
| reStructuredText | `restructuredtext` | `.rst` | Yes | — | — | — |
| Ruby | `ruby` | `.rb`, `.rake`, `.gemspec`, `.ru`, `.rbi`, … | Yes | Yes | **ruby-lsp** → **solargraph** | rdbg |
| Rust | `rust` | `.rs` | Yes | Yes | **rust-analyzer** | **codelldb** |
| Sass | `sass` | `.sass` | Yes | — | — | — |
| Scala | `scala` | `.scala`, `.sc`, `.sbt`, `.mill` | Yes | Yes | metals | — |
| SCSS | `scss` | `.scss` | Yes | — | **vscode-css-language-server** | — |
| Shell Script | `shellscript` | `.sh`, `.bash`, `.zsh`, `.ksh`, `.ash`, … | Yes | Yes | **bash-language-server** | **bash-debug** |
| SQL | `sql` | `.sql`, `.ddl`, `.dml`, `.psql`, `.pgsql`, … | Yes | — | **sqls** | — |
| Svelte | `svelte` | `.svelte` | Yes | — | **svelte-language-server** + **oxlint** | — |
| Swift | `swift` | `.swift`, `.swiftinterface` | Yes | Yes | sourcekit-lsp | lldb-dap |
| Terraform | `terraform` | `.tf`, `.tofu`, `.tftest.hcl` | Yes | Yes | **terraform-ls** | — |
| Terraform Variables | `terraform-vars` | `.tfvars` | Yes | Yes | **terraform-ls** | — |
| TOML | `toml` | `.toml` | Yes | Yes | **taplo** | — |
| TypeScript | `typescript` | `.ts`, `.mts`, `.cts` | Yes | Yes | **vtsls** → **typescript-language-server** + **angular-language-server** + **oxlint** | **js-debug** |
| TypeScript JSX | `typescriptreact` | `.tsx` | Yes | Yes | **vtsls** → **typescript-language-server** + **oxlint** | **js-debug** |
| Vue | `vue` | `.vue` | Yes | — | **vue-language-server**, **vtsls** + **oxlint** | — |
| XML | `xml` | `.xml`, `.xsd`, `.xsl`, `.xslt`, `.svg`, … | Yes | Yes | **lemminx** | — |
| YAML | `yaml` | `.yaml`, `.yml` | Yes | Yes | **yaml-language-server** | — |
| Zig | `zig` | `.zig`, `.zon` | Yes | Yes | **zls** | **codelldb** |
Notes:
- **C, C++, Objective-C, Swift**: `lldb-dap` comes with LLVM or Xcode. C and C++ can also be debugged with **CodeLLDB** or GDB 14+; see [C and C++](/en/languages/cpp).
- **Ruby**: `rdbg` comes from the `debug` gem.
- **GDScript**: the language server and debugger are the Godot editor itself; see [Godot](/en/languages/godot).
- **Arduino**: needs Arduino IDE 2 (Arduino Language Server, Arduino CLI and clangd).
- **Vue**: the Vue server and vtsls run together; vtsls handles the TypeScript part.
## Run, test and debug
Run configurations are detected from the build files in your project. The table shows what is detected for each ecosystem; see [Run configurations](/en/guide/run), [Testing](/en/guide/testing) and [Debugging](/en/guide/debugging) for how to use them.
| Ecosystem | Detected from | Configurations |
| ----------------------- | ----------------------------------------------------------------------------- | ---------------------------------------------------------------------------- |
| JavaScript / TypeScript | `package.json`, `deno.json` | package scripts (npm, pnpm, Yarn, Bun), Deno tasks |
| Rust | `Cargo.toml` | `cargo build`, `cargo run`, `cargo test` |
| Go | `go.mod` | Go Build, Go Test |
| Java / Kotlin | `pom.xml`, Gradle build files | Maven goals, Gradle tasks, Application, JAR Application, Spring Boot, Tomcat |
| Python | `pyproject.toml`, `setup.py`, `requirements.txt`, `manage.py`, a `.venv`, ... | `python -m <package>`, pytest, Django `runserver` and tests |
| PHP | `composer.json`, `phpunit.xml` | Composer scripts, PHPUnit or Pest, Laravel `artisan serve` |
| Ruby | `Gemfile`, `Rakefile`, `*.gemspec`, `config.ru` | Rails server, Rake tasks, RSpec, Minitest |
| C / C++ | `CMakeLists.txt` | CMake targets |
| C# | `.sln`, `.slnx`, `.csproj` | .NET Project, `dotnet test` |
| GDScript | `project.godot` | Godot |
| Any | `justfile`, `Makefile` | just recipes, Make targets |
Running, testing (with a test tree and **Rerun Failed Tests**) and debugging are tested end to end on sample projects for TypeScript, Python, Rust, Go, Java, Kotlin and PHP; debugging is also tested for C and C++. Code intelligence is tested with each of these languages' real servers (TypeScript, Python, Rust, Go, Java, Kotlin, PHP).
## Changing or adding a language
- To change a built-in definition, put a file with the same `id` in the `languages` folder of the configuration directory; it replaces the built-in one at the next start. See [Add a language](/en/languages/adding) for the format.
- Plugins can add languages, with their own servers and debuggers. See [Language features](/en/plugins/guides/language-features).
+145
View File
@@ -0,0 +1,145 @@
---
title: "Java and Kotlin"
description: "How OpenIDEA provides code insight, builds, runs, tests and debugs Java and Kotlin projects with JDT LS, Kotlin LSP, Maven and Gradle."
---
Java code insight comes from the Eclipse JDT Language Server (jdtls), extended with bundles that ship with OpenIDEA. Kotlin uses JetBrains Kotlin LSP. Maven and Gradle projects are imported automatically, and run, test and debug work through the same run configurations for both languages. See the [support matrix](/en/languages/index) for how this compares with other languages.
## Language servers
| Language | Server | Fallback | Debugger |
| ---------------------- | ----------------------------------------- | ------------------------------------------------------- | --------------------------------------- |
| Java | Eclipse JDT Language Server (`jdtls`) | `kmp-lsp` | Microsoft java-debug, loaded into jdtls |
| Kotlin | Kotlin LSP (JetBrains, `kotlin-lsp`) | Kotlin Language Server (fwcd, `kotlin-language-server`) | Kotlin Debug Adapter (fwcd) |
| Groovy, Gradle scripts | `groovy-language-server` (you provide it) | | |
| JSP | none (highlighting only) | | |
A fallback starts only while the server listed before it is not installed. To pick one explicitly, open **Settings › Languages & Frameworks › Language Servers**, select either server and use **Language server for Java** (or Kotlin): **Automatic** uses the first one available, **None** turns code insight off. The choice can be global or for the current project.
`kmp-lsp` gives syntax-based Java code insight without running a JVM. It has no JDT compiler analysis, no Lombok support and none of the JDT refactorings. Building and running Java Application configurations, classpath discovery and the Java debugger need jdtls.
### What ships with the IDE
On top of plain jdtls, OpenIDEA loads its own extension bundles from the [`java/`](https://github.com/l0ng-ai/openidea/tree/main/java) folder of the repository:
- **java-usages**: Find Usages that tells reads from writes and, for a field, includes its getters, setters and builders (Lombok ones too). Find Usages searches project sources first (**Project Files**) and searches dependency jars only when you ask for **Project and Libraries**.
- **Spring configuration navigation**: see [Spring](#spring).
- **kotlin-bridge**: in Maven projects that mix Java and Kotlin, Java code sees the Kotlin classes. See [Kotlin in Maven projects](#kotlin-in-maven-projects).
- **Lombok**: a patched Lombok agent (1.18.48 with fixes for current JDT LS) is built into the app, so no download is needed.
Library classes without sources open as decompiled `jdt://` documents. jdtls downloads Maven and Eclipse sources for dependencies when they are available.
## Setup
### Installing the servers
When you open a Java or Kotlin file and its server is missing, a notification offers **Install**. You can also install, reinstall or uninstall each tool in **Settings › Languages & Frameworks › Language Servers**. Everything goes into the IDE's own tools folder and never needs administrator rights:
| Tool | Installed from |
| ------------------------------------ | ----------------------------------------------------------- |
| Eclipse JDT Language Server | The latest milestone from download.eclipse.org |
| Java Debugger (Microsoft java-debug) | The `vscjava.vscode-java-debug` extension on Open VSX |
| Lombok (jdtls agent) | Bundled with the app |
| Kotlin LSP (JetBrains) | GitHub release of `Kotlin/kotlin-lsp` |
| Kotlin Language Server (fwcd) | GitHub release of `fwcd/kotlin-language-server`; needs Java |
| Kotlin Debug Adapter | GitHub release of `fwcd/kotlin-debug-adapter`; needs Java |
| kmp-lsp | GitHub release of `Hessesian/kmp-lsp` |
A `jdtls` already on your `PATH` (Homebrew, Scoop or a release archive) is used too. OpenIDEA starts it on the JVM directly instead of through its Python launcher script, so Python is not required. When you start a debug session without java-debug, the IDE offers to install it and restarts jdtls with the bundle.
Groovy has no installer: build [groovy-language-server](https://github.com/GroovyLanguageServer/groovy-language-server) yourself and put it on `PATH` as `groovy-language-server`, or set its executable in **Language Servers**.
### JDK detection
OpenIDEA looks for installed JDKs in `JAVA_HOME`, the `java` on `PATH`, `/Library/Java/JavaVirtualMachines` (system and user), `/usr/lib/jvm`, Homebrew, SDKMAN, mise, asdf, `~/.jdks` and the usual vendor folders under `Program Files` on Windows. JDKs it downloaded are found as well. Use **Detect again** next to a JDK list to rescan.
Two JDK settings live in **Settings › Languages & Frameworks › Java**:
- **Project JDK › SDK**: compiles and runs the project. It is the default runtime of jdtls, the JDK for Run/Debug, and `JAVA_HOME` for Maven and Gradle. **Auto** picks, per module, an installed JDK matching the Java version the module targets.
- **Language Server › JDK**: runs jdtls only, so a project on Java 8 or 17 can still use a current jdtls. **Auto** uses the project JDK when it is new enough, else the first installed JDK that is. If none is, Eclipse Temurin is downloaded automatically into a separate language-server runtime folder.
jdtls needs JDK 21 or newer, and some jdtls releases need a newer one. OpenIDEA reads the requirement from the installed jdtls. Every detected JDK is also registered with jdtls as a runtime (`JavaSE-17`, `JavaSE-21`…), so a module that targets Java 17 compiles against JDK 17, not against the JDK jdtls runs on.
**Language level** sets the Java source and bytecode level when no build file sets it. Every JDK list also has **Download JDK…**, which downloads Eclipse Temurin of the version you choose into the IDE's JDK folder. The same download is offered when jdtls, a run configuration or a Maven or Gradle sync has no JDK that is new enough.
Kotlin Language Server (fwcd) runs on Java 21 at most. When `JAVA_HOME` points to a newer JDK, the IDE starts the server on the newest installed JDK that fits.
### Memory and JVM options
Each project window starts its own jdtls, with its own workspace and index. jdtls runs with these JVM options by default:
```text
-XX:+UseG1GC -XX:G1PeriodicGCInterval=60000 -XX:MinHeapFreeRatio=10 -XX:MaxHeapFreeRatio=30 -Xmx2G -Xms100m
```
G1 with a periodic collection hands memory back to the system once an import or build is over. To change the limits, enter options in **Settings › Languages & Frameworks › Java › Language Server › JVM options**, for example `-Xmx1g` or `-Xmx4g`. An option you set replaces the default for the same setting, and choosing another collector (such as `-XX:+UseZGC`) drops the default collector options. The setting can be global or per project.
**Share JDT LS across builds** runs one jdtls for all linked builds inside a local project window, instead of one per build. Separate project windows still use separate processes.
The jdtls workspace is kept in the IDE's data directory (`lsp/jdtls/`), not in your project. jdtls writes no `.project`, `.classpath` or `.settings` files into the project. See [Data directories and logs](/en/reference/data-directories).
### Lombok
When the project uses Lombok (in its build file, an inherited parent POM or Gradle build, or its imports), OpenIDEA adds a Lombok agent to jdtls. It uses the agent from **Language Servers**, which is the bundled one by default. If that agent is missing, the IDE falls back to the newest Lombok in your Maven or Gradle caches. A `-javaagent` you put in **JVM options** takes precedence. Your project's own Lombok dependency is not changed.
## Build tools
jdtls imports Maven and Gradle projects (including the Gradle wrapper) automatically. A folder without a build file is opened as a plain Java project, and jars under `lib/` are added to its classpath. OpenIDEA also shows **Maven** and **Gradle** tool windows for running goals and tasks, reloading, profiles and dependency analysis. Their settings are in **Settings › Build, Execution, Deployment › Maven** and **Gradle**:
- Maven: **JRE**, **Maven home path** (default: the project's `mvnw`, else `mvn` on `PATH`), **User settings file**, **Toolchains file**, **Local repository**, **Active profiles**, **Thread count** and **Use Maven Daemon (mvnd)**. The profiles you choose in the Maven tool window are passed to jdtls too, so the editor sees the same profile-specific dependencies.
- Gradle: **Build and run using** (Gradle or OpenIDEA), **Gradle JVM**, **Distribution** (wrapper or local installation), **Gradle home** and **Gradle user home**.
The IDE passes its proxy settings to jdtls, so dependency resolution works behind a proxy. See [Build tools](/en/guide/build-tools) for the tool windows.
### Kotlin in Maven projects
In a Maven project with Kotlin sources, OpenIDEA builds the Kotlin code in the background after you save. It uses the project's own Maven configuration and writes to a private output folder. The compiled Kotlin classes are then added to jdtls as libraries, so Java code can call Kotlin code from the same or another module. Errors from this build appear in **Problems** under `kotlin-bridge`. The last good build stays in use until the next build succeeds. The bridge covers compiled Kotlin APIs only. Navigating from Java into Kotlin sources, analysis of unsaved Kotlin edits and cross-language refactoring are not part of it.
## Run and test
| What | How it runs |
| --------------------------------- | ------------------------------------------------------------------------------------------------------------------------ |
| `main` method | **Application** configuration on the module's runtime or test classpath. Java 25 instance `main` methods are recognized. |
| `@SpringBootApplication` class | **Spring Boot** configuration, with active profiles and update actions |
| Executable jar | **JAR Application** configuration |
| WAR | **Tomcat Server** configuration on a local Tomcat installation (the IDE never writes into it) |
| JUnit 4, JUnit 5 and TestNG tests | Maven (Surefire `-Dtest=…`) or Gradle (`--tests …`) test configurations |
Gutter icons on main methods, test classes and test methods create these configurations, and Maven and Gradle run configurations are detected from the build files. For Gradle, **Build and run using** decides whether an Application runs as a Gradle `JavaExec` task (the default) or is launched by OpenIDEA on the classpath reported by jdtls.
Test results are read from the JUnit XML reports Maven and Gradle write, so passed tests are listed as well. The test tree supports rerunning failed tests. VM options of a Gradle run configuration are applied to the JVMs Gradle forks (`run`, `bootRun`, `test`). When a classpath is too long for the command line (common on Windows), it is moved into an argument file or a manifest-only jar.
See [Run configurations](/en/guide/run) and [Testing](/en/guide/testing).
## Debugging
Java debugging uses Microsoft java-debug inside jdtls, and Kotlin debugging uses Kotlin Debug Adapter. You can debug:
- Application, Spring Boot, JAR Application and Tomcat Server configurations.
- Maven and Gradle configurations that start a JVM (tests, `spring-boot:run`, `bootRun`, `run`): the IDE adds a JDWP agent and attaches when the JVM is ready, while output and the test tree stay in the Run tool window.
- Any process started elsewhere, with a **Remote JVM Debug** configuration.
**Run › Debugging Actions › Reload Changed Classes** (HotSwap) compiles your edits and reloads them into the running session. Spring Boot and Tomcat configurations also offer **Run › Update Application** (`⌘F10` / `Ctrl+F10`) with the **On 'Update' action** choices.
See [Debugging](/en/guide/debugging).
## Frameworks
### Spring
- **Spring Boot run configurations** are created from `@SpringBootApplication` classes, and they are listed in the **Services** tool window by default.
- **Configuration navigation**: Go to Definition on a key in `application.yml` or `.properties` opens the Java members bound to it (`@ConfigurationProperties`, `@Value`, `Environment.getProperty`). It also works the other way, from `${…}` placeholders in Java, YAML, properties or Spring XML. Keys described by `spring-configuration-metadata.json` in dependencies resolve to their source classes. **Find Usages** on a key lists configuration declarations and Java usages together.
- **Spring Web endpoints**: request mappings in Java and Kotlin controllers get a gutter icon, plus **Generate Request in HTTP Client** and **Generate Requests for Controller** actions that write requests to a scratch `.http` file. Search Everywhere matches request paths in its **All** tab. See [HTTP Client](/en/guide/http-client).
- **Facets** in **Project Structure** (`⌘;` / `Ctrl+Alt+Shift+S`) record the frameworks a module uses (Web, Spring, JPA). A Web facet also adds its `WEB-INF/lib/*.jar` to the jdtls classpath.
### MyBatis
Navigation between MyBatis mapper interfaces and their XML statements is available from the MyBatis Navigator plugin ([`plugins/official/mybatis`](https://github.com/l0ng-ai/openidea/tree/main/plugins/official/mybatis)). It is not part of the IDE itself.
## Limitations
- One jdtls runs per project window. With several large projects open at once, memory adds up. Lower `-Xmx` in **JVM options** or close windows you are not using. **Share JDT LS across builds** helps only within one window.
- Without jdtls (for example with `kmp-lsp` only), Application configurations, classpath-based features and Java debugging are not available.
- Java tests run through Maven or Gradle. A project without either build tool has no test runner.
- The Kotlin bridge is Maven only and exposes compiled Kotlin classes to Java. It does not give Java-to-Kotlin source navigation or refactoring.
- Groovy needs a `groovy-language-server` you build yourself, and JSP has syntax highlighting only.
+111
View File
@@ -0,0 +1,111 @@
---
title: "JavaScript and TypeScript"
description: "Language servers, Node.js detection, npm scripts, tests, debugging and framework support for JavaScript, TypeScript, Vue, Svelte, Astro and Angular."
---
OpenIDEA edits JavaScript (`.js`, `.mjs`, `.cjs`, `.jsx`) and TypeScript (`.ts`, `.mts`, `.cts`, `.tsx`) with highlighting and structure that work without any setup, and gets completion, navigation, refactoring and diagnostics from a TypeScript language server. Vue, Svelte, Astro and Angular projects get their framework's own server on top. See the [support matrix](/en/languages/index) for every language at a glance.
## Language servers
| Files | Server | Notes |
| ------------------------------- | -------------------------------------- | --------------------------------------------------------------------------------------------------- |
| JS, JSX, TS, TSX | `vtsls` | Default TypeScript server. |
| JS, JSX, TS, TSX | `typescript-language-server` | Fallback: used only while `vtsls` is not installed. |
| `.vue` | `vue-language-server` + `vtsls` | Vue's hybrid mode: TypeScript inside `.vue` files comes from `vtsls` with `@vue/typescript-plugin`. |
| `.svelte` | `svelte-language-server` | |
| `.astro` | `astro-ls` | Needs Node.js and a TypeScript library. |
| TS and HTML in Angular projects | `angular-language-server` (`ngserver`) | Only below `angular.json` or `node_modules/@angular/core`. |
| All of the above | `oxlint` | Only below an `.oxlintrc.json`, `.oxlintrc.jsonc`, `oxlint.config.ts` or `oxlint.config.mts`. |
The TypeScript servers start for a workspace marked by `tsconfig.json`, `jsconfig.json` or `package.json`. Files outside any `tsconfig.json` / `jsconfig.json` are still registered with tsserver, so loose scripts get code insight too.
Two behaviours differ from the servers' defaults: renaming a symbol never introduces an `import { a as b }` alias, and moving a file does not make the server rewrite imports on its own; the IDE asks for the import updates first and shows them as a preview you can undo.
### TypeScript version
**Settings › Languages & Frameworks › TypeScript** picks the TypeScript that `vtsls` and `typescript-language-server` load:
- **Bundled** (default): the language server's own TypeScript.
- The project's `node_modules/typescript`, when it is installed.
- Any TypeScript `lib` directory, chosen with the folder button.
The Vue, Astro and Angular servers use the selected version too. TypeScript 7 (the native port) has no language-service API, so the IDE installs `typescript-language-server`, Vue, Svelte and Astro servers together with `typescript@6`.
## Setup
### Installing the servers
When you open a file whose server is missing, a notification names it (for example "vtsls (TypeScript) is not installed") with **Install** and **Language Servers…** buttons. **Install** runs npm (or pnpm) and puts the package in the IDE's own tools folder, so nothing is installed globally and no `sudo` is needed. This needs `npm` or `pnpm` on `PATH`.
All servers, debuggers and formatters are listed in **Settings › Languages & Frameworks › Language Servers**, where you can install, reinstall, point a server at your own executable, or change its arguments, environment and initialization options. A server already on your `PATH` is used as well.
| Tool | Installed with |
| -------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------- |
| `vtsls`, `typescript-language-server` | npm (`@vtsls/language-server`; `typescript-language-server` + `typescript@6`) |
| `vue-language-server`, `svelte-language-server`, `astro-ls`, `angular-language-server` | npm |
| `oxlint` | npm |
| Prettier, oxfmt | npm |
| JavaScript Debugger (vscode-js-debug) | GitHub release (`microsoft/vscode-js-debug`) |
The Astro and Angular servers also run from the project's own `node_modules/.bin` when it has them.
### Node.js
**Settings › Languages & Frameworks › Node.js** has:
- **Node interpreter**: the `node` that runs npm scripts, Node files and the Node-based language servers. **Default** uses `node` on `PATH`. The list shows every Node.js found on `PATH` and those installed by nvm, Volta, fnm, mise and asdf.
- **Run in target**: run Node and package-manager commands locally or in one of the run targets from **Settings › Build, Execution, Deployment › Run Targets**.
- **Package manager**: `npm`, `pnpm`, `yarn` or `bun` for the detected script configurations. **Auto** reads `packageManager` in `package.json`, then the lock file (`pnpm-lock.yaml`, `yarn.lock`, `bun.lock` / `bun.lockb`, `package-lock.json`), and otherwise uses npm.
## Formatting
**Reformat Code** (`⌥⌘L` / `Ctrl+Alt+L`) uses the language server unless an external formatter is configured to win:
- **Prettier** (**Settings › Languages & Frameworks › Prettier**) formats JavaScript, JSX, TypeScript, TSX, JSON, Vue, HTML, CSS, SCSS, Less, GraphQL, YAML and Markdown by default. The executable is found in the project's `node_modules`, then the managed installation, then `PATH`, unless you set **Prettier path**.
- **oxfmt** (**Settings › Languages & Frameworks › oxfmt**) replaces Prettier for an enabled language in files below an oxfmt configuration (`.oxfmtrc.json`, `oxfmt.config.ts`), or when it is the selected provider.
On both pages, **Formatting provider** chooses between **Automatic (language server first)** and the tool itself, **Run on save** formats on save, and **Enabled languages** limits which languages the tool handles.
## Run and test
OpenIDEA detects run configurations when the project opens:
- One configuration per `package.json` script (`npm run build`, `pnpm run dev`, …), for the root package, nested packages and workspace members. Scripts named `test`, `jest`, `vitest` or `test:*` are test configurations.
- One per task in `deno.json` / `deno.jsonc` (`deno task <name>`).
In `package.json`, the gutter shows run icons on each `scripts` entry to run or debug it.
**Run Context Configuration** (`⌃⇧R` / `Ctrl+Shift+F10`) on a source file:
- A test file (`*.test.*`, `*.spec.*`, or anything under `__tests__/`) runs through the closest test script with the file added. The gutter icons on `test`, `it` and `describe` blocks run one test or suite by adding `-t <name>`.
- Any other `.js` file runs with `node` (with `deno run` or `bun run` in a project with `deno.json` or `bun.lock`).
- A `.ts` file runs with Node.js directly on 22.18+ / 23.6+, with `--experimental-strip-types` on 22.6–22.17 and 23.0–23.5, else through `tsx` or `ts-node`.
Jest and Vitest output is turned into the test tree, with **Rerun Failed Tests**. **Run with Coverage** works for scripts that run `vitest` or `jest` (Vitest needs a coverage provider such as `@vitest/coverage-v8`; the IDE offers to add it) and for `node` / `tsx` / `ts-node` runs through `c8`. See [Testing](/en/guide/testing) and [Coverage](/en/guide/coverage).
## Debugging
Debugging uses the JavaScript Debugger (vscode-js-debug); if it is missing, the first debug session offers to install it.
- **Debug** works for npm script configurations, Node.js files and `deno run` / `deno test`. Child processes are attached automatically.
- **Attach to Node.js** is a run configuration type for a process started with `--inspect` on this or another machine (default port 9229), with local and remote root mapping.
- **Attach to Process…** in the Run menu lists running Node.js processes.
With **Do not step into library scripts** (**Settings › Build, Execution, Deployment › Debugger › Stepping**, on by default), stepping skips Node.js internals and `node_modules`. More in [Debugging](/en/guide/debugging).
## Framework support
- **React**: JSX and TSX get their own highlighting modes and full TypeScript server support. There is no React-specific tooling beyond that.
- **Vue**: `.vue` files are highlighted per block and served by Vue Language Server in hybrid mode. In a Vue CLI project without `tsconfig.json`, webpack's `resolve.alias` entries (such as `@/`) are read so imports resolve. Prettier formats `.vue` files. Emmet works in templates.
- **Svelte**: Svelte Language Server; highlighting uses the HTML mode (script as TypeScript, style as CSS). Emmet works in markup.
- **Astro**: Astro Language Server with its own highlighting mode. It needs Node.js; if it can't find one, select it in **Settings › Languages & Frameworks › Node.js**.
- **Angular**: in projects with `angular.json` or `@angular/core` installed, templates in HTML files and inline `template:` strings get Angular highlighting, and the Angular Language Server adds template completion, navigation and rename. The server is configured for the project's Angular version.
## Limitations
- No built-in ESLint integration; oxlint is the bundled linter. You can still add any server with **Add Language Server…** on the Language Servers page.
- No browser (Chrome/Edge) debugging; only Node.js and Deno are debugged.
- Bun runs but can't be debugged: its inspector uses the WebKit protocol, which vscode-js-debug can't attach to.
- Deno has run and debug support but no Deno language server; Deno files use the TypeScript servers.
- Coverage of Deno tasks is not supported.
- Emmet expands in HTML, Vue and Svelte, not in JSX.
+88
View File
@@ -0,0 +1,88 @@
---
title: "PHP"
description: "Intelephense, PHP interpreters and language level, Composer, PHPUnit and Pest, PHPStan, Xdebug debugging and Laravel support."
---
OpenIDEA highlights PHP files (`.php`, `.phtml`, `.phpt` and older `.php3`–`.php5`, plus files starting with `<?php` or a `php` shebang) out of the box. Completion, navigation, refactoring and diagnostics come from Intelephense. Composer scripts and PHPUnit/Pest suites are detected as run configurations, and Xdebug sessions run through the PHP Debug adapter. See the [support matrix](/en/languages/index) for other languages.
## Language servers
| Server | When it runs |
| -------------- | ------------------------------------------------------------------------------------------------------------------------------------- |
| `intelephense` | Default. Workspace root: `composer.json`, `.phpactor.json` or `index.php`. |
| `phpactor` | Fallback: used only while Intelephense is not installed. |
| `laravel-lsp` | Laravel projects only (below an `artisan` file): hover, definition and completion inside `config('…')`, `route('…')` and `view('…')`. |
Intelephense checks code against the project's PHP version (see [Language level](#language-level)) and indexes the stubs and include paths you configure.
## Setup
### Installing the tools
When a `.php` file opens and Intelephense is missing, a notification offers **Install**. The IDE installs it with npm into its own tools folder (needs `npm` or `pnpm` on `PATH`). Everything is also listed in **Settings › Languages & Frameworks › Language Servers**.
| Tool | Installed with |
| ------------------ | ---------------------------------------------------------------------- |
| Intelephense | npm (`intelephense`) |
| Laravel LSP | GitHub release (`laravel/lsp`) |
| PHP Debug (Xdebug) | Open VSX (`xdebug.php-debug`); needs Node.js to run |
| phpactor | Not installable from the IDE; install it yourself and put it on `PATH` |
### PHP interpreter
**Settings › Languages & Frameworks › PHP**, section **CLI Interpreter**:
- **PHP interpreter**: the `php` that runs scripts, PHPUnit, Composer, `artisan` and PHP-based servers such as phpactor. **Default** uses `php` on `PATH`. Its folder is put first on `PATH`, so `vendor/bin/phpunit` and other `#!/usr/bin/env php` scripts use the same PHP. The list includes PHP found on `PATH` (also versioned binaries like `php8.3`), Homebrew, phpbrew, phpenv, Laravel Herd, and on Windows XAMPP and Laragon.
- **Run in target**: run PHP and Composer commands locally or in a run target from **Settings › Build, Execution, Deployment › Run Targets**.
- The page shows the selected PHP's version and the loaded Xdebug version (or **Not installed**).
### Language level
**PHP language level** on the same page sets the version Intelephense checks against (5.6 to 8.5); newer syntax and functions are reported. **Auto** uses `config.platform.php` from `composer.json`, else the lowest version `require.php` allows. The status bar shows the current level; click it to switch.
Also on this page:
- **Include paths**: folders outside the project that Intelephense indexes too, such as a platform your module extends.
- **Additional stubs**: Intelephense stub sets on top of its defaults (for example `wordpress`). Each `ext-*` in `require` / `require-dev` of `composer.json` adds its own stubs automatically.
### Composer
In a Composer project without `vendor/`, or whose installed packages differ from `composer.lock`, a banner "Composer dependencies are not installed" appears above PHP files and `composer.json`, with **Install** (runs `composer install` on the configured PHP) and **Ignore**.
## Static analysis and formatting
- **PHPStan**: when the project has `vendor/bin/phpstan`, saved files are checked with it and its findings appear as diagnostics. The **PHPStan › Configuration file** setting picks the config; empty uses `phpstan.neon`, `phpstan.neon.dist` or `phpstan.dist.neon` in the project root.
- **PHP-CS-Fixer**: when a folder above the file has `.php-cs-fixer.php` or `.php-cs-fixer.dist.php` and the project has `vendor/bin/php-cs-fixer`, **Reformat Code** (`⌥⌘L` / `Ctrl+Alt+L`) uses PHP-CS-Fixer instead of the language server.
## Run and test
Detected when the project has `composer.json` or a PHPUnit configuration:
- `composer <script>` for each script in `composer.json` (Composer event hooks are skipped); a script named `test` is a test configuration.
- **PHPUnit** (or **Pest**, when `vendor/bin/pest` exists) when there is `phpunit.xml`, `phpunit.xml.dist`, `phpunit.dist.xml`, a `phpunit/phpunit` or `pestphp/pest` dependency, or a runner in `vendor/bin`.
- `artisan serve` in Laravel projects.
The PHPUnit run configuration type has **Target**, **Configuration file**, **Test filter** and **PHPUnit options** fields.
**Run Context Configuration** (`⌃⇧R` / `Ctrl+Shift+F10`) runs a `*Test.php` file through the PHPUnit configuration, and any other PHP file with the selected interpreter. In `*Test.php` files and files under `test/` or `tests/`, gutter icons run a single test class or method, or a Pest test. Results appear in the test tree with **Rerun Failed Tests**. See [Testing](/en/guide/testing).
## Debugging
Debugging uses PHP Debug (vscode-php-debug) with Xdebug. The selected PHP must load the Xdebug extension; the PHP settings page shows whether it does.
- **Debug** works on PHP files, PHPUnit/Pest and `artisan serve`. The IDE turns on Xdebug for the session with `-d` options and a free port, so you don't need to edit `php.ini`, and another IDE listening on 9003 doesn't conflict.
- **PHP Remote Debug (Xdebug)** is a run configuration type that listens for incoming Xdebug connections (default port 9003), for requests served elsewhere such as php-fpm or a container. Its **Start the remote side with** field shows the matching `php -dxdebug.mode=debug …` command; for web requests use the `XDEBUG_SESSION` cookie or query parameter. Set **Local root** and **Remote root** to map paths.
See [Debugging](/en/guide/debugging).
## Framework support
- **Laravel**: Laravel LSP for `config()`, `route()` and `view()` strings, and an `artisan serve` configuration.
- **WordPress** and similar platforms: add their stubs under **Additional stubs** and the platform folder under **Include paths**.
## Limitations
- No Blade or Twig template support; only `.php` files get PHP code insight.
- phpactor is not installed by the IDE.
- Run with Coverage is not available for PHPUnit.
- Debugging needs the Xdebug extension in your PHP; the IDE does not install it.
+133
View File
@@ -0,0 +1,133 @@
---
title: "Python"
description: "Python code intelligence with basedpyright and Ruff, interpreter and virtualenv detection, pytest and Django runs, and debugging with debugpy."
---
OpenIDEA edits Python through a type-checking language server (basedpyright by default) and, in projects configured for Ruff, the Ruff server next to it. The IDE finds your interpreters and virtualenvs, runs scripts, pytest and Django tests with the project interpreter, and debugs them with debugpy. For other languages, see the [support matrix](/en/languages/index).
## Language server
Python files (`.py`, `.pyi`, `.pyw`, `SConstruct`, `SConscript` and files with a `python` shebang) are served by one type checker at a time, plus Ruff:
| Server | Role | Installed with |
| ------------ | ------------------------------------------------------------------------------ | ---------------------------- |
| basedpyright | Default type checker: completion, navigation, hover, diagnostics, refactorings | pip, in a private virtualenv |
| Pyright | Used only while basedpyright is not installed | npm |
| ty | Used only while neither of the above is installed | pip, in a private virtualenv |
| Pyrefly | Used only while none of the above is installed | pip, in a private virtualenv |
| Ruff | Lint, format, organize imports, fix all | pip, in a private virtualenv |
basedpyright starts in its `standard` type-checking mode instead of its own stricter default. A `[tool.basedpyright]` section in `pyproject.toml` or a `pyrightconfig.json` in the project still wins.
Ruff starts only for files below a Ruff configuration: a `ruff.toml`, a `.ruff.toml`, or a `pyproject.toml` that contains a `[tool.ruff]` table. basedpyright has no formatter, so formatting comes from Ruff.
To pick the type checker yourself, open **Settings › Languages & Frameworks › Language Servers**, select any of the four servers and change **Language server for Python**. **Automatic** uses the first installed one in the order above; you can also choose one server or **None**.
## Setup
### Installing the servers
When you open a Python file and the server is missing, OpenIDEA shows a notification such as **basedpyright is not installed** with an **Install** button. You can also install, reinstall or point to your own executable in **Settings › Languages & Frameworks › Language Servers**.
- basedpyright, ty, Pyrefly and Ruff go into a private virtualenv inside the IDE's tools folder. The IDE uses uv when it is on `PATH`, else `python3 -m venv`. Python 3 or uv must be installed.
- Pyright is installed with npm, so it needs Node.js.
- Nothing is installed into your project's environment.
The tools folder is in the local data directory; see [Data directories and logs](/en/reference/data-directories).
### Interpreters and virtualenvs
The project interpreter is chosen in this order:
1. The interpreter set in **Settings › Languages & Frameworks › Python Interpreter**.
2. A virtualenv in the project: `.venv`, `venv`, `env` or `.env` (a folder with `pyvenv.cfg`), or a pixi environment in `.pixi/envs/`.
3. The Python version the project pins for mise or asdf (`mise.toml`, `.tool-versions` and the other mise config files).
The interpreter list on that page also shows what the IDE detects on the machine: the active `VIRTUAL_ENV`, virtualenvs made by virtualenvwrapper, pipenv and Poetry, pyenv versions, Pythons installed by uv, mise and asdf installs, conda environments (Miniconda, Anaconda, Miniforge, Mambaforge, micromamba, and every env that `conda env list` shows), and the `python` executables on `PATH`.
The selected interpreter is passed to basedpyright and Pyright (`python.pythonPath`), and its virtualenv is set as `VIRTUAL_ENV` for the language server and for run configurations.
The **Python Interpreter** item in the status bar shows the current interpreter. Click it to switch, or to open **Add New Interpreter…** and **Interpreter Settings…**.
### Creating an environment
On **Settings › Languages & Frameworks › Python Interpreter**, choose **Add Python Interpreter** and pick the **Interpreter type**:
- **Virtualenv Environment**: `python -m venv` from the base interpreter you pick.
- **uv Environment**: `uv venv <location> --python <base>` (uv must be on `PATH`).
- **Poetry Environment**: `poetry env use <base>` in the project.
- **Conda Environment**: `conda create -p <location>`, with the base interpreter's Python version.
You can also select an existing interpreter or environment folder. The new interpreter becomes the project interpreter.
### Packages
The same page lists the installed packages of the selected interpreter. You can search them, **Install** a package by name, **Upgrade** and **Uninstall**. The IDE uses the interpreter's pip, or `uv pip` for environments without pip (uv creates them that way).
Other options on the page:
- **Activate virtualenv** (section **Terminal**): new terminal sessions run the virtualenv's activate script.
- **Documentation string format** (section **Docstrings**): Plain, reStructuredText, Epytext, NumPy or Google. Docstring tags and keywords are highlighted in that format.
### Formatting
**Reformat Code** (`⌘⌥L` / `Ctrl+Alt+L`) uses Ruff. In **Settings › Languages & Frameworks › Ruff** you can set the Ruff path, a configuration directory, the **Formatting provider** (**Automatic (language server first)** or Ruff), and **Run on save**. Without a configuration directory, Ruff reads the nearest `pyproject.toml` (`[tool.ruff]`), `ruff.toml` or `.ruff.toml`.
## Run and test
OpenIDEA detects run configurations in folders that contain `pyproject.toml`, `setup.py`, `setup.cfg`, `requirements.txt`, `pytest.ini`, `tox.ini` or `manage.py`, or a project virtualenv:
| Configuration | When |
| --------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `python -m <package>` | A package in the project root or `src/` has a `__main__.py` |
| `pytest` | The project uses pytest: `pytest.ini`, `tox.ini`, `conftest.py`, `[tool:pytest]` in `setup.cfg`, pytest in `pyproject.toml` or a requirements file, or pytest in the environment |
| `manage.py runserver` | A `manage.py` is present (Django) |
| `manage.py test` | A `manage.py` is present (Django tests) |
Tools run through the project's manager: `uv run` when there is a `uv.lock`, `poetry run` with a `poetry.lock`, `pdm run` with a `pdm.lock`, the tools of `.venv` or `venv`, or `pixi run` for a pixi environment. When you select an interpreter in settings, the configurations use it directly.
In the editor, gutter icons run or debug:
- a file's `if __name__ == "__main__":` block,
- pytest functions `test_*` and classes `Test*`,
- `unittest.TestCase` subclasses and their test methods.
A test file runs with pytest even when the project declares no test setup (`python -m pytest` with the project interpreter, so pytest must be installed there). In a Django project, `TestCase` tests can also run with `manage.py test`. Results of pytest and of Django's unittest runner appear in the test tree, where **Rerun Failed Tests** runs only the failures.
You can also create **pytest** and **Django tests** configurations in **Run › Edit Configurations…**. See [Run configurations](/en/guide/run) and [Testing](/en/guide/testing).
| Action | macOS | Windows / Linux |
| --------------------------- | ----- | ---------------- |
| Run Context Configuration | `⌃⇧R` | `Ctrl+Shift+F10` |
| Debug Context Configuration | `⌃⇧D` | none |
Run with Coverage uses coverage.py in the project interpreter, and Profile uses py-spy. When one is missing, the IDE says so and offers the command that installs it (for example `uv add --dev coverage` in a uv project). See [Coverage](/en/guide/coverage) and [Profiler](/en/guide/profiler).
Jupyter notebooks (`.ipynb`) run on a Python interpreter; see [Notebooks](/en/guide/notebook).
## Debugging
Python debugging uses debugpy. If it is missing when you start a debug session, OpenIDEA offers to install it. The IDE keeps a private copy of debugpy in its tools folder and loads it into whichever interpreter runs your program, so you do not need debugpy in the project environment.
- Scripts, `python -m` modules and console scripts such as `pytest` or `uvicorn` can be debugged. Programs started through `uv run`, `poetry run`, `pdm run`, `pipenv run`, `hatch run` or `rye run` run in that manager's environment.
- Debugging `manage.py` enables Django support in debugpy.
- **Attach to Process…** (`⌥⇧F5` / `Ctrl+Alt+F5`) lists local Python processes under **Python (debugpy)**.
- The **Python Remote Debug (debugpy)** configuration attaches to a process started with `python -m debugpy --listen`, locally or on another machine, with local and remote root mapping.
Options:
- **Settings › Build, Execution, Deployment › Python Debugger**: **Attach to subprocess automatically while debugging** and **Gevent compatible**.
- **Settings › Build, Execution, Deployment › Debugger › Stepping**: **Do not step into library scripts** (debugpy's `justMyCode`).
See [Debugging](/en/guide/debugging) for breakpoints, stepping and the debugger tool window.
## Framework support
Django is the only framework with dedicated support: the `manage.py runserver` and `manage.py test` configurations, the **Django tests** configuration type (target, settings module, options), running single `TestCase` tests from the gutter, and Django mode in the debugger. Other frameworks (Flask, FastAPI and so on) run as ordinary scripts or console scripts.
## Limitations
- Only one type checker runs at a time. Ruff runs only in projects with a Ruff configuration.
- Code intelligence and diagnostics come from the language server; the IDE has no Python analysis of its own.
- Coverage and profiling need coverage.py and py-spy in your environment; the IDE does not keep its own copies of them.
- Gutter run icons come from a line-based scan, so unusual test layouts may not get an icon. They can still run through a pytest configuration.
+71
View File
@@ -0,0 +1,71 @@
---
title: "Ruby"
description: "Ruby LSP and Solargraph, interpreter detection with version managers, Rake, RSpec and Minitest runs, rdbg debugging and Rails."
---
OpenIDEA highlights Ruby files (`.rb`, `.rake`, `.gemspec`, `.ru`, `.rbi`, `.jbuilder`, `.podspec`, and `Gemfile`, `Rakefile`, `Podfile`, `Vagrantfile` and similar names) and ERB templates without setup. Code insight comes from Ruby LSP. Rake tasks, RSpec, Minitest and `rails server` are detected as run configurations, and debugging uses `rdbg` from Ruby's `debug` gem. See the [support matrix](/en/languages/index) for other languages.
## Language servers
| Server | When it runs |
| -------------------- | ------------------------------------------------------------------------------------------ |
| `ruby-lsp` (Shopify) | Default. Workspace root: `Gemfile`, `.ruby-version` or `.rubocop.yml`. |
| `solargraph` | Fallback: used only while Ruby LSP is not installed. Root: `Gemfile` or `.solargraph.yml`. |
ERB files (`.erb`, `.html.erb` and friends) are highlighted but have no language server.
## Setup
### Ruby interpreter
**Settings › Languages & Frameworks › Ruby › Ruby interpreter** chooses the Ruby that runs the `ruby`, `rake` and `rspec` configurations and whose standard library and Bundler gems appear under **External Libraries** in the project tree.
**Auto** (default) picks the install matching `.ruby-version` (searched from the project upward), else the version pinned by mise or asdf, else `ruby` on `PATH`. The list shows Rubies installed by rbenv, rvm, chruby (`~/.rubies`, `/opt/rubies`), mise and asdf, Homebrew's Ruby, and the ones on `PATH` (version-manager shims excluded).
### Installing the language server
When a Ruby file opens and no server is installed, a notification offers **Install**. Everything is also listed in **Settings › Languages & Frameworks › Language Servers**.
| Tool | Installed with |
| ----------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- |
| Ruby LSP | `gem install ruby-lsp` into the project's Ruby (it re-launches itself under Bundler, which breaks in a private gem home whose path has spaces) |
| Solargraph | `gem install` into the IDE's own tools folder |
| `rdbg` (debugger) | Not installed by the IDE: run `gem install debug` or add `debug` to the Gemfile |
Both installs need `gem` on `PATH`. Gem executables are found even when the Ruby's gem `bin` directory is not on `PATH` (as with Homebrew's Ruby).
## Run and test
Detected when the project root has a `Gemfile`, `Rakefile`, `.ruby-version`, `config.ru` or a `.gemspec`:
- **rails server** when `bin/rails` exists.
- One **Rake** configuration per task defined in the `Rakefile`, including `namespace` blocks and `Rake::TestTask` / `RSpec::Core::RakeTask` tasks. `test`, `spec`, `*:test` and `*:spec` are test configurations.
- **RSpec** when the project has `spec/`, `.rspec`, or `rspec` in the Gemfile.
- Otherwise, with a `test/` folder and no `test` Rake task, a **minitest** configuration that loads every `*_test.rb` / `test_*.rb` under `test/`.
With a `Gemfile`, these run through `bundle exec`. The **Ruby**, **Rake** and **RSpec** run configuration types have a **Run with Bundler (bundle exec)** option and a **Launcher** field for a binstub or other command.
**Run Context Configuration** (`⌃⇧R` / `Ctrl+Shift+F10`):
- A `*_spec.rb` file runs through RSpec with the file as the target.
- A `*_test.rb` / `test_*.rb` file runs through `rake test` (as `TEST=<file>`) or the minitest configuration.
- Any other `.rb` file runs with `ruby`, with the project's source folders added to the load path (`-I`).
In Minitest and Test::Unit files, gutter icons run one test class, module or test method (`--name=Class#test_x`). Minitest results go to the test tree with each test's real file and line, through a reporter the IDE loads into the run without changing the project. **Rerun Failed Tests** is available. See [Testing](/en/guide/testing).
**Run with Coverage** works for `ruby`, `bundle`, `rake`, `rspec` and `rails` runs through SimpleCov; a Bundler project needs `simplecov` in its Gemfile. See [Coverage](/en/guide/coverage).
## Debugging
**Debug** works for Ruby files and the detected Rake, RSpec, minitest and `rails server` configurations. The IDE starts `rdbg` from the project's Ruby (and `bundle` from the same install), so install the `debug` gem for that Ruby first. See [Debugging](/en/guide/debugging).
## Framework support
- **Rails**: a `rails server` configuration, the project's gems (Rails included) under External Libraries, ERB highlighting, and coverage of Rails runs. There is no Rails-specific navigation (routes, controllers to views) beyond what Ruby LSP provides.
## Limitations
- RSpec runs a whole file or suite; there are no gutter icons for single `describe` / `it` examples.
- No attach or remote debugging for Ruby processes.
- ERB templates have no code insight.
- The IDE does not install the `debug` gem.
+80
View File
@@ -0,0 +1,80 @@
---
title: "Rust"
description: "Rust code intelligence with rust-analyzer, toolchain and linter settings, cargo run and test configurations, and debugging with CodeLLDB."
---
OpenIDEA uses rust-analyzer for Rust code intelligence, runs and tests crates with cargo, and debugs them with CodeLLDB. Cargo workspaces are supported. For other languages, see the [support matrix](/en/languages/index).
## Language server
rust-analyzer serves `.rs` files. A workspace starts at the nearest `Cargo.toml` or `rust-project.json`.
While rust-analyzer is still loading a project, it has no answers for navigation yet. OpenIDEA waits for it instead of reporting "not found": **Declaration or Usages** (`⌘B` / `Ctrl+B`) right after opening a project lands once the server is ready, for up to 30 seconds.
Diagnostics come from rust-analyzer's own analysis and from `cargo check` (or Clippy) after each save.
### Installing rust-analyzer
If rust-analyzer is missing, OpenIDEA shows a **rust-analyzer is not installed** notification with an **Install** button. You can also install it, or select your own binary, in **Settings › Languages & Frameworks › Language Servers**.
- With rustup on `PATH`, the IDE runs `rustup component add rust-analyzer`.
- Without rustup, it downloads the rust-analyzer build from Open VSX into its own tools folder.
`~/.cargo/bin/rust-analyzer` is a rustup proxy that fails when the active toolchain lacks the component. In that case the IDE uses the newest installed toolchain that has rust-analyzer; if none has it, the server counts as missing and you get the install offer.
## Setup
**Settings › Languages & Frameworks › Rust** has these options:
| Option | What it does |
| ------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Toolchain location** | The toolchain cargo run configurations and rust-analyzer use (set through `PATH` and `RUSTUP_TOOLCHAIN`). **Default** is the rustup default or `PATH`. The list shows rustup toolchains (in `RUSTUP_HOME` or `~/.rustup`) and a cargo on `PATH` that rustup does not manage. |
| **Standard library** | Sources of `std` for navigation and completion (`RUST_SRC_PATH`). Detected from the toolchain when empty. |
| **External tool** (External Linters) | **Cargo Check** or **Clippy**. |
| **Run external linter on save** | rust-analyzer runs the external tool after each save and reports its diagnostics. On by default. |
| **Additional arguments** (Rustfmt) | Passed to rustfmt when reformatting, for example `--edition 2024`. |
**Reformat Code** (`⌘⌥L` / `Ctrl+Alt+L`) formats with rustfmt through rust-analyzer.
Crates and the Rust standard library appear under **External Libraries** in the Project view.
## Run and test
OpenIDEA reads `Cargo.toml` (and the members of a `[workspace]`) and detects:
| Configuration | When |
| ---------------------- | ----------------------------------------------------------------------------------------------------- |
| `cargo run <bin>` | One per binary: `src/main.rs`, `src/bin/*` and `[[bin]]` targets (with `-p <package>` in a workspace) |
| `cargo test <package>` | One per workspace member |
| `cargo test` | Always (`--workspace` in a workspace) |
| `cargo build` | Always |
In the editor, gutter icons run or debug:
- `fn main` in `src/main.rs`, `src/bin/*.rs` and `examples/*.rs`,
- functions marked `#[test]`, `#[tokio::test]`, `#[async_std::test]`, `#[actix_rt::test]`, `#[rstest]` or `#[test_case]`,
- `mod tests` and modules marked `#[cfg(test)]`, which run every test inside.
A single test runs as `cargo test -- <path> --exact`. Results appear in the test tree; **Rerun Failed Tests** runs only the failures. See [Run configurations](/en/guide/run) and [Testing](/en/guide/testing).
| Action | macOS | Windows / Linux |
| --------------------------- | ----- | ---------------- |
| Run Context Configuration | `⌃⇧R` | `Ctrl+Shift+F10` |
| Debug Context Configuration | `⌃⇧D` | none |
| Build Project | `⌘F9` | `Ctrl+F9` |
**Build Project** runs `cargo build --all-targets`, **Rebuild Project** runs `cargo clean` first, and **Build Artifacts** runs `cargo build --release`. Compiler messages go to the Build tool window and the editor.
**Run with Coverage** uses `cargo llvm-cov` for `cargo test`, `cargo run` and `cargo nextest` configurations. If it is missing, the IDE offers `cargo install cargo-llvm-cov` (the `llvm-tools-preview` rustup component is needed too). See [Coverage](/en/guide/coverage).
## Debugging
Rust debugging uses CodeLLDB. If it is missing when you start a debug session, OpenIDEA offers to install it; it downloads the CodeLLDB release from GitHub into its tools folder. The cargo run and test configurations and the gutter icons can be debugged: CodeLLDB builds the target with cargo and starts the resulting binary.
**Attach to Process…** (`⌥⇧F5` / `Ctrl+Alt+F5`) lists native processes under **Native (LLDB)**. See [Debugging](/en/guide/debugging).
## Limitations
- CodeLLDB publishes no Windows ARM64 build, so the IDE cannot install it there.
- rust-analyzer's own extension commands, such as Expand Macro or View Syntax Tree, are not available as IDE actions.
- The profiler supports `cargo run` only on Linux (with `perf`). See [Profiler](/en/guide/profiler).
+52
View File
@@ -0,0 +1,52 @@
---
title: "SQL"
description: "SQL files, the sqls language server, and running SQL against a data source with schema-aware completion."
---
SQL files (`.sql`, `.ddl`, `.dml`, `.psql`, `.pgsql`, `.mysql`) get highlighting out of the box. Most SQL support, including running statements and completing table and column names, comes from the [Database tool window](/en/guide/database): any editable SQL file can be bound to a data source and works like a query console. For other languages, see the [support matrix](/en/languages/index).
## Language server
The SQL language definition names [sqls](https://github.com/sqls-server/sqls). When it is missing, opening a SQL file shows a "sqls is not installed" notification with **Install**, which runs `go install github.com/sqls-server/sqls@latest` into the IDE's tools folder. This needs Go on your `PATH`. A `sqls` already on your `PATH` is used as is.
The IDE does not pass your data sources to sqls. Without its own configuration file, sqls knows no schema, so you can ignore the notification if you only use the database features below. To turn the server off, select sqls in **Settings › Languages & Frameworks › Language Servers** and clear **Use sqls for SQL files**.
## SQL files and data sources
Define data sources in the **Database** tool window first (see [Database](/en/guide/database)). Supported databases are SQLite, PostgreSQL, MySQL and MariaDB, Microsoft SQL Server, ClickHouse and Oracle, plus compatible products such as Amazon Aurora, Redshift, CockroachDB, TiDB and Supabase.
Every editable SQL file shows a toolbar above the editor. Its **Data source** list binds the file to one data source. With only one data source configured, files use it automatically. **Query Console** (`⌘⇧L` / `Ctrl+Shift+Q`) opens the query console of the data source selected in the Database tool window (or the only one).
The toolbar has:
- **Execute**: runs the statement at the caret, or every statement in the selection. Also **Execute SQL Statement** (`⌘↩` / `Ctrl+Enter`).
- **Explain Plan**: shows the query plan of the statement at the caret.
- **Cancel Running Statement** and **Browse Query History**.
- A transaction mode switch (**Tx: Auto** / **Tx: Manual**) with **Commit** and **Roll Back** in manual mode (not for ClickHouse).
- A schema (or, for SQL Server, database) list that sets where unqualified names resolve.
Results open in result tabs. Statements are split at `;`, respecting strings, quoted identifiers, comments, PostgreSQL dollar quoting and Oracle PL/SQL blocks.
### Parameters
Statements can contain parameters: `:name`, `?`, `?1`, `@name` and `$1`. Before running, the IDE asks for their values and inserts each value as SQL text, so string values need their quotes. Named parameters share one value; every bare `?` is asked for separately.
## Editing support
The IDE itself provides the following, using the dialect and the introspected schema of the bound data source:
- Completion of keywords, schemas, tables, views, columns, functions, aliases and join conditions. Without a data source, it completes keywords and built-in functions only.
- Parameter info for the target columns inside `INSERT … VALUES (…)`.
- Inspections for unresolved tables, columns and aliases.
- **Go to Declaration or Usages** for aliases and CTEs in the text, and for tables and columns (revealed in the Database tool window).
- **Quick Documentation** for tables and columns.
- **Reformat Code** with a built-in SQL formatter.
- Live templates for SQL contexts (statement, query and code positions).
None of this needs sqls.
## Limitations
- Without a bound data source there is no schema, so completion offers only keywords and functions, and statements cannot run.
- The dialect follows the bound data source. Unbound files are analyzed as SQLite, and there is no per-file dialect setting.
- sqls is not configured by the IDE.
+94
View File
@@ -0,0 +1,94 @@
---
title: "Unity"
description: "Use OpenIDEA as Unity's external script editor, browse a Unity project, and keep .meta files in step with your assets."
---
OpenIDEA recognizes a folder as a Unity project when it has an `Assets` folder and `ProjectSettings/ProjectVersion.txt`. It opens scripts that Unity sends it, hides Unity's caches, moves `.meta` files along with their assets, and uses the C# language server for code intelligence. No Unity package or editor plugin is needed.
## Set OpenIDEA as the external script editor
<Steps>
<Step title="Open External Tools in Unity">
In Unity, open **Edit › Preferences › External Tools** (on macOS, **Unity › Settings** or **Unity › Preferences**, then **External Tools**).
</Step>
<Step title="Choose OpenIDEA">
Under **External Script Editor**, choose **Browse** and select the OpenIDEA executable (on macOS, `OpenIDEA.app`).
</Step>
<Step title="Set the arguments">
Set **External Script Editor Args** to:
```text
$(ProjectPath) --file $(File) --line $(Line) --column $(Column)
```
Unity quotes the expanded project and file paths itself, including paths with spaces. Do not add another pair of quotes around the placeholders.
</Step>
</Steps>
Double-click a script in Unity's Project window, or a source location in the Console. OpenIDEA opens the project, or focuses the window that already has it open, opens the file and puts the caret at the line and column. Other open tabs stay where they are.
### How the arguments work
These arguments belong to the application executable:
| Argument | Meaning |
| ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| first path (`$(ProjectPath)`) | The project folder to open. With it, a cold start opens the Unity project instead of a single file. |
| `--file <path>` | The file to open. A relative path resolves against the project folder, or against the working directory when no project is given. Must name an existing file. |
| `--line <n>` | 1-based line. `0` and `-1` (what Unity sends when it has no position) mean "unknown": the file opens without a caret position. |
| `--column <n>` | 1-based column. Omitted or unknown, the caret goes to the start of the line. |
`--file=...`, `--line=...` and `--column=...` also work. Invalid arguments or a missing file are reported as an error. Without a project folder, `--file` opens in an open project window that contains the file, or in LightEdit.
If you call the executable yourself, quote paths as your shell requires. The separately installed `openidea` command has a different syntax (`--line` and `--column` come before the file, and there is no `--file`); see [Command line](/en/reference/cli).
<Note>
On Windows, run Unity and OpenIDEA with the same permissions (both elevated or both not). If a running OpenIDEA does
not react to a double-click, restart both with matching permissions and try again.
</Note>
## The Unity project view
When a Unity project is open, the **Project** tool window switches to the **Unity** pane. Its root shows the editor version read from `ProjectVersion.txt` (for example `Unity 6000.0.62f1`).
The Unity pane hides:
- `.meta` files;
- generated folders at the project root: `Library`, `Temp`, `Obj`, `obj`, `Logs`, `Build`, `Builds`, `UserSettings`, `.vs`, `.idea`, `.vscode`;
- generated solution and project files at the root (`.csproj`, `.sln`, `.slnx` and similar).
To show them, open the pane's options menu and turn on **Show Meta Files** or **Show Generated Files** under **Appearance**. The **Project** and **Project Files** panes always show everything.
`Assets` and `Packages` are source folders and `ProjectSettings` is a resources folder. The generated folders listed above are excluded from indexing, search and file watching, even before Unity creates them, so `Library/PackageCache` does not slow the IDE down or show up in search results.
## Assets and .meta files
File operations on assets under `Assets` keep Unity's metadata consistent:
- **Rename and move** take the `.meta` file with the asset. This also applies to file moves caused by refactorings.
- **Delete** removes the asset and its `.meta` file together; Local History keeps a copy of both.
- **Copy** gives the copies new GUIDs and rewrites references between the copied files (in text-serialized YAML assets and in `.asmdef` / `.asmref` files), so the copy does not clash with the original. Binary-serialized assets (prefabs, scenes, materials and so on saved in binary mode) cannot be copied this way; switch Unity's asset serialization to **Force Text** first.
- The `Assets` folder itself cannot be moved, copied or deleted, and selecting only a `.meta` file whose asset exists is rejected: select the asset instead.
- New file names are checked against existing `.meta` files too.
When Git asks whether to add a new file you created in the IDE, the `.meta` file Unity generates for it is added along with it.
## Code intelligence
C# in a Unity project uses the same language server as any other C# project, csharp-ls; see [C# and .NET](/en/languages/csharp) for installation. A Unity project is a workspace root for it even before a solution exists.
csharp-ls reads the `.sln` and `.csproj` files that Unity generates. When they are missing, the Unity pane shows:
> C# project files are missing. In Unity, select an external script editor in Preferences > External Tools, then regenerate project files.
The IDE notices when Unity writes those files and picks them up without reopening the project.
**Navigate › Declaration or Usages** on a Unity type such as `MonoBehaviour` opens the decompiled source read-only. Decompiled files are not checked for errors, since they often use a different C# language version than your project.
## Limitations
- OpenIDEA does not generate Unity's C# project files; Unity has to.
- There is no Unity debugger integration: you cannot attach to the Unity editor or a player from OpenIDEA.
- There are no Unity-specific inspections, run configurations or ShaderLab support.
- Opening files from Unity works for local projects only, not projects in WSL.
+85
View File
@@ -0,0 +1,85 @@
---
title: "API reference"
description: "Every interface of the openidea:plugin WIT package, with a one-line summary and a link to its source."
---
The plugin API is the WIT package `openidea:[email protected]` in [`wit/`](https://github.com/l0ng-ai/openidea/tree/main/wit). The WIT files are the reference: each interface, type and function is documented in comments there. 1.5.0 is the version on `main`; the latest released version is 1.4.0, whose WIT is kept in [`sdk/rust/openidea-plugin/tests/api/released/`](https://github.com/l0ng-ai/openidea/tree/main/sdk/rust/openidea-plugin/tests/api/released).
<Info>
A generated API reference is planned. Until then, use the WIT files below, `cargo doc` for the Rust SDK
([`sdk/rust/openidea-plugin`](https://github.com/l0ng-ai/openidea/tree/main/sdk/rust/openidea-plugin)), and the table
mapping every SDK function to its WIT function and capability in
[`plugins/README.md`](https://github.com/l0ng-ai/openidea/blob/main/plugins/README.md#the-api).
</Info>
## Worlds
[`plugin.wit`](https://github.com/l0ng-ai/openidea/blob/main/wit/plugin.wit) defines the worlds: the base `plugin` world (every import, plus the `lifecycle`, `commands` and `event-handler` exports) and one optional world per extra export (`plugin-providers`, `plugin-views`, `plugin-hover`, …). See [Architecture and sandbox](/en/plugins/architecture#worlds-imports-and-exports).
## Host interfaces (imports)
The plugin calls these. Most are gated by a [capability](/en/plugins/capabilities).
| Interface | File | Summary |
| ---------------- | ------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `types` | [types.wit](https://github.com/l0ng-ai/openidea/blob/main/wit/types.wit) | Shared types: positions (0-based lines, UTF-16 columns), ranges, errors, the `extension` carrier for later additions |
| `log` | [host.wit](https://github.com/l0ng-ai/openidea/blob/main/wit/host.wit) | Writes to the IDE log, tagged with the plugin id |
| `ui` | [host.wit](https://github.com/l0ng-ai/openidea/blob/main/wit/host.wit) | Notifications (also for one project, API 1.5), status bar items, context keys, pickers, input boxes, confirmations, folder chooser, clipboard, opening URLs, the UI language |
| `storage` | [host.wit](https://github.com/l0ng-ai/openidea/blob/main/wit/host.wit) | The plugin's persistent key-value storage (64 MiB) |
| `command-runner` | [host.wit](https://github.com/l0ng-ai/openidea/blob/main/wit/host.wit) | Runs IDE actions and other plugins' commands (API 1.4, `ide.commands`) |
| `output` | [host.wit](https://github.com/l0ng-ai/openidea/blob/main/wit/host.wit) | Named, read-only Output tabs per plugin (API 1.4) |
| `workspace` | [workspace.wit](https://github.com/l0ng-ai/openidea/blob/main/wit/workspace.wit) | Open projects: reading, writing, finding and searching files, symbols, the project of a path, opening a project |
| `editor` | [workspace.wit](https://github.com/l0ng-ai/openidea/blob/main/wit/workspace.wit) | Text editors: document, selections, versioned edits, reveal; open editors and opening files |
| `invocation` | [workspace.wit](https://github.com/l0ng-ai/openidea/blob/main/wit/workspace.wit) | Where a command or event came from: its project, editor and selected paths |
| `vcs` | [workspace.wit](https://github.com/l0ng-ai/openidea/blob/main/wit/workspace.wit) | Git for a project's repository: status, remotes, fetch, log, blame, diff |
| `fs` | [fs.wit](https://github.com/l0ng-ai/openidea/blob/main/wit/fs.wit) | Files outside the open projects (`fs.read:` / `fs.write:`) and the plugin's private directory |
| `decorations` | [decorations.wit](https://github.com/l0ng-ai/openidea/blob/main/wit/decorations.wit) | Editor annotations and project-tree badges (API 1.4) |
| `commit` | [commit.wit](https://github.com/l0ng-ai/openidea/blob/main/wit/commit.wit) | The commit being prepared and its message |
| `events` | [events.wit](https://github.com/l0ng-ai/openidea/blob/main/wit/events.wit) | Subscribing to workbench events, optionally filtered by path |
| `tasks` | [events.wit](https://github.com/l0ng-ai/openidea/blob/main/wit/events.wit) | Long-running work with a progress indicator and a longer time budget |
| `diagnostics` | [io.wit](https://github.com/l0ng-ai/openidea/blob/main/wit/io.wit) | Publishing problems for files and reading the Problems view |
| `net` | [io.wit](https://github.com/l0ng-ai/openidea/blob/main/wit/io.wit) | HTTP(S) requests (`net:` / `net.http:` / `net:ask`) |
| `secrets` | [io.wit](https://github.com/l0ng-ai/openidea/blob/main/wit/io.wit) | Secrets in the OS keychain, used only through `{{secret:<name>}}` in requests |
| `process` | [io.wit](https://github.com/l0ng-ai/openidea/blob/main/wit/io.wit) | Running approved programs once, with an environment, or behind a handle |
| `ipc` | [io.wit](https://github.com/l0ng-ai/openidea/blob/main/wit/io.wit) | Local sockets and named pipes on the project machine (API 1.4) |
| `auth` | [io.wit](https://github.com/l0ng-ai/openidea/blob/main/wit/io.wit) | OAuth loopback redirects received on the desktop (API 1.4) |
| `views` | [views.wit](https://github.com/l0ng-ai/openidea/blob/main/wit/views.wit) | Refreshing and revealing views, messages to web views, terminals, content tabs, opening locations |
| `view-types` | [views.wit](https://github.com/l0ng-ai/openidea/blob/main/wit/views.wit) | The declarative view model: trees, lists, forms, tables, messages, `html` bodies, view events |
| `settings` | [views.wit](https://github.com/l0ng-ai/openidea/blob/main/wit/views.wit) | The plugin's own settings, project over global, or the schema default |
| `run` | [run.wit](https://github.com/l0ng-ai/openidea/blob/main/wit/run.wit) | Starting the plugin's run configurations in the Run tool window (API 1.4) |
| `run-targets` | [run-targets.wit](https://github.com/l0ng-ai/openidea/blob/main/wit/run-targets.wit) | Project run targets that wrap commands, owned by the plugin (API 1.4) |
| `database` | [database.wit](https://github.com/l0ng-ai/openidea/blob/main/wit/database.wit) | The plugin's own project data sources and their credentials (API 1.4) |
| `mcp` | [mcp.wit](https://github.com/l0ng-ai/openidea/blob/main/wit/mcp.wit) | The command line of the built-in MCP server's stdio bridge, never its URL or token (API 1.4) |
| `detail-types` | [details.wit](https://github.com/l0ng-ai/openidea/blob/main/wit/details.wit) | What the pane beside the Services tree shows for a plugin's node |
## Plugin interfaces (exports)
The IDE calls these. The first three are in every code plugin; the rest are optional and each comes with its own world.
| Interface | File | Summary |
| -------------------------- | ------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------- |
| `lifecycle` | [exports.wit](https://github.com/l0ng-ai/openidea/blob/main/wit/exports.wit) | `activate` and `deactivate` of an instance |
| `commands` | [exports.wit](https://github.com/l0ng-ai/openidea/blob/main/wit/exports.wit) | Runs one of the plugin's declared commands |
| `event-handler` | [exports.wit](https://github.com/l0ng-ai/openidea/blob/main/wit/exports.wit) | Receives batches of subscribed events |
| `syntax-provider` | [exports.wit](https://github.com/l0ng-ai/openidea/blob/main/wit/exports.wit) | Highlighting, outline and folding for plugin languages without a grammar |
| `run-target-validation` | [exports.wit](https://github.com/l0ng-ai/openidea/blob/main/wit/exports.wit) | Checks a launch through one of the plugin's run targets before it starts (API 1.5) |
| `providers` | [providers.wit](https://github.com/l0ng-ai/openidea/blob/main/wit/providers.wit) | Completion, inline completion, code actions, navigation and line markers for declared languages |
| `language-features` | [language-features.wit](https://github.com/l0ng-ai/openidea/blob/main/wit/language-features.wit) | Formatting, signature help, highlights, links, code lenses, folds, colours, symbols, gutter actions and more (API 1.4) |
| `injections` | [injections.wit](https://github.com/l0ng-ai/openidea/blob/main/wit/injections.wit) | Ranges of embedded languages inside a document (API 1.4) |
| `hover-provider` | [hover.wit](https://github.com/l0ng-ai/openidea/blob/main/wit/hover.wit) | Markdown added to the hover of any language |
| `inlay-hints-provider` | [inlay.wit](https://github.com/l0ng-ai/openidea/blob/main/wit/inlay.wit) | Inline hints in any language |
| `semantic-tokens-provider` | [semantic.wit](https://github.com/l0ng-ai/openidea/blob/main/wit/semantic.wit) | Semantic colouring in any language |
| `diagnostics-provider` | [diagnostics.wit](https://github.com/l0ng-ai/openidea/blob/main/wit/diagnostics.wit) | Problems in the current, possibly unsaved document |
| `file-actions` | [file-actions.wit](https://github.com/l0ng-ai/openidea/blob/main/wit/file-actions.wit) | Quick fixes and refactorings that create, rename or delete files |
| `document-indexer` | [indexing.wit](https://github.com/l0ng-ai/openidea/blob/main/wit/indexing.wit) | Cross-file definitions, references and gutter markers, indexed in the background |
| `endpoints-provider` | [endpoints.wit](https://github.com/l0ng-ai/openidea/blob/main/wit/endpoints.wit) | A project's HTTP endpoints for Search Everywhere and the HTTP client |
| `view-provider` | [views.wit](https://github.com/l0ng-ai/openidea/blob/main/wit/views.wit) | Content and events of declared views and custom editors |
| `detail-provider` | [details.wit](https://github.com/l0ng-ai/openidea/blob/main/wit/details.wit) | The detail pane of the plugin's nodes in the Services tool window |
| `commit-checker` | [commit.wit](https://github.com/l0ng-ai/openidea/blob/main/wit/commit.wit) | Before-commit checks |
| `run-configurations` | [run.wit](https://github.com/l0ng-ai/openidea/blob/main/wit/run.wit) | Dynamic run configurations for a project (API 1.4) |
| `test-results` | [run.wit](https://github.com/l0ng-ai/openidea/blob/main/wit/run.wit) | Parsing test output and selecting tests to rerun (API 1.4) |
| `mcp-provider` | [mcp.wit](https://github.com/l0ng-ai/openidea/blob/main/wit/mcp.wit) | Tools the plugin contributes to the built-in MCP server (API 1.4) |
## Other languages
The SDK carries an identical copy of the WIT files in [`sdk/rust/openidea-plugin/wit`](https://github.com/l0ng-ai/openidea/tree/main/sdk/rust/openidea-plugin/wit). To write a plugin in another language, generate bindings for the `plugin` world (or an optional world) with your language's component-model tooling, build the component, and validate it with `openidea-plugin check`.
+102
View File
@@ -0,0 +1,102 @@
---
title: "Architecture and sandbox"
description: "How plugin code runs: WebAssembly components, WIT worlds, host calls, the sandbox, resource limits and the one-thread-per-plugin model."
---
A code plugin is a WebAssembly **component** (component model, WASI preview 2) that runs inside the IDE in [wasmtime](https://wasmtime.dev). It has no direct access to files, the network, the environment or processes. Everything it does outside pure computation goes through functions the IDE provides, and each of those checks the [capabilities](/en/plugins/capabilities) the user granted.
## The pieces
| Part | Where | Role |
| ---------- | ---------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- |
| Plugin API | [`wit/`](https://github.com/l0ng-ai/openidea/tree/main/wit) | The contract: the WIT package `openidea:[email protected]`, its interfaces and worlds |
| Rust SDK | [`sdk/rust/openidea-plugin`](https://github.com/l0ng-ai/openidea/tree/main/sdk/rust/openidea-plugin) | Typed wrappers over the WIT, the `Plugin` trait, `register_plugin!`, and a mock host for `cargo test` |
| Packages | [`crates/ide-extension`](https://github.com/l0ng-ai/openidea/tree/main/crates/ide-extension) | The manifest, capabilities and their risk, the grant ledger, contribution points, `.oiplugin` packages and the marketplace index |
| Runtime | [`crates/ide-plugin`](https://github.com/l0ng-ai/openidea/tree/main/crates/ide-plugin) | The wasmtime engine, one `Store` per plugin, limits, and one actor thread per active plugin |
| Host | [`crates/ide-host/src/plugins`](https://github.com/l0ng-ai/openidea/tree/main/crates/ide-host/src/plugins) | Implements the host API, installs and supervises plugins; the same code runs in the desktop app and in `openidea-server` |
For how the host is put together internally, see [Plugin host](/en/contribute/architecture/plugin-host) in the contributor docs.
## Worlds, imports and exports
The plugin API is one WIT package. Its base world, `plugin`, lists what a component may **import** (host interfaces it calls) and what it must **export** (plugin interfaces the IDE calls):
- Imports: `log`, `ui`, `storage`, `fs`, `workspace`, `editor`, `decorations`, `invocation`, `vcs`, `commit`, `events`, `tasks`, `diagnostics`, `net`, `secrets`, `process`, `ipc`, `output`, `auth`, `command-runner`, `views`, `run`, `run-targets`, `database`, `mcp`, `settings`.
- Exports: `lifecycle` (activate, deactivate), `commands` (run a command), `event-handler` (a batch of events).
Optional features are separate worlds that include `plugin` and add one export. A plugin exports one only when its manifest declares the matching contribution:
| World | Adds export | Declared with | SDK `register_plugin!` flag |
| ------------------------------ | -------------------------- | ---------------------------------------------------------- | --------------------------- |
| `plugin-syntax` | `syntax-provider` | a language without a grammar | `syntax` |
| `plugin-providers` | `providers` | `[[contributes.providers]]` | `providers` |
| `plugin-views` | `view-provider` | `views`, `customEditors` | `views` |
| `plugin-indexing` | `document-indexer` | `documentIndexers` | `indexing` |
| `plugin-semantic-tokens` | `semantic-tokens-provider` | `semantic-tokens` provider kind | `semantic_tokens` |
| `plugin-hover` | `hover-provider` | `hover` provider kind | `hover` |
| `plugin-inlay-hints` | `inlay-hints-provider` | `inlay-hints` provider kind | `inlay_hints` |
| `plugin-diagnostics` | `diagnostics-provider` | `diagnostics` provider kind | `diagnostics` |
| `plugin-file-actions` | `file-actions` | `code-actions` provider kind | `file_actions` |
| `plugin-details` | `detail-provider` | views with `location = "services"` | `details` |
| `plugin-commit-checks` | `commit-checker` | `commitChecks` | `commit_checks` |
| `plugin-endpoints` | `endpoints-provider` | `endpoints` | `endpoints` |
| `plugin-run-configurations` | `run-configurations` | `runConfigurations` | `run_configurations` |
| `plugin-test-results` | `test-results` | run detectors with `framework = "plugin"` | `test_results` |
| `plugin-language-features` | `language-features` | API 1.4 provider kinds | `language_features` |
| `plugin-injections` | `injections` | `injections` provider kind | `injections` |
| `plugin-mcp` | `mcp-provider` | `mcpTools` | `mcp` |
| `plugin-run-target-validation` | `run-target-validation` | nothing; called for the plugin's own run targets (API 1.5) | `run_target_validation` |
In Rust you list the flags: `register_plugin!(MyPlugin, providers, views)`. Each world also has an export-only twin (`syntax`, `editor-providers`, `views-provider`, …) that component tools merge with `plugin`.
## Calls in both directions
- **IDE → plugin** (exports): the IDE calls `commands.run` when the user runs a command, `event-handler.handle` with batches of subscribed events, provider functions when the editor needs completion, hover and so on, and `view-provider.render` to draw a view.
- **Plugin → IDE** (imports): inside any of those calls the plugin calls host functions such as `editor.text-editor.document`, `workspace.project.read-file` or `net.fetch`. A host function the plugin lacks a capability for returns `error::denied("<capability>")`; the plugin keeps running and can degrade.
All 1.x calls are **synchronous**: a host call blocks the plugin's instance until it returns, and editor providers receive the whole document text with each call. Positions are 0-based lines and UTF-16 columns, as in the editor and LSP (the syntax provider uses UTF-8 byte offsets, which the IDE converts).
## The sandbox
Each plugin gets its own wasmtime `Store`:
- no preopened directories, no environment variables, no network, no inherited stdin or stdout;
- from WASI, only clocks and randomness;
- stderr is captured: `eprintln!` output goes to the IDE log, and a panic's message is shown with the crash.
Files, network, processes, sockets and secrets are reachable only through host functions, each gated by a capability. Paths are resolved to their real location before they are checked, so symlinks cannot escape a project root or a granted directory.
Web views and web custom editors run in an `<iframe sandbox="allow-scripts">` loaded from the IDE's own `openidea-plugin` URL scheme: an opaque origin with no network, no cookies or storage, no IDE API, and only the page's own files. The page talks to the plugin's code through `postMessage`.
## Limits
Every call into a plugin is bounded:
| Limit | Value |
| ----------------------------------------------------------------------------- | ---------------------------------------------------------------------------- |
| CPU | A fuel budget of about 5·10⁹ wasm instructions per call |
| Commands, events, activation, deactivation | 5 s wall clock |
| A call that opened a task (`tasks.begin`) | 60 s, renewed each time the task reports progress or checks for cancellation |
| Editor providers (syntax, completion, code actions, navigation, line markers) | Results later than 50 ms are dropped; the call is interrupted at 500 ms |
| Memory | 256 MiB of linear memory |
| Idle instance | Reclaimed after 5 minutes without calls |
Waiting for the user (a picker, an input box, a confirmation) does not count against the time limit. Individual host functions have their own caps (file sizes, number of handles, response sizes), listed in [`plugins/README.md`](https://github.com/l0ng-ai/openidea/blob/main/plugins/README.md).
## Threads and crashes
Each active plugin runs on its own thread (an actor that owns the plugin's runtime). Callers queue jobs and wait on a reply, so calls into one instance never run concurrently. An editor caller can stop waiting at its soft deadline while the call goes on until the hard deadline; calls can be cancelled from any thread. Wall-clock limits use wasmtime's epoch interruption.
A panic, trap, permission violation or exceeded limit drops the instance. The user is told once, and the plugin starts afresh the next time it is needed, after a pause that doubles with each crash in a row (1 s, 2 s, …, at most 60 s). After three crashes in a row the plugin is marked **Failed** for the session, with the reason in **Settings › Plugins**; **Restart**, toggling it off and on, or **Reload from disk** starts it again. A call that returns normally resets the count. A crashing plugin never affects the IDE itself.
**Settings › Plugins** shows each plugin's activations, recent call latency (p95), crashes, late editor results and idle reclaims.
## Remote projects
Plugin code runs where the project is. When a window opens a remote project, the desktop sends its enabled code plugins and their grants to `openidea-server`, which runs them next to the files: file reads, searches, processes, network requests and diagnostics all act on the remote machine, and `process:` programs are resolved there. Secrets stay in the desktop's keychain; the server asks for one when a request needs it. Views, notifications, status items and terminals of remote plugins show in the window as usual.
Current limits: project plugins (`.openidea/plugins`) don't run in remote projects yet, and a plugin's storage on the server starts empty.
## Async and API 2.0
The 1.x API is synchronous by design. WASI 0.3 async components are planned for API 2.0, behind an adapter in the host that keeps 1.x plugins running. See [API versions](/en/plugins/versioning).
+96
View File
@@ -0,0 +1,96 @@
---
title: "Permissions and capabilities"
description: "The capability model: what a plugin declares, what the user approves, and what each capability unlocks."
---
Plugin code can only do what its **capabilities** allow. A plugin declares them in `plugin.toml`; the user approves them when installing, and again when a new version asks for more. Calling a gated function without its capability returns `Error::Denied("<capability>")`. The plugin keeps running and can degrade (Git Toolbox still shows the branch without `vcs.write`).
```toml
[capabilities]
required = ["project.read", "editor.write"]
optional = ["net:api.example.com", "secrets"]
```
Logging, notifications, status bar items, tasks and runtime contributions need no capability.
## The permission dialog
Installing a package, or allowing a linked or project plugin to run, opens a dialog (_Install Word Tools 1.2.0?_ or _Allow Word Tools to run?_) that lists every requested capability with:
- its id (`net:api.example.com`) and a description of what it allows;
- a risk badge: **Low**, **Medium** or **High**;
- a checkbox for **optional** capabilities (low-risk ones are pre-checked) or a bullet for **required** ones, which you accept by installing.
When any granted capability is high-risk, the dialog warns that _High-risk permissions are equivalent to running arbitrary code on this machine._ It also warns when the package is not signed, or is signed with a different key than the installed version (see [Policy and security](/en/plugins/publishing/policy)).
Later:
- An update that asks for nothing new installs with the grants the installed version has. One that asks for more shows **Needs permission** in **Settings › Plugins** and stays off until you click **Review Permissions…** and allow it.
- You can revoke optional capabilities in the plugin's details in **Settings › Plugins**.
- Linked plugins (`openidea-plugin link`) are granted per folder; project plugins (`.openidea/plugins`) run only in trusted projects and only after you allow them.
## Capability reference
| Capability | Risk | Unlocks | API |
| ------------------- | ------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------- |
| `project.read` | Low | `Project::root`, `read_file`, `find_files`, `symbols`, `search_text`, `host::projects`, publishing diagnostics, `problems`, opening editors, decorations | 1.0 |
| `project.write` | Medium | `Project::write_file`: creates and replaces text files inside the open projects | 1.3 |
| `editor.read` | Low | `TextEditor::document`, `selections`, `host::open_editors` | 1.0 |
| `editor.write` | Medium | `TextEditor::apply_edits` | 1.0 |
| `vcs.read` | Low | `vcs_status`, `vcs_remotes`, `vcs_log`, `vcs_blame`, `vcs_diff`, the pending commit, before-commit checks | 1.0 |
| `vcs.write` | Medium | `vcs_fetch` (trusted projects only), `host::set_commit_message` | 1.0 |
| `storage` | Low | `host::storage`, 64 MiB per plugin | 1.0 |
| `events` | Low | File opened, closed and saved, files changed, editor focused, repository changed, settings changed | 1.0 |
| `events.input` | Medium | `action-performed` and `input-mode-changed` events (keystrokes and editor input) | 1.0 |
| `clipboard` | Medium | `host::copy_to_clipboard` and the clipboard API in the plugin's web views | 1.0 |
| `secrets` | Medium | `{{secret:<name>}}` in request headers, `secret_is_set`, `secret_set`, `secret_delete` | 1.0 |
| `net:<host>` | Medium | `host::fetch` over HTTPS (and loopback HTTP) to that host; `*.example.com` matches subdomains | 1.0 |
| `net:ask` | Medium | Ask the user at the first request to an HTTPS host that isn't declared | 1.5 |
| `net.http:<host>` | Medium | Unencrypted HTTP to that host | 1.4 |
| `database.sources` | Medium | Manage only the plugin's own project database sources and write their credentials; trusted projects only | 1.4 |
| `fs.read:<path>` | High | `host::fs` reads outside the projects, under that path | 1.0 (the `fs` interface: 1.3) |
| `fs.write:<path>` | High | `host::fs` writes outside the projects, under that path; does not imply reading | 1.3 |
| `process:<program>` | High | Run that program: `run_process`, `run_command`, `start`, terminals, language servers, debug adapters, runs | 1.0 |
| `ipc:<path>` | High | Local Unix sockets or Windows named pipes on the project machine | 1.4 |
| `ide.commands` | High | Run any IDE action or another plugin's command as if the user chose it, including running programs, committing and pushing. The plugin's own commands are exempt. | 1.4 |
The **API** column is the oldest plugin API the manifest may admit when it declares the capability. For example, a manifest with `net.http:` must require `engines.openidea = "^1.4"` or later; installing and `openidea-plugin check` refuse it otherwise.
## Processes
`process:<program>` is the most powerful capability: it is equivalent to running arbitrary code on the machine.
- Declare a program name (`process:checkstyle`). On grant, the IDE resolves it to an absolute path on the `PATH` of the machine where the project is, and records the file's content hash. It checks both again right before every start; a replaced file is refused until the user approves it again.
- In **Settings › Plugins** the user can choose a different executable for a `process:` grant, globally or for the current project. The new choice is approved and pinned the same way. The plugin keeps calling its declared program name.
- `process:` grants are also derived from what a contribution starts: a language's `language-servers` and `debugger`, and a run detector's `command`. These are optional: refusing one keeps the rest of the plugin working.
- Processes run in the project root of a trusted project. Plugins can only add environment variables, never replace or remove them.
## Files outside the project
- `fs.read:<path>` grants reading a directory (at any depth) or one file outside the open projects. The path is resolved to its real location before it is compared, so a symlink out of the granted directory is refused, and `/opt/see` does not cover `/opt/seeyon`. Granting a whole file system (`/`, `C:\`) is refused. `~/` stands for the home directory.
- `fs.write:<path>` grants creating and replacing files under that path. Writes are atomic, keep the file's encoding and BOM, are capped at 16 MiB, and are refused while an editor holds unsaved changes for the same file.
- `host::fs::plugin_dir()` returns a private directory that belongs only to this plugin. Reading and writing inside it needs no capability. It survives reloads, updates and restarts, and is removed on uninstall.
## Network
- `net:<host>` allows HTTPS (and HTTP to loopback) to that host on any port. `*.example.com` matches any subdomain, not the apex.
- `net.http:<host>` allows unencrypted HTTP; it does not imply HTTPS. An HTTPS → HTTP redirect also needs `net.http:`.
- Each redirect is checked before connecting. Responses are limited to 8 MiB. Requests run on the machine where the project is.
- `net:ask` (API 1.5) lets a plugin reach HTTPS hosts it didn't list. The first request to a new host asks the user, naming the plugin and the host, with **Allow once**, **Always allow** and **Deny** (remote sessions offer **Allow for this session** and **Deny**). "Always" is remembered per exact host and listed in the plugin's permissions, where it can be removed. Calls without a window (scheduled work, event handlers, providers) never ask and get `denied`. Plain HTTP never goes through this flow.
## Secrets
Secrets are declared in the manifest and entered by the user in **Settings › Plugins**; they are kept in the system keychain.
```toml
[[contributes.secrets]]
name = "api-key"
label = "API key"
description = "Used only with the OpenAI backend."
```
The plugin never reads a secret. It writes `{{secret:api-key}}` in a request header and the host fills it in on the way out. `host::secret_is_set` says whether the user entered one. API 1.4 adds `secret_set` and `secret_delete` (for example to store an OAuth token), which still never return the value. In remote projects secrets stay in the desktop's keychain.
## Trusted projects
Some functions only work in trusted projects, whatever the plugin was granted: running processes, `vcs_fetch` and the newer Git reads, database sources, run targets and project plugins. In a project opened in Restricted Mode, the marketplace can be browsed but plugin code that runs cannot be changed.
+60
View File
@@ -0,0 +1,60 @@
---
title: "Example plugins"
description: "A guided tour of the plugins in plugins/official: what each one shows and which parts of the API it uses."
---
The plugins in [`plugins/official/`](https://github.com/l0ng-ai/openidea/tree/main/plugins/official) are built and signed by the OpenIDEA release workflow and listed in the marketplace under the `openidea.*` ids. They are kept working as the API changes, and they are meant to be read: each one shows how to use part of the API. Build your own plugin in your own repository and use these as a reference.
Each folder has a `plugin.toml`, the Rust source in `src/`, native tests, and a checked-in component in `dist/plugin.wasm`.
## Data-only plugins
| Plugin | What it shows | Uses |
| ----------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------- |
| [harbor-night](https://github.com/l0ng-ai/openidea/tree/main/plugins/official/harbor-night) | A dark colour theme with a UI theme and an editor colour scheme. The `theme` template of `openidea-plugin new` is copied from it. | `contributes.themes` with `editor-scheme` |
| [material-icons](https://github.com/l0ng-ai/openidea/tree/main/plugins/official/material-icons) | A complete file and folder icon theme with more than 100 icons. | `contributes.iconThemes` |
## Code plugins
| Plugin | What it shows | Uses |
| ----------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------- |
| [string-tools](https://github.com/l0ng-ai/openidea/tree/main/plugins/official/string-tools) | 26 text commands (case conversion, Base64/URL/HTML/JSON encoding, line sorting) applied to every selection at once. | Commands, `editor.read`/`editor.write`, versioned multi-cursor edits |
| [key-promoter](https://github.com/l0ng-ai/openidea/tree/main/plugins/official/key-promoter) | Shows the shortcut of an action you ran with the mouse and counts missed shortcuts. | `on-event:action-performed`, `events.input`, `storage` |
| [git-toolbox](https://github.com/l0ng-ai/openidea/tree/main/plugins/official/git-toolbox) | Fetches repositories in the background and shows ahead/behind counts in the status bar. | `on-schedule`, events, tasks, `vcs.read`/`vcs.write`, status bar items |
| [checkstyle](https://github.com/l0ng-ai/openidea/tree/main/plugins/official/checkstyle) | Runs Checkstyle on save; problems in the editor and Problems view, quick fixes. | `process:checkstyle`, `on-event:file-saved`, diagnostics, `code-actions` provider |
| [license-header](https://github.com/l0ng-ai/openidea/tree/main/plugins/official/license-header) | Reports files without a license header when they open, adds it with a quick fix, scans the project in a task. | File events, `code-actions` provider, menus, settings, tasks |
| [ai-completion](https://github.com/l0ng-ai/openidea/tree/main/plugins/official/ai-completion) | Inline (ghost text) completion from a local Ollama model or OpenAI. | `inline-completion` provider, `net:` grants, `secrets` |
| [mybatis](https://github.com/l0ng-ai/openidea/tree/main/plugins/official/mybatis) | Navigation between MyBatis mapper interfaces and XML, gutter markers, completion across files. | `definition`, `line-markers` and `completion` providers, `project.read` |
| [csv-editor](https://github.com/l0ng-ai/openidea/tree/main/plugins/official/csv-editor) | A table editor for CSV/TSV that edits the same text document as the text view, with shared undo. | `customEditors` with a declarative table, `view-provider` export |
| [regex-tester](https://github.com/l0ng-ai/openidea/tree/main/plugins/official/regex-tester) | A tool window that tests a regular expression against the editor's selection. | `views` with an `html` body that talks to the code, settings, menus |
| [graphviz](https://github.com/l0ng-ai/openidea/tree/main/plugins/official/graphviz) | A DOT language with a live preview beside the text; problems come from the rendering page. | Web view custom editor (`web = "web/index.html"`), a language, file icons and templates |
| [solidity](https://github.com/l0ng-ai/openidea/tree/main/plugins/official/solidity) | Solidity support: detection, highlighting, structure and folding without a tree-sitter grammar, plus Forge/Hardhat run configurations. | `languages`, `syntax-provider` export (world `plugin-syntax`), `runDetectors` |
<Note>
The graphviz web page is built from `web-src/` into `web/` by `plugins/build.sh` and is not checked in. Run
`plugins/build.sh official/graphviz` before installing it from a checkout.
</Note>
## Small API 1.4 examples
These four are minimal examples of API 1.4 extension points rather than feature plugins. They declare `engines.openidea = "^1.4"`, and the two that run programs ship fake executables you replace with a real tool. Each folder's README says how to try it.
| Plugin | What it shows | Uses |
| ----------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------- |
| [blame-annotations](https://github.com/l0ng-ai/openidea/tree/main/plugins/official/blame-annotations) | **Example: Show Blame Annotations** draws Git blame after each line, also from a code lens. | `vcs.blame`, `decorations.set`, `code-lenses` language feature |
| [container-runner](https://github.com/l0ng-ai/openidea/tree/main/plugins/official/container-runner) | Registers a project run target that wraps commands (a DDEV-style wrapper), opens a keyed terminal, writes an Output channel. | `run-targets.register`, terminal sessions, `output`, window status items |
| [test-runner](https://github.com/l0ng-ai/openidea/tree/main/plugins/official/test-runner) | Dynamic run configurations, a test output parser and gutter run buttons for a toy test format. | `run-configurations` and `test-results` exports, `run.start`, `gutter-actions` |
| [todo-finder](https://github.com/l0ng-ai/openidea/tree/main/plugins/official/todo-finder) | **Example: Find TODOs** lists matches in a tool window, also from a search result's context menu. | `project.search-text`, `diagnostics.problems`, list views, `searchResults.context` menu |
## Test fixtures
[`crates/ide-plugin/tests/fixtures/`](https://github.com/l0ng-ai/openidea/tree/main/crates/ide-plugin/tests/fixtures) holds small plugins used by the host's integration tests. Several are the shortest working example of one feature: `settings-pages` (multi-page settings with a table), `file-templates`, `indexer` (background indexing, Find Usages and Rename), `symbols`, `diagnostics`, `hover`, `inlay-hints`, `semantic-tokens`, `files-events`, `private-dir`, `web-pty`, `web-log` and `web-content`.
## Build one from a checkout
```sh
plugins/build.sh official/string-tools
cargo test --manifest-path plugins/official/string-tools/Cargo.toml
```
Then install the folder with **Settings › Plugins › Install Plugin from Disk…**, or link it with `openidea-plugin link plugins/official/string-tools`.
+326
View File
@@ -0,0 +1,326 @@
---
title: "Commands and actions"
description: "Declare commands, give them shortcuts, place them in menus and the status bar, and react to workbench events and commits."
---
A command is the basic unit of a code plugin: something the user can run. Every
`[[contributes.commands]]` entry becomes an IDE action with the stable id
`plugin.<plugin-id>.<command-id>`. Like a built-in action it shows in
**Find Action** (`⌘⇧A` / `Ctrl+Shift+A`) and **Search Everywhere**, can be bound in
**Settings › Keymap**, and can be placed in menus, toolbars and the status bar.
Declared commands are live as soon as the plugin is enabled, **before** any plugin code
loads. The code is instantiated the first time one of them runs (every command implies
`on-command:<id>` activation), so a plugin with many commands costs nothing at start-up.
See [Lifecycle](/en/plugins/lifecycle) for activation and idle reclaim.
## Declare a command
```toml
[[contributes.commands]]
id = "count" # [a-z0-9][a-z0-9._-]*
title = "Word Tools: Count Words"
category = "Word Tools" # group in Find Action and the keymap editor
when = "editorFocus" # optional enablement
# icon = "run" # API 1.4: built-in icon name or a package SVG
```
| Field | Required | Notes |
| ---------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------- |
| `id` | yes | Unique in the plugin. The action id is `plugin.<plugin-id>.<id>`. |
| `title` | yes | 1–256 bytes. Translated through the plugin's `locales/<tag>.json`. |
| `category` | no | Up to 256 bytes. Groups the command in Find Action and **Settings › Keymap**. |
| `when` | no | A `when` clause (below). When it is false the command is disabled; menu entries show greyed out. |
| `icon` | no | API 1.4 (`engines.openidea = "^1.4"`). A built-in icon name or a package `.svg` path; used by toolbar menus (`editor.title`, `mainToolbar`). |
### `when` clauses
`when` uses the same expressions as built-in actions: `key`, `key == value`,
`key != value`, `!`, `&&`, `||` and parentheses. Useful keys:
- `editorFocus`: a text editor has focus.
- `editorLangId == sql`: the focused editor's language id.
- `workspaceContains:pom.xml`: the open project contains that exact path (relative to
the project root, no globs). In a workspace it is true when any project contains it,
and it updates as files change. An IDE that doesn't know the key treats it as false.
- `plugin.<plugin-id>.<key>`: a key the plugin set itself with `host::set_context`
(below).
## Run the command
Implement [`Plugin::run_command`](https://github.com/l0ng-ai/openidea/blob/main/sdk/rust/openidea-plugin/src/lib.rs)
(WIT export `commands.run`). `ctx` is the invoking window: `ctx.project()`,
`ctx.editor()` and `ctx.selected_paths()`.
```rust
use openidea_plugin::{Error, Invocation, NotificationLevel, Plugin, host, register_plugin};
#[derive(Default)]
struct WordTools;
impl Plugin for WordTools {
fn run_command(&mut self, id: &str, ctx: &Invocation, _arg: Option<&str>) -> Result<Option<String>, Error> {
let editor = ctx.editor().ok_or("no file is open")?;
match id {
"count" => {
let words = editor.document()?.text.split_whitespace().count();
host::notify(NotificationLevel::Info, "Word count", &format!("{words} words"));
Ok(None)
}
// Every selection (a cursor: empty) in one undoable change.
"shout" => {
editor.transform_selections(|s| s.to_uppercase())?;
Ok(None)
}
_ => Err(Error::NotFound(format!("command {id}"))),
}
}
}
register_plugin!(WordTools);
```
This needs `editor.read` and `editor.write` under `[capabilities]`
([Capabilities](/en/plugins/capabilities)). The
[String Tools](https://github.com/l0ng-ai/openidea/tree/main/plugins/official/string-tools)
plugin is a complete example with 26 commands.
What the result means:
- `Ok(Some(text))` is shown to the user; `Ok(None)` shows nothing.
- An error shows _"`<title>` failed: …"_ and the plugin keeps running. `"msg".into()` is
`Error::Failed`.
- `Error::Cancelled` (the user gave up) and `Error::Denied` (a capability is missing) are
not counted as the plugin failing.
### Arguments and selected files
- `arg` is an optional string. It is `None` when the command runs from Find Action or a
shortcut, and set when another feature runs `plugin.<plugin-id>.<command-id>` with an
argument, or when a notification button, code lens or gutter action passes one.
- `ctx.selected_paths()` lists the files and folders a Project view or editor tab menu ran
the command on (the argument is then the first of them); it is empty otherwise.
- From `problems.context` and `searchResults.context` menus, `selected_paths()` holds the
problem's or match's file and `arg` is JSON `{"path": "…", "line": 0, "column": 0}`
(0-based line, UTF-16 column).
### Limits
A command call has a 5-second wall-clock budget and a CPU fuel budget. For longer work,
open a task with `host::begin_task(title, cancellable)`: while it is open the call may run
for 60 seconds, and each `report` or `is_cancelled` renews that budget. Dialogs
(`host::pick`, `input`, `confirm`, …) don't count against the budget while they wait.
## Keyboard shortcuts
```toml
[[contributes.keymaps]]
command = "count"
key = "Ctrl+Alt+C"
mac = "Meta+Alt+C" # optional; macOS uses `key` as written when it is missing
```
- Modifiers: `Meta`, `Ctrl`, `Alt`, `Shift`. Keys: `A`–`Z`, `0`–`9`, `F1`–`F24`, `Enter`,
`Escape`, `Tab`, `Space`, `Backspace`, `Delete`, `Insert`, `Home`, `End`, `PageUp`,
`PageDown`, `Up`, `Down`, `Left`, `Right`, `Minus`, `Equal`, `Comma`, `Period`, `Slash`,
`Semicolon`, `Quote`.
- A chord is two strokes separated by a space: `Meta+K Meta+C`.
- Every stroke except a function key needs `Ctrl`, `Alt` or `Meta`: plain keys are for typing.
- Without `mac`, macOS gets `key` unchanged, so `Ctrl` stays `Ctrl`. Give `mac` with `Meta`
when you want `⌘`.
- A plugin shortcut is a default below the IDE's: it is dropped when the keymap preset or
the user already uses that keystroke, or when the action already has a binding. The user
can rebind it in **Settings › Keymap**.
## Menus
```toml
[[contributes.menus]]
menu = "editor.context"
command = "test-selection"
when = "editorLangId == markdown" # optional
```
| `menu` | Where | API |
| ----------------------- | ----------------------------------------------------------------------------- | --- |
| `editor.context` | Editor right-click menu | 1.0 |
| `projectView.context` | Project tool window right-click menu | 1.0 |
| `editorTab.context` | Editor tab right-click menu | 1.0 |
| `tools` | **Tools** in the main menu | 1.0 |
| `viewItem.context` | Right-click menu of the items of one of the plugin's own views (needs `view`) | 1.2 |
| `commit.message` | Toolbar above the commit message (Commit tool window and Commit dialog) | 1.3 |
| `editor.title` | Actions area of the editor tab bar | 1.4 |
| `mainToolbar` | Main toolbar | 1.4 |
| `problems.context` | Right-click menu of a problem in the Problems tool window | 1.4 |
| `searchResults.context` | Right-click menu of a search result | 1.4 |
Using a menu newer than 1.0 means declaring at least that API in `engines.openidea`.
Every entry names a command the plugin declares. The menu's `when` applies alongside the
command's own `when`.
**Toolbar icons.** `editor.title`, `mainToolbar` and `commit.message` entries show an
icon: the command's `icon`, else the plugin's manifest `icon`.
**Submenus** (API 1.3). In a context menu (`*.context`), entries that name the same
`submenu` are grouped under it in declaration order. A submenu with one visible entry
shows it as a plain entry. The label is translated through the plugin's catalog.
```toml
[[contributes.menus]]
menu = "editor.context"
command = "arthas.watch"
submenu = "Arthas"
```
**View item menus** (API 1.2). `viewItem.context` needs `view`, the id of one of the
plugin's own views that its code renders as a `tree`, `list` or table (not a web view).
Each command receives the item's key as its argument. Only the menu's `when` can test
`viewItem` (the key) and `viewItemKind` (the key up to its first `:`, so `pod` for
`pod:default/nginx`). For table rows the key is the decimal row index. See
[Views and decorations](/en/plugins/guides/views).
```toml
[[contributes.menus]]
menu = "viewItem.context"
view = "pods"
command = "kube.logs"
when = "viewItemKind == pod"
```
## Status bar items
```toml
[[contributes.statusBar]]
id = "branch"
text = "main" # 1–128 bytes; code can replace it
tooltip = "Current branch" # up to 1 KiB
command = "refresh" # one of the plugin's commands, run on click
align = "right" # left or right (default right)
priority = 100 # 0–1000
```
A declared item shows before the code runs. From code:
- `host::set_status_item(id, &StatusItem { text, tooltip, command })` shows or updates an
item in every window (at most 8 per plugin; kept while the plugin is idle);
`remove_status_item(id)` removes it.
- `host::set_window_status_item(id, item)` (API 1.4) overrides it in the invoking window
only; `remove_window_status_item(id)` falls back to the global item. Only valid from a
command or event of a window.
## Context keys from code
`host::set_context(key, value)` sets `plugin.<plugin-id>.<key>` for `when` clauses
(`true`, a number or text up to 1024 bytes). Keys are 1–64 characters of
`A-Za-z0-9_-`, at most 100 per plugin. A key set from a command or event applies to that
window; one set outside any applies to every window, and a window's value wins. Keys
survive a window reload and idle reclaim and are dropped when the plugin stops.
```rust
host::set_context("ready", true)?; // when = "plugin.acme.word-tools.ready"
```
## Notifications with buttons
`host::notify(level, title, body)` shows a balloon in the window the command or event
came from (every window when there is none). `host::notify_with_actions` adds up to three
buttons, each running one of the plugin's declared commands:
```rust
use openidea_plugin::{NotificationAction, NotificationLevel, host};
let retry = NotificationAction::new("Retry", "check").argument("again");
host::notify_with_actions(NotificationLevel::Warning, "Check failed", "The server did not answer.", &[retry])?;
```
API 1.5 adds `Project::notify(level, title, body, actions)`: a balloon only in a window of
that project (the most recently focused one), also from scheduled or background work. If
the project has closed it fails with `Error::NotFound`. Labels are at most 256 bytes and
arguments 64 KiB; an undeclared command rejects the whole notification.
## Running other commands
`host::execute_command(id, arg)` (API 1.4, WIT `command-runner.execute`) runs a stable
IDE action id or `plugin.<plugin-id>.<command-id>` in the invoking window. A plugin's own
commands need no capability; IDE actions and other plugins' commands need the high-risk
`ide.commands` capability. IDE actions return `None`; plugin commands return their result.
Calls nest at most 8 deep and only from a command or event of a window.
## Events
Commands are one entry point; workbench events are the other. Subscribe with
`host::subscribe(EventKind::FileSaved)` in `activate`, or declare
`activation = ["on-event:file-saved"]` to be woken by the first one, then handle batches
in `Plugin::handle_events(events, ctx)` (WIT `events.wit`, export `event-handler`).
| Kind | Capability | Delivered |
| -------------------- | -------------- | ---------------------------------------------------------------------- |
| `file-opened` | `events` | A file opens in an editor |
| `file-saved` | `events` | After the file is written |
| `file-closed` | `events` | API 1.4: an editor closes (JSON path string) |
| `files-changed` | `events` | API 1.3: debounced created / modified / removed paths in open projects |
| `editor-focused` | `events` | The focused editor changes |
| `vcs-changed` | `events` | A repository's state changed (commit, checkout, fetch…) |
| `settings-changed` | `events` | API 1.4: the plugin's changed setting keys |
| `action-performed` | `events.input` | An IDE action ran: id, title, source, shortcut |
| `input-mode-changed` | `events.input` | API 1.4: Vim mode changes in the focused editor |
| `scheduled` | none | The `on-schedule:<secs>` period elapsed |
Events arrive in batches, oldest first, per window. An idle plugin collects them for 2
seconds before it is woken. `host::subscribe_paths(kind, &["config/*.yaml"])` (API 1.3)
filters `file-opened`, `file-saved` and `files-changed` by project-relative globs. Kinds
newer than the SDK arrive as `Event::Other { kind, data }`; match the
`#[non_exhaustive]` enum with a `_` arm. [Key Promoter](https://github.com/l0ng-ai/openidea/tree/main/plugins/official/key-promoter)
(`action-performed`) and [Git Toolbox](https://github.com/l0ng-ai/openidea/tree/main/plugins/official/git-toolbox)
(`on-schedule`, `vcs-changed`) are working examples.
## Commits
A plugin can act on the commit the user is preparing (API 1.3, `commit.wit`):
- **A button above the message:** a `commit.message` menu entry.
- **Read the draft:** `host::pending_commit()` (`vcs.read`) returns what Commit would
commit now: per file its repository, path, status and a `git diff` of only the included
changes (1 MiB in all, then `truncated`), plus the message, Amend and a `revision`.
Unsaved editors of those files are saved first; nothing is staged.
- **Write the message:** `host::set_commit_message(revision, text)` (`vcs.write`) replaces
it only while the draft is still at `revision`; otherwise it fails with `Error::Stale`
and the user's text is kept.
```rust
let draft = host::pending_commit()?.ok_or("no repository")?;
let message = ask_the_model(&draft.changes)?;
host::set_commit_message(draft.revision, &message)?;
```
Both work from a command or event of a window in a trusted project.
**Before-commit checks.** Each `[[contributes.commitChecks]]` (`id`, `title`) is listed in
**Commit Options**, on by default. When the user commits, after the IDE's own checks, the
IDE calls the `commit-checker` export. Register it with
`register_plugin!(MyPlugin, commit_checks)` and implement
`openidea_plugin::commit::CommitChecks`:
```rust
use openidea_plugin::commit::{CommitChecks, CommitProblem};
use openidea_plugin::{CommitDraft, Error, Invocation};
impl CommitChecks for MyPlugin {
fn check(&mut self, _check: &str, draft: &CommitDraft, _ctx: &Invocation) -> Result<Vec<CommitProblem>, Error> {
if draft.message.lines().next().is_some_and(|l| l.len() > 72) {
return Ok(vec![CommitProblem::new("The subject line is longer than 72 characters")]);
}
Ok(vec![])
}
}
```
Problems stop the commit and show beside the message with the check's title until the
user changes it or chooses **Commit Anyway**. A check that fails, times out or lacks
`vcs.read` is reported as a problem too.
## Related
- [Manifest](/en/plugins/manifest) for the full `plugin.toml` reference.
- [Views and decorations](/en/plugins/guides/views) for tool windows whose buttons run code.
- [API reference](/en/plugins/api/index) for every host function.
+105
View File
@@ -0,0 +1,105 @@
---
title: "Databases"
description: "Create and manage plugin-owned data sources in the Database tool window, store their passwords securely and connect them."
---
A plugin that starts or knows about a database (a local dev stack, a cloud tool, a
container runner) can add a data source to the project's **Database** tool window, so the
user doesn't have to type the connection settings. The plugin manages only the sources it
created; it never sees user-created sources, other plugins' sources or any stored
password. Once a source exists, the user works with it through the normal
[Database tools](/en/guide/database).
This is the `database` host interface (`database.wit`, API 1.4).
## Manifest
```toml
engines.openidea = "^1.4"
wasm = "dist/plugin.wasm"
[capabilities]
optional = ["database.sources"] # medium risk
```
`database.sources` is needed for every call, and every call needs a trusted, open project.
## Create a source and connect
```rust
use openidea_plugin::{Error, Invocation, PluginDataSource};
fn run_command(&mut self, _id: &str, ctx: &Invocation, _arg: Option<&str>) -> Result<Option<String>, Error> {
let project = ctx.project().ok_or("Open a project first")?;
project.upsert_data_source(&PluginDataSource {
id: "local".into(),
name: "Local service".into(),
driver: "postgres".into(),
host: Some("127.0.0.1".into()),
port: Some(5432),
database: Some("app".into()),
user: Some("app".into()),
..Default::default()
})?;
// If the plugin knows the password: project.set_data_source_credential("local", &password)?;
project.connect_data_source("local")?;
Ok(None)
}
```
| Rust SDK (`Project`) | WIT | Result |
| ------------------------------------------ | ------------------------------------------------ | -------------------------------------------------------- |
| `upsert_data_source(&PluginDataSource)` | `database.upsert-source(project, source)` | Creates or replaces one of the plugin's sources |
| `remove_data_source(id)` | `database.remove-source(project, id)` | Removes the source and its credentials |
| `data_sources()` | `database.sources(project)` | The plugin's own ids only, never settings or credentials |
| `set_data_source_credential(id, password)` | `database.set-credential(project, id, password)` | Writes the password to the Database secure store |
| `connect_data_source(id)` | `database.connect(project, id)` | Asks this project's window to connect and introspect |
## The data source record
`PluginDataSource` (WIT `database.data-source`) holds non-secret settings only:
| Field | Notes |
| ---------------------------------- | -------------------------------------------------------------------------------------------------------- |
| `id` | Private to the plugin, 1–128 bytes. The host allocates the real connection id. |
| `name` | Shown in the Database tree, at most 256 bytes. |
| `driver` | `sqlite`, `postgres`, `mysql` (MariaDB too), `sqlServer`, `clickhouse`, `oracle`. |
| `host`, `port`, `database`, `user` | Optional. For SQLite, `database` is the file path (absolute or relative to the project). |
| `url` | Optional. Selects URL-only connection settings; its driver must agree. A `sqlite:` URL works for SQLite. |
| `options` | Up to 128 `(name, value)` pairs: the non-secret driver properties of the Database Advanced tab. |
Never put credentials in a settings field. URLs containing a password or credential query
parameters, and password, token or secret options, are rejected. Use
`set_data_source_credential` instead. There is no function that reads a credential back,
not even one the plugin just wrote.
## Where it is stored
- Owned sources are written to the project's `.openidea/data-sources.toml` with
`pluginOwner` and `pluginSourceId`. Updates keep unrelated entries, comments and
formatting. The file must use the normal `[[data-source]]` table format; inline table
arrays are rejected. No password is written there.
- The Database tree refreshes when sources change and labels owned sources with the
plugin id. Duplicating one creates a user-owned copy.
- Disabling the plugin keeps its sources. Uninstalling removes them and their credentials,
also in closed projects the host remembers.
## Connecting
`connect_data_source` is valid only from a command or event of that project's window. It
uses the usual Database connection flow, password prompt and introspection. Waiting for the
window doesn't count against the plugin's call budget; the window must answer within 180
seconds, and the call can be cancelled.
## Limits and errors
- At most 64 sources per plugin and project; each value or optional field at most 16 KiB;
a credential at most 64 KiB.
- An unknown id, or one owned by someone else, returns `not-found` without revealing whether
another owner's source exists.
- In remote projects, sources and credentials are stored where the Database subsystem runs,
on the project server. A host without that subsystem returns
`invalid("Database subsystem is unavailable on this host")`.
The [`data-tools` fixture](https://github.com/l0ng-ai/openidea/tree/main/crates/ide-plugin/tests/fixtures/data-tools)
calls every function from commands.
+206
View File
@@ -0,0 +1,206 @@
---
title: "Diagnostics and inspections"
description: "Report problems in the editor and the Problems tool window, check unsaved text as the user types, and offer quick fixes."
---
A plugin reports problems in two ways, and both show as editor squiggles and in the
**Problems** tool window next to the language servers' results:
| Approach | How | When to use |
| ------------------------ | ------------------------------------------------------ | -------------------------------------------------------------------------- |
| **Publish** | `host::publish_diagnostics(path, items)` from any code | Results of an external tool, a save, a project scan. Needs `project.read`. |
| **Provide while typing** | `diagnostics` provider kind (API 1.3) | Fast checks of the current, unsaved text. No extra capability. |
Quick fixes for either come from the `code-actions` provider kind.
## The diagnostic record
Both approaches use the same record (WIT `diagnostics.diagnostic` in `io.wit`; Rust
`openidea_plugin::Diagnostic`):
| Field | Notes |
| ------------- | -------------------------------------------------------------------------- |
| `range` | 0-based lines, UTF-16 columns. |
| `severity` | `Error`, `Warning`, `Info`, `Hint`. |
| `message` | Up to 4 KiB for providers. |
| `code` | Rule or check id (`LineLength`), up to 128 bytes for providers. |
| `source` | The tool that found it when it is not the plugin itself (`checkstyle`). |
| `unnecessary` | Shown faded (WIT tag `unnecessary`). |
| `deprecated` | Struck through (WIT tag `deprecated`). |
| `related` | Other places that explain it (`RelatedInformation { location, message }`). |
```rust
use openidea_plugin::{Diagnostic, Range, Severity};
let d = Diagnostic::new(range, Severity::Warning, "Line is longer than 100 characters").code("LineLength");
```
## Publish problems
`host::publish_diagnostics(path, &items)` replaces this plugin's problems for one file
(an absolute path in an open project); an empty list clears them. It needs `project.read`.
[Checkstyle](https://github.com/l0ng-ai/openidea/tree/main/plugins/official/checkstyle) is
the reference example: on `file-saved` it runs the `checkstyle` program inside a
cancellable task, parses the XML report and publishes the result.
```toml
activation = ["on-event:file-saved", "on-command:*"]
[capabilities]
required = ["project.read", "events", "storage", "process:checkstyle"]
[[contributes.providers]]
kinds = ["code-actions"]
languages = ["java"]
```
```rust
fn check(&mut self, path: &str) -> Result<usize, Error> {
let project = host::project_of(path).ok_or_else(|| Error::NotFound(path.into()))?;
let task = host::begin_task("Checkstyle", true);
let args = vec!["-f".to_owned(), "xml".into(), "-c".into(), "/google_checks.xml".into(), path.to_owned()];
let out = project.run_process("checkstyle", &args, None)?;
if task.is_cancelled() {
return Err(Error::Cancelled);
}
let problems = parse_report(&out.stdout);
let text = project.read_file(path)?;
let diagnostics: Vec<Diagnostic> = problems.iter().map(|p| diagnostic(p, &text)).collect();
host::publish_diagnostics(path, &diagnostics)?;
self.problems.insert(path.to_owned(), problems); // kept for quick fixes
Ok(diagnostics.len())
}
```
[License Header](https://github.com/l0ng-ai/openidea/tree/main/plugins/official/license-header)
does the same on `file-opened` and `file-saved` without an external program.
## Check unsaved text while typing
The `diagnostics` kind (API 1.3) asks the plugin about the current document, unsaved
edits included:
```toml
engines.openidea = "^1.3"
[[contributes.providers]]
kinds = ["diagnostics"]
languages = ["text", "plaintext"] # "*" matches any language
```
```rust
use openidea_plugin::diagnostics::{DiagnosticsProvider, Document};
use openidea_plugin::{Diagnostic, Range, Severity, register_plugin, text};
impl DiagnosticsProvider for MyPlugin {
fn diagnostics(&mut self, doc: &Document) -> Vec<Diagnostic> {
doc.text
.match_indices("BAD")
.map(|(offset, _)| {
let range = Range { start: text::position(&doc.text, offset), end: text::position(&doc.text, offset + 3) };
Diagnostic::new(range, Severity::Warning, "Avoid BAD").code("bad-word")
})
.collect()
}
}
register_plugin!(MyPlugin, diagnostics);
```
The optional WIT export is `diagnostics-provider.diagnostics(document)`, in the worlds
`plugin-diagnostics` / `editor-diagnostics`.
How the host calls it:
- About **300 ms** after a document opens or after its latest edit, under the editor
provider budget (50 ms soft, 500 ms hard). Superseded requests are cancelled and older
versions dropped.
- Documents over 2 MiB and virtual library documents are skipped.
- The returned list replaces what this provider reported for the document; an empty list
removes it. At most 5,000 diagnostics per document; messages are cut at 4 KiB, `source`
and `code` at 128 bytes.
- Providers run without an interactive invocation: they can't open prompts or write to
the editor.
Provider results and published results are separate producers (`plugin-provider:<plugin-id>`
and `plugin:<plugin-id>`); each replaces only its own. Closing the document or project, or
disabling, reloading or a failing plugin clears provider results. Results stay with the
window and project that asked, even when two windows edit the same path differently.
## Quick fixes
Declare the `code-actions` kind and implement `Providers::code_actions`. The actions join
the language servers' under **Show Context Actions** (`⌥⏎` / `Alt+Enter`). A code action
carries edits for any files, applied as one undoable change.
```rust
use openidea_plugin::providers::{CodeAction, Document, Providers};
use openidea_plugin::{Position, Range, TextEdit};
impl Providers for Checkstyle {
fn code_actions(&mut self, doc: &Document, selection: Range) -> Vec<CodeAction> {
let Some(problems) = self.problems.get(&doc.path) else { return vec![] };
let lines: Vec<&str> = doc.text.split('\n').collect();
problems
.iter()
.filter(|p| p.check == "FileTabCharacter" && (selection.start.line..=selection.end.line).contains(&p.line))
.filter_map(|p| {
let line = lines.get(p.line as usize)?;
let whole = Range::new(Position::new(p.line, 0), Position::new(p.line, line.encode_utf16().count() as u32));
Some(CodeAction {
title: "Checkstyle: Replace tabs with spaces".into(),
kind: Some("quickfix".into()),
preferred: true,
edits: vec![(doc.path.clone(), vec![TextEdit { range: whole, text: line.replace('\t', " ") }])],
data: None,
})
})
.collect()
}
}
```
`code_actions` runs under the 50 ms editor budget, which is why Checkstyle answers from
the results of its last check kept in memory.
### Computing the edits later
An action can leave `edits` empty and set `data` (the plugin's own reference). The host
then calls `resolve_code_action(doc, action)` only when the user picks it, under the
5-second command budget instead of 50 ms. During resolution `host::pick`, `pick_many`,
`input` and `confirm` ask in the editor's window, and the wait is not charged to the budget.
Dismissal returns `None` (or `false` for `confirm`): return `Error::Cancelled` or an action
without edits to abandon the fix. Lazy actions are never resolved for the intention
preview or for Actions on Save. Other command-only APIs stay unavailable.
### Fixes that create, rename or delete files
Implement `file_actions::FileActions` alongside, and register both:
`register_plugin!(MyPlugin, providers, file_actions)`. The same `code-actions` kind covers
both. See [File actions](/en/plugins/guides/file-actions).
## Reading the Problems view
`host::problems(path)` (API 1.4, WIT `diagnostics.problems`) returns the current Problems
reports of the invoking window's project, grouped by file and source, including language
server and plugin diagnostics, optionally for one file. It needs `project.read` and an
invoking window, and returns an empty list for remote projects.
[TODO Finder](https://github.com/l0ng-ai/openidea/tree/main/plugins/official/todo-finder)
counts them in a status bar item.
## Testing
```rust
use openidea_plugin::mock::MockHost;
let mock = MockHost::install();
mock.open_editor("/project/a.txt", "text", "a BAD word", &[]);
mock.refresh_diagnostics(&mut MyPlugin::default());
assert_eq!(mock.provider_diagnostics()["/project/a.txt"][0].code.as_deref(), Some("bad-word"));
```
`refresh_diagnostics` runs a provider against the mock editor's current version;
`provider_diagnostics()` holds its answers and `diagnostics()` what the plugin published.
Debounce and cancellation are exercised only by the real host. See
[Testing](/en/plugins/testing).
+210
View File
@@ -0,0 +1,210 @@
---
title: "File actions"
description: "Quick fixes that create, rename or delete files, reading and writing project files, and files outside the project."
---
This page covers how a plugin works with files:
- **File actions** (`file-actions.wit`, API 1.3): quick fixes and refactorings that create,
rename or delete files, applied as one undoable workspace edit.
- **Project files** (`workspace.wit`): read, write, find and search files inside the open
projects.
- **Other files** (`fs.wit`, API 1.3): files outside the projects, under explicit grants,
and the plugin's own private directory.
- **File templates**: entries of the **File › New…** menu, no code needed.
## File actions
A file action is a code action whose result is file operations plus text edits. Declare
the `code-actions` provider kind (the same kind as text-only quick fixes), implement
`openidea_plugin::file_actions::FileActions`, and register it together with `providers`:
```toml
engines.openidea = "^1.3"
wasm = "dist/plugin.wasm"
[[contributes.providers]]
kinds = ["code-actions"]
languages = ["markdown"]
```
```rust
use openidea_plugin::file_actions::{FileAction, FileActions, FileOperation};
use openidea_plugin::providers::{Document, Providers};
use openidea_plugin::{Error, Invocation, Plugin, Range, register_plugin};
#[derive(Default)]
struct Notes;
impl Plugin for Notes {
fn run_command(&mut self, id: &str, _ctx: &Invocation, _arg: Option<&str>) -> Result<Option<String>, Error> {
Err(Error::NotFound(id.into()))
}
}
impl Providers for Notes {}
impl FileActions for Notes {
fn file_actions(&mut self, doc: &Document, _selection: Range) -> Vec<FileAction> {
let Some(stem) = doc.path.strip_suffix(".md") else { return vec![] };
let notes = format!("{stem}.notes.md");
vec![FileAction {
title: "Create a notes file".into(),
kind: Some("quickfix".into()),
operations: vec![FileOperation::Create { path: notes.clone(), text: "# Notes\n".into(), overwrite: false }],
open: Some(notes),
..Default::default()
}]
}
}
register_plugin!(Notes, providers, file_actions);
```
The actions are listed with the text-only code actions under **Show Context Actions**
(`⌥⏎` / `Alt+Enter`) and have the same 50 ms editor budget.
### The `FileAction` record
| Field | Notes |
| ------------ | ------------------------------------------------------------------------------------------------------------------------- |
| `title` | Shown in the menu. |
| `kind` | `quickfix`, `refactor`, … |
| `preferred` | Marks the preferred fix. |
| `operations` | Applied first, in order: `Create { path, text, overwrite }`, `Rename { from, to }`, `Delete { path }`. |
| `edits` | `(path, Vec<TextEdit>)` per file, applied after the operations; may target files they create. |
| `open` | A file opened once the action is applied. |
| `data` | The plugin's own reference; with it and no operations or edits, they are computed when the user picks the action (below). |
Rules:
- Operations and edits are one workspace edit: if any step fails nothing is applied, and
one Undo reverses all of it. Undoing a `Delete` restores the file.
- `Create` fails when the file exists unless `overwrite`; missing parent directories are
created.
- Paths are absolute or relative to the project root and must stay inside it; a path
outside disables the action.
### Resolving on pick
Return an action with `data` and empty `operations` and `edits` to defer the work. When the
user picks it, the host calls `resolve_file_action(doc, action)` under the 5-second command
budget instead of 50 ms. As with `resolve_code_action`, `host::pick`, `pick_many`, `input`
and `confirm` ask in the editor's window and the wait doesn't count. Return
`Error::Cancelled` or an action with nothing to apply to abandon it.
```rust
fn resolve_file_action(&mut self, _doc: &Document, mut action: FileAction) -> Result<FileAction, Error> {
let Some(name) = host::input("New file name", Some("notes.md"))? else { return Err(Error::Cancelled) };
action.operations = vec![FileOperation::Create { path: name.clone(), text: String::new(), overwrite: false }];
action.open = Some(name);
Ok(action)
}
```
## Project files
Inside the open projects, a plugin uses its `Project` handle (`ctx.project()` in commands
and events, `host::project_of(path)` in providers, `host::projects()` for every open
project). All of these need `project.read` except `write_file`:
| Method | Notes |
| -------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `root()` | Absolute path of the project root. |
| `read_file(path)` | UTF-8 text, at most 16 MiB; unsaved editor contents win; missing is `Error::NotFound`. |
| `write_file(path, text)` | API 1.3, needs `project.write`: replaces or creates the file (and missing directories) in one step, keeping its encoding and BOM. Refused while an editor holds unsaved changes for it. |
| `exists(path)`, `stat(path)` | Existence (hidden and ignored files included) and kind, size, modified time. |
| `find_files(glob, limit)` | Root-relative glob (`**/*Mapper.xml`); honours `.gitignore` and exclusions; absolute paths. |
| `search_text(&TextQuery, limit)` | API 1.4: Find in Files with unsaved documents and project exclusions, at most 10,000 matches; `limit_hit` says there may be more. |
| `symbols(query, limit)` | API 1.3: fuzzy search of the in-memory symbol index (no language server); may be partial while indexing. |
Paths are absolute or relative to the root. `..` and symlinks that leave the root are
refused. In a remote project these run on the server, next to the files.
Two related calls need no capability and work only from a command or event of a window:
`host::pick_directory(title, initial)` (API 1.3) opens the native folder chooser, and
`host::open_project(path, new_window)` (API 1.3) runs the normal **File › Open…** flow,
including the trust prompt. Paths belong to the UI machine.
## Files outside the projects
`host::fs` (WIT `fs.wit`, API 1.3) reads and writes files outside the open projects: the
configuration of a tool the plugin integrates with, a licence file, a program installed
elsewhere. Every call is checked against a grant:
```toml
engines.openidea = "^1.3"
[capabilities]
optional = ["fs.read:~/.config/mytool", "fs.write:~/.config/mytool/state.json"]
```
```rust
use openidea_plugin::host::{self, fs};
use openidea_plugin::LogLevel;
let config = fs::read_file("~/.config/mytool/config.toml")?;
for entry in fs::read_dir("~/.config/mytool")? {
host::log(LogLevel::Info, &format!("{} ({:?})", entry.name, entry.kind));
}
fs::write_file("~/.config/mytool/state.json", "{}")?;
```
| Function | Grant | Notes |
| -------------------------- | ----------------- | --------------------------------------------------------------------------------------- |
| `read_file(path)` | `fs.read:<path>` | Text decoded as the editor decodes it, at most 16 MiB; unsaved editor contents win. |
| `read_bytes(path)` | `fs.read:<path>` | Bytes, at most 16 MiB. Files that are not text must be read this way. |
| `stat(path)` | `fs.read:<path>` | Kind, size, modified time; missing is `Error::NotFound`. |
| `read_dir(path)` | `fs.read:<path>` | Entries sorted by name, at most 4096. |
| `write_file(path, text)` | `fs.write:<path>` | Atomic replace or create, keeps encoding and BOM (new files are UTF-8), at most 16 MiB. |
| `write_bytes(path, bytes)` | `fs.write:<path>` | The same for bytes. |
| `plugin_dir()` | none | The plugin's private directory (below). |
- Paths are absolute or start with `~/`. A grant covers a directory (any depth) or one
file. The path is resolved to its real location before comparing, so a symlink out of the
granted directory is refused, and `/opt/see` doesn't cover `/opt/seeyon`.
- Granting a whole file system (`/`, `C:\`) is refused when the manifest is read.
- `fs.write` doesn't imply `fs.read`. Writes fail with `Error::Invalid` when the directory
is missing, the text can't be encoded, or an editor holds unsaved changes for the file.
- Both are high-risk capabilities: the install page tells the user so.
### The private directory
`host::fs::plugin_dir()` returns a directory that belongs only to this plugin, separate
from its installed code and its key-value `storage`. Reading and writing inside it needs no
`fs.*` or `project.read` grant, even during activation or provider calls without a project.
It is created owner-only (0700 on Unix, an owner-only ACL on Windows), survives idle
reclaim, reloads, updates and restarts, and is removed on uninstall. Use it for generated
content or caches larger than `storage`. A path in it works with `host::open_location` and
as an argument to an approved process.
## File templates
`[[contributes.fileTemplates]]` adds entries to the **File › New…** menu. They need no code and grant
no file access: files are created only when the user invokes a template, through the IDE's
normal New File flow.
```toml
[[contributes.fileTemplates]]
id = "graph"
name = "Graphviz Graph"
extension = "dot"
path = "templates/graph.dot"
```
With `engines.openidea = "^1.3"`, `fileName` creates an exact file name (dotfiles too)
without a name prompt, and `variables` add required text fields to the new-file dialog,
inserted with `$NAME$` or `${NAME}`:
```toml
[[contributes.fileTemplates]]
id = "ignore"
name = "Docker ignore"
fileName = ".dockerignore"
path = "templates/dockerignore.txt"
variables = [{ name = "OUTPUT", prompt = "Output folder" }]
```
Variable names are unique ASCII identifiers and can't replace built-in variables (`NAME`,
`PACKAGE`, `END`, …). `$PACKAGE$` follows the built-in rules for the extension (source roots
for Java and Kotlin, Composer PSR-4 for PHP, …).
+183
View File
@@ -0,0 +1,183 @@
---
title: "Icon themes"
description: "Write a file icon theme plugin that maps file names, extensions and folders to SVG icons, with optional patterns, variants and a settings page."
---
A file icon theme replaces the icons of files and folders in the Project tool window,
editor tabs, Search Everywhere and other lists, and can override built-in IDE icons. Like a
colour theme it is **data only**: no code and no capabilities.
[Material Icons](https://github.com/l0ng-ai/openidea/tree/main/plugins/official/material-icons)
is the complete example: 106 icons covering 189 extensions, 71 file names and 92 folder
names.
If a plugin only needs icons for the file types it adds, use `fileIcons` instead (see the
end of this page): those show whatever icon theme is in use.
## Start
```sh
openidea-plugin new my-icons --template icon-theme
```
This writes a manifest, `icons/theme.json` and four SVGs:
```toml
id = "acme.my-icons"
name = "My Icons"
version = "0.1.0"
description = "File and folder icons."
engines.openidea = "^1.0"
[[contributes.iconThemes]]
id = "my-icons"
label = "My Icons"
path = "icons/theme.json"
```
A plugin may contribute several icon themes. Install it like any plugin, then in
**Settings › Plugins** select it and press **Apply** next to the icon theme (**Use Default
Icons** switches back), or pick it as **File icon theme** in **Settings › Appearance &
Behavior › Appearance**. The first icon theme of a plugin is selected automatically when the
plugin is installed or enabled while no icon theme is selected.
## The theme file
`theme.json` maps names to SVG files (paths relative to the package):
```json
{
"iconDefinitions": { "rust": "icons/rust.svg", "folder-src": "icons/folder-src.svg" },
"file": "file",
"folder": "folder",
"folderExpanded": "folder-open",
"fileExtensions": { "rs": "rust", "d.ts": "typescript-def" },
"fileNames": { "cargo.toml": "cargo" },
"folderNames": { "src": "folder-src" }
}
```
| Key | Meaning |
| -------------------------- | ---------------------------------------------------------------------------- |
| `iconDefinitions` | Icon id to SVG path (or `{ "light", "dark" }`, below). |
| `file` | Icon for files nothing else matches. |
| `folder`, `folderExpanded` | Icons for closed and open folders nothing else matches. |
| `fileExtensions` | Extension (without the dot) to icon id. Multi-part extensions work (`d.ts`). |
| `fileNames` | Exact file name to icon id. |
| `folderNames` | Folder name to icon id. |
Matching:
- Files match by exact name, then the longest extension (`a.test.ts` tries `test.ts`, then
`ts`), then `file`.
- Folders match by name, then `folder` / `folderExpanded`. Folders with a role (source
root, excluded, module) keep their built-in icons.
- Names are case-insensitive.
Limits: up to 5000 icons of at most 64 KiB each, and 20,000 mappings in all. Every SVG is
sanitised on install: allow-listed elements and attributes only; no scripts, event
handlers, external references, `url()` or entities.
## Optional fields
```json
{
"iconDefinitions": { "rust": { "light": "icons/rust.svg", "dark": "icons/rust_dark.svg" } },
"folderOpen": "folder-open",
"patterns": [
{ "pattern": "^.*\\.spec\\.ts$", "icon": "test-ts", "ignoreCase": true },
{ "pattern": "/fixtures/", "icon": "fixture", "path": true, "group": "tests" },
{ "pattern": "^src$", "icon": "folder-src", "target": "folder", "openIcon": "folder-src-open" }
],
"overrides": { "chevron-right": "arrow-right", "symbol-class": "class" },
"groups": [{ "id": "tests", "name": "Tests", "enabled": false }],
"choices": [
{
"id": "arrows",
"name": "Arrows",
"default": "plain",
"options": [
{ "id": "plain", "name": "Default" },
{ "id": "plus", "name": "Plus-Minus", "overrides": { "chevron-right": "plus" } }
]
}
],
"accent": true
}
```
- **Light and dark.** An `iconDefinitions` value may be `{ "light", "dark" }`; the dark SVG
is used with dark themes. Icon ids may not end in `_dark`.
- **`patterns`** are regular expressions checked in order, before `fileNames` and
`fileExtensions`. `target` is `file` (default) or `folder`; with `path` a pattern matches
the whole path (with `/` separators) instead of the name; `ignoreCase` ignores case.
Patterns that could backtrack badly (a repeated group, too many ways to match one name),
lookaround, inline flags and named groups are refused.
- **`openIcon`** (folder patterns) and **`folderOpen`** (other folders) are used for folders
that hold a file open in the editor, when the theme's hollow-folders option is on.
- **`overrides`** replace built-in IDE icons by name (`chevron-right`, `folder-source`, …);
symbol icons (`symbol-<kind>`, `class`, `method`, …) are the code element icons.
- **`groups`** are packs of patterns the user turns on or off; `enabled` is the default.
- **`choices`** are variants the user picks one of; the chosen option's `overrides` win over
the top-level `overrides`.
- **`accent: true`**: elements with `data-tint="true"` take the colour the user picks.
- Elements with `data-themed="true"` take the accent colour of **Settings › Appearance &
Behavior › Appearance**, or the colour theme's custom accent.
## A settings page
`settings` describes the theme's page under **Settings › Plugins**, named after the theme's
label. A theme without `settings` has no page.
```json
{
"settings": [
{
"title": "Icons",
"items": [
{ "option": "hideFiles", "invert": true, "label": "Show file icons", "hint": "In trees, lists and tabs" },
{ "option": "groups", "columns": 2 },
{ "option": "choice", "choice": "arrows" },
{ "option": "iconSize", "label": "Icon size", "default": 16 }
]
}
]
}
```
Sections have a `title` and `items`. Each item names an `option` the IDE implements, with
the theme's `label`, an optional `hint` and, where the option needs one, a `default`:
| `option` | Control | Item fields |
| ----------------------------------------------------------------------------------- | -------------------- | ---------------------------------------------------- |
| `hideFiles`, `hideFolders`, `hideUiIcons`, `hidePsiIcons`, `hollowFolders` | Checkbox | `invert`: checked means off |
| `groups` | A checkbox per group | `columns` (1–4), `label?` (group names are shown) |
| `choice` | The choice's options | `choice` (its id), `label?` (default: its name) |
| `accent`, `monochrome` | Checkbox and colour | `default` (`#rrggbb`); `accent` needs `accent: true` |
| `saturation` (0–20), `opacity` (10–100), `iconSize` (12–32), `rowHeight` (16–48 px) | Checkbox and number | `default` within the range |
Values are kept per theme in the `appearance.fileIconThemeOptions` setting and apply while
the theme is selected.
## Icons for a plugin's own file types
A plugin that adds a language or file type can ship icons for it without being an icon
theme. They show whatever icon theme is in use:
```toml
[[contributes.fileIcons]]
id = "graphviz"
patterns = ["*.dot", "*.gv"] # file name globs, no directories
icon = "icons/graphviz.svg"
iconDark = "icons/graphviz_dark.svg" # optional
```
[Graphviz](https://github.com/l0ng-ai/openidea/tree/main/plugins/official/graphviz) uses it
for `.dot` files. SVGs are sanitised like icon theme SVGs.
## Tips
- Material Icons generates its SVGs and `theme.json` with a script (`generate.py`, not part
of the package); for a large theme, generate rather than hand-edit.
- Keep icons readable at 16 px and on both light and dark backgrounds, or provide `dark`
variants.
- `openidea-plugin check` validates the theme file and every SVG as installing would.
+217
View File
@@ -0,0 +1,217 @@
---
title: "Indexing"
description: "Publish cross-file associations that the IDE uses for navigation, gutter markers, Find Usages and Rename, and share a project's HTTP endpoints."
---
Editor providers answer questions about **one** document while the user types. Some
features need the whole project: a MyBatis mapper method and its XML statement, a route
and its controller. For those, a plugin can publish data once per document and let the
host answer from memory:
- **Document indexers** (`indexing.wit`, API 1.2): definitions, references, aliases and
gutter markers keyed by strings the plugin chooses. The host resolves Go to Declaration,
gutter markers and, since API 1.3, Find Usages and Rename from them, without calling the
plugin again.
- **Endpoint providers** (`endpoints.wit`, API 1.3): a project's HTTP routes for Search
Everywhere and the HTTP client.
For a quick lookup in the host's existing symbol index, `Project::symbols(query, limit)`
(API 1.3) is enough; see [File actions](/en/plugins/guides/file-actions#project-files).
## Declare a document indexer
```toml
engines.openidea = "^1.3"
wasm = "dist/plugin.wasm"
[capabilities]
required = ["project.read"]
[[contributes.documentIndexers]]
extensions = ["java", "xml"]
```
Implement `openidea_plugin::indexing::DocumentIndexer` and register `indexing`
(`register_plugin!(MyPlugin, providers, indexing)` when the plugin also has interactive
providers). Components built with other tools target the `plugin-indexing` world.
Indexers need `project.read` and run only in trusted projects.
## Publish edges
`index(doc)` receives the document's text (the editor's while it is open) and the symbols
the host's syntax support found in it, and returns its `Edges`:
| Field | Meaning |
| ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `definitions` | Where keys are declared. |
| `references` | Ranges that go to the definitions of their key on Go to Declaration (`⌘B` / `Ctrl+B`) and Ctrl/⌘-click. Elsewhere the language server answers as usual. |
| `aliases` | `Alias { prefix, target }`: keys starting with `prefix` also resolve as if they started with `target` (both end with `#`), for inherited members. |
| `markers` | Gutter icons on the anchor's first line that go to the definitions of their key. |
A key is any string the plugin chooses (`com.example.UserMapper#findById`); keys are
compared as is. Ranges are UTF-16. The edges of a document replace its previous ones.
From the [indexer fixture](https://github.com/l0ng-ai/openidea/tree/main/crates/ide-plugin/tests/fixtures/indexer),
a line-oriented test language where `def key` defines, `ref key` refers and
`alias prefix# target#` aliases:
```rust
use openidea_plugin::indexing::{Alias, Anchor, Document, DocumentIndexer, Edges};
use openidea_plugin::{Position, Range, register_plugin};
impl DocumentIndexer for Indexer {
fn index(&mut self, doc: &Document) -> Edges {
let mut out = Edges::default();
for (line, text) in doc.text.lines().enumerate() {
let Some((kind, value)) = text.split_once(' ') else { continue };
let range = Range {
start: Position { line: line as u32, character: kind.len() as u32 + 1 },
end: Position { line: line as u32, character: text.encode_utf16().count() as u32 },
};
let anchor = Anchor { key: value.into(), range };
match kind {
"def" => out.definitions.push(anchor),
"ref" => {
out.references.push(anchor.clone());
out.markers.push(anchor);
}
"alias" => {
if let Some((prefix, target)) = value.split_once(' ') {
out.aliases.push(Alias { prefix: prefix.into(), target: target.into() });
}
}
_ => {}
}
}
out
}
}
register_plugin!(Indexer, indexing);
```
`doc.symbols` lists the host's symbols (`name`, `kind`, `container`, `range`), so a plugin
can anchor keys to declarations without parsing the language itself.
### Resolution rules
- When a key has definitions in the same detected module, other modules' definitions are
left out. Several targets open a chooser.
- Alias chains are followed with bounded depth and candidates.
- A marker's tooltip lists its targets.
## Branded markers (API 1.3)
A `[[contributes.markerIcons]]` entry with `markers` gives markers whose key starts with
one of its prefixes a package icon and an optional tooltip; the longest matching prefix
wins, then declaration order. Other markers keep the navigation glyph.
```toml
[[contributes.markerIcons]]
id = "usages"
icon = "icons/usages.svg"
iconDark = "icons/usages_dark.svg"
markers = ["usages#"] # up to 16 prefixes of 1–256 bytes
tooltip = "Usages of this class" # 1–256 bytes, shown above the targets
```
A marker published as `Anchor { key: "usages#User".into(), range }` then shows that icon.
IDEs older than API 1.3 refuse a manifest with `markers` or `tooltip`.
## Find Usages and Rename (API 1.3)
With no extra export, the graph also answers:
- **Find Usages** on a published definition or reference lists every definition and
reference of its key alongside the language server's results.
- **Rename** replaces every such anchor with the new name, merged with the language
server's edits into one workspace edit. It always shows a preview, even for one file;
cancelling changes nothing, and applying it is one undoable change.
For this to work, anchors must cover **exactly the name**: no quotes, delimiters or
surrounding syntax, in UTF-16 columns (an astral character is two columns). Markers are
excluded, since their ranges may cover an annotation or a whole line.
- Alias-linked keys follow the same targets and module preference as navigation.
- Queries flush queued indexing work but don't rescan the project; external changes must
reach the file watcher and be indexed first. If a document changed, wait for indexing and
rename again.
- When no language server can rename at the position, an anchor on a code identifier (per
the file's syntax tree) is not renamed, because the declaration, imports and file name
would be missed. Anchors in strings, comments, attribute values and files without a
grammar are still renamed from the index.
## How indexing runs
- Initial discovery reuses the host's project index. The editor schedules cancellable
batches of at most 16 files; edits and file changes invalidate the affected entries
before they are indexed again.
- Reloading, disabling or a failing plugin drops its graph. Nothing is answered from a
document version older than the editor's.
Limits: 2 MiB per document, 20,000 files per graph, 4,096 definitions and references and
4,096 markers per document, 256 aliases, 200,000 entries per graph, 16 project/window
graphs and 256 candidates per lookup. A document over a limit is not indexed; editing is
never blocked.
To try the fixture, build it with `plugins/build.sh crates/ide-plugin/tests/fixtures/indexer`
and load the folder as a development plugin in a trusted project with `.idx` files; its
README lists the steps. `cargo test -p ide-host --test plugin_indexing` runs the real
component contract checks.
## Project HTTP endpoints (API 1.3)
A plugin that knows a framework's routes can share them with the IDE: Search Everywhere's
**Endpoints** section, URL completion in `.http` files, and an **Open in HTTP Client** gutter
action on each declaration.
```toml
engines.openidea = "^1.3"
[capabilities]
required = ["project.read"]
[[contributes.endpoints]]
files = ["config/routes.rb", "routes/**/*.php"]
```
```rust
use openidea_plugin::endpoints::{Endpoint, EndpointsProvider};
use openidea_plugin::{Error, Invocation, Location, Position, Range, register_plugin};
impl EndpointsProvider for Routes {
fn endpoints(&mut self, ctx: &Invocation) -> Result<Vec<Endpoint>, Error> {
let project = ctx.project().ok_or("no project")?;
let root = project.root()?;
let text = project.read_file("config/routes.rb")?;
Ok(parse_routes(&text)
.into_iter()
.map(|(line, method, path, handler)| Endpoint {
method, // None accepts any method
path, // path parameters in braces: /photos/{id}
framework: "Rails".into(),
handler: Some(handler), // display name, e.g. PhotosController#show
location: Location {
path: format!("{root}/config/routes.rb"),
range: Range::new(Position::new(line, 0), Position::new(line, 0)),
},
})
.collect())
}
}
register_plugin!(Routes, endpoints);
```
- `endpoints(ctx)` runs the first time the IDE needs the project's routes, and again after a
file matching `files` is created, changed or removed; in between the host answers from its
cache. Return the complete list (an empty list clears it).
- It runs in the background and can't prompt. Each endpoint's location must be inside the
project.
- **Open in HTTP Client** opens the generated-requests scratch file; requests start at
`http://localhost:8080`, path parameters become HTTP client variables, and any-method
routes use GET.
- The host validates the whole batch before replacing the last valid result: up to 10,000
endpoints, 4 MiB of route metadata, 16 MiB of declaration documents.
- Disabling, uninstalling, reloading or crashing withdraws the routes; idle reclaim keeps
them cached. This API adds no Endpoints tool window.
@@ -0,0 +1,397 @@
---
title: "Language features"
description: "Add completion, hover, navigation, inlay hints, semantic colours, formatting and more to any language, or contribute a whole new language."
---
Plugins extend the editor at three levels:
1. **A language, declaratively.** `[[contributes.languages]]` adds file detection,
comments, indentation, language servers and a debugger, with no code.
`[[contributes.snippets]]` adds live templates.
2. **Syntax for a language the IDE has no grammar for.** The optional `syntax-provider`
export returns highlighting, structure and folds.
3. **Editor providers** for any language, built-in or not: completion, hover, go to
definition, gutter markers, semantic tokens, inlay hints, formatting, code lenses and
the rest. Declare them under `[[contributes.providers]]`.
Quick fixes and problems are on [Diagnostics and inspections](/en/plugins/guides/diagnostics);
project-wide navigation built from every file is on [Indexing](/en/plugins/guides/indexing).
## Declare providers
```toml
engines.openidea = "^1.3"
wasm = "dist/plugin.wasm"
[[contributes.providers]]
kinds = ["completion", "hover"]
languages = ["markdown"] # editor language ids; "*" for any (1–64 entries)
```
Only plugins whose declaration matches the document's language are called, and loading
the code is part of the first call, so no `activation` entry is needed. The host calls only
the kinds you declare. Providers need code (`wasm`) but no extra capability: the document
comes with the call. Host functions you call from a provider keep their usual capability
checks.
| Kind | Export (WIT) | Rust trait, `register_plugin!` name | API |
| ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------- | ---------------------------------------------------------- | --- |
| `completion` | `providers` | `providers::Providers`, `providers` | 1.0 |
| `inline-completion` | `providers` | `providers::Providers`, `providers` | 1.0 |
| `definition` | `providers` | `providers::Providers`, `providers` | 1.0 |
| `line-markers` | `providers` | `providers::Providers`, `providers` | 1.0 |
| `code-actions` | `providers`, `file-actions` | see [Diagnostics](/en/plugins/guides/diagnostics) | 1.0 |
| `semantic-tokens` | `semantic-tokens-provider` | `semantic_tokens::SemanticTokens`, `semantic_tokens` | 1.3 |
| `hover` | `hover-provider` | `hover::HoverProvider`, `hover` | 1.3 |
| `inlay-hints` | `inlay-hints-provider` | `inlay_hints::InlayHints`, `inlay_hints` | 1.3 |
| `diagnostics` | `diagnostics-provider` | see [Diagnostics](/en/plugins/guides/diagnostics) | 1.3 |
| `formatting`, `signature-help`, `document-highlights`, `document-links`, `code-lenses`, `selection-ranges`, `folding`, `colors`, `document-symbols`, `implementation`, `type-definition`, `gutter-actions` | `language-features` | `language_features::LanguageFeatures`, `language_features` | 1.4 |
| `injections` | `injections` | `language_features::Injections`, `injections` | 1.4 |
Combine exports in one call: `register_plugin!(MyPlugin, providers, hover, language_features)`.
Every trait method has a default that returns nothing, so implement only what you declared.
## A minimal provider
```rust
use openidea_plugin::hover::{Hover, HoverProvider};
use openidea_plugin::providers::{CompletionItem, CompletionKind, Document, Providers};
use openidea_plugin::{Error, Invocation, Plugin, Position, register_plugin, text};
#[derive(Default)]
struct Todos;
impl Plugin for Todos {
fn run_command(&mut self, id: &str, _ctx: &Invocation, _arg: Option<&str>) -> Result<Option<String>, Error> {
Err(Error::NotFound(id.into()))
}
}
impl Providers for Todos {
fn completion(&mut self, _doc: &Document, _at: Position, _trigger: Option<&str>) -> Vec<CompletionItem> {
let mut item = CompletionItem::new("todo", CompletionKind::Snippet);
item.insert_text = "TODO(${1:owner}): $0".into();
item.snippet = true;
item.detail = Some("TODO comment".into());
vec![item]
}
}
impl HoverProvider for Todos {
fn hover(&mut self, doc: &Document, at: Position) -> Option<Hover> {
let offset = text::offset(&doc.text, at)?;
doc.text[offset..].starts_with("TODO").then(|| Hover::new("**TODO**: tracked work"))
}
}
register_plugin!(Todos, providers, hover);
```
Every provider gets a `Document` with the absolute `path`, the editor `language` id, a
`version` and the full `text`, unsaved edits included. Positions are 0-based lines and
UTF-16 columns; `openidea_plugin::text::offset` and `text::position` convert between them
and byte offsets.
## Budgets
Editor providers run on the editing path:
- A result later than about **50 ms** is dropped; a call still running at **500 ms** is
interrupted. Every plugin providing a kind is asked at the same time, so each has the
whole budget.
- Providers share the plugin's single instance with its commands, events and views. They
see the same memory but also wait behind them: a provider call queued behind a long
command is dropped at 50 ms like a slow one. Keep commands and event handlers short.
- `inline-completion` is exempt from the 50 ms (it may need the network) but is cancelled
as soon as the user types on. Check `Task::is_cancelled` or let the host interrupt it.
- `formatting` and code-action resolution use the 5-second command budget.
## Completion, inline completion, navigation and gutter markers
These are methods of `providers::Providers` (WIT `providers`):
- **`completion(doc, at, trigger)`** returns `CompletionItem`s that join the language
servers' list. `kind` (`CompletionKind`) picks the icon. An item can be a `snippet`
(`$1`, `${1:name}`, `$0`), replace a `replace` range, match on `filter_text` instead of its
label, sort by `sort_text`, carry `documentation`, and bring `additional_edits` elsewhere
in the document (an import).
- **`inline_completion(doc, at)`** returns ghost text at the caret
(`InlineSuggestion { insert_text, replace }`).
[AI Completion](https://github.com/l0ng-ai/openidea/tree/main/plugins/official/ai-completion)
is a working example.
- **`definition(doc, at)`** returns `Location`s, merged with the language server's for
Go to Declaration (`⌘B` / `Ctrl+B`).
- **`line_markers(doc)`** returns gutter icons with a `tooltip` and an optional `target`
opened on click.
```rust
use openidea_plugin::providers::{Document, LineMarker, Providers};
impl Providers for Mapper {
fn line_markers(&mut self, doc: &Document) -> Vec<LineMarker> {
self.links(doc)
.into_iter()
.map(|(line, target)| LineMarker {
line,
icon: Some("implemented".into()),
tooltip: "Go to the XML statement".into(),
target: Some(target),
})
.collect()
}
}
```
[MyBatis Navigator](https://github.com/l0ng-ai/openidea/tree/main/plugins/official/mybatis)
uses `definition`, `line-markers` and `completion` for Java and XML.
**Marker icons.** `icon` accepts an icon name (including registered plugin icons), the
aliases `navigate` (default), `implemented`, `implementing`, `overridden`, `overriding`
and `run`, or a package SVG declared as a marker icon (API 1.3):
```toml
[[contributes.markerIcons]]
id = "resource"
icon = "icons/resource.svg"
iconDark = "icons/resource_dark.svg" # optional
```
Return `icon: Some("icons/resource.svg".into())` to use it. Marker SVGs are at most
64 KiB and sanitised like view icons; unknown icons keep the navigation glyph. For a
clickable command instead of navigation, use `gutter-actions` (below).
## Semantic tokens
`semantic-tokens` (API 1.3) colours tokens in documents of the declared languages,
including languages the plugin doesn't contribute, such as `php`:
```rust
use openidea_plugin::semantic_tokens::{Document, SemanticToken, SemanticTokens, TokenKind, TokenModifier};
use openidea_plugin::{Range, text};
impl SemanticTokens for MyPlugin {
fn semantic_tokens(&mut self, doc: &Document) -> Vec<SemanticToken> {
let (start, end) = (text::position(&doc.text, 10), text::position(&doc.text, 14));
vec![SemanticToken::new(Range { start, end }, TokenKind::Method).modifier(TokenModifier::Static)]
}
}
```
A token covers part of one line and carries an LSP token type and modifiers (kebab-case
strings in the WIT). Tokens join the language server's and win where they overlap one, so
the colour scheme styles them like the server's. They are requested again after each edit;
at most 65,536 per document.
## Hover
`hover` (API 1.3) adds Markdown to the hover. The host asks the language server and the
plugins together and shows the server's hover first, then each plugin's below a rule.
`Hover::new(markdown).range(range)` names the text the hover describes (used when the
server names none). At most 64 KiB of Markdown per plugin.
## Inlay hints
`inlay-hints` (API 1.3) shows short labels inside the code:
```rust
use openidea_plugin::inlay_hints::{Document, InlayHint, InlayHints, InlayKind};
use openidea_plugin::Range;
impl InlayHints for MyPlugin {
fn inlay_hints(&mut self, _doc: &Document, range: Range) -> Vec<InlayHint> {
vec![InlayHint::new(range.start, ": string").kind(InlayKind::Type).tooltip("returns string")]
}
}
```
`range` is the visible part of the document; hints outside it are dropped. `kind`
(`Type`, `Parameter` or `Other`) decides which **Inlay Hints** setting shows the hint.
`.padding_left()` and `.padding_right()` add spaces. Labels are cut at 256 bytes, tooltips
at 1 KiB, and at most 10,000 hints per call.
## More language features (API 1.4)
Declare any of these kinds with `engines.openidea = "^1.4"`, implement
`language_features::LanguageFeatures` and register `language_features`:
| Kind | Method | How the host uses it |
| --------------------- | ------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `formatting` | `format(doc, range, options)` | **Settings › Languages & Frameworks › Plugin Formatters** picks a plugin per language. Used by Reformat Code, selection formatting and format on save. 5-second budget. |
| `signature-help` | `signature_help(doc, at)` | Used when the language server returns no signatures. |
| `document-highlights` | `document_highlights(doc, at)` | Appended to the server's highlights (`text`, `read`, `write`). |
| `document-links` | `document_links(doc)` | Ctrl/⌘-click targets: absolute paths, project-relative paths or HTTP(S) URLs. |
| `code-lenses` | `code_lenses(doc)` | Shown beside the server's lenses. A click runs one of the plugin's declared commands. |
| `selection-ranges` | `selection_ranges(doc, at)` | **Extend Selection**, innermost to outermost; falls back to tree-sitter. |
| `folding` | `folding(doc)` | Merged with tree-sitter folds. |
| `colors` | `colors(doc)`, `color_presentations(doc, color)` | Colour swatches and the colour picker; channels are floats in 0–1. |
| `document-symbols` | `document_symbols(doc)` | Structure and breadcrumbs, only when the server returns no symbols. |
| `implementation` | `implementation(doc, at)` | Merged with the server's implementations. |
| `type-definition` | `type_definition(doc, at)` | Merged with the server's type declarations. |
| `gutter-actions` | `gutter_actions(doc)` | Gutter buttons that run one of the plugin's declared commands, with an optional argument. |
A code lens that offers a command, from the
[Blame Annotations](https://github.com/l0ng-ai/openidea/tree/main/plugins/official/blame-annotations)
example:
```rust
use openidea_plugin::language_features::{CodeLens, Document, LanguageFeatures};
use openidea_plugin::Range;
impl LanguageFeatures for BlameAnnotations {
fn code_lenses(&mut self, _doc: &Document) -> Vec<CodeLens> {
vec![CodeLens {
range: Range::default(),
title: "Show blame annotations".into(),
command: "annotate".into(), // a command this plugin declares
argument: None,
}]
}
}
register_plugin!(BlameAnnotations, language_features);
```
[Test Runner](https://github.com/l0ng-ai/openidea/tree/main/plugins/official/test-runner)
shows `gutter-actions`. Rules that apply to all of them:
- A missing or unavailable language server lets plugin results through; other server
errors stay errors.
- `document-symbol.parent` is an index into the returned flat list. A parent must come
first and contain its children; invalid parents become roots, depth is capped at 64.
- Each list holds at most 10,000 items, text at most 64 KiB per string; invalid ranges are
dropped. Format edits must be valid and must not overlap.
- Disabling the selected formatter reports it as unavailable rather than silently using
another one.
## Embedded languages (injections)
`injections` (API 1.4) marks ranges of a host document that contain another language,
such as SQL inside string literals. Declare the kind for the **host** language,
implement `Injections` and register `injections`:
```rust
use openidea_plugin::language_features::{Document, Injection, Injections};
use openidea_plugin::text;
impl Injections for MyPlugin {
fn injections(&mut self, doc: &Document) -> Vec<Injection> {
let Some(start) = doc.text.find("[[") else { return vec![] };
let Some(len) = doc.text[start + 2..].find("]]") else { return vec![] };
vec![Injection {
range: openidea_plugin::Range {
start: text::position(&doc.text, start + 2),
end: text::position(&doc.text, start + 2 + len),
},
language: "sql".into(),
}]
}
}
```
What the host does with the ranges:
- Highlights each range with the embedded language's parser and the current colour
scheme, refreshed 300 ms after edits.
- Routes completion, hover, diagnostics and the API 1.4 features to the embedded
language's **plugin** providers, through a virtual document that keeps the original path
and positions and blanks out text outside the ranges.
- When the embedded language has a configured language server, routes completion, hover,
signature help, navigation, references, highlights and code actions to it, each range in
its own in-memory sibling document (never written to disk). Formatting and rename stay
with the host language's server.
Limits: at most 1,000 non-overlapping ranges and 32 embedded languages per document;
documents over 2 MiB are skipped; injections are not recursive.
## Syntax for a plugin language
For a language the plugin contributes and the IDE has no tree-sitter grammar for, export
`syntax-provider` (world `plugin-syntax`). Offsets here are **UTF-8 byte** ranges into the
text; the IDE converts them.
```rust
use openidea_plugin::syntax::{Fold, Highlight, Symbol, SyntaxProvider, Token};
impl SyntaxProvider for Solidity {
fn highlights(&mut self, _language: &str, text: &str) -> Vec<Token> {
lex(text).map(|(range, keyword)| Token { range, kind: if keyword { Highlight::Keyword } else { Highlight::Variable } }).collect()
}
fn outline(&mut self, _language: &str, text: &str) -> Vec<Symbol> { outline(text) } // optional
fn folding(&mut self, _language: &str, text: &str) -> Vec<Fold> { // optional
openidea_plugin::syntax::bracket_folds(text, b'{', b'}', &[])
}
}
register_plugin!(Solidity, syntax);
```
It runs once per document version (cached) on the editing path, so keep it fast; a
hand-written lexer is typical. Add `activation = ["on-language:<id>"]` so the component
loads with the first file. Results are validated: clamped to the text, overlaps dropped.
[Solidity](https://github.com/l0ng-ai/openidea/tree/main/plugins/official/solidity) is the
complete example.
## Contributing a language
```toml
[[contributes.languages]]
path = "languages/solidity.toml"
```
The file uses the IDE's own [language definition format](/en/languages/adding) (the same
as `languages/<id>.toml` in the repository):
```toml
id = "solidity"
name = "Solidity"
extensions = ["sol"]
line-comment = "//"
block-comment = ["/*", "*/"]
indent = { tab-size = 4, insert-spaces = true }
[[language-servers]]
name = "solidity-language-server"
command = "nomicfoundation-solidity-language-server"
args = ["--stdio"]
root-markers = ["hardhat.config.ts", "foundry.toml", "package.json"]
install-hint = "npm install -g @nomicfoundation/solidity-language-server"
```
- Plugin languages join the registry like built-in ones: detection (`extensions`,
`filenames`, `first-line`), comments, indentation, `ui-mode`, `framework-ui-modes`, and
the `language-servers` and `debugger` they declare.
- Each language server or debugger command becomes an optional `process:<program>`
capability. Refusing it keeps the rest of the plugin working.
- A plugin can't replace a built-in or user definition: their ids, extensions and file
names win. Users can still map files to a plugin language in **Settings › File Types**.
- In a remote project the definitions go to the server, which starts the servers there.
`openidea-plugin new my-lang --template language` starts a data-only language plugin.
## Snippets
```toml
[[contributes.snippets]] # API 1.4
language = "sql"
path = "snippets/sql.json"
```
```json
{
"Select rows": {
"prefix": ["sel", "select"],
"body": ["SELECT ${1:column}", "FROM ${2:table}$0"],
"description": "Select rows from a table"
}
}
```
`prefix` and `body` take a string or an array of strings (body lines join with newlines).
`$1`, `${1:default}` and a final `$0` become linked live-template fields; `\$` is a literal
dollar. Snippets show in completion, expand with Tab, and are listed read-only under
**Settings › Editor › Live Templates**, grouped by plugin. No code or capability is needed.
Limits: 1 MiB per file, 1,000 entries, 32 prefixes per entry, 64 KiB per body.
## Testing
On your machine the SDK calls its mock host, so call your trait methods directly in
`cargo test`. `MockHost::provider_document()` returns the current unsaved snapshot of the
mock editor. See [Testing](/en/plugins/testing).
+123
View File
@@ -0,0 +1,123 @@
---
title: "MCP tools"
description: "Add tools to the IDE's built-in MCP server, implemented by plugin code under the plugin's own permissions."
---
OpenIDEA has a built-in MCP server that AI agents use to work with the open project (see
[MCP server](/en/guide/mcp)). A plugin can add its own tools to it: declare each tool with a
JSON Schema for its input, and implement the calls in code. This is the `mcp-provider`
export and the `mcp` host interface (`mcp.wit`, API 1.4).
## Declare a tool
```toml
engines.openidea = "^1.4"
wasm = "dist/plugin.wasm"
[capabilities]
required = ["project.read"]
[[contributes.mcpTools]]
name = "find_notes"
description = "Find notes in this project"
inputSchema = "find-notes.schema.json"
readOnly = true
destructive = false
```
`find-notes.schema.json`:
```json
{
"type": "object",
"properties": { "text": { "type": "string" } },
"required": ["text"],
"additionalProperties": false
}
```
| Field | Notes |
| ------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `name` | `[a-z0-9_]{1,40}`. The registered name is the plugin id with every character outside `[a-z0-9_]` replaced by `_`, then `__`, then the name: `acme_notes__find_notes`. It must fit in 64 bytes. |
| `description` | 1–4096 bytes. The server listing identifies the plugin the tool comes from. |
| `inputSchema` | A package path to a self-contained JSON Schema describing an object, at most 256 KiB. Local `$ref` fragments work; network and file references are refused. |
| `readOnly` | Required. `false` tools are hidden and can't be called in Restricted Mode. |
| `destructive` | Required. `true` makes every call ask the project's window for confirmation, with the same UI as the built-in run tools. |
The schema is validated at install and by `openidea-plugin check`. A plugin with tools needs
code (`wasm`).
## Implement the calls
Implement `openidea_plugin::McpProvider` and register the `mcp` export:
```rust
use openidea_plugin::{Error, Invocation, McpProvider, Plugin, TextQuery, register_plugin};
#[derive(Default)]
struct Notes;
impl Plugin for Notes {
fn run_command(&mut self, id: &str, _ctx: &Invocation, _arg: Option<&str>) -> Result<Option<String>, Error> {
Err(Error::NotFound(id.into()))
}
}
impl McpProvider for Notes {
fn call(&mut self, tool: &str, arguments: &str, ctx: &Invocation) -> Result<String, Error> {
match tool {
"find_notes" => {
let args: serde_json::Value = serde_json::from_str(arguments).map_err(|e| Error::Invalid(e.to_string()))?;
let text = args["text"].as_str().unwrap_or_default();
let project = ctx.project().ok_or("project required")?;
let found = project.search_text(&TextQuery { pattern: text.into(), ..Default::default() }, 100)?;
let lines: Vec<_> = found.matches.iter().map(|m| format!("{}:{}: {}", m.location.path, m.location.range.start.line + 1, m.line)).collect();
Ok(lines.join("\n"))
}
_ => Err(Error::NotFound(tool.into())),
}
}
}
register_plugin!(Notes, mcp);
```
This example uses `serde_json`, so add it to `Cargo.toml`.
- `tool` is the local name (`find_notes`), `arguments` the JSON text of the input,
already checked against the schema (at most 1 MiB). `ctx` is the project's invocation
context.
- Return text. For structured results, return JSON encoded as text. Results are at most
4 MiB.
- The WIT export is `mcp-provider.call(tool, arguments, ctx)`; the world `plugin-mcp`
includes the base plugin world, and the export-only `mcp-tools` world composes with it.
## How calls run
- Enabled tools are listed on every request, so disabling or uninstalling the plugin
withdraws them immediately.
- A call runs in the plugin's own instance with its own grants, CPU fuel and the 5-second
command budget. The MCP client grants no extra permissions: a call to a host function the
plugin lacks a capability for returns `Error::Denied`.
- A tool has the project's invocation context but is not a user command or event: APIs
reserved to commands and events (dialogs, `start_run`, opening tabs, …) return `invalid`.
- A `destructive` call that the user rejects or cancels, or that has no window to ask in,
fails.
- If two enabled plugins normalise to the same tool name, both tools are withheld.
- The built-in server currently serves local projects; tool calls for remote projects get
its "not supported for remote projects yet" response.
## Pointing an agent at the server
`host::mcp_stdio_command()` (WIT `mcp.stdio-command`, no capability) returns the argv of the
stdio bridge for the invoking project, for example
`["/path/openidea", "--mcp-stdio", "--project", "/project"]`. A plugin that launches or
configures an agent can pass it on. It returns `None` while the server is off, without a
project window, or on a remote host without its own running server. It never exposes the
server's HTTP URL or token.
## Testing
Call `McpProvider::call` directly against the SDK's mock host in `cargo test`. The
[`data-tools` fixture](https://github.com/l0ng-ai/openidea/tree/main/crates/ide-plugin/tests/fixtures/data-tools)
declares read-only, writing and destructive tools and is used by the host's tests.
+282
View File
@@ -0,0 +1,282 @@
---
title: "Run targets"
description: "Contribute run configurations, parse test output into the test tree, start runs from code, wrap runs in containers with run targets, and run approved programs."
---
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:
| Goal | Use | API |
| ---------------------------------------------------------------------- | ----------------------------------------------------------------- | --------- |
| Offer a fixed command when the project has a marker file | `[[contributes.runDetectors]]` (no code) | 1.0 |
| Compute the project's configurations in code | `[[contributes.runConfigurations]]` + `run-configurations` export | 1.4 |
| Turn test output into the IDE's test tree | `test-results` export, `framework = "plugin"` | 1.4 |
| Start one of its configurations from a command | `host::start_run` (WIT `run.start`) | 1.4 |
| Make every run of the project go through a wrapper (DDEV, a container) | `host::register_run_target` (WIT `run-targets`) | 1.4 |
| Check the wrapper's runtime before each launch | `run-target-validation` export | 1.5 |
| Run a program and read its output | `Project::run_process`, `host::run_command`, `host::start` | 1.0 / 1.3 |
## Run detectors
A run detector is a static configuration offered in projects that contain one of its
marker files at the root:
```toml
[[contributes.runDetectors]]
id = "forge-test"
name = "forge test"
files = ["foundry.toml"] # offered in projects with any of these at the root
command = "forge" # one program; becomes the process:forge capability
args = ["test"]
test = true # results go to the test tree
# framework = "plugin" # API 1.4: parse the output with the test-results export
```
The configuration appears among the detected ones while `process:<command>` is granted
and starts the approved program. No code is needed.
[Solidity](https://github.com/l0ng-ai/openidea/tree/main/plugins/official/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`:
```toml
engines.openidea = "^1.4"
[capabilities]
required = ["process:openidea-example-tests"]
[[contributes.runConfigurations]]
files = ["example.tests"] # 1–16 marker files; the export is asked only when one exists
```
```rust
use openidea_plugin::{PluginRunConfiguration, RunConfigurations, register_plugin};
impl RunConfigurations for TestRunner {
fn configurations(&mut self, _root: &str) -> Vec<PluginRunConfiguration> {
vec![PluginRunConfiguration {
id: "tests".into(),
name: "Example tests".into(),
program: "openidea-example-tests".into(), // a declared, granted process: name
test: true,
framework: Some("plugin".into()),
..Default::default()
}]
}
}
register_plugin!(TestRunner, 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`:
```rust
use openidea_plugin::{PluginTestEvent, TestFailed, TestPassed, TestResults, TestStart};
impl TestResults for TestRunner {
fn parse(&mut self, _config: &str, chunk: &str) -> Vec<PluginTestEvent> {
self.pending.push_str(chunk); // chunks are not whole lines
let mut events = vec![];
while let Some(end) = self.pending.find('\n') {
let line: String = self.pending.drain(..=end).collect();
let Some((kind, name)) = line.trim().split_once(' ') else { continue };
let id = name.to_owned();
match kind {
"start" => events.push(PluginTestEvent::TestStarted(TestStart { id, parent: None, name: name.into(), location: None })),
"pass" => events.push(PluginTestEvent::Passed(TestPassed { id, duration_ms: None })),
"fail" => events.push(PluginTestEvent::Failed(TestFailed {
id, duration_ms: None, message: "failed".into(), expected: None, actual: None, location: None,
})),
_ => {}
}
}
events
}
fn select_tests(&mut self, config: &str, tests: &[Vec<String>]) -> Option<Vec<String>> {
// Full replacement argv that runs only these tests; None when unsupported.
(config == "tests" && tests.iter().all(|p| p.len() == 1)).then(|| tests.iter().map(|p| p[0].clone()).collect())
}
}
```
- `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](https://github.com/l0ng-ai/openidea/tree/main/plugins/official/test-runner)
example, where a gutter button passes the test name as the argument:
```rust
fn run_command(&mut self, _id: &str, _ctx: &Invocation, arg: Option<&str>) -> Result<Option<String>, Error> {
let args = arg.into_iter().map(str::to_owned).collect::<Vec<_>>();
host::start_run("tests", &args, false).map(Some)
}
```
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.
```rust
use openidea_plugin::{PluginRunTarget, host};
let project = ctx.project().ok_or("Open a project first")?;
host::register_run_target(&project, &PluginRunTarget {
id: "ddev".into(),
name: "DDEV".into(),
program: "ddev".into(), // needs process:ddev
args: vec!["exec".into()],
workdir_args: vec!["--dir={cwd}".into(), "--".into()],
container_root: Some("/var/www/html".into()),
commands: vec![("composer".into(), vec!["composer".into()])],
})?;
```
### 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:
```rust
use openidea_plugin::PluginCommandOverride;
PluginCommandOverride { name: "composer".into(), args: vec!["composer".into()], workdir_args: Some(vec![]) }
```
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](https://github.com/l0ng-ai/openidea/tree/main/plugins/official/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:
```rust
use openidea_plugin::{RunTargetLaunch, RunTargetValidation, register_plugin};
impl RunTargetValidation for MyPlugin {
fn validate(&mut self, project: &str, _target_id: &str, _launch: &RunTargetLaunch) -> Result<(), String> {
if runtime_is_running(project) { Ok(()) } else { Err("Project runtime is stopped".into()) }
}
}
register_plugin!(MyPlugin, run_target_validation);
```
`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
| Function | What it does |
| ---------------------------------------------------- | ------------------------------------------------------------------------------------------------ |
| `Project::run_process(program, &args, stdin)` | Runs the pinned program in the project root and waits: `{ status, stdout, stderr }`, 4 MiB each. |
| `host::run_command(&Command, Some(&project), stdin)` | API 1.3: the same, with environment variables added by `Command::env`. |
| `host::start(&Command, Some(&project))` | API 1.3: a `Child` left running: `poll`, `write`, `close_stdin`, `wait`, `kill`. At most 8. |
| `host::open_terminal(title, program, &args)` | A Terminal tab running the program (see [Views](/en/plugins/guides/views)). |
```rust
use openidea_plugin::host::{self, Command};
let project = ctx.project().ok_or("Open a project first")?;
let out = host::run_command(&Command::new("forge").arg("build").env("FOUNDRY_PROFILE", "ci"), Some(&project), None)?;
```
- 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.
+660
View File
@@ -0,0 +1,660 @@
---
title: "Themes"
description: "Write a colour theme plugin: the theme and editor scheme files, how themes are applied, importing IntelliJ themes, porting VS Code themes, and the full token reference."
---
OpenIDEA ships seven built-in themes: Islands Light, Islands Dark, High Contrast,
Solarized Light, Solarized Dark, One Light and One Dark. Other themes come from **theme
plugins**.
A theme plugin is data only. Like an IntelliJ theme (a `.theme.json` plus an optional
`.icls` editor scheme), it has no code: no WebAssembly, nothing to compile, no
capabilities.
```text
my-theme/
├─ plugin.toml # manifest
└─ themes/
├─ my-theme.theme.json # UI colours (IntelliJ: .theme.json)
└─ my-theme.scheme.json # editor colours, optional (IntelliJ: .icls)
```
The quickest start is `openidea-plugin new my-theme --template theme`, which copies
[Harbor Night](https://github.com/l0ng-ai/openidea/tree/main/plugins/official/harbor-night),
a complete dark theme that sets every themeable token.
## The manifest
```toml
id = "acme.harbor-night"
name = "Harbor Night"
version = "1.0.0"
description = "A dark theme: slate surfaces, a teal accent and a warm syntax palette."
authors = ["You <[email protected]>"]
license = "MIT"
engines.openidea = "^1.0"
categories = ["themes"]
[[contributes.themes]] # one entry per theme; a plugin may ship several
path = "themes/harbor-night.theme.json"
editor-scheme = "themes/harbor-night.scheme.json" # optional
```
There is no `wasm` and no `[capabilities]`. Paths are relative to the package directory
and may not leave it. The other manifest fields are on [Manifest](/en/plugins/manifest).
## The theme file
```jsonc
{
"id": "harbor-night", // unique within the plugin
"name": "Harbor Night", // shown in Settings
"kind": "dark", // "dark" or "light": the built-in base
"tokens": {
"--bg-app": "#14171c",
"--accent": "#2b9e93",
},
}
```
- **`tokens`** are the CSS variables of
[`src/styles/tokens.css`](https://github.com/l0ng-ai/openidea/blob/main/src/styles/tokens.css),
the IDE's single source of colours. Components never use colour literals, so every
surface, border, text colour, syntax colour and status colour can be themed. The
[reference](#token-reference) lists them all.
- **`kind`** picks the built-in base. While the theme is active the IDE runs on Islands Dark
or Islands Light, and every token the theme leaves out keeps that base's value. Icons use
the light or dark set that matches the kind. A partial theme still looks consistent, but
listing every token keeps it stable when the IDE's own palette changes.
- Token names are `--` followed by lowercase letters, digits and `-` (at most 64 bytes).
- Values are plain CSS values: hex (`#rgb`, `#rrggbb`, `#rrggbbaa`), `rgb()`, `hsl()`,
`color-mix()`, `var(--other-token)`, or for the few non-colour tokens a keyword
(`italic`, `normal`, `none`) or a shadow list. `url()`, `image()`, `;`, braces, `<`, `>`,
`\` and line breaks are rejected.
- `//` and `/* */` comments are allowed, as in VS Code theme files.
- Tokens that tokens.css derives from others (for example `--bg-editor` is
`var(--bg-island)`, and the coverage and flame graph colours are mixed from the status
colours) follow automatically. You may still set them.
- Metrics (`--fs-*`, `--h-*`, `--radius-*`) and fonts (`--font-ui`, `--font-mono`) can be
overridden too, but a theme normally leaves them alone so that compact mode and the user's
font settings keep working.
### The editor colour scheme file
```jsonc
{
"name": "Harbor Night",
"tokens": { "--syn-keyword": "#e0a46e", "--caret": "#5ce0d2" },
}
```
Its tokens are merged over the theme's, and the scheme wins where both set a token. By
convention it holds the _Syntax_, _Editor_ and _ANSI palette_ groups of the reference, the
parts IntelliJ keeps in an `.icls` file. The split only organises the files: a theme can
put everything in its theme file.
## How a theme is applied
The selected theme is injected as one stylesheet on `:root` that outranks the built-in
theme blocks, and `data-theme` is switched to the theme's `kind`. Three things still layer on
top of it:
- an **Accent colour** picked in **Settings › Appearance & Behavior › Appearance** (other
than the default),
- an explicit scheme or attribute overrides in **Settings › Editor › Color Scheme**,
- the appearance options that change only metrics (compact mode).
The selection is remembered. When the plugin is disabled or uninstalled, the built-in theme
returns.
Stripe tooltips use `--bg-stripe-tooltip`, `--fg-stripe-tooltip`, `--border-stripe-tooltip`
and `--shadow-stripe-tooltip`. A theme that omits them inherits the corresponding base
tooltip tokens.
## Install, apply and iterate
There is no build step: the package is the folder.
<Steps>
<Step title="Start from the template">
Run `openidea-plugin new my-theme --template theme` (or copy `plugins/official/harbor-night`) and rename `id`,
`name` and the theme files.
</Step>
<Step title="Edit the colours">Keep `kind` truthful (`light` for light backgrounds).</Step>
<Step title="Install">
In **Settings › Plugins**, open the gear menu and choose **Install Plugin from Disk…** (also an action in Find
Action) and pick the package folder, or **Install Plugin from Package File…** and pick its `plugin.toml` or a `.zip`
of the folder.
</Step>
<Step title="Apply">
In **Settings › Plugins**, select the plugin and press **Apply** next to the theme. **Use Default Theme** switches
back. Plugin themes are also listed under **Theme** in **Settings › Appearance & Behavior › Appearance**.
</Step>
</Steps>
To iterate without reinstalling, link the folder with `openidea-plugin link` (it is read in
place), or put or symlink it in `<project>/.openidea/plugins/<name>/` of a trusted project.
Then press **Reload** on the plugin, or run the **Reload Plugins** action, to pick up your
edits. If a file or value is invalid, **Settings › Plugins** shows the load error with the
file and line.
To share a theme, publish it like any plugin; see [Packaging](/en/plugins/publishing/packaging).
## Importing IntelliJ themes
To use a theme you already have in a JetBrains IDE, open the gear menu in **Settings ›
Plugins** (a project must be open):
- **Import Theme from JetBrains IDE…** lists the theme plugins installed in the JetBrains IDEs
(and Android Studio) on this computer, one row per plugin of each IDE, with the plugins of
the themes the IDEs use first. It reads the settings folder of each IDE's newest version:
`~/Library/Application Support/JetBrains` (macOS), `%APPDATA%\JetBrains` (Windows),
`~/.config/JetBrains` and `~/.local/share/JetBrains` (Linux).
- **Import IntelliJ Theme from File…** takes a theme plugin downloaded from the JetBrains
Marketplace (`.jar`, or a `.zip` with the plugin's `lib/*.jar`) or a `.theme.json` (its
`editorScheme` file is read from the same folder).
The whole plugin becomes one OpenIDEA theme plugin, `intellij.<plugin id>`, with each of its
themes and their editor schemes. It is installed through the usual install page, and the
theme in use doesn't change: choose one of its themes under **Theme** in **Settings ›
Appearance & Behavior › Appearance**. Importing the plugin again replaces it. Both commands
are also actions (`plugins.importInstalledIntellijTheme`, `plugins.importIntellijTheme`).
### Porting an IntelliJ theme for publishing
Only port themes whose licence allows redistribution, keep their licence file, and credit
the original authors in `authors` and `description`.
`scripts/theme/import-intellij-theme.ts` in the repository converts an IntelliJ
`.theme.json`, and optionally its `.icls` editor scheme, into a plugin folder you can edit
before publishing. It needs only Node 22.
```sh
pnpm theme:import-intellij path/to/MyTheme.theme.json path/to/MyTheme.icls --out ~/my-theme --author "Original Author"
# or: node scripts/theme/import-intellij-theme.ts …
```
It maps the keys in the tables below, resolves named colours from the theme's `colors`
section and `*` wildcards, and takes `kind` from `"dark"` (or the `.icls` `parent_scheme`).
Tokens without an IntelliJ counterpart keep the built-in values for that kind. Themes that
ship only a `.theme.json` with an `editorScheme` path need that `.icls` passed as the second
argument; themes that ship no `.icls` keep the built-in syntax colours.
#### `.theme.json` to UI theme
| OpenIDEA token | IntelliJ `ui` key (first present wins) |
| -------------------------------------------------- | ------------------------------------------------------------------------- |
| `--bg-app` | `Panel.background` |
| `--bg-frame` | `MainWindow.background`, `MainToolbar.background` |
| `--bg-island`, `--bg-elev` | `ToolWindow.background` |
| `--bg-popup` | `Popup.background`, `PopupMenu.background` |
| `--bg-hover` | `List.hoverBackground`, `Tree.hoverBackground` |
| `--bg-selected`, `--bg-tree-selected` | `List.selectionBackground`, `Tree.selectionBackground` |
| `--bg-tree-selected-inactive` | `Tree.selectionInactiveBackground` |
| `--bg-input`, `--bg-field` | `TextField.background` |
| `--border` | `Borders.color` |
| `--border-strong`, `--border-window` | `Component.borderColor`, `Borders.ContrastBorderColor` |
| `--fg`, `--fg-emphasis` | `Label.foreground` |
| `--fg-secondary`, `--fg-muted` | `Label.infoForeground`, `Component.infoForeground` |
| `--fg-subtle`, `--fg-faint` | `Label.disabledForeground` |
| `--accent` | `Button.default.startBackground`, `Component.focusColor` |
| `--fg-on-accent`, `--fg-button-primary` | `Button.default.foreground` |
| `--link`, `--link-hover` | `Link.activeForeground`, `Link.hoverForeground` |
| `--danger`, `--warning` | `Component.errorFocusColor`, `Component.warningFocusColor` |
| `--bg-tooltip`, `--fg-tooltip`, `--border-tooltip` | `ToolTip.background`, `.foreground`, `.borderColor` |
| `--bg-button`, `--border-button` | `Button.startBackground`, `Button.startBorderColor` |
| `--bg-tab-selected` | `EditorTabs.underlinedTabBackground` |
| `--border-tab-selected` | `EditorTabs.underlinedBorderColor` (Islands), `EditorTabs.underlineColor` |
| `--fg-completion-match` | `CompletionPopup.matchForeground` |
| `--bg-toolbar-hover`, `--bg-toolbar-pressed` | `ActionButton.hoverBackground`, `ActionButton.pressedBackground` |
| `--bg-stripe-hover`, `--bg-stripe-selected` | `ToolWindow.Button.hoverBackground`, `.selectedBackground` |
| `--scrollbar-thumb`, `--scrollbar-thumb-hover` | `ScrollBar.Mac.thumbColor`, `ScrollBar.Mac.hoverThumbColor` |
| `--bg-toggle-*` | `ToggleButton.onBackground`, `.offBackground`, `.buttonColor` |
| `--bg-feedback-*`, `--border-feedback-*` | `Banner.infoBackground`, `Banner.errorBorderColor`, … |
The full table is `UI_KEYS` in
[`src/features/plugins/intellijTheme.ts`](https://github.com/l0ng-ai/openidea/blob/main/src/features/plugins/intellijTheme.ts).
#### `.icls` to editor scheme
| OpenIDEA token | `.icls` option |
| ------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------- |
| `--fg-editor`, `--bg-editor` | attribute `TEXT` (foreground, background) |
| `--caret`, `--bg-current-line` | colour `CARET_COLOR`, `CARET_ROW_COLOR` |
| `--bg-editor-selection`, `--fg-editor-selection` | colour `SELECTION_BACKGROUND`, `SELECTION_FOREGROUND` |
| `--fg-line-number`, `--fg-line-number-current` | colour `LINE_NUMBERS_COLOR`, `LINE_NUMBER_ON_CARET_ROW_COLOR` |
| `--bg-editor-gutter` | colour `EDITOR_GUTTER_BACKGROUND`, else the editor background (as the new UI paints it; `GUTTER_BACKGROUND` is the classic UI's) |
| `--border-indent-guide`, `--border-right-margin`, `--fg-whitespace` | colour `INDENT_GUIDE`, `RIGHT_MARGIN_COLOR`, `WHITESPACES` |
| `--vcs-*`, `--gutter-*` | colour `FILESTATUS_*`, `MODIFIED_LINES_COLOR`, `ADDED_LINES_COLOR`, `DELETED_LINES_COLOR` |
| `--syn-keyword`, `--syn-string`, `--syn-number` | `DEFAULT_KEYWORD`, `DEFAULT_STRING`, `DEFAULT_NUMBER` |
| `--syn-comment` (+ `--syn-comment-style`), `--syn-doc-comment` | `DEFAULT_LINE_COMMENT` (+ `FONT_TYPE`), `DEFAULT_DOC_COMMENT` |
| `--syn-doc-comment-tag`, `--syn-doc-comment-tag-value`, `--syn-doc-markup` (+ `-bg`) | `DEFAULT_DOC_COMMENT_TAG`, `DEFAULT_DOC_COMMENT_TAG_VALUE`, `DEFAULT_DOC_MARKUP` |
| `--syn-function`, `--syn-function-call` | `DEFAULT_FUNCTION_DECLARATION`, `DEFAULT_FUNCTION_CALL` |
| `--syn-type`, `--syn-constant`, `--syn-property` | `DEFAULT_CLASS_NAME`, `DEFAULT_CONSTANT`, `DEFAULT_INSTANCE_FIELD` |
| `--syn-variable`, `--syn-param`, `--syn-builtin` | `DEFAULT_LOCAL_VARIABLE`, `DEFAULT_PARAMETER`, `DEFAULT_PREDEFINED_SYMBOL` |
| `--syn-annotation`, `--syn-decorator` | `DEFAULT_METADATA` |
| `--syn-punct`, `--syn-escape` | `DEFAULT_OPERATION_SIGN`, `DEFAULT_VALID_STRING_ESCAPE` |
| `--syn-tag`, `--syn-attribute`, `--syn-todo`, `--syn-invalid` | `DEFAULT_TAG`, `DEFAULT_ATTRIBUTE`, `TODO_DEFAULT_ATTRIBUTES`, `BAD_CHARACTER` |
| `--syn-rainbow-1` … `--syn-rainbow-5` (Semantic highlighting) | `RAINBOW_COLOR1` … `RAINBOW_COLOR5` |
| `--wave-error`, `--wave-warning` | `ERRORS_ATTRIBUTES`, `WARNING_ATTRIBUTES` (effect colour) |
| `--bg-matched-brace`, `--bg-identifier-under-caret` | `MATCHED_BRACE_ATTRIBUTES`, `IDENTIFIER_UNDER_CARET_ATTRIBUTES` |
| `--bg-text-search-result`, `--bg-search-match` | `TEXT_SEARCH_RESULT_ATTRIBUTES`, `SEARCH_RESULT_ATTRIBUTES` |
| `--syn-self`, `--syn-keyword-arg`, `--syn-docstring`, `--syn-predefined` | `PY.SELF_PARAMETER`, `PY.KEYWORD_ARGUMENT`, `PY.DOC_COMMENT`, `PY.BUILTIN_NAME` |
| `--ansi-*` | `CONSOLE_*_OUTPUT` (`CONSOLE_DARKGRAY_OUTPUT` is bright black, `CONSOLE_GRAY_OUTPUT` white) |
The full tables are `ICLS_COLORS` and `ICLS_ATTRIBUTES` in the same file. An attribute
written as `<value />` has no colours of its own and shows as plain text (`TEXT`'s
foreground), as in IntelliJ, not with the parent scheme's colour.
## Porting a VS Code theme
A VS Code theme (`themes/*.json` in the extension) has `type` (`dark` / `light` / `hc`),
`colors` (workbench) and `tokenColors` (TextMate scopes). Set `kind` from `type`, then map
by hand:
| OpenIDEA token | VS Code `colors` key |
| ------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `--bg-frame` | `titleBar.activeBackground`, `activityBar.background` |
| `--bg-island`, `--bg-app` | `editor.background`, `sideBar.background` |
| `--bg-elev` | `sideBarSectionHeader.background`, `editorGroupHeader.tabsBackground` |
| `--bg-popup`, `--border-popup` | `editorWidget.background`, `editorWidget.border` |
| `--bg-hover` | `list.hoverBackground` |
| `--bg-selected`, `--bg-tree-selected` | `list.activeSelectionBackground` |
| `--bg-tree-selected-inactive` | `list.inactiveSelectionBackground` |
| `--border` | `panel.border`, `sideBar.border` |
| `--fg` | `foreground`, `sideBar.foreground` |
| `--fg-muted`, `--fg-subtle` | `descriptionForeground`, `disabledForeground` |
| `--accent`, `--fg-on-accent` | `button.background`, `button.foreground` |
| `--focus-ring-soft` | `focusBorder` |
| `--link` | `textLink.foreground` |
| `--bg-input`, `--bg-field` | `input.background` |
| `--bg-tab-selected`, `--border-tab-selected` | `tab.activeBackground`, `tab.activeBorderTop` |
| `--scrollbar-thumb`, `--scrollbar-thumb-hover` | `scrollbarSlider.background`, `scrollbarSlider.hoverBackground` |
| `--danger`, `--warning`, `--info` | `errorForeground`, `editorWarning.foreground`, `editorInfo.foreground` |
| `--vcs-modified`, `--vcs-added`, `--vcs-deleted`, `--vcs-untracked`, `--vcs-ignored`, `--vcs-conflict` | `gitDecoration.modifiedResourceForeground`, `.addedResourceForeground`, `.deletedResourceForeground`, `.untrackedResourceForeground`, `.ignoredResourceForeground`, `.conflictingResourceForeground` |
| `--fg-editor` | `editor.foreground` |
| `--caret` | `editorCursor.foreground` |
| `--bg-current-line` | `editor.lineHighlightBackground` |
| `--bg-editor-selection`, `--bg-editor-selection-inactive`, `--fg-editor-selection` | `editor.selectionBackground`, `editor.inactiveSelectionBackground`, `editor.selectionForeground` |
| `--fg-line-number`, `--fg-line-number-current` | `editorLineNumber.foreground`, `editorLineNumber.activeForeground` |
| `--border-indent-guide`, `--border-indent-guide-active` | `editorIndentGuide.background1`, `editorIndentGuide.activeBackground1` |
| `--fg-whitespace` | `editorWhitespace.foreground` |
| `--bg-text-search-result`, `--bg-text-search-result-current` | `editor.findMatchHighlightBackground`, `editor.findMatchBackground` |
| `--bg-matched-brace` | `editorBracketMatch.background` |
| `--syn-bracket-1` … `--syn-bracket-3` (Rainbow brackets) | `editorBracketHighlight.foreground1` … `foreground3` |
| `--bg-identifier-under-caret`, `-write` | `editor.wordHighlightBackground`, `editor.wordHighlightStrongBackground` |
| `--wave-error`, `--wave-warning` | `editorError.foreground`, `editorWarning.foreground` |
| `--gutter-modified`, `--gutter-added`, `--gutter-deleted` | `editorGutter.modifiedBackground`, `.addedBackground`, `.deletedBackground` |
| `--fg-inlay`, `--bg-inlay` | `editorInlayHint.foreground`, `editorInlayHint.background` |
| `--ansi-*` | `terminal.ansiBlack` … `terminal.ansiBrightWhite` |
`tokenColors` to syntax tokens, by TextMate scope:
| OpenIDEA token | Scope |
| --------------------------------------------------------- | -------------------------------------------------------------- |
| `--syn-keyword` | `keyword`, `storage.type`, `storage.modifier` |
| `--syn-string` | `string` |
| `--syn-escape` | `constant.character.escape` |
| `--syn-number` | `constant.numeric` |
| `--syn-constant` | `constant.language`, `variable.other.constant` |
| `--syn-comment`, `--syn-doc-comment` | `comment`, `comment.block.documentation` |
| `--syn-function`, `--syn-function-call` | `entity.name.function`, `meta.function-call` |
| `--syn-type` | `entity.name.type`, `entity.name.class`, `support.class` |
| `--syn-variable`, `--syn-param` | `variable`, `variable.parameter` |
| `--syn-property` | `variable.other.property`, `variable.other.member` |
| `--syn-builtin`, `--syn-predefined` | `support.function`, `support.variable` |
| `--syn-self` | `variable.language.self`, `variable.language.this` |
| `--syn-annotation`, `--syn-decorator` | `meta.decorator`, `storage.type.annotation` |
| `--syn-punct` | `punctuation`, `keyword.operator` |
| `--syn-tag`, `--syn-attribute` | `entity.name.tag`, `entity.other.attribute-name` |
| `--syn-regexp` | `string.regexp` |
| `--syn-markup-heading`, `--syn-markup-code`, `--syn-link` | `markup.heading`, `markup.inline.raw`, `markup.underline.link` |
| `--syn-invalid` | `invalid` |
A `fontStyle: "italic"` on comments maps to `"--syn-comment-style": "italic"`.
## Testing a theme
- Install it (or link it), apply it, and walk through: the Project tool window with a
selection, the editor with a few languages, Search Everywhere, a popup menu, a tooltip,
Settings, the terminal (`ls --color`), a diff, and the VCS colours in the Commit tool
window.
- Text should keep at least WCAG AA contrast (4.5:1) on its surfaces. The built-in themes
are checked by `src/features/settings/__tests__/contrast.test.ts`.
- For changes to the repository's own template: `pnpm vitest run scripts/theme` validates
the template against tokens.css and keeps the token reference current, and
`cargo test -p ide-plugin` loads the template through the plugin host.
## Token reference
The values of the two base themes, generated from `src/styles/tokens.css` (`pnpm
gen:theme-docs` regenerates the copy in `plugins/themes.md`). Groups marked _UI theme_
conventionally go in the theme file, groups marked _editor scheme_ in the scheme file.
256 themeable tokens: 125 in the UI theme file, 131 in the editor colour scheme file.
### Surfaces (UI theme)
| Token | Islands Dark | Islands Light |
| ----------------------- | ------------ | ------------- |
| `--bg-app` | `#191a1c` | `#ffffff` |
| `--bg-elev` | `#212326` | `#f7f8f9` |
| `--bg-popup` | `#26282c` | `#ffffff` |
| `--bg-floating-toolbar` | `#33353b` | `#ffffff` |
| `--bg-hover` | `#2e2f30` | `#ededed` |
| `--bg-selected` | `#2a4371` | `#d0dffe` |
| `--bg-selected-strong` | `#2e4d89` | `#bdd3ff` |
| `--bg-current-line` | `#1f2024` | `#f5f8fe` |
| `--bg-debug-line` | `#2a5091` | `#d4e2ff` |
| `--bg-backdrop` | `#26282c` | `#e9eaee` |
| `--bg-input` | `#191a1c` | `#ffffff` |
### Lines (UI theme)
| Token | Islands Dark | Islands Light |
| ----------------- | ------------ | ------------- |
| `--border` | `#26282c` | `#e9eaee` |
| `--border-popup` | `#33353b` | `#d1d3d9` |
| `--border-strong` | `#40434a` | `#d1d3d9` |
| `--border-window` | `#40434a` | `#d1d3d9` |
| `--control-off` | `#5f6269` | `#c3c5cb` |
### Text (UI theme)
| Token | Islands Dark | Islands Light |
| ---------------- | ------------ | ------------- |
| `--fg-strong` | `#dfe1e5` | `#000000` |
| `--fg-emphasis` | `#d1d3d9` | `#000000` |
| `--fg` | `#d1d3d9` | `#000000` |
| `--fg-secondary` | `#9fa2a8` | `#5f6269` |
| `--fg-muted` | `#8c8f95` | `#6c707e` |
| `--fg-subtle` | `#73767c` | `#73767c` |
| `--fg-faint` | `#5f6269` | `#9fa2a8` |
| `--fg-gutter` | `#4b5059` | `#aeb3c2` |
| `--fg-icon` | `#ced0d6` | `#6c707e` |
### Accent (UI theme)
| Token | Islands Dark | Islands Light |
| -------------- | ------------ | ------------- |
| `--accent` | `#3871e1` | `#3871e1` |
| `--link` | `#71a1fe` | `#2f5eb9` |
| `--link-hover` | `#92b7ff` | `#2e4d89` |
### Charts (UI theme)
| Token | Islands Dark | Islands Light |
| ---------------- | ------------ | ------------- |
| `--chart-series` | `#e07a7c` | `#d9686a` |
### Status (UI theme)
| Token | Islands Dark | Islands Light |
| --------------------- | ------------ | ------------- |
| `--danger` | `#f57e84` | `#c54e58` |
| `--warning` | `#d59637` | `#a56906` |
| `--success` | `#6db083` | `#338555` |
| `--db-connected` | `#2fe02f` | `#00ff00` |
| `--db-connected-ring` | `#1d6b1d` | `#5b995b` |
| `--info` | `#548af7` | `#3871e1` |
### Vcs (UI theme)
| Token | Islands Dark | Islands Light |
| ------------------------ | ------------ | ------------- |
| `--vcs-modified` | `#70aeff` | `#0033b3` |
| `--vcs-added` | `#73bd79` | `#067d17` |
| `--vcs-deleted` | `#6f737a` | `#6c707e` |
| `--vcs-untracked` | `#e88f89` | `#b23247` |
| `--vcs-ignored` | `#d69a6b` | `#8c4f00` |
| `--vcs-conflict` | `#de6a66` | `#de1b2e` |
| `--gutter-modified` | `#375fad` | `#88adf7` |
| `--gutter-added` | `#549159` | `#7fc784` |
| `--gutter-deleted` | `#868a91` | `#767a8a` |
| `--diff-modified` | `#385570` | `#c2d8f2` |
| `--diff-inserted` | `#294436` | `#bee6be` |
| `--diff-deleted` | `#484a4a` | `#d6d6d6` |
| `--diff-modified-stripe` | `#43698d` | `#b6d2f2` |
| `--diff-inserted-stripe` | `#447152` | `#aadeaa` |
| `--diff-deleted-stripe` | `#656e76` | `#c8c8c8` |
| `--diff-conflict-stripe` | `#8f5247` | `#ffc8bd` |
### Syntax (editor scheme)
| Token | Islands Dark | Islands Light |
| ------------------------------------ | ----------------------------------- | ----------------------------------- |
| `--syn-keyword` | `#cf8e6d` | `#0033b3` |
| `--syn-string` | `#6aab73` | `#067d17` |
| `--syn-type` | `#bcbec4` | `#000000` |
| `--syn-function` | `#56a8f5` | `#00627a` |
| `--syn-function-call` | `#bcbec4` | `#080808` |
| `--syn-comment` | `#7a7e85` | `#8c8c8c` |
| `--syn-comment-style` | `normal` | `italic` |
| `--syn-doc-comment` | `#5f826b` | `#8c8c8c` |
| `--syn-doc-comment-tag` | `#67a37c` | `#8c8c8c` |
| `--syn-doc-comment-tag-effect` | `none` | `underline #999999` |
| `--syn-doc-comment-tag-value` | `#abadb3` | `#3d3d3d` |
| `--syn-doc-markup` | `#68a67e` | `#8c8c8c` |
| `--syn-doc-markup-bg` | `transparent` | `#e2ffe2` |
| `--syn-docstring` | `#5f826b` | `#067d17` |
| `--syn-annotation` | `#b3ae60` | `#9e880d` |
| `--syn-decorator` | `#b3ae60` | `#9e880d` |
| `--syn-number` | `#2aacb8` | `#1750eb` |
| `--syn-punct` | `#bcbec4` | `#080808` |
| `--syn-variable` | `#bcbec4` | `#080808` |
| `--syn-global` | `var(--syn-variable)` | `var(--syn-variable)` |
| `--syn-namespace` | `var(--fg-editor)` | `var(--fg-editor)` |
| `--syn-param` | `#bcbec4` | `#080808` |
| `--syn-property` | `#c77dbb` | `#871094` |
| `--syn-constant` | `#c77dbb` | `#871094` |
| `--syn-python-local-variable` | `var(--syn-variable)` | `var(--syn-variable)` |
| `--syn-python-global-variable` | `var(--syn-global)` | `var(--syn-global)` |
| `--syn-python-instance-attribute` | `var(--syn-variable)` | `var(--syn-variable)` |
| `--syn-python-class-attribute` | `var(--syn-property)` | `var(--syn-property)` |
| `--syn-python-constant` | `var(--syn-constant)` | `var(--syn-constant)` |
| `--syn-python-property` | `var(--syn-property)` | `var(--syn-property)` |
| `--syn-python-type-parameter` | `var(--syn-type)` | `var(--syn-type)` |
| `--syn-python-builtin` | `var(--syn-builtin)` | `var(--syn-builtin)` |
| `--syn-python-predefined-definition` | `var(--syn-predefined)` | `var(--syn-predefined)` |
| `--syn-python-predefined-usage` | `var(--syn-predefined)` | `var(--syn-predefined)` |
| `--syn-python-fstring` | `#a5c261` | `var(--syn-string)` |
| `--syn-python-bytes` | `#a5c261` | `var(--syn-string)` |
| `--syn-python-docstring-tag` | `var(--syn-doc-comment-tag)` | `var(--syn-doc-comment-tag)` |
| `--syn-python-docstring-tag-effect` | `var(--syn-doc-comment-tag-effect)` | `var(--syn-doc-comment-tag-effect)` |
| `--syn-python-docstring-keyword` | `var(--syn-doc-comment-tag-value)` | `var(--syn-doc-comment-tag-value)` |
| `--syn-php-variable` | `var(--syn-property)` | `var(--syn-property)` |
| `--syn-php-function-call` | `var(--syn-function)` | `var(--syn-function)` |
| `--syn-builtin` | `#8888c6` | `#000080` |
| `--syn-self` | `#94558d` | `#94558d` |
| `--syn-keyword-arg` | `#aa4926` | `#660099` |
| `--syn-predefined` | `#b200b2` | `#b200b2` |
| `--syn-escape` | `#cf8e6d` | `#0037a6` |
| `--syn-regexp` | `#42c3d4` | `#264eff` |
| `--syn-tag` | `#d5b778` | `#0033b3` |
| `--syn-attribute` | `#bcbec4` | `#174ad4` |
| `--syn-todo` | `#8bb33d` | `#008dde` |
| `--syn-link` | `#548af7` | `#006dcc` |
| `--syn-markup-heading` | `#cf8e6d` | `#871094` |
| `--syn-markup-code` | `#6aab73` | `#067d17` |
| `--syn-invalid` | `#f75464` | `#f50000` |
| `--syn-inline-value` | `#868a91` | `#8c8c8c` |
| `--syn-rainbow-1` | `#6fafbd` | `#9b3b6a` |
| `--syn-rainbow-2` | `#d79b6e` | `#114d77` |
| `--syn-rainbow-3` | `#9fbf6b` | `#9c6a2c` |
| `--syn-rainbow-4` | `#c98bc6` | `#005910` |
| `--syn-rainbow-5` | `#8e9cf0` | `#bc5150` |
| `--syn-bracket-1` | `#e8c25b` | `#0431fa` |
| `--syn-bracket-2` | `#d78bd6` | `#319331` |
| `--syn-bracket-3` | `#4ea7f0` | `#7b3814` |
### Editor (editor scheme)
| Token | Islands Dark | Islands Light |
| ----------------------------------- | ------------------------ | ------------------------ |
| `--fg-editor` | `#bcbec4` | `#080808` |
| `--caret` | `#ced0d6` | `#000000` |
| `--fg-line-number` | `#4b5059` | `#aeb3c2` |
| `--fg-line-number-current` | `#a1a3ab` | `#767a8a` |
| `--border-gutter` | `#26282c` | `#ebecf0` |
| `--border-indent-guide` | `#323438` | `#ebecf0` |
| `--border-indent-guide-active` | `#4e5157` | `#aeb3c2` |
| `--border-right-margin` | `#323438` | `#ebecf0` |
| `--bg-editor-selection` | `#214283` | `#a6d2ff` |
| `--bg-editor-selection-inactive` | `#1d3460` | `#c9e3ff` |
| `--fg-editor-selection` | `inherit` | `inherit` |
| `--bg-text-search-result` | `#114957` | `#fcd47e` |
| `--bg-text-search-result-current` | `#165e70` | `#f2bf57` |
| `--bg-matched-brace` | `#43454a` | `#93d9d9` |
| `--matched-brace-weight` | `var(--fw-code-bold)` | `inherit` |
| `--bg-matched-tag` | `#3f4045` | `#c9ecec` |
| `--bg-fold-placeholder` | `#393b40` | `#e9f5e6` |
| `--fg-fold-placeholder` | `#868991` | `#414d41` |
| `--fg-fold-marker` | `#6f737a` | `#8b8e94` |
| `--fg-whitespace` | `#6f737a` | `#b5b7bd` |
| `--fg-unused` | `#6f737a` | `#8c8c8c` |
| `--bg-identifier-under-caret` | `#373b39` | `#edebfc` |
| `--bg-identifier-under-caret-write` | `#402f33` | `#fce8f4` |
| `--fg-inlay` | `#868a91` | `#73767c` |
| `--bg-inlay` | `#393b40` | `#00000012` |
| `--wave-error` | `#fa6675` | `#f50000` |
| `--wave-warning` | `#f2c55c` | `#f2bf57` |
| `--editor-error-decoration` | `underline wavy #fa6675` | `underline wavy #f50000` |
| `--editor-warning-decoration` | `underline wavy #f2c55c` | `none` |
| `--editor-warning-background` | `transparent` | `#fcf4d4` |
| `--bg-breakpoint-line` | `#40252b` | `#faeae6` |
| `--fg-completion-match` | `#538af9` | `#2f5eb9` |
| `--bg-editor-inline` | `#212326` | `#f7f8f9` |
| `--bg-editor-banner` | `#233558` | `#f7f8ff` |
| `--fg-weak-warning` | `#6f737a` | `#a8adbd` |
| `--fg-bulb` | `#f2c55c` | `#ffaf0f` |
### ANSI palette (editor scheme)
| Token | Islands Dark | Islands Light |
| ----------------------- | ------------ | ------------- |
| `--ansi-black` | `#000000` | `#000000` |
| `--ansi-red` | `#f0524f` | `#e5372a` |
| `--ansi-green` | `#5c962c` | `#367a29` |
| `--ansi-yellow` | `#a68a0d` | `#ad8d2b` |
| `--ansi-blue` | `#3993d4` | `#3866d1` |
| `--ansi-magenta` | `#a771bf` | `#a33cae` |
| `--ansi-cyan` | `#00a3a3` | `#3c8b8d` |
| `--ansi-white` | `#808080` | `#8b8e94` |
| `--ansi-bright-black` | `#595959` | `#73767c` |
| `--ansi-bright-red` | `#ff4050` | `#f05046` |
| `--ansi-bright-green` | `#4fc414` | `#2e9b3f` |
| `--ansi-bright-yellow` | `#e5bf00` | `#b58e00` |
| `--ansi-bright-blue` | `#1fb0ff` | `#3d82f0` |
| `--ansi-bright-magenta` | `#ed7eed` | `#c04bcf` |
| `--ansi-bright-cyan` | `#00e5e5` | `#1b9ea3` |
| `--ansi-bright-white` | `#ffffff` | `#5f6269` |
### Terminal ANSI palette (editor scheme)
| Token | Islands Dark | Islands Light |
| -------------------------------- | ------------ | ------------- |
| `--terminal-ansi-black` | `#000000` | `#000000` |
| `--terminal-ansi-red` | `#f27481` | `#c93645` |
| `--terminal-ansi-green` | `#6bcc62` | `#357d1f` |
| `--terminal-ansi-yellow` | `#e0ce70` | `#b2a400` |
| `--terminal-ansi-blue` | `#5594fa` | `#225cd6` |
| `--terminal-ansi-magenta` | `#c092fa` | `#6e32e5` |
| `--terminal-ansi-cyan` | `#47ccbd` | `#007a80` |
| `--terminal-ansi-white` | `#ced0d6` | `#bec0c5` |
| `--terminal-ansi-bright-black` | `#4e5157` | `#6c707e` |
| `--terminal-ansi-bright-red` | `#ff6b7a` | `#ff5462` |
| `--terminal-ansi-bright-green` | `#67ff59` | `#3dcc3d` |
| `--terminal-ansi-bright-yellow` | `#ffec1a` | `#d4bb00` |
| `--terminal-ansi-bright-blue` | `#3399ff` | `#009dff` |
| `--terminal-ansi-bright-magenta` | `#d970ff` | `#d659ff` |
| `--terminal-ansi-bright-cyan` | `#40ffe9` | `#31c4b3` |
| `--terminal-ansi-bright-white` | `#ffffff` | `#d2d4d9` |
### Shadows and window controls (UI theme)
| Token | Islands Dark | Islands Light |
| ------------------------- | -------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------ |
| `--shadow-popup` | `0 6px 20px rgba(0, 0, 0, 0.45)` | `0 6px 20px rgba(0, 0, 0, 0.12)` |
| `--shadow-dialog` | `0 0 0 0.5px rgba(0, 0, 0, 0.6), 0 10px 24px rgba(0, 0, 0, 0.35), 0 24px 72px rgba(0, 0, 0, 0.45)` | `0 0 0 0.5px rgba(0, 0, 0, 0.1), 0 10px 24px rgba(0, 0, 0, 0.1), 0 24px 72px rgba(0, 0, 0, 0.2)` |
| `--traffic-close` | `#ff5f57` | `#ff5f57` |
| `--traffic-close-border` | `#e0443e` | `#e0443e` |
| `--traffic-close-pressed` | `#bf4942` | `#bf4942` |
| `--traffic-glyph` | `#4d0000` | `#4d0000` |
| `--traffic-off` | `#4b4c4f` | `#dddddd` |
| `--traffic-off-border` | `#404144` | `#c8c8c8` |
### Additive (UI theme)
| Token | Islands Dark | Islands Light |
| ------------------------- | -------------------------------- | -------------------------------- |
| `--bg-stripe-tooltip` | `var(--bg-tooltip)` | `#27282e` |
| `--fg-stripe-tooltip` | `var(--fg-tooltip)` | `#ffffff` |
| `--border-stripe-tooltip` | `var(--border-tooltip)` | `#383a42` |
| `--shadow-stripe-tooltip` | `var(--shadow-tooltip)` | `0 2px 8px rgba(0, 0, 0, 0.24)` |
| `--bg-tooltip` | `#33353b` | `#ffffff` |
| `--fg-tooltip` | `#d1d3d9` | `#000000` |
| `--border-tooltip` | `#33353b` | `#d1d3d9` |
| `--bg-control` | `#26282c` | `#ffffff` |
| `--bg-control-hover` | `#33353b` | `#f7f8f9` |
| `--bg-active` | `#40434a` | `#dddfe4` |
| `--bg-scrim` | `rgba(0, 0, 0, 0.45)` | `rgba(0, 0, 0, 0.16)` |
| `--fg-on-accent` | `#ffffff` | `#ffffff` |
| `--shadow-tooltip` | `0 4px 12px rgba(0, 0, 0, 0.4)` | `0 4px 12px rgba(0, 0, 0, 0.12)` |
| `--shadow-balloon` | `0 8px 24px rgba(0, 0, 0, 0.45)` | `0 8px 24px rgba(0, 0, 0, 0.14)` |
### Islands contract (UI theme)
| Token | Islands Dark | Islands Light |
| -------------------------------- | --------------------- | --------------------- |
| `--bg-frame` | `#26282c` | `#e9eaee` |
| `--bg-island` | `#191a1c` | `#ffffff` |
| `--bg-stripe-hover` | `#ffffff17` | `#00000012` |
| `--bg-stripe-selected` | `#ffffff29` | `#00000020` |
| `--fg-stripe-selected-active` | `#ffffff` | `#ffffff` |
| `--fg-stripe-label` | `#9fa2a8` | `#5f6269` |
| `--bg-tab-selected` | `#233558` | `#e3ebfe` |
| `--border-tab-selected` | `#2e4d89` | `#a7c5ff` |
| `--bg-tab-selected-inactive` | `#26282c` | `#e9eaee` |
| `--border-tab-selected-inactive` | `#40434a` | `#d1d3d9` |
| `--bg-tab-hover` | `#ffffff17` | `#00000012` |
| `--border-editor-tabs` | `#26282c` | `#e9eaee` |
| `--bg-tree-selected` | `#2a4371` | `#d0dffe` |
| `--bg-tree-selected-inactive` | `#33353b` | `#e9eaee` |
| `--bg-tree-hover` | `#ffffff10` | `#00000008` |
| `--bg-toolbar-hover` | `#ffffff17` | `#00000012` |
| `--bg-toolbar-pressed` | `#ffffff29` | `#00000020` |
| `--bg-toolbar-selected` | `#2e4d89` | `#d0dffe` |
| `--bg-toolbar-selected-hover` | `#2f5eb9` | `#bdd3ff` |
| `--bg-button-primary-hover` | `var(--accent-hover)` | `var(--accent-hover)` |
| `--fg-button-primary` | `#ffffff` | `#ffffff` |
| `--bg-button` | `transparent` | `#ffffff` |
| `--border-button` | `#40434a` | `#d1d3d9` |
| `--bg-field` | `#191a1c` | `#ffffff` |
| `--bg-checkbox` | `#191a1c` | `#ffffff` |
| `--border-checkbox` | `#5f6269` | `#a8adbd` |
| `--bg-toggle-on` | `#338555` | `#4e9d6c` |
| `--fg-toggle-on` | `#ffffff` | `#ffffff` |
| `--bg-toggle-off` | `#191a1c` | `#f7f8f9` |
| `--fg-toggle-off` | `#73767c` | `#73767c` |
| `--border-toggle` | `#5f6269` | `#c3c5cb` |
| `--bg-toggle-knob` | `#5f6269` | `#c3c5cb` |
| `--focus-ring-soft` | `#3d6185` | `#97c3f3` |
| `--scrollbar-thumb` | `#ffffff33` | `#00000033` |
| `--scrollbar-thumb-hover` | `#ffffff59` | `#00000080` |
| `--bg-search-match` | `#ba9752` | `#fee6b1` |
| `--bg-search-match-current` | `#f2c55c` | `#fcd47e` |
| `--fg-search-match` | `#191a1c` | `#000000` |
| `--bg-dialog` | `#191a1c` | `#f7f8f9` |
| `--bg-popup-inline` | `#26282c` | `#f7f8f9` |
| `--bg-popup-advertiser` | `#26282c` | `#f7f8f9` |
| `--bg-feedback-info` | `#233558` | `#f7f8ff` |
| `--border-feedback-info` | `#2e4d89` | `#bdd3ff` |
| `--bg-feedback-error` | `#56272b` | `#fff6f5` |
| `--border-feedback-error` | `#80383e` | `#ffc4c5` |
| `--bg-feedback-warning` | `#44321d` | `#fff6e9` |
| `--border-feedback-warning` | `#694820` | `#f4cd9a` |
| `--bg-feedback-success` | `#203b2a` | `#f5faf3` |
| `--border-feedback-success` | `#29583c` | `#bbdbc2` |
| `--bg-counter` | `#73767c` | `#8b8e94` |
| `--fg-counter` | `#000000` | `#ffffff` |
| `--bg-progress-track` | `#40434a` | `#dddfe4` |
+398
View File
@@ -0,0 +1,398 @@
---
title: "Views and decorations"
description: "Tool windows, custom editors, web views, Services nodes, editor tabs and terminals opened by code, and editor and project tree decorations."
---
A plugin can put UI in several places:
| Where | Declare | Content |
| ------------------------------------------- | ---------------------------------------------------- | ----------------------------------------------------------------------- |
| A tool window | `[[contributes.views]]` | Rendered by code (tree, list, form, table, message, HTML) or a web page |
| The **Services** tool window | `[[contributes.views]]` with `location = "services"` | A tree rendered by code, plus a detail pane |
| An editor for certain files | `[[contributes.customEditors]]` | Rendered by code or a web page; shares the file's text document |
| Editor tabs and terminals | nothing (opened from code) | Content tabs, log tabs, terminal sessions, Output channels |
| Annotations in editors and the Project tree | nothing (set from code) | Decorations |
Gutter markers and gutter buttons are editor providers: see
[Language features](/en/plugins/guides/language-features). File icons are on
[Icon themes](/en/plugins/guides/icon-themes).
## Declare a tool window
```toml
[[contributes.views]]
id = "todos"
title = "TODO Example"
icon = "search" # an IDE icon name (up to 64 bytes) or a package SVG
location = "right" # left, right (default), bottom, services
```
| Field | API | Notes |
| ----------- | --- | ---------------------------------------------------------------------------------------------------------------------------------------- |
| `id` | 1.0 | Unique in the plugin. |
| `title` | 1.0 | 1–256 bytes, translated through the plugin's catalog. |
| `icon` | 1.0 | Icon name or package `.svg` (at most 64 KiB, sanitised). |
| `location` | 1.0 | `left`, `right`, `bottom`; `services` needs API 1.3. |
| `web` | 1.0 | A page of the package (`web/index.html`) instead of content rendered by code. See _Web views_. |
| `group` | 1.3 | Views of one plugin with the same group share a tool window, one tab each, in declaration order. |
| `when` | 1.3 | A `when` clause; while false the view is removed from the stripe and its group's tabs, and is neither rendered nor activated. |
| `secondary` | 1.3 | `true` places it in the secondary split of its dock: **Left Bottom**, **Right Bottom** or **Bottom Right**. A saved user placement wins. |
| `detail` | 1.3 | `true` shows the selected tree or list item's detail tabs beside it (not with `web`). |
| `header` | 1.3 | `none` for an ungrouped tool-window web view that draws its own top bar (see _Web views_). Default `default`. |
A view without `web` needs code (`wasm`). Showing a view activates the plugin
(`on-view:<id>`).
### Grouped and conditional views
```toml
engines.openidea = "^1.3"
[[contributes.views]]
id = "endpoints"
title = "Spring"
group = "spring"
when = "workspaceContains:pom.xml || workspaceContains:build.gradle"
[[contributes.views]]
id = "beans"
title = "Beans"
group = "spring"
when = "plugin.my-plugin.ready"
```
The first declaration of a group supplies the window's title, icon, location and
`secondary`, even when that view is hidden; the window disappears when all its tabs are
hidden. Tabs load on first selection and stay mounted while they apply, so web views keep
their state when you switch tabs. `when` is evaluated again on context changes, file
creation and removal, and project changes. These fields only control visibility; they
grant no access.
## Render a view
Register `register_plugin!(MyPlugin, views)` and implement `openidea_plugin::views::Views`
(WIT export `view-provider`). A view is data: a toolbar, a body and a status line. The IDE
draws it with its own components (theme, keyboard, speed search) and sends back what the
user does.
From the [TODO Finder](https://github.com/l0ng-ai/openidea/tree/main/plugins/official/todo-finder)
example:
```rust
use openidea_plugin::views::{Body, ListItem, View, ViewEvent, Viewport, Views};
use openidea_plugin::{Error, Invocation, Location, TextQuery, host, register_plugin};
fn query() -> TextQuery {
TextQuery { pattern: "TODO".into(), case_sensitive: true, ..Default::default() }
}
impl Views for TodoFinder {
fn render(&mut self, _view: &str, _viewport: Option<Viewport>, ctx: &Invocation) -> Result<View, Error> {
let found = ctx.project().ok_or("Open a project first")?.search_text(&query(), 500)?;
let items = found
.matches
.into_iter()
.map(|m| {
let detail = format!("{}:{}", m.location.path, m.location.range.start.line + 1);
ListItem::new(&serde_json::to_string(&m.location).unwrap(), &m.line).detail(&detail)
})
.collect();
Ok(View {
toolbar: vec![],
body: Body::List(items),
status: found.limit_hit.then(|| "Showing the first 500 matches".into()),
})
}
fn handle(&mut self, _view: &str, event: ViewEvent, _ctx: &Invocation) -> Result<(), Error> {
if let ViewEvent::Activate(key) = event {
let location: Location = serde_json::from_str(&key).map_err(|e| Error::Invalid(e.to_string()))?;
host::open_location(&location.path, location.range.start.line, location.range.start.character)?;
}
Ok(())
}
}
register_plugin!(TodoFinder, views);
```
When content changes, call `host::refresh_view(view)` and the IDE calls `render` again.
`host::reveal_view(view)` brings one of the plugin's views to the front in the invoking
window. For a tool window, `ctx.editor()` is the window's active text editor, so toolbar
buttons can act on what the user is editing.
### Bodies
| Body | Notes |
| ------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `Body::Message(text)` | A centred message: empty states and errors. `View::message(text)` builds one. |
| `Body::Tree(items)` | A flat list of `TreeItem`s whose `parent` names an earlier key. `.expandable(expanded)` shows an expander before the children are known; `Expand` asks for them. |
| `Body::List(items)` | `ListItem::new(key, label)` with `.icon()` and `.detail()` (grey text after the label). |
| `Body::Form(fields)` | `Field::new(key, label, value)`: `Text`, `Multiline`, `Checkbox`, `Choice(options, chosen)`. API 1.3 adds `FieldValue::label(text)` and `FieldValue::readonly_text(text, multiline)`. |
| `Body::Table(table)` | `columns`, `total_rows`, `first_row`, `rows`, `editable`. The IDE asks only for the rows it shows (`viewport`); `Viewport::range(viewport, total, default_rows)` clamps it. |
| `Body::sortable_table(&table, sort)` | A table with clickable headers. A click sends a column and direction (`ViewEvent::sort()`); sort your rows and render again. |
| `Body::Html(html)` | HTML in a sandboxed frame (below). |
Toolbar buttons: `View::new(body).button(Button::new("refresh", "Refresh").icon("refresh"))`
and `.status("12 items")`.
### Events
`ViewEvent` carries `Button(id)`, `Activate(key)` (double click or Enter), `Select(key)`,
`Expand(key)`, `Collapse(key)`, `Field(key, value)` (`true`/`false` for checkboxes),
`Cell { row, column, value }`, `InsertRow(before)`, `DeleteRows(rows)` and `Message(text)`
from an HTML body. Table row clicks and Space decode with `event.table_select()`;
double click and Enter with `event.table_activate()` (API 1.3, absolute 0-based rows).
Bodies, fields and events added by later APIs travel as `Other { kind, data }`: an IDE that
doesn't know a body says it needs updating, and leaves out a field type it doesn't know.
Item right-click menus use `viewItem.context` (see
[Commands and actions](/en/plugins/guides/commands#menus)).
## Custom editors
A custom editor opens files matching its patterns and edits the file's text document:
```toml
[[contributes.customEditors]]
id = "table"
title = "Table"
patterns = ["*.csv", "*.tsv"] # 1–16 file name globs, no directories
priority = "default" # or "option": only through Open With
layout = "tab" # or "split" / "preview": beside the text editor
# web = "web/index.html" # a web page instead of rendered content
```
- `priority = "default"` opens matching files in the plugin's editor; `option` makes it
available through **Open With**. **Open as Text** opens the text editor.
- `layout = "split"` or `"preview"` opens the file in the text editor with the plugin's
editor beside it and the **Editor Only** / **Editor and Preview** / **Preview Only**
switch in the tab strip, as for Markdown; the value is the layout files start in.
`"tab"` (default) is a tab of its own.
- In `render` and `handle`, `ctx.editor()` is the editor's document. Read it with
`document()` and change it with `apply_edits(version, edits)`. The text editor of the
same file follows, Undo/Redo is one history with it, and Save, the dirty mark and the
close prompts are the document's.
[CSV Editor](https://github.com/l0ng-ai/openidea/tree/main/plugins/official/csv-editor)
turns table edits into text edits:
```rust
fn handle(&mut self, _view: &str, event: ViewEvent, ctx: &Invocation) -> Result<(), Error> {
let editor = ctx.editor().ok_or_else(|| Error::Invalid("the table editor needs a document".into()))?;
let doc = editor.document()?;
let edits = edits_for(&doc.text, &event)?;
if !edits.is_empty() {
// Stale if the user typed in the text view meanwhile; the table renders again.
editor.apply_edits(doc.version, &edits)?;
}
Ok(())
}
```
It needs `editor.read` and `editor.write`.
## HTML bodies
`Body::Html` runs in a sandboxed frame: an opaque origin under a CSP that allows only its
inline scripts and styles and `data:` images, with no network and no IDE API. It talks to
the plugin only through `openidea.postMessage(text)` and `openidea.onMessage(fn)`, which
arrive as `ViewEvent::Message` and are answered with `host::post_view_message(view, text)`.
[Regex Tester](https://github.com/l0ng-ai/openidea/tree/main/plugins/official/regex-tester)
works this way. Rendering again reloads the frame, so let the page keep its own state.
The frame follows the IDE theme without reloading. Its root carries
`data-theme="light|dark"` and these CSS variables with concrete colour values:
| Variable | Meaning |
| ----------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------- |
| `--openidea-bg`, `--openidea-bg-elevated`, `--openidea-bg-hover`, `--openidea-bg-selected`, `--openidea-bg-input` | Surfaces (the frame itself is transparent) |
| `--openidea-fg`, `--openidea-fg-strong`, `--openidea-fg-muted` | Text |
| `--openidea-border`, `--openidea-focus-ring`, `--openidea-link` | Lines, focus, links |
| `--openidea-accent`, `--openidea-fg-on-accent` | Accent and text on it |
| `--openidea-danger`, `--openidea-warning`, `--openidea-success`, `--openidea-info` | Status colours |
| `--openidea-font-ui`, `--openidea-font-mono`, `--openidea-font-size` | Fonts (the IDE's bundled web fonts are not available) |
Scripts also get `openidea.theme` (`{ dark, vars }`) and `openidea.onThemeChange(fn)`.
Variables are only ever added.
## Web views
A view or custom editor can be a web page shipped in the package, for UIs the declarative
bodies can't express (a canvas, a diagram editor). It needs no code:
```toml
[[contributes.customEditors]]
id = "preview"
title = "Preview"
patterns = ["*.dot", "*.gv"]
layout = "split"
web = "web/index.html" # the page; its directory is what it can load
```
- The page's directory (never the package root) is part of the declaration: every file
under it is checked (at most 4096 files and 256 MiB, no links), installed and signed with
the package.
- The page loads into an `<iframe sandbox="allow-scripts">` from the IDE's own URL scheme:
opaque origin, no network, no cookies or storage, only the page's own files. Build it
with relative paths (Vite: `base: "./"`).
- The same `openidea` object as in HTML bodies is available before the page's scripts run,
plus:
| `window.openidea` | What it does |
| ------------------------------------ | -------------------------------------------------------------------------------------------------------------------- |
| `document.get()` | `{ version, text, readOnly, name }` of the custom editor's document (none in a tool window) |
| `document.edit(version, edits)` | Applies `[{ from, to, insert }]` (UTF-16 offsets) as one undoable change; rejects with `stale` when not at `version` |
| `document.replace(version, text)` | The same with the whole text |
| `document.onChange(fn)` | `{ version, text }` after changes made elsewhere (text editor, Undo, another window) |
| `saveFile({ name, data, filters? })` | The IDE's save dialog; returns the chosen path or `null` |
| `openFile({ filters? })` | The IDE's open dialog; `{ name, data }` or `null` |
| `postMessage` / `onMessage` | To and from the plugin's code, when it has some |
IDE shortcuts the page doesn't handle (Save, Close, Find Action…) still work while it has
focus; editing keys stay with the page. With the `clipboard` capability the page may use
`navigator.clipboard`.
**Custom header** (API 1.3). An ungrouped tool-window web view can declare
`header = "none"`: once the page is ready, the IDE replaces the tool-window header with
corner actions (**Options**, **Hide**) and sets `--openidea-corner-inset-width` (`64px`) and
`--openidea-corner-inset-height` (`34px`) so the page can pad its own top bar:
```css
.toolbar {
min-height: var(--openidea-corner-inset-height, 0px);
padding-right: var(--openidea-corner-inset-width, 0px);
}
```
[Graphviz](https://github.com/l0ng-ai/openidea/tree/main/plugins/official/graphviz) is a
complete example: a Vite + React page in `web-src/`, built into `web/` by
`plugins/build.sh official/graphviz`, whose code publishes the errors Graphviz reports as
problems.
### Page protocols (API 1.3)
With `engines.openidea = "^1.3"`, a page can send JSON messages through
`openidea.postMessage` to ask the IDE for more. All of them run only programs granted with
`process:<program>`, in the window's trusted project root, never through a shell:
| `op` | What it opens |
| ------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------- |
| `pty-spawn`, `pty-input`, `pty-resize`, `pty-kill`, `pty-ack` | An interactive PTY owned by the frame; output arrives as `pty-data`, `pty-exit`, `pty-error`. At most 8 sessions per plugin. |
| `open-log-tab` | A read-only editor tab following a program's output, with argument toggles. |
| `open-content-tab` | An editor tab on content the plugin supplies, with toolbar actions answered by `content-tab-reply`. |
The message fields and limits are in the
[plugin guide](https://github.com/l0ng-ai/openidea/blob/main/plugins/README.md#web-frame-ptys-api-13);
the built fixtures `web-pty`, `web-log` and `web-content` under
`crates/ide-plugin/tests/fixtures/` exercise them.
## Services views and detail panes
`location = "services"` (API 1.3) puts a view's tree in the **Services** tool window next
to Docker and the run configurations, instead of a tool window of its own:
```toml
engines.openidea = "^1.3"
[[contributes.views]]
id = "kube"
title = "Kubernetes"
icon = "icons/kubernetes.svg"
location = "services"
[capabilities]
optional = ["process:kubectl"]
```
The tree's items are Services nodes: `render`, `handle`, the toolbar (acting on the
selected node) and `viewItem.context` menus work as in a tool window. The pane beside the
tree comes from the `detail-provider` export: `register_plugin!(MyPlugin, views, details)`
and implement `openidea_plugin::details::Details`:
```rust
use openidea_plugin::details::{Details, Pane, ProcessLog, Tab};
impl Details for MyPlugin {
fn detail(&mut self, _view: &str, key: &str, _ctx: &Invocation) -> Result<Vec<Tab>, Error> {
let pod = key.trim_start_matches("pod:");
let log = ProcessLog::new("kubectl", &["logs", "-f", "--tail=1000", pod])
.toggle("ts", "Timestamps", &["--timestamps"]);
Ok(vec![Tab::new("log", "Log", log), Tab::new("inspect", "Inspect", Pane::Text(self.describe(pod)?))])
}
}
```
A node has at most 8 tabs; the IDE switches them without calling the plugin and asks again
after `host::refresh_view(view)`. A tab is a `Pane::Message`, a read-only `Pane::Text`
(at most 1 MiB) or a `ProcessLog`: the IDE runs that program (it needs the
`process:<program>` grant) in the project root and follows its output (ANSI stripped, the
last 5000 lines, **Soft-wrap**, **Clear**, the exit code at the end). A toggle appends its
`args` and restarts the program. It stops when the pane closes or Services is hidden.
**List and detail views** (API 1.3). A tree or list view in any location can declare
`detail = true` to show the same `Details` tabs beside the selected item, with a resizable
divider. The host asks after the item's `select` event and again after `refresh_view`.
## Tabs and terminals opened by code
These are called from a command or event of a window:
| Function | Opens |
| ----------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `host::open_location(path, line, column)` | A project file in an editor, caret at the position (`project.read`; a file in the plugin's private directory also works). |
| `host::open_editor(path, selection)` | API 1.4: open a project file and select a range. |
| `host::open_terminal(title, program, args)` | A Terminal tab running an approved `process:<program>` in the project root. |
| `host::open_terminal_session(key, title, &cmd)` | API 1.4: a keyed terminal; reopening the key focuses it. Write to it with `host::write_terminal(key, text)`; its exit arrives as a `terminal-exited` event. |
| `host::open_log_tab(key, title, &log)` | API 1.3: the read-only log tab, from a `ProcessLog`. At most 32 per window; not yet in remote projects. |
| `host::open_content_tab(&ContentTab { .. })` | API 1.4: a content editor tab without a web view; actions arrive as `content-tab-action` events, answered with `host::reply_content_tab`. |
| `host::output_append(channel, text)` | API 1.4: a read-only **Output** tab per channel (32 channels, 1 MiB each); `output_clear`, `output_reveal`. |
```rust
use openidea_plugin::{ContentAction, ContentTab, host};
host::open_content_tab(&ContentTab {
key: "service-config".into(),
title: "service.toml".into(),
file_name: Some("service.toml".into()),
language: Some("toml".into()),
content: "name = \"demo\"\n".into(),
read_only: false,
actions: vec![ContentAction { id: "save".into(), label: "Apply".into(), primary: true, save: true, ..Default::default() }],
})?;
```
## Decorations
Decorations (API 1.4, WIT `decorations.wit`) annotate editors and the Project tree. They
need `project.read`; paths must be inside an open project.
```rust
use openidea_plugin::{Decoration, FileDecoration, Position, Range, host};
let at = Position::new(3, 20);
host::set_decorations(&path, "blame", &[Decoration {
range: Range::new(at, at),
style: "muted".into(),
hover: Some("Fix the parser".into()),
after: Some(" Ada · 1a2b3c4d".into()), // plain text shown after the range
}])?;
host::set_file_decoration(&path, "status", &FileDecoration {
badge: Some("M".into()), // at most two characters
style: "modified".into(),
tooltip: Some("Changed by the tool".into()),
})?;
```
- Styles map to theme tokens: `highlight`, `search`, `added`, `modified`, `removed`,
`warning`, `error`, `info`, `underline`, `strikethrough`, `muted`. Unknown styles are
ignored.
- A set (`set_id`) belongs to the plugin and project and is shared by all windows of that
project. Setting replaces the set's items for that file; `clear_decorations(set_id)` and
`clear_file_decorations(set_id)` remove a set everywhere.
- Limits: 50 editor sets and 50 tree sets per plugin, 10,000 editor items per file, 4 MiB of
metadata. Disabling, unloading or a crash withdraws them; idle reclaim keeps them.
[Blame Annotations](https://github.com/l0ng-ai/openidea/tree/main/plugins/official/blame-annotations)
turns `Project::vcs_blame` into end-of-line decorations.
+69
View File
@@ -0,0 +1,69 @@
---
title: "Overview"
description: "What OpenIDEA plugins can do, the two kinds of plugin, and the current state of the plugin API."
---
OpenIDEA plugins extend the IDE with commands, editor features, tool windows, themes, icons and languages. A plugin is a folder with a `plugin.toml` manifest. If it has code, the code is a sandboxed WebAssembly component that can only do what the user allowed.
<Warning>
The plugin system is a **preview**. **Settings › Plugins** shows a **Preview** badge: the API and the plugins may
still change. Released API versions only grow, so a plugin built for API 1.0 keeps loading in later 1.x IDEs (see [API
versions](/en/plugins/versioning)).
</Warning>
## What a plugin can do
- Add **commands**. They become IDE actions: they show in Find Action and Search Everywhere, can be bound to keys, and can be placed in menus, the main toolbar and the status bar.
- Read and change project files and the focused editor, react to workbench events (file opened or saved, VCS changes, actions run) and run background tasks with progress.
- Provide **editor features** for any language: completion, inline completion, quick fixes, navigation, hover, inlay hints, semantic highlighting, diagnostics while typing, formatting, code lenses, gutter markers and more.
- Show **tool windows** and **custom editors**, either declarative (trees, lists, forms, tables) or as a sandboxed web page.
- Contribute **run configurations**, test result parsers and run targets, **database sources**, and **MCP tools** for the built-in MCP server.
- Call web services, store secrets in the system keychain, keep data in their own storage, and run programs the user approved.
- Contribute **settings pages**, **file templates**, **snippets**, **themes**, **file icon themes** and **languages**.
## Two kinds of plugin
| Kind | Contains | Needs code | Examples |
| ----------- | ----------------------------------------------------------------------------------------------------------------------------------------- | ---------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Data-only | A manifest and data files: themes, icon themes, language definitions, menus, shortcuts, settings, file templates, snippets, run detectors | No | [harbor-night](https://github.com/l0ng-ai/openidea/tree/main/plugins/official/harbor-night), [material-icons](https://github.com/l0ng-ai/openidea/tree/main/plugins/official/material-icons) |
| Code plugin | The above plus a WebAssembly component (`wasm = "dist/plugin.wasm"`) | Yes | [string-tools](https://github.com/l0ng-ai/openidea/tree/main/plugins/official/string-tools), [git-toolbox](https://github.com/l0ng-ai/openidea/tree/main/plugins/official/git-toolbox) |
Declared contributions work as soon as the plugin is enabled, before any code loads: commands appear in Find Action, themes can be applied, settings pages exist. Code is loaded only when it is first needed (a command runs, a file of a declared language opens, and so on) and is unloaded again after 5 minutes without use.
Code plugins are WebAssembly components targeting the `plugin` world in [`wit/`](https://github.com/l0ng-ai/openidea/tree/main/wit). The Rust SDK ([`sdk/rust/openidea-plugin`](https://github.com/l0ng-ai/openidea/tree/main/sdk/rust/openidea-plugin)) is the supported way to write them; any language with component-model tooling (`componentize-py`, `jco componentize`, TinyGo) can target the same world.
## API version
| | |
| ------------------------- | ------------------------------------------------------------ |
| Latest released API | **1.4.0**, shipped with OpenIDEA 0.1.40 |
| In development | **1.5.0** on the `main` branch, planned for the next release |
| Format of the WIT package | `openidea:plugin@<version>` |
Declare the API your plugin needs with `engines.openidea` in the manifest, for example `engines.openidea = "^1.4"`.
## Where plugins come from
- **Marketplace:** **Settings › Plugins › Available** lists the official plugins and the community plugins of the [registry](https://github.com/l0ng-ai/openidea-plugins). See [Publish to the marketplace](/en/plugins/publishing/marketplace).
- **Disk:** **Install Plugin from Disk…** (a folder) or **Install Plugin from Package File…** (an `.oiplugin` or `.zip`) in the gear menu of **Settings › Plugins**.
- **Linked folders:** `openidea-plugin link` loads a folder you are developing, without installing it.
- **Project plugins:** packages in `<project>/.openidea/plugins/<name>/` load in trusted projects only, after you approve their permissions.
## Next steps
<CardGroup cols={2}>
<Card title="Quickstart" href="/en/plugins/quickstart">
Scaffold, build and run a plugin in a few minutes.
</Card>
<Card title="Example plugins" href="/en/plugins/examples">
A tour of the plugins in `plugins/official`.
</Card>
<Card title="Architecture and sandbox" href="/en/plugins/architecture">
How plugin code runs and what limits it.
</Card>
<Card title="Manifest" href="/en/plugins/manifest">
Every field of `plugin.toml`.
</Card>
</CardGroup>
The full, authoritative guide is [`plugins/README.md`](https://github.com/l0ng-ai/openidea/blob/main/plugins/README.md) in the repository.
+120
View File
@@ -0,0 +1,120 @@
---
title: "Lifecycle and events"
description: "When plugin code is loaded and unloaded, how events reach it, and how it keeps data, settings and state across reloads and updates."
---
## States
**Settings › Plugins** shows each plugin's state:
| Status | Meaning |
| -------------------- | ----------------------------------------------------------------------------------------------------------------------- |
| **Enabled** | Contributions are live. Code (if any) is not loaded right now. |
| **Running** | An instance of the code is active. |
| **Disabled** | Turned off by the user. |
| **Needs permission** | A linked or project plugin not yet allowed, or an update that asks for new capabilities. Click **Review Permissions…**. |
| **Failed** | It crashed three times in a row, or failed to start. The reason is shown; **Restart** tries again. |
Declared contributions (commands, menus, themes, settings pages, …) are live as soon as the plugin is enabled, before any code is loaded.
## Activation
Code is instantiated lazily, when one of the plugin's commands runs or when an event listed in `activation` fires (see [Manifest › Activation](/en/plugins/manifest#activation)). On each new instance the IDE calls `Plugin::activate`; subscribe to events and restore state there. An error from `activate` fails the activation.
An instance unused for **5 minutes** is reclaimed: `deactivate` runs and the plugin is not running again until its next event. `on-schedule` activates it when due but does not keep it alive. Keep nothing in memory you can't rebuild in `activate`.
```rust
use openidea_plugin::{Context, Error, EventKind, Plugin, host};
impl Plugin for MyPlugin {
fn activate(&mut self, _ctx: &Context) -> Result<(), Error> {
host::subscribe(EventKind::FileSaved)?;
self.count = host::storage::get("count")?.and_then(|v| v.parse().ok()).unwrap_or(0);
Ok(())
}
}
```
## Events
| Kind | Capability | Delivered when |
| ----------------------------------------------------- | -------------- | --------------------------------------------------------------------------------------- |
| `file-opened` | `events` | A file opens in an editor |
| `file-saved` | `events` | A file is written |
| `file-closed` | `events` | A file's editor closes (API 1.4) |
| `files-changed` | `events` | Files are created, modified or removed in open projects, debounced (API 1.3) |
| `editor-focused` | `events` | The focused editor changes |
| `vcs-changed` | `events` | A repository's state changed (commit, checkout, fetch…) |
| `settings-changed` | `events` | Some of this plugin's settings changed (API 1.4) |
| `action-performed` | `events.input` | An IDE action ran: id, title, source (keyboard, menu, toolbar…), shortcut |
| `input-mode-changed` | `events.input` | The focused editor's Vim mode changed (API 1.4) |
| `scheduled` | none | The `on-schedule` period elapsed |
| `run-exited`, `terminal-exited`, `content-tab-action` | none | A run, terminal or content tab this plugin started finished or sent an action (API 1.4) |
Subscribe in code with `host::subscribe(EventKind::…)` (it lasts for the session, across idle reclaim) or declare `activation = ["on-event:<kind>"]` to receive events before first use. `host::subscribe_paths(kind, globs)` (API 1.3) narrows `file-opened`, `file-saved` and `files-changed` to project-relative globs; the host filters before waking the plugin.
Events arrive in batches at `Plugin::handle_events`, oldest first, per window. A running plugin gets them within a fraction of a second; an idle one collects them for 2 seconds before it is woken, so frequent events don't keep it alive.
Kinds are strings, so later IDEs can add kinds without changing the API. An IDE refuses a kind it doesn't know with `invalid`; a kind newer than the plugin's SDK arrives as `Event::Other { kind, data }` with JSON data. Match the SDK's event enums with a `_` arm.
## Background work
Commands, events and activation have 5 seconds. For longer work, open a task:
```rust
let task = host::begin_task("Scanning files", true);
for (i, file) in files.iter().enumerate() {
if task.is_cancelled() { break; }
task.report(i as u32, Some(files.len() as u32), None);
// …
}
// dropping `task` ends it
```
While a task is open, the call may run for 60 seconds, and each `report` or `is_cancelled` renews that budget. The task shows with the IDE's background tasks.
## Storage
| | Survives idle reclaim | Survives restart and update | Removed on uninstall |
| ------------------------------------------------------- | --------------------- | --------------------------- | -------------------- |
| Instance memory (`self`) | No | No | — |
| `host::storage` (key-value, needs `storage`) | Yes | Yes | Yes |
| `host::fs::plugin_dir()` (private files, no capability) | Yes | Yes | Yes |
`host::storage` holds at most 64 MiB per plugin, counting the UTF-8 bytes of keys and values. Use `plugin_dir()` for caches or generated files larger than that. Storage is per plugin, not per version, so an update keeps it.
## Settings
A plugin declares its settings with a JSON schema; the IDE generates the page under **Settings › Plugins** (searchable, with project-level overrides):
```toml
[contributes.settings]
schema = "settings.schema.json"
```
```json
{
"properties": {
"context": { "type": "string", "title": "Context", "default": "" },
"tailLines": { "type": "integer", "title": "Log lines", "default": 200 },
"shell": { "type": "string", "enum": ["sh", "bash"], "default": "sh" }
}
}
```
Types are `boolean`, `string` (optionally with `enum` or `multiline`), `number` and `integer`, each with a `default`. Read values with `host::setting`, `setting_bool`, `setting_string` or `setting_number`: you get the user's value (project over global) or the schema's default, and a stored value of the wrong type falls back to the default. Subscribe to `settings-changed` (API 1.4) to react to changes.
API 1.3 adds multiple pages (`[[contributes.settings.pages]]`), groups, string lists, tables and action buttons that run one of the plugin's commands. See [`plugins/README.md`](https://github.com/l0ng-ai/openidea/blob/main/plugins/README.md#multiple-pages-groups-lists-and-tables-api-13) and the [`settings-pages` fixture](https://github.com/l0ng-ai/openidea/tree/main/crates/ide-plugin/tests/fixtures/settings-pages).
Secrets are not settings: declare them with `[[contributes.secrets]]` (see [Permissions and capabilities](/en/plugins/capabilities#secrets)).
## Reload, update and roll back
- **Reload while developing:** select the plugin in **Settings › Plugins** and click **Reload from disk**, or run the **Reload Plugins** action. Linked plugins are read from their folder, so a rebuild plus reload picks up new code and manifest changes.
- **Updates:** the IDE checks the marketplace daily. When it finds newer versions of installed plugins it says so once, with **Update All**; the gear menu has **Update All Plugins** too. An update that asks for nothing new keeps the existing grants.
- **Versions on disk:** installed plugins live in `<app-data>/plugins/<id>/<version>/` with an `active` pointer that is switched only once the new version is in place. If an update's code fails to start the first time, the IDE rolls back to the previous version automatically and tells you why. **Roll Back to `<version>`** in the plugin's details does it by hand.
- **Disable and uninstall:** disabling stops the code and withdraws its contributions; storage stays. Uninstalling removes the plugin, its storage and its private directory.
## Crashes
A panic, trap, permission violation or exceeded limit drops the instance. The plugin starts again the next time it is needed, after a pause that doubles with each crash in a row. After three crashes in a row it is **Failed** for the session. Calls that return normally reset the count. See [Architecture and sandbox](/en/plugins/architecture#threads-and-crashes).
+151
View File
@@ -0,0 +1,151 @@
---
title: "Manifest"
description: "Every field of plugin.toml: identity, API requirement, code, activation, capabilities and contributions."
---
Every plugin is a directory with a `plugin.toml` at its root. The manifest says who the plugin is, which plugin API it needs, where its code is (if any), when to load it, what it may do, and what it contributes to the IDE.
## Example
```toml
id = "acme.word-tools"
name = "Word Tools"
version = "1.2.0"
description = "Counts and transforms words."
authors = ["You <[email protected]>"]
homepage = "https://example.com"
license = "MIT"
change-notes = "1.2.0 — adds Shout."
categories = ["tools"]
icon = "icon.svg"
icon-dark = "icon_dark.svg"
engines.openidea = "^1.2"
wasm = "dist/plugin.wasm"
activation = ["on-language:markdown"]
[capabilities]
required = ["editor.read", "editor.write"]
optional = ["net:api.example.com"]
[[contributes.commands]]
id = "count"
title = "Word Tools: Count Words"
category = "Word Tools"
when = "editorFocus"
[[contributes.keymaps]]
command = "count"
key = "Ctrl+Alt+C"
mac = "Meta+Alt+C"
```
## Top-level fields
| Field | Required | Description |
| ------------------ | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `id` | Yes | Segments of `[a-z0-9][a-z0-9_-]*` joined by `.`, at most 64 characters. The part before the first `.` is the publisher namespace (`acme` in `acme.word-tools`); a signed package must use its publisher's prefix. |
| `name` | Yes | Display name. Must not be empty. |
| `version` | Yes | A semantic version such as `1.2.0`. |
| `description` | No | One or two sentences shown in Settings › Plugins and the marketplace. |
| `authors` | No | List of strings. |
| `homepage` | No | URL shown in the plugin's details. |
| `license` | No | License name, for example an SPDX id. |
| `change-notes` | No | Release notes shown under **What's New** in the plugin's details. |
| `categories` | No | Up to 3 marketplace categories (see below). |
| `icon` | No | Package SVG for Settings › Plugins and the marketplace. At most 16 KiB, sanitised (no scripts, external references or `foreignObject`); an invalid icon fails the package. Needs `engines.openidea = "^1.2"`. |
| `icon-dark` | No | Variant used with dark themes. Needs `icon`. |
| `engines.openidea` | Yes | A semver requirement on the plugin API, for example `"^1.4"`. The IDE refuses a package whose requirement doesn't match its API. See [API versions](/en/plugins/versioning). |
| `wasm` | No | Path of the component. Omit it for data-only plugins. |
| `activation` | No | Events that load the code (see below). Needs `wasm`. |
| `[capabilities]` | No | `required` and `optional` lists of capability strings. See [Permissions and capabilities](/en/plugins/capabilities). |
| `[contributes]` | No | Contribution points (see below). |
Unknown top-level fields are an error.
All paths are relative to the package directory and may not leave it. The manifest is validated as a whole: if one contribution is invalid, nothing is installed or loaded. Contribution types the IDE does not know (from a newer API) are skipped with a warning.
### Categories
`languages`, `themes`, `icon-themes`, `linters` (linters and formatters), `vcs`, `ai`, `frameworks`, `tools`, `other`.
Without `categories`, a plugin that contributes themes, icon themes or languages is filed under those, any other under `other`. Categories the IDE doesn't know, and any after the first three it knows, are ignored; `openidea-plugin check` warns about both.
### Activation
Code is loaded lazily. Without `activation`, it loads the first time one of the plugin's commands runs.
| Event | Fires when |
| --------------------- | ----------------------------------------------------------------------------------- |
| `on-command:<id>` | That command runs; implied for every command. `on-command:*` means any of them. |
| `on-language:<id>` | A file of that language is opened (`*`: any language). |
| `on-file:<glob>` | A project file matching the glob is opened. |
| `on-view:<id>` | One of the plugin's views or custom editors is shown. |
| `on-schedule:<secs>` | Every `secs` seconds, at least 60. The plugin gets a `scheduled` event. |
| `on-event:<kind>` | A workbench event of that kind (see [Lifecycle and events](/en/plugins/lifecycle)). |
| `on-startup-finished` | Shortly after the workbench is ready. Use sparingly. |
Duplicates are an error. Some event kinds need a newer API: `on-event:files-changed` needs `^1.3`; `file-closed`, `settings-changed`, `input-mode-changed`, `run-exited`, `terminal-exited` and `content-tab-action` need `^1.4`.
### Capabilities
```toml
[capabilities]
required = ["project.read", "process:checkstyle"]
optional = ["net:api.openai.com", "secrets"]
```
The user approves **required** capabilities as a whole when installing. **Optional** ones can be refused individually, and the plugin must cope with `Error::Denied` when it lacks them. The full list and what each unlocks is in [Permissions and capabilities](/en/plugins/capabilities).
## Contribution points
Each contribution point is a table (`[contributes.settings]`) or an array of tables (`[[contributes.commands]]`). Contributions are live as soon as the plugin is enabled, before its code loads.
| Point | Declaration | Code needed | Guide |
| ------------------- | -------------------------------------------------------------------------------- | ------------- | ------------------------------------------------------------------------------------------------------------------------------- |
| `commands` | `id`, `title`, `category?`, `when?`, `icon?` | to run them | [Commands and actions](/en/plugins/guides/commands) |
| `menus` | `menu`, `command`, `when?`, `view?`, `submenu?` | no | [Commands and actions](/en/plugins/guides/commands) |
| `keymaps` | `command`, `key`, `mac?` | no | [Commands and actions](/en/plugins/guides/commands) |
| `statusBar` | `id`, `text`, `tooltip?`, `command?`, `align?`, `priority?` | no | [Views and decorations](/en/plugins/guides/views) |
| `settings` | `schema` (a JSON file), or `pages` (API 1.3), `title?` | no | [Lifecycle and events](/en/plugins/lifecycle#settings) |
| `secrets` | `name`, `label`, `description?` | to use them | [Permissions and capabilities](/en/plugins/capabilities#secrets) |
| `providers` | `kinds`, `languages` | yes | [Language features](/en/plugins/guides/language-features) |
| `documentIndexers` | `extensions` | yes | [Indexing](/en/plugins/guides/indexing) |
| `endpoints` | `files` (API 1.3) | yes | [README: Project HTTP endpoints](https://github.com/l0ng-ai/openidea/blob/main/plugins/README.md#project-http-endpoints-api-13) |
| `views` | `id`, `title`, `icon?`, `location`, `web?`, `group?`, `when?` | without `web` | [Views and decorations](/en/plugins/guides/views) |
| `customEditors` | `id`, `title`, `patterns`, `priority`, `layout?`, `web?` | without `web` | [Views and decorations](/en/plugins/guides/views) |
| `markerIcons` | `id`, `icon`, `iconDark?`, `markers?`, `tooltip?` (API 1.3): gutter marker icons | no | [Language features](/en/plugins/guides/language-features) |
| `fileIcons` | `id`, `patterns`, `icon`, `iconDark?` | no | [Icon themes](/en/plugins/guides/icon-themes) |
| `fileTemplates` | `id`, `name`, `extension`, `path`; API 1.3: `fileName`, `variables` | no | [File actions](/en/plugins/guides/file-actions) |
| `snippets` | `language`, `path` (API 1.4) | no | [Language features](/en/plugins/guides/language-features) |
| `commitChecks` | `id`, `title` | yes | [README: Commits](https://github.com/l0ng-ai/openidea/blob/main/plugins/README.md#commits) |
| `runDetectors` | `id`, `name`, `files`, `command`, `args?`, `test?`, `framework?` | no | [Run targets](/en/plugins/guides/run-targets) |
| `runConfigurations` | `files` (API 1.4) | yes | [Run targets](/en/plugins/guides/run-targets) |
| `mcpTools` | `name`, `description`, `inputSchema`, `readOnly`, `destructive` (API 1.4) | yes | [MCP tools](/en/plugins/guides/mcp) |
| `themes` | `path`, `editor-scheme?` | no | [Themes](/en/plugins/guides/themes) |
| `iconThemes` | `id`, `label`, `path` | no | [Icon themes](/en/plugins/guides/icon-themes) |
| `languages` | `path` (a `languages/<id>.toml` file) | no | [Adding a language](/en/languages/adding) |
Item ids (commands, views, themes, …) match `[a-z0-9][a-z0-9._-]*`, at most 64 characters. Menus, shortcuts and status bar items refer to the plugin's own declared commands; inside the IDE a command's action id is `plugin.<plugin id>.<command id>`.
## Translations
A package translates its own text with `locales/<tag>.json` files (`fr.json`, `zh-CN.json`). No manifest entry is needed. Each file maps the English source text to its translation:
```json
{ "Count Words": "Compter les mots", "Refresh": "Actualiser" }
```
The IDE applies the catalog of the selected UI language (the exact tag, then the language, then a variant such as `zh-TW` for `zh`) to the titles, categories, labels and tooltips the manifest declares, and to the text views show. Text the plugin builds itself (notifications, error messages, `html` bodies) is not translated: read `host::locale()` when building it. `openidea-plugin check` refuses a catalog that is not a JSON object of strings.
## The 0.1 format
Manifests in the old 0.1 format (`openidea-api = "0.1"`, `permissions = [...]`) still load: their permissions become required capabilities and their contributions work. Their code no longer runs; rebuild it against the 1.x SDK and switch to `engines.openidea` and `[capabilities]`. You can't mix the two formats in one manifest.
## Checking a manifest
```sh
openidea-plugin check # the current folder
openidea-plugin check path/to/plugin
```
`check` validates the manifest, every contribution and the component exactly as installing would. See [Testing and debugging](/en/plugins/testing).
Loaded 100 of 255 files, more files were not shown because too many files have changed in this diff. Show more