дзеркало
https://github.com/l0ng-ai/openidea.git
синхронізовано 2026-11-09 19:25:54 +02:00
feat(plugins): plugin system with extension points, consent, Wasm runtime and plugin API 1.0 (#971)
A plugin system built on WebAssembly components (wasmtime, WASI p2), with a typed WIT API (`openidea:[email protected]`) and a Rust SDK, shipped as a preview. - Extension points declared in `plugin.toml` (commands, languages, themes, icon themes, menus, key bindings, status items, settings, views, custom editors, file icons and templates, run detectors, editor providers), validated before anything takes effect and projected onto the workbench's own registries: plugin themes and file icon themes are chosen in Settings › Appearance and Quick Switch, plugin shortcuts are edited in Settings › Keymap, plugin problems are grouped in Settings › Inspections. - Capabilities with risk levels and consent at install, pinned `process:` programs, secrets in the OS keychain. - Runtime: one thread per active plugin, fuel/time/memory budgets, soft and hard deadlines on the editing path, cancellation, crash backoff, idle reclaim, a precompiled-component cache; remote projects run code plugins on `openidea-server`. - Web views and custom editors with a sandboxed page, a document bridge joined to the text editor's undo history, and split or preview layouts. - Distribution: signed `.oiplugin` packages, a static marketplace index with revocation, versioned installs with rollback, install from a `.zip` or a folder. The release workflow signs and publishes the official plugins and their index, which released builds use as the default marketplace. - Official plugins: String Tools, Key Promoter, Git Toolbox, AI Completion, Checkstyle, MyBatis Navigator, CSV Editor, Solidity, Material Icons, Harbor Night, Excalidraw, Graphviz, Regex Tester, License Header. Closes #732 Co-authored-by: Deng Liu <[email protected]> Co-authored-by: florentvdry <[email protected]>
Цей коміт міститься в:
450 files changed
+47018
-3058
No files matched your search
@@ -0,0 +1 @@
|
||||
/target
|
||||
+333
@@ -0,0 +1,333 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.104"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "2.13.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3ded4057c258ba199e2d26386d3af3780957ecaee6c4ef4041c6b4b8b97c0b06"
|
||||
|
||||
[[package]]
|
||||
name = "equivalent"
|
||||
version = "1.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "00d174d5400e5e8fd687ad1049e2f578285fa914201b1af7e8b112a4546bd826"
|
||||
|
||||
[[package]]
|
||||
name = "foldhash"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb"
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.17.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a"
|
||||
dependencies = [
|
||||
"foldhash",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
||||
|
||||
[[package]]
|
||||
name = "id-arena"
|
||||
version = "2.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954"
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "2.14.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cc4e190f5d26ca7051642629da2c52fc03bde85a03197c99408dcd291734c855"
|
||||
dependencies = [
|
||||
"equivalent",
|
||||
"hashbrown",
|
||||
"serde",
|
||||
"serde_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
|
||||
|
||||
[[package]]
|
||||
name = "leb128fmt"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2"
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.35"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5d15f86d12da44cd8d4ec3c73984537eb7f4cadcb9776a575a0a8a05ca11005c"
|
||||
|
||||
[[package]]
|
||||
name = "macro-string"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6e7b3a5bbb2f63aaa223a671ea1bd0e7bc16bd30ce387324cf63995deaddbbd2"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.8.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98"
|
||||
|
||||
[[package]]
|
||||
name = "openidea-plugin"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"wit-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "openidea-plugin-openidea-git-toolbox"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"openidea-plugin",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "prettyplease"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2bfe0f4c752e450fc2faf62654f1c134747922825d5b04ca717b8874f41a40c0"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.107"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.47"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "semver"
|
||||
version = "1.0.28"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.229"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba"
|
||||
dependencies = [
|
||||
"serde_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_core"
|
||||
version = "1.0.229"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.229"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.154"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e7e9cc8b1b85264074fbcc02a88680c4096b1e47df8f739dceb03bf482f04bd6"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"memchr",
|
||||
"serde",
|
||||
"serde_core",
|
||||
"zmij",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "3.0.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d78c8dee4c7bf0e14673097256fed6142ce9d3b85a408189d07482442145823b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.27"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a2c754d6c33795a1c324727428e5a7dedb5b06195f9890bdbcba760d3e246563"
|
||||
|
||||
[[package]]
|
||||
name = "wasm-encoder"
|
||||
version = "0.262.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4495a6f85dd94c411c749303efcbc5defade0b0cbdbc0541495241ab917a9b5a"
|
||||
dependencies = [
|
||||
"leb128fmt",
|
||||
"wasmparser",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-metadata"
|
||||
version = "0.262.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c86ff6668190d9fb70372b306b4f9fe8930f0e2f57ce72e4922bf2f117900949"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"indexmap",
|
||||
"wasm-encoder",
|
||||
"wasmparser",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasmparser"
|
||||
version = "0.262.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "974d643b125b272607e2fff8012ea14a8328acd7621e4bafa7afb32500052b1b"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"hashbrown",
|
||||
"indexmap",
|
||||
"semver",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen"
|
||||
version = "0.63.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b85a987ab61d365382d25c7d47e26edd48941b3bf6e3fbf76eff918bc71dab06"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"wit-bindgen-rust-macro",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen-core"
|
||||
version = "0.63.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "775e122e390603abfacd548027016cd0113a6e2bf2f8ce6bdd3658b013456ea8"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"heck",
|
||||
"wit-parser",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen-rust"
|
||||
version = "0.63.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "84e26e13b9d972026778d2ffb7985736de116026fdc8d9464f966e12df2eb240"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"heck",
|
||||
"indexmap",
|
||||
"prettyplease",
|
||||
"syn",
|
||||
"wasm-metadata",
|
||||
"wit-bindgen-core",
|
||||
"wit-component",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen-rust-macro"
|
||||
version = "0.63.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "16a008a0d6b4840677326beb4edbeb210a2f1b7d0183fb7cc33f466b90fd7fb6"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"macro-string",
|
||||
"prettyplease",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"wit-bindgen-core",
|
||||
"wit-bindgen-rust",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wit-component"
|
||||
version = "0.262.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "078def49fdb49cab405c3006e05793b3fdaf7069126ba6b9c97d01ec8c98fe4c"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bitflags",
|
||||
"indexmap",
|
||||
"log",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
"serde_json",
|
||||
"wasm-encoder",
|
||||
"wasm-metadata",
|
||||
"wasmparser",
|
||||
"wit-parser",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wit-parser"
|
||||
version = "0.262.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "00f0dc051466ffe46787721fca3d088536f158ae400be044f1b1c386ba9397ad"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"hashbrown",
|
||||
"id-arena",
|
||||
"indexmap",
|
||||
"log",
|
||||
"semver",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
"serde_json",
|
||||
"unicode-ident",
|
||||
"wasmparser",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zmij"
|
||||
version = "1.0.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b"
|
||||
@@ -0,0 +1,21 @@
|
||||
[package]
|
||||
name = "openidea-plugin-openidea-git-toolbox"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
publish = false
|
||||
|
||||
# A plugin builds on its own (target wasm32-wasip2), outside any IDE workspace.
|
||||
[workspace]
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib", "rlib"]
|
||||
|
||||
[dependencies]
|
||||
openidea-plugin = { path = "../../../sdk/rust/openidea-plugin" }
|
||||
|
||||
[profile.release]
|
||||
opt-level = "s"
|
||||
lto = true
|
||||
strip = true
|
||||
codegen-units = 1
|
||||
panic = "abort"
|
||||
Бінарний файл не відображається.
@@ -0,0 +1 @@
|
||||
ad545605b33c6f4e55a06a376333de3162739ed920b5d7d277942e50a4420083
|
||||
@@ -0,0 +1,26 @@
|
||||
id = "openidea.git-toolbox"
|
||||
name = "Git Toolbox"
|
||||
version = "0.1.0"
|
||||
description = "Fetches your repositories in the background and shows the branch with how far it is ahead of and behind its upstream in the status bar."
|
||||
authors = ["OpenIDEA contributors"]
|
||||
license = "Apache-2.0"
|
||||
change-notes = "0.1.0 — first release: auto-fetch every 5 minutes, ahead/behind in the status bar, Fetch Now."
|
||||
engines.openidea = "^1.0"
|
||||
wasm = "dist/plugin.wasm"
|
||||
# Fetches on schedule (also after being reclaimed while idle) and refreshes
|
||||
# the status bar when the focused editor or a repository changes.
|
||||
activation = ["on-schedule:300", "on-event:editor-focused", "on-event:vcs-changed", "on-command:*"]
|
||||
|
||||
[capabilities]
|
||||
# `vcs.write` runs `git fetch` (only in trusted projects).
|
||||
required = ["project.read", "vcs.read", "vcs.write", "events", "storage"]
|
||||
|
||||
[[contributes.commands]]
|
||||
id = "fetch"
|
||||
title = "Git Toolbox: Fetch Now"
|
||||
category = "Git Toolbox"
|
||||
|
||||
[[contributes.commands]]
|
||||
id = "refresh"
|
||||
title = "Git Toolbox: Refresh Status"
|
||||
category = "Git Toolbox"
|
||||
@@ -0,0 +1,242 @@
|
||||
//! Git Toolbox for OpenIDEA: background fetch of every open project's
|
||||
//! repository (on the plugin's `on-schedule`, which also fires after the
|
||||
//! instance was reclaimed while idle), and the focused project's branch with
|
||||
//! its ahead/behind counts in the status bar.
|
||||
|
||||
use openidea_plugin::host::{self, storage};
|
||||
use openidea_plugin::{
|
||||
Error, Event, Invocation, NotificationLevel, Plugin, Project, RepositoryStatus, StatusItem, register_plugin,
|
||||
};
|
||||
|
||||
const ITEM: &str = "branch";
|
||||
const LAST_FETCH: &str = "last-fetch:";
|
||||
/// Root of the project shown in the status bar.
|
||||
const SHOWN: &str = "shown-root";
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct GitToolbox;
|
||||
|
||||
fn now_secs() -> u64 {
|
||||
std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).map(|d| d.as_secs()).unwrap_or(0)
|
||||
}
|
||||
|
||||
/// `main ↑1 ↓2`; a detached HEAD shows its commit.
|
||||
pub fn label(s: &RepositoryStatus) -> String {
|
||||
let mut out = match (&s.branch, &s.commit) {
|
||||
(Some(b), _) => b.clone(),
|
||||
(None, Some(c)) => c.chars().take(8).collect(),
|
||||
(None, None) => "(no commits)".to_owned(),
|
||||
};
|
||||
if s.upstream.is_some() {
|
||||
if s.ahead > 0 {
|
||||
out.push_str(&format!(" ↑{}", s.ahead));
|
||||
}
|
||||
if s.behind > 0 {
|
||||
out.push_str(&format!(" ↓{}", s.behind));
|
||||
}
|
||||
}
|
||||
if let Some(op) = &s.operation {
|
||||
out.push_str(&format!(" ({op})"));
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
fn ago(secs: u64) -> String {
|
||||
match secs {
|
||||
0..60 => "just now".into(),
|
||||
60..3600 => format!("{} min ago", secs / 60),
|
||||
_ => format!("{} h ago", secs / 3600),
|
||||
}
|
||||
}
|
||||
|
||||
fn tooltip(s: &RepositoryStatus) -> String {
|
||||
let mut lines = vec![s.root.clone()];
|
||||
match &s.upstream {
|
||||
Some(up) => lines.push(format!("{} ahead, {} behind {up}", s.ahead, s.behind)),
|
||||
None => lines.push("No upstream branch".into()),
|
||||
}
|
||||
if s.changed_files > 0 {
|
||||
lines.push(format!("{} changed files", s.changed_files));
|
||||
}
|
||||
let last = storage::get(&format!("{LAST_FETCH}{}", s.root)).ok().flatten().and_then(|v| v.parse::<u64>().ok());
|
||||
lines.push(match last {
|
||||
Some(t) => format!("Fetched {}", ago(now_secs().saturating_sub(t))),
|
||||
None => "Not fetched yet".into(),
|
||||
});
|
||||
lines.push("Click to fetch now".into());
|
||||
lines.join("\n")
|
||||
}
|
||||
|
||||
/// Shows `project`'s repository in the status bar (or hides the item).
|
||||
fn show(project: &Project) -> Result<(), Error> {
|
||||
match project.vcs_status()? {
|
||||
Some(s) => {
|
||||
let _ = storage::set(SHOWN, &project.root()?);
|
||||
host::set_status_item(
|
||||
ITEM,
|
||||
&StatusItem { text: label(&s), tooltip: Some(tooltip(&s)), command: Some("fetch".into()) },
|
||||
);
|
||||
}
|
||||
None => host::remove_status_item(ITEM),
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// The project the status bar follows: the invoking window's, else the one
|
||||
/// shown last, else the first open one.
|
||||
fn current(ctx: &Invocation) -> Option<Project> {
|
||||
if let Some(p) = ctx.project() {
|
||||
return Some(p);
|
||||
}
|
||||
let projects = host::projects().ok()?;
|
||||
let shown = storage::get(SHOWN).ok().flatten();
|
||||
let index = projects.iter().position(|p| p.root().ok() == shown).unwrap_or(0);
|
||||
projects.into_iter().nth(index)
|
||||
}
|
||||
|
||||
/// Fetches `projects` in one cancellable task. Returns how many were fetched.
|
||||
fn fetch(projects: &[Project]) -> Result<usize, Error> {
|
||||
let task = host::begin_task("Fetching", true);
|
||||
let total = projects.len() as u32;
|
||||
let mut fetched = 0;
|
||||
let mut last_error = None;
|
||||
for (i, p) in projects.iter().enumerate() {
|
||||
if task.is_cancelled() {
|
||||
return Err(Error::Cancelled);
|
||||
}
|
||||
let root = p.root()?;
|
||||
let name = root.rsplit(['/', '\\']).next().unwrap_or(&root).to_owned();
|
||||
task.report(i as u32, Some(total), Some(&name));
|
||||
if p.vcs_status()?.is_none() {
|
||||
continue;
|
||||
}
|
||||
match p.vcs_fetch() {
|
||||
Ok(()) => {
|
||||
fetched += 1;
|
||||
let _ = storage::set(&format!("{LAST_FETCH}{root}"), &now_secs().to_string());
|
||||
}
|
||||
Err(Error::Cancelled) => return Err(Error::Cancelled),
|
||||
Err(e) => {
|
||||
host::log(openidea_plugin::LogLevel::Warn, &format!("fetch {root}: {e}"));
|
||||
last_error = Some(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
task.report(total, Some(total), None);
|
||||
match last_error {
|
||||
Some(e) if fetched == 0 => Err(e),
|
||||
_ => Ok(fetched),
|
||||
}
|
||||
}
|
||||
|
||||
impl Plugin for GitToolbox {
|
||||
fn run_command(&mut self, id: &str, ctx: &Invocation, _arg: Option<&str>) -> Result<Option<String>, Error> {
|
||||
let project = current(ctx).ok_or("no project is open")?;
|
||||
match id {
|
||||
"fetch" => {
|
||||
let n = fetch(std::slice::from_ref(&project))?;
|
||||
show(&project)?;
|
||||
Ok((n == 0).then(|| "The project is not in a git repository.".to_owned()))
|
||||
}
|
||||
"refresh" => {
|
||||
show(&project)?;
|
||||
Ok(None)
|
||||
}
|
||||
_ => Err(Error::NotFound(format!("command {id}"))),
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_events(&mut self, events: &[Event], ctx: &Invocation) {
|
||||
if events.contains(&Event::Scheduled) {
|
||||
let projects = host::projects().unwrap_or_default();
|
||||
if let Err(e) = fetch(&projects)
|
||||
&& e != Error::Cancelled
|
||||
{
|
||||
host::notify(NotificationLevel::Warning, "Git Toolbox: fetch failed", &e.to_string());
|
||||
}
|
||||
}
|
||||
if let Some(p) = current(ctx) {
|
||||
let _ = show(&p);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
register_plugin!(GitToolbox);
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use openidea_plugin::mock::MockHost;
|
||||
|
||||
fn status(ahead: u32, behind: u32) -> RepositoryStatus {
|
||||
RepositoryStatus {
|
||||
root: "/project".into(),
|
||||
branch: Some("main".into()),
|
||||
commit: Some("0123456789abcdef".into()),
|
||||
upstream: Some("origin/main".into()),
|
||||
ahead,
|
||||
behind,
|
||||
..RepositoryStatus::default()
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn labels() {
|
||||
assert_eq!(label(&status(0, 0)), "main");
|
||||
assert_eq!(label(&status(1, 2)), "main ↑1 ↓2");
|
||||
let detached = RepositoryStatus { branch: None, ..status(0, 0) };
|
||||
assert_eq!(label(&detached), "01234567");
|
||||
let rebasing = RepositoryStatus { operation: Some("rebase".into()), ..status(0, 3) };
|
||||
assert_eq!(label(&rebasing), "main ↓3 (rebase)");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn scheduled_fetch_updates_the_status_bar() {
|
||||
let host = MockHost::install();
|
||||
host.add_project("/other");
|
||||
host.set_vcs_status(Some(status(0, 4)));
|
||||
GitToolbox.handle_events(&[Event::Scheduled], &host.invocation());
|
||||
assert_eq!(host.fetches(), 2, "every open project");
|
||||
let item = &host.status_items()["branch"];
|
||||
assert_eq!(item.text, "main ↓4");
|
||||
assert!(item.tooltip.as_deref().unwrap().contains("Fetched just now"));
|
||||
assert_eq!(item.command.as_deref(), Some("fetch"));
|
||||
let task = &host.tasks()[0];
|
||||
assert!(task.ended && task.reports.last() == Some(&(2, Some(2), None)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn focus_changes_only_refresh() {
|
||||
let host = MockHost::install();
|
||||
host.set_vcs_status(Some(status(1, 0)));
|
||||
GitToolbox.handle_events(&[Event::EditorFocused(Some("/project/a.rs".into()))], &host.invocation());
|
||||
assert_eq!(host.fetches(), 0);
|
||||
assert_eq!(host.status_items()["branch"].text, "main ↑1");
|
||||
host.set_vcs_status(None);
|
||||
GitToolbox.handle_events(&[Event::VcsChanged("/project".into())], &host.invocation());
|
||||
assert!(host.status_items().is_empty(), "not a repository any more");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn failures_and_cancellation() {
|
||||
let host = MockHost::install();
|
||||
host.set_vcs_status(Some(status(0, 0)));
|
||||
host.fail_fetch(Some(Error::Failed("no network".into())));
|
||||
GitToolbox.handle_events(&[Event::Scheduled], &host.invocation());
|
||||
assert_eq!(host.notifications()[0].body, "no network");
|
||||
let err = GitToolbox.run_command("fetch", &host.invocation(), None).unwrap_err();
|
||||
assert_eq!(err, Error::Failed("no network".into()));
|
||||
host.fail_fetch(Some(Error::Cancelled));
|
||||
GitToolbox.handle_events(&[Event::Scheduled], &host.invocation());
|
||||
assert_eq!(host.notifications().len(), 1, "a cancelled fetch is not an error to report");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn without_vcs_write_it_still_shows_the_branch() {
|
||||
let host = MockHost::install().with_capabilities(&["project.read", "vcs.read", "storage"]);
|
||||
host.set_vcs_status(Some(status(0, 1)));
|
||||
GitToolbox.handle_events(&[Event::Scheduled], &host.invocation());
|
||||
assert!(host.notifications()[0].body.contains("vcs.write"));
|
||||
assert_eq!(host.status_items()["branch"].text, "main ↓1");
|
||||
}
|
||||
}
|
||||
Reference in new issue
Заблокувати користувача