mirror of
https://github.com/l0ng-ai/openidea.git
synced 2026-11-07 00:07:29 +00:00
A plugin system built on WebAssembly components (wasmtime, WASI p2), with a typed WIT API (`openidea:plugin@1.0.0`) 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 <5108773+dengliu@users.noreply.github.com> Co-authored-by: florentvdry <16673235+florentvdry@users.noreply.github.com>
93 lines
3.6 KiB
Rust
93 lines
3.6 KiB
Rust
//! Declarative views and custom editors as the
|
|
//! window commands `plugin_view_render` / `plugin_view_event` serve them —
|
|
//! the desktop for local projects, `openidea-server` for remote ones.
|
|
|
|
use super::{PluginEditor, PluginHost};
|
|
use ide_core::{DocId, IdeError, IdeResult};
|
|
use ide_plugin::{EditorContext, Invocation, View, ViewEvent, Viewport};
|
|
use std::sync::Arc;
|
|
|
|
/// The window's project, plus the editor the call is about: the document a
|
|
/// custom editor shows, else (a tool window) the window's active editor, as
|
|
/// a command gets it, so a view's buttons can act on what the user is editing.
|
|
pub fn view_invocation(
|
|
host: &crate::Host,
|
|
window: &str,
|
|
doc: Option<DocId>,
|
|
editor: Option<PluginEditor>,
|
|
) -> IdeResult<Invocation> {
|
|
let project = host.project(window).ok();
|
|
let editor = match doc {
|
|
None => editor.map(PluginEditor::context),
|
|
Some(doc) => {
|
|
let project = project.as_ref().ok_or_else(|| IdeError::not_found("no project is open in this window"))?;
|
|
let info = project.documents.snapshot(doc)?;
|
|
Some(EditorContext { path: info.path.0, language: info.language.0, selections: Vec::new() })
|
|
}
|
|
};
|
|
Ok(Invocation { client: Some(window.to_owned()), workspace_root: project.as_ref().map(|p| p.root.clone()), editor })
|
|
}
|
|
|
|
async fn blocking<T: Send + 'static>(f: impl FnOnce() -> IdeResult<T> + Send + 'static) -> IdeResult<T> {
|
|
tokio::task::spawn_blocking(f).await.map_err(|e| IdeError::internal(e.to_string()))?
|
|
}
|
|
|
|
#[allow(clippy::too_many_arguments)]
|
|
pub async fn render(
|
|
host: &crate::Host,
|
|
plugins: Arc<PluginHost>,
|
|
window: &str,
|
|
plugin_id: String,
|
|
view: String,
|
|
viewport: Option<Viewport>,
|
|
doc: Option<DocId>,
|
|
editor: Option<PluginEditor>,
|
|
) -> IdeResult<View> {
|
|
let invocation = view_invocation(host, window, doc, editor)?;
|
|
blocking(move || plugins.render_view(&plugin_id, &view, viewport, invocation)).await
|
|
}
|
|
|
|
#[allow(clippy::too_many_arguments)]
|
|
pub async fn event(
|
|
host: &crate::Host,
|
|
plugins: Arc<PluginHost>,
|
|
window: &str,
|
|
plugin_id: String,
|
|
view: String,
|
|
event: ViewEvent,
|
|
doc: Option<DocId>,
|
|
editor: Option<PluginEditor>,
|
|
) -> IdeResult<()> {
|
|
let invocation = view_invocation(host, window, doc, editor)?;
|
|
blocking(move || plugins.view_event(&plugin_id, &view, event, invocation)).await
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use crate::{Host, HostConfig};
|
|
|
|
#[tokio::test]
|
|
async fn a_tool_window_call_carries_the_active_editor() {
|
|
let tmp = tempfile::tempdir().unwrap();
|
|
let host = Host::new(HostConfig {
|
|
config_dir: tmp.path().join("config"),
|
|
data_dir: tmp.path().join("data"),
|
|
local_data_dir: tmp.path().join("data"),
|
|
languages: Arc::new(ide_language::LanguageRegistry::load(None).unwrap()),
|
|
syntax: Arc::default(),
|
|
sink_for: Box::new(|_| Arc::new(ide_core::NullSink)),
|
|
owner: |w| w,
|
|
runtime: tokio::runtime::Handle::current(),
|
|
project_environment: false,
|
|
});
|
|
let range = ide_core::Range::new(ide_core::Pos::new(1, 0), ide_core::Pos::new(1, 4));
|
|
let active = PluginEditor { path: "/p/a.rs".into(), language: "rust".into(), selections: vec![range] };
|
|
let inv = view_invocation(&host, "main", None, Some(active)).unwrap();
|
|
let editor = inv.editor.expect("the active editor");
|
|
assert_eq!((editor.path.to_string_lossy().as_ref(), editor.selections.as_slice()), ("/p/a.rs", &[range][..]));
|
|
assert_eq!(inv.client.as_deref(), Some("main"));
|
|
assert!(view_invocation(&host, "main", None, None).unwrap().editor.is_none());
|
|
}
|
|
}
|