//! The synchronous workspace import uses the real project index, also from a provider. use ide_core::CancellationToken; use ide_core::{NullSink, Pos, Range}; use ide_host::plugins::{Consent, HostWorkspace, PluginHost, PluginWorkspace}; use ide_host::{Host, HostConfig}; use ide_plugin::{Invocation, Limits, ProviderDocument}; use std::path::{Path, PathBuf}; use std::sync::{Arc, OnceLock}; use std::time::{Duration, Instant}; const WAIT: Duration = Duration::from_secs(30); struct Fixture { _tmp: tempfile::TempDir, _runtime: tokio::runtime::Runtime, host: Arc, plugins: PluginHost, root: PathBuf, other: PathBuf, } impl Fixture { fn new(capability: bool) -> Self { let tmp = tempfile::tempdir().unwrap(); let base = dunce::canonicalize(tmp.path()).unwrap(); let (root, other) = (base.join("project"), base.join("other")); for dir in [&root, &other] { std::fs::create_dir_all(dir).unwrap(); } std::fs::write(root.join("Order.php"), ", _| {}), Limits { edit_soft: Duration::from_secs(5), edit_hard: Duration::from_secs(10), ..Limits::default() }, ) .unwrap(); plugins.set_workspace(Some(Arc::new(HostWorkspace(Arc::downgrade(&host))))); plugins.set_project_policy(Arc::new(|_| true)); let source = Path::new(env!("CARGO_MANIFEST_DIR")).join("../ide-plugin/tests/fixtures/symbols"); let package = base.join("fixture"); std::fs::create_dir_all(package.join("dist")).unwrap(); std::fs::copy(source.join("dist/plugin.wasm"), package.join("dist/plugin.wasm")).unwrap(); let manifest = std::fs::read_to_string(source.join("plugin.toml")).unwrap(); let manifest = if capability { manifest } else { manifest.replace("required = [\"project.read\"]", "required = []") }; std::fs::write(package.join("plugin.toml"), manifest).unwrap(); plugins.install_from_dir(&package, &Consent::all()).unwrap(); Self { _tmp: tmp, _runtime: runtime, host, plugins, root, other } } fn invoke(&self, command: &str, root: &Path, query: &str) -> ide_core::IdeResult> { self.plugins.run_command( "test.symbols", command, Some(query), Invocation { workspace_root: Some(root.to_path_buf()), ..Default::default() }, ) } fn search(&self, query: &str) -> String { self.invoke("symbols", &self.root, query).unwrap().unwrap() } } impl Drop for Fixture { fn drop(&mut self) { self.host.close_project("main"); self.host.close_project("other"); } } #[test] fn symbols_reach_commands_and_providers_without_a_language_server() { let f = Fixture::new(true); let all = f.search("Order|10"); assert!(all.contains("OrderController\tclass\t"), "{all}"); assert!(all.contains("OrderService\tclass\t"), "{all}"); assert!(!all.contains("OrderElsewhere")); assert_eq!(f.search("OCo|1").lines().count(), 1); assert!(f.search("OrderController.item|1").starts_with("item\tmethod\tOrderController\t")); assert!(f.search("|10").is_empty()); assert!(f.search("Order|0").is_empty()); assert!(f.search("missing|10").is_empty()); assert!(f.search("Order|4294967295").contains("OrderController")); assert!( f.invoke("symbols", &f.root, &format!("{}|1", "é".repeat(129))) .unwrap_err() .message .contains("256 UTF-8 bytes") ); let other = f.invoke("symbols", &f.other, "Order|10").unwrap().unwrap(); assert!(other.contains("OrderElsewhere") && !other.contains("OrderController")); for language in ["php", "java"] { let targets = f.plugins.provide_definition( &ProviderDocument { path: f.root.join(if language == "php" { "Usage.php" } else { "Usage.java" }), language: language.into(), version: 1, text: "OCo".into(), }, Pos::new(0, 3), ); assert_eq!(targets.len(), 1); assert_eq!(targets[0].path, f.root.join("Order.php")); let indexed = HostWorkspace(Arc::downgrade(&f.host)) .symbols(&f.root, "OCo", 1, &CancellationToken::new(), Instant::now() + WAIT) .unwrap(); assert_eq!(targets[0].range, indexed[0].location.range); } } #[test] fn reindex_and_closed_project_replace_results_and_invalidate_retained_handles() { let f = Fixture::new(true); f.invoke("remember", &f.root, "").unwrap(); let p = f.host.project("main").unwrap(); let path = f.root.join("Order.php"); std::fs::write(&path, "(); std::fs::write(&path, text).unwrap(); project.index.update(&[path], &[]); assert!(project.index.wait_idle(WAIT)); assert_eq!(f.search("BulkSymbol|4294967295").lines().count(), 1000); } #[test] fn symbol_queries_require_project_read_even_with_an_invocation_project() { let f = Fixture::new(false); let error = f.invoke("symbols", &f.root, "Order|10").unwrap_err(); assert!(error.message.contains("project.read"), "{error}"); assert!( f.plugins .provide_definition( &ProviderDocument { path: f.root.join("Usage.php"), language: "php".into(), version: 1, text: "OCo".into(), }, Pos::new(0, 3) ) .is_empty() ); } #[test] fn symbol_search_excludes_external_content_roots_before_ranking() { let f = Fixture::new(true); let project = f.host.project("main").unwrap(); std::fs::write(f.other.join("Exact.php"), "