//! 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, editor: Option, ) -> IdeResult { 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(f: impl FnOnce() -> IdeResult + Send + 'static) -> IdeResult { 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, window: &str, plugin_id: String, view: String, viewport: Option, doc: Option, editor: Option, ) -> IdeResult { 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, window: &str, plugin_id: String, view: String, event: ViewEvent, doc: Option, editor: Option, ) -> 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()); } }