diff --git a/crates/ide-host/src/plugins/api.rs b/crates/ide-host/src/plugins/api.rs index 53e2bf18a..040fc1c8e 100644 --- a/crates/ide-host/src/plugins/api.rs +++ b/crates/ide-host/src/plugins/api.rs @@ -51,6 +51,17 @@ pub trait PluginWorkspace: Send + Sync { fn indexed_files(&self, _root: &Path, _matches: &dyn Fn(&Path) -> bool, _limit: usize) -> Option> { None } + /// Current index of the project rooted at `root`; never reads disk or calls LSP. + fn symbols( + &self, + _root: &Path, + _query: &str, + _limit: usize, + _cancel: &CancellationToken, + _deadline: Instant, + ) -> Result, HostError> { + Err(HostError::NotFound("no project index is available".into())) + } /// Version and text of the open document at `path`. fn document(&self, path: &Path) -> Option<(u64, String)>; /// Applies `edits` to the open document at `path` if it is still at @@ -1297,6 +1308,26 @@ impl HostApi for PluginApi { } } + fn symbols( + &self, + root: &Path, + query: &str, + limit: usize, + cancel: &CancellationToken, + deadline: Instant, + ) -> Result, HostError> { + if query.len() > 256 { + return Err(HostError::Invalid("symbol query exceeds 256 UTF-8 bytes".into())); + } + self.shared.workspace().ok_or_else(|| HostError::NotFound("no project index is available".into()))?.symbols( + root, + query, + limit.min(1000), + cancel, + deadline, + ) + } + fn find_files(&self, root: &Path, glob: &str, limit: usize) -> Result, HostError> { let matcher = globset::GlobBuilder::new(glob) .literal_separator(true) @@ -1872,6 +1903,57 @@ impl PluginWorkspace for HostWorkspace { project.index.files_matching(root, matches, limit) } + fn symbols( + &self, + root: &Path, + query: &str, + limit: usize, + cancel: &CancellationToken, + deadline: Instant, + ) -> Result, HostError> { + let project = HostWorkspace::projects(self) + .into_iter() + .find(|p| p.root == root) + .ok_or_else(|| HostError::NotFound("the project is no longer open".into()))?; + let stopped = || cancel.is_cancelled() || Instant::now() >= deadline; + let interrupted = || { + if cancel.is_cancelled() { + HostError::Cancelled + } else { + HostError::Failed("symbol search timed out".into()) + } + }; + let symbols = project + .index + .find_symbols_until( + query, + ide_index::SymbolFilter::All, + limit.min(1000), + &|path| path.starts_with(root), + &stopped, + ) + .ok_or_else(interrupted)?; + let mut out = Vec::new(); + for s in symbols { + if stopped() { + return Err(interrupted()); + } + out.push(ide_plugin::SymbolInfo { + name: s.name, + kind: serde_json::to_value(s.kind) + .ok() + .and_then(|k| k.as_str().map(str::to_owned)) + .unwrap_or_else(|| "unknown".into()), + container: s.container, + location: ide_plugin::Location { path: s.path.0, range: s.range }, + }); + } + if stopped() { + return Err(interrupted()); + } + Ok(out) + } + fn document(&self, path: &Path) -> Option<(u64, String)> { let (project, doc) = self.open_document(path)?; let snap = project.documents.snapshot(doc).ok()?; diff --git a/crates/ide-host/tests/plugin_symbols.rs b/crates/ide-host/tests/plugin_symbols.rs new file mode 100644 index 000000000..9214a50e7 --- /dev/null +++ b/crates/ide-host/tests/plugin_symbols.rs @@ -0,0 +1,232 @@ +//! 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"), " bool + Sync), + stopped: &(impl Fn() -> bool + Sync), + ) -> Option> { + let recent = loop { + if stopped() { + return None; + } + if let Some(recent) = self.shared.recent.try_lock_for(Duration::from_millis(1)) { + break recent.clone(); + } + }; + let data = loop { + if stopped() { + return None; + } + if let Some(data) = self.shared.data.try_read_for(Duration::from_millis(1)) { + break data; + } + }; + matching::find_symbols_until( + &data, + &matching::Recents { paths: &recent }, + query, + filter, + limit, + include, + stopped, + ) + } + /// Symbols named exactly `name` (of `kind`, if given): `.card` selectors for `class="card"`. pub fn symbols_named(&self, name: &str, kind: Option, limit: usize) -> Vec { matching::symbols_named(&self.shared.data.read(), name, kind, limit) diff --git a/crates/ide-index/src/matching.rs b/crates/ide-index/src/matching.rs index dcb9b3839..6f5d27418 100644 --- a/crates/ide-index/src/matching.rs +++ b/crates/ide-index/src/matching.rs @@ -121,22 +121,35 @@ fn scan( tiebreak: &(impl Fn(&Cand, &Cand) -> Ordering + Sync), per_file: impl Fn(u32, &FileEntry, &mut Vec) + Sync, ) -> Vec { + scan_until(data, pool, tiebreak, &|| false, per_file).unwrap_or_default() +} + +fn scan_until( + data: &IndexData, + pool: usize, + tiebreak: &(impl Fn(&Cand, &Cand) -> Ordering + Sync), + stopped: &(impl Fn() -> bool + Sync), + per_file: impl Fn(u32, &FileEntry, &mut Vec) + Sync, +) -> Option> { data.files .par_iter() .enumerate() .with_min_len(256) - .fold(Vec::new, |mut acc, (slot, e)| { + .try_fold(Vec::new, |mut acc, (slot, e)| { + if stopped() { + return None; + } if let Some(e) = e { per_file(slot as u32, e, &mut acc); if acc.len() >= pool * 2 { acc = keep_best(acc, pool, tiebreak); } } - acc + Some(acc) }) - .reduce(Vec::new, |mut a, b| { + .try_reduce(Vec::new, |mut a, b| { a.extend(b); - if a.len() >= pool * 2 { keep_best(a, pool, tiebreak) } else { a } + Some(if a.len() >= pool * 2 { keep_best(a, pool, tiebreak) } else { a }) }) } @@ -423,9 +436,21 @@ pub(crate) fn find_symbols( filter: SymbolFilter, limit: usize, ) -> Vec { + find_symbols_until(data, recents, query, filter, limit, &|_| true, &|| false).unwrap_or_default() +} + +pub(crate) fn find_symbols_until( + data: &IndexData, + recents: &Recents, + query: &str, + filter: SymbolFilter, + limit: usize, + include: &(impl Fn(&Path) -> bool + Sync), + stopped: &(impl Fn() -> bool + Sync), +) -> Option> { let q: String = query.trim().chars().filter(|c| !c.is_whitespace()).collect(); if q.is_empty() || limit == 0 { - return Vec::new(); + return Some(Vec::new()); } let (container_q, name_q) = split_symbol_query(&q); let name = Needle::new(name_q); @@ -436,13 +461,16 @@ pub(crate) fn find_symbols( sa.name.len().cmp(&sb.name.len()).then_with(|| file(data, a.slot).rel.cmp(&file(data, b.slot).rel)) }; let pool = limit * 4 + 64; - let cands = scan(data, pool, &tiebreak, |slot, e, acc| { - if e.symbols.is_empty() { + let cands = scan_until(data, pool, &tiebreak, stopped, |slot, e, acc| { + if e.symbols.is_empty() || !include(&e.path) { return; } with_matcher(|m| { let mut buf = Vec::new(); for (i, s) in e.symbols.iter().enumerate() { + if stopped() { + break; + } if filter == SymbolFilter::Types && !s.kind.is_type_like() { continue; } @@ -451,11 +479,15 @@ pub(crate) fn find_symbols( } } }); - }); + })?; + if stopped() { + return None; + } let mut out: Vec<(u32, Cand, Vec)> = with_matcher(|m| { let mut buf = Vec::new(); cands .into_iter() + .take_while(|_| !stopped()) .filter_map(|c| { let s = sym(&c); let e = file(data, c.slot); @@ -472,7 +504,8 @@ pub(crate) fn find_symbols( b.0.cmp(&a.0).then_with(|| tiebreak(&a.1, &b.1)).then_with(|| sym(&a.1).range.cmp(&sym(&b.1).range)) }); out.truncate(limit); - out.into_iter() + let results = out + .into_iter() .map(|(score, c, indices)| { let e = file(data, c.slot); let s = &e.symbols[c.sym as usize]; @@ -487,7 +520,8 @@ pub(crate) fn find_symbols( indices, } }) - .collect() + .collect(); + (!stopped()).then_some(results) } /// Symbols named exactly `name` (and of `kind`, when given), by path then position. diff --git a/crates/ide-index/src/tests.rs b/crates/ide-index/src/tests.rs index 765f8cc17..cd89e2bc5 100644 --- a/crates/ide-index/src/tests.rs +++ b/crates/ide-index/src/tests.rs @@ -652,3 +652,80 @@ fn plugin_inventory_respects_excludes_and_symbols_require_matching_text() { assert!(symbols.iter().any(|s| s.name == "find")); assert!(index.symbols_for_text(&path, &java.replace("find", "renamed")).is_none()); } + +#[test] +fn symbol_search_can_stop_during_scanning_and_lock_waits() { + use std::sync::atomic::AtomicUsize; + let fx = sample(); + let idx = fx.open(); + let calls = AtomicUsize::new(0); + let stopped = || calls.fetch_add(1, Ordering::Relaxed) >= 5; + assert!(idx.find_symbols_until("a", SymbolFilter::All, 20, &|_| true, &stopped).is_none()); + assert!(calls.load(Ordering::Relaxed) > 5); + for recent in [false, true] { + let _data = (!recent).then(|| idx.shared.data.write()); + let _recent = recent.then(|| idx.shared.recent.lock()); + let deadline = std::time::Instant::now() + Duration::from_millis(10); + assert!( + idx.find_symbols_until("a", SymbolFilter::All, 20, &|_| true, &|| std::time::Instant::now() >= deadline) + .is_none() + ); + } +} + +#[test] +fn scoped_symbol_search_preserves_fuzzy_ranking_within_allowed_paths() { + let fx = sample(); + let idx = fx.open(); + let allowed = fx.root.join("src/order"); + let symbols = + idx.find_symbols_until("OCo", SymbolFilter::All, 1, &|path| path.starts_with(&allowed), &|| false).unwrap(); + assert_eq!(symbols.len(), 1); + assert_eq!(symbols[0].name, "OrderController"); + assert!( + idx.find_symbols_until("OtherCollection", SymbolFilter::All, 1, &|path| path.starts_with(&allowed), &|| false) + .unwrap() + .is_empty() + ); +} + +#[cfg(unix)] +#[test] +fn incremental_symbols_resolve_symlink_ancestors_before_scope_checks() { + let fx = Fixture::new(&[("src/inside.rs", "fn inside_symbol() {}\n")]); + let outside = fx._dir.path().join("outside"); + std::fs::create_dir(&outside).unwrap(); + std::fs::write(outside.join("secret.rs"), "fn secret_symbol() {}\n").unwrap(); + std::os::unix::fs::symlink(&outside, fx.root.join("escape")).unwrap(); + std::os::unix::fs::symlink(fx.root.join("src"), fx.root.join("alias")).unwrap(); + let idx = fx.open(); + idx.update(&[fx.root.join("escape/secret.rs"), fx.root.join("alias/inside.rs")], &[]); + assert!(idx.wait_idle(WAIT)); + assert!(symbols(&idx, "secret_symbol", SymbolFilter::All).is_empty()); + let inside = idx.find_symbols("inside_symbol", SymbolFilter::All, 10); + assert_eq!(inside.len(), 1); + assert_eq!(inside[0].path.0, fx.root.join("src/inside.rs")); +} + +#[cfg(unix)] +#[test] +fn persisted_symbols_do_not_load_through_escaping_symlink_ancestors() { + let fx = Fixture::new(&[("src/old.rs", "fn old_symbol() {}\n")]); + drop(fx.open()); + let outside = fx._dir.path().join("outside"); + std::fs::rename(fx.root.join("src"), &outside).unwrap(); + std::os::unix::fs::symlink(outside, fx.root.join("src")).unwrap(); + let initial = Arc::new(Mutex::new(Vec::new())); + let events = initial.clone(); + let sink: SharedSink = Arc::new(move |event: IdeEvent| { + if let IdeEvent::IndexState { symbols, .. } = event { + events.lock().push(symbols); + } + }); + let languages = Arc::new(LanguageRegistry::load(None).unwrap()); + let idx = ProjectIndex::open(&fx.root, &fx.cache, Vec::new(), Vec::new(), languages, Arc::new(Syntax::new()), sink) + .unwrap(); + assert!(idx.wait_idle(WAIT)); + assert!(symbols(&idx, "old_symbol", SymbolFilter::All).is_empty()); + assert_eq!(initial.lock()[0], 0, "unsafe cached symbols must never be published"); +} diff --git a/crates/ide-index/src/worker.rs b/crates/ide-index/src/worker.rs index 3928edd42..79453206c 100644 --- a/crates/ide-index/src/worker.rs +++ b/crates/ide-index/src/worker.rs @@ -174,7 +174,10 @@ impl Shared { Ok(records) => { let entries: Vec = records .into_par_iter() - .filter(|(rel, _)| !filter.is_excluded(&root.join(rel), false)) + .filter(|(rel, _)| { + let path = root.join(rel); + !filter.is_excluded(&path, false) && dunce::canonicalize(&path).is_ok_and(|real| real == path) + }) .map(|(rel, rec)| FileEntry::new(root.join(&rel), rel, rec)) .collect(); let mut d = self.data.write(); @@ -420,7 +423,10 @@ impl Shared { let mut seen = HashSet::new(); let mut by_dir: HashMap> = HashMap::new(); for path in changed { - if !seen.insert(path.clone()) { + // A watcher path may have a symlink ancestor even when its spelling + // is inside the root. Resolve it on the worker before reading it. + let Ok(path) = dunce::canonicalize(&path) else { continue }; + if !filter.contains(&path) || !seen.insert(path.clone()) { continue; } let Ok(meta) = std::fs::symlink_metadata(&path) else { continue }; diff --git a/crates/ide-plugin/src/runtime.rs b/crates/ide-plugin/src/runtime.rs index baa2b4c53..8c075a7c8 100644 --- a/crates/ide-plugin/src/runtime.rs +++ b/crates/ide-plugin/src/runtime.rs @@ -122,6 +122,19 @@ pub trait HostApi: Send + Sync { .filter(|root| real.starts_with(crate::resolve_real(root))) .max_by_key(|root| root.components().count()) } + + /// Searches the current in-memory index of an open project. + fn symbols( + &self, + root: &Path, + query: &str, + limit: usize, + cancel: &CancellationToken, + deadline: Instant, + ) -> Result, HostError> { + let _ = (root, query, limit, cancel, deadline); + Err(HostError::NotFound("no project index is available".into())) + } /// Unsaved text of an open editor's file. fn buffer_text(&self, path: &Path) -> Option; /// Version and text of the open document at `path`. diff --git a/crates/ide-plugin/src/types.rs b/crates/ide-plugin/src/types.rs index 5e518edbb..e54b56b0e 100644 --- a/crates/ide-plugin/src/types.rs +++ b/crates/ide-plugin/src/types.rs @@ -433,6 +433,15 @@ pub struct Location { pub range: ide_core::Range, } +/// A symbol in the current project index. Ranges use UTF-16 columns. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct SymbolInfo { + pub name: String, + pub kind: String, + pub container: Option, + pub location: Location, +} + #[derive(Clone, Debug, PartialEq, Eq)] pub struct CompletionItem { pub label: String, diff --git a/crates/ide-plugin/src/wit_host.rs b/crates/ide-plugin/src/wit_host.rs index d1c69a1fa..a0f080273 100644 --- a/crates/ide-plugin/src/wit_host.rs +++ b/crates/ide-plugin/src/wit_host.rs @@ -511,6 +511,39 @@ impl workspace::Host for StoreData { self.host.project_of(Path::new(&path)).map(|root| self.push(ProjectRes { root })).transpose() } + fn symbols( + &mut self, + project: Resource, + query: String, + limit: u32, + ) -> Fallible> { + if let Err(e) = self.require(Capability::ProjectRead) { + return Ok(Err(e)); + } + if query.len() > 256 { + return Ok(Err(wit::Error::Invalid("symbol query exceeds 256 UTF-8 bytes".into()))); + } + let root = &self.table.get(&project)?.root; + Ok(self + .host + .symbols(root, &query, limit.min(1000) as usize, &self.cancel.token(), self.deadline) + .map(|symbols| { + symbols + .into_iter() + .map(|s| workspace::SymbolInfo { + name: s.name, + kind: s.kind, + container: s.container, + location: wit::Location { + path: to_ipc(&s.location.path), + range: range_to_wit(&s.location.range), + }, + }) + .collect() + }) + .map_err(to_wit)) + } + fn projects(&mut self) -> Fallible>> { if let Err(e) = self.require(Capability::ProjectRead) { return Ok(Err(e)); diff --git a/crates/ide-plugin/tests/api_compat.rs b/crates/ide-plugin/tests/api_compat.rs index 4ed1461aa..9f27c96a1 100644 --- a/crates/ide-plugin/tests/api_compat.rs +++ b/crates/ide-plugin/tests/api_compat.rs @@ -31,6 +31,7 @@ fn components() -> Vec { repo().join("crates/ide-plugin/tests/fixtures/semantic-tokens/dist/plugin.wasm"), repo().join("crates/ide-plugin/tests/fixtures/hover/dist/plugin.wasm"), repo().join("crates/ide-plugin/tests/fixtures/inlay-hints/dist/plugin.wasm"), + repo().join("crates/ide-plugin/tests/fixtures/symbols/dist/plugin.wasm"), ]; let official = repo().join("plugins/official"); let mut dirs: Vec<_> = std::fs::read_dir(&official).unwrap().map(|e| e.unwrap().path()).collect(); diff --git a/crates/ide-plugin/tests/fixtures/commit/dist/plugin.wasm b/crates/ide-plugin/tests/fixtures/commit/dist/plugin.wasm index 30247799c..87f0133bf 100644 Binary files a/crates/ide-plugin/tests/fixtures/commit/dist/plugin.wasm and b/crates/ide-plugin/tests/fixtures/commit/dist/plugin.wasm differ diff --git a/crates/ide-plugin/tests/fixtures/commit/dist/plugin.wasm.sources b/crates/ide-plugin/tests/fixtures/commit/dist/plugin.wasm.sources index 003aeb1b8..59aeb5604 100644 --- a/crates/ide-plugin/tests/fixtures/commit/dist/plugin.wasm.sources +++ b/crates/ide-plugin/tests/fixtures/commit/dist/plugin.wasm.sources @@ -1 +1 @@ -13db3ba9045429fef5973e83075a51a315076408a03ec3c1b837c0229365fcbe +3f444f1cc3b7f495f26378594fd9f98418146cd8b2275310e53d4905d0bb5c30 diff --git a/crates/ide-plugin/tests/fixtures/endpoints/dist/plugin.wasm b/crates/ide-plugin/tests/fixtures/endpoints/dist/plugin.wasm index b09c15c53..4e4f1e858 100644 Binary files a/crates/ide-plugin/tests/fixtures/endpoints/dist/plugin.wasm and b/crates/ide-plugin/tests/fixtures/endpoints/dist/plugin.wasm differ diff --git a/crates/ide-plugin/tests/fixtures/endpoints/dist/plugin.wasm.sources b/crates/ide-plugin/tests/fixtures/endpoints/dist/plugin.wasm.sources index 99bb14143..2a197f645 100644 --- a/crates/ide-plugin/tests/fixtures/endpoints/dist/plugin.wasm.sources +++ b/crates/ide-plugin/tests/fixtures/endpoints/dist/plugin.wasm.sources @@ -1 +1 @@ -6c2a59bf732acebd74ad4d05824be3aafbd501964be01205ca1e0d5a6135d113 +26ae74bb9df6f88bca87a31d20fb00d52cd9464047df3525d0e47b5bf6b2b2ca diff --git a/crates/ide-plugin/tests/fixtures/file-templates/dist/plugin.wasm b/crates/ide-plugin/tests/fixtures/file-templates/dist/plugin.wasm index 84917b1f0..ca73c072e 100644 Binary files a/crates/ide-plugin/tests/fixtures/file-templates/dist/plugin.wasm and b/crates/ide-plugin/tests/fixtures/file-templates/dist/plugin.wasm differ diff --git a/crates/ide-plugin/tests/fixtures/file-templates/dist/plugin.wasm.sources b/crates/ide-plugin/tests/fixtures/file-templates/dist/plugin.wasm.sources index 2b148fec1..436aa5dc3 100644 --- a/crates/ide-plugin/tests/fixtures/file-templates/dist/plugin.wasm.sources +++ b/crates/ide-plugin/tests/fixtures/file-templates/dist/plugin.wasm.sources @@ -1 +1 @@ -183bf50a6ee479128440ef4bd86939f24b036b699a24842a6efd1d3f43c6cd10 +24b3c81c74355174aa2fd484677bbac38020ecd119aff419d0479a942e2e4dbd diff --git a/crates/ide-plugin/tests/fixtures/grouped-views/dist/plugin.wasm b/crates/ide-plugin/tests/fixtures/grouped-views/dist/plugin.wasm index f5fc8e5d9..a679243d7 100644 Binary files a/crates/ide-plugin/tests/fixtures/grouped-views/dist/plugin.wasm and b/crates/ide-plugin/tests/fixtures/grouped-views/dist/plugin.wasm differ diff --git a/crates/ide-plugin/tests/fixtures/grouped-views/dist/plugin.wasm.sources b/crates/ide-plugin/tests/fixtures/grouped-views/dist/plugin.wasm.sources index a49e9bfbe..768e2239e 100644 --- a/crates/ide-plugin/tests/fixtures/grouped-views/dist/plugin.wasm.sources +++ b/crates/ide-plugin/tests/fixtures/grouped-views/dist/plugin.wasm.sources @@ -1 +1 @@ -97f7f6fe965e95510d5556feb40329c312e5430e631479be6c9646046a888478 +0bb069928979ba22a2d6bd977108d905ff870ef981aad6ef4930834ad5c35c35 diff --git a/crates/ide-plugin/tests/fixtures/hello/dist/plugin.wasm b/crates/ide-plugin/tests/fixtures/hello/dist/plugin.wasm index aa239bbee..3e720c89d 100644 Binary files a/crates/ide-plugin/tests/fixtures/hello/dist/plugin.wasm and b/crates/ide-plugin/tests/fixtures/hello/dist/plugin.wasm differ diff --git a/crates/ide-plugin/tests/fixtures/hello/dist/plugin.wasm.sources b/crates/ide-plugin/tests/fixtures/hello/dist/plugin.wasm.sources index d71a97246..42768ddd6 100644 --- a/crates/ide-plugin/tests/fixtures/hello/dist/plugin.wasm.sources +++ b/crates/ide-plugin/tests/fixtures/hello/dist/plugin.wasm.sources @@ -1 +1 @@ -f73de3fdfe5b343f26ff4dd1ae150c975273ce398d518f6f2100b0baa8074ffc +da7c5e58d2d26b7613a8ba5b50d4ec7788438f186b4abcfda90a9bf0c34ab591 diff --git a/crates/ide-plugin/tests/fixtures/hover/dist/plugin.wasm b/crates/ide-plugin/tests/fixtures/hover/dist/plugin.wasm index 37642818a..b6e97341e 100644 Binary files a/crates/ide-plugin/tests/fixtures/hover/dist/plugin.wasm and b/crates/ide-plugin/tests/fixtures/hover/dist/plugin.wasm differ diff --git a/crates/ide-plugin/tests/fixtures/hover/dist/plugin.wasm.sources b/crates/ide-plugin/tests/fixtures/hover/dist/plugin.wasm.sources index be93a9417..59d8676a5 100644 --- a/crates/ide-plugin/tests/fixtures/hover/dist/plugin.wasm.sources +++ b/crates/ide-plugin/tests/fixtures/hover/dist/plugin.wasm.sources @@ -1 +1 @@ -a985162e44066c6bf6c93d01af1b01d40d305c6be43072d4f33d4f1f19a4dee0 +65583ded79c9fb4de0ed856d04aa08e58c69368d3a9235a57afa50ea757eb45f diff --git a/crates/ide-plugin/tests/fixtures/indexer/dist/plugin.wasm b/crates/ide-plugin/tests/fixtures/indexer/dist/plugin.wasm index f70d375de..fb9ca9d87 100644 Binary files a/crates/ide-plugin/tests/fixtures/indexer/dist/plugin.wasm and b/crates/ide-plugin/tests/fixtures/indexer/dist/plugin.wasm differ diff --git a/crates/ide-plugin/tests/fixtures/indexer/dist/plugin.wasm.sources b/crates/ide-plugin/tests/fixtures/indexer/dist/plugin.wasm.sources index 03c382e9a..7b1eece32 100644 --- a/crates/ide-plugin/tests/fixtures/indexer/dist/plugin.wasm.sources +++ b/crates/ide-plugin/tests/fixtures/indexer/dist/plugin.wasm.sources @@ -1 +1 @@ -5063a997e11258386e88ba129d42262f5050bfba41c8ea069152b2dfba007fa7 +0c0e4f3611971307268cc8e2cc6fb96734923f10dbccbd8950639d86036c682c diff --git a/crates/ide-plugin/tests/fixtures/inlay-hints/dist/plugin.wasm b/crates/ide-plugin/tests/fixtures/inlay-hints/dist/plugin.wasm index a55184ae9..365ad9036 100644 Binary files a/crates/ide-plugin/tests/fixtures/inlay-hints/dist/plugin.wasm and b/crates/ide-plugin/tests/fixtures/inlay-hints/dist/plugin.wasm differ diff --git a/crates/ide-plugin/tests/fixtures/inlay-hints/dist/plugin.wasm.sources b/crates/ide-plugin/tests/fixtures/inlay-hints/dist/plugin.wasm.sources index 951d0bc92..a951825a6 100644 --- a/crates/ide-plugin/tests/fixtures/inlay-hints/dist/plugin.wasm.sources +++ b/crates/ide-plugin/tests/fixtures/inlay-hints/dist/plugin.wasm.sources @@ -1 +1 @@ -9a3d097559f6685e82024bd84cb0396875762b7136636fdc9bd11cb41875373a +c09efb8c9d0be262b7a25099639213e304fbfd5b0b91ad3d80672fb8148c0e4a diff --git a/crates/ide-plugin/tests/fixtures/jobs/dist/plugin.wasm b/crates/ide-plugin/tests/fixtures/jobs/dist/plugin.wasm index 640b15a49..261df6af6 100644 Binary files a/crates/ide-plugin/tests/fixtures/jobs/dist/plugin.wasm and b/crates/ide-plugin/tests/fixtures/jobs/dist/plugin.wasm differ diff --git a/crates/ide-plugin/tests/fixtures/jobs/dist/plugin.wasm.sources b/crates/ide-plugin/tests/fixtures/jobs/dist/plugin.wasm.sources index d2a7ba675..174a21ba1 100644 --- a/crates/ide-plugin/tests/fixtures/jobs/dist/plugin.wasm.sources +++ b/crates/ide-plugin/tests/fixtures/jobs/dist/plugin.wasm.sources @@ -1 +1 @@ -6ca0c03e31e0cd67192bb86c93b1feb294fec79cf432ceb57b9cdc3e42eb6b02 +7c728e98b1e1d4ea3d03831c263298fc4792599a81138e40dfd4c48ce6c4a47e diff --git a/crates/ide-plugin/tests/fixtures/misbehave/dist/plugin.wasm.sources b/crates/ide-plugin/tests/fixtures/misbehave/dist/plugin.wasm.sources index b05ba59a6..0bc51a89c 100644 --- a/crates/ide-plugin/tests/fixtures/misbehave/dist/plugin.wasm.sources +++ b/crates/ide-plugin/tests/fixtures/misbehave/dist/plugin.wasm.sources @@ -1 +1 @@ -a3b813b172441c5fad462b249c69dadda2b8fb1e2b387bda2487f7df3f729338 +d8626558aba036e4d476df9929c6e16089dd0a1e43101b48dd978946889c7616 diff --git a/crates/ide-plugin/tests/fixtures/path-events/dist/plugin.wasm b/crates/ide-plugin/tests/fixtures/path-events/dist/plugin.wasm index 38d968683..afd0a867f 100644 Binary files a/crates/ide-plugin/tests/fixtures/path-events/dist/plugin.wasm and b/crates/ide-plugin/tests/fixtures/path-events/dist/plugin.wasm differ diff --git a/crates/ide-plugin/tests/fixtures/path-events/dist/plugin.wasm.sources b/crates/ide-plugin/tests/fixtures/path-events/dist/plugin.wasm.sources index 237f52d5c..5da119f95 100644 --- a/crates/ide-plugin/tests/fixtures/path-events/dist/plugin.wasm.sources +++ b/crates/ide-plugin/tests/fixtures/path-events/dist/plugin.wasm.sources @@ -1 +1 @@ -848356d2e0fbaef341436ea70a4698915cce705d3ccea4dd4e50900b12c97d33 +512b7d41868034322fccc15f100c85e9f53335c22aec4d7492a14723b54cd874 diff --git a/crates/ide-plugin/tests/fixtures/semantic-tokens/dist/plugin.wasm b/crates/ide-plugin/tests/fixtures/semantic-tokens/dist/plugin.wasm index 27c78ab8b..bf350cf23 100644 Binary files a/crates/ide-plugin/tests/fixtures/semantic-tokens/dist/plugin.wasm and b/crates/ide-plugin/tests/fixtures/semantic-tokens/dist/plugin.wasm differ diff --git a/crates/ide-plugin/tests/fixtures/semantic-tokens/dist/plugin.wasm.sources b/crates/ide-plugin/tests/fixtures/semantic-tokens/dist/plugin.wasm.sources index bfe0396ca..c4f8084f6 100644 --- a/crates/ide-plugin/tests/fixtures/semantic-tokens/dist/plugin.wasm.sources +++ b/crates/ide-plugin/tests/fixtures/semantic-tokens/dist/plugin.wasm.sources @@ -1 +1 @@ -07001ff22cd33d5ae6f96b584707e66d31f2f93423cf72a993e6f11c9f0ec319 +de7a217d3931c62387718b231a2c895438a210cc2e2b2cbb374312a1cf86a68d diff --git a/crates/ide-plugin/tests/fixtures/storage/dist/plugin.wasm b/crates/ide-plugin/tests/fixtures/storage/dist/plugin.wasm index 9c2511c36..b76eea9cc 100644 Binary files a/crates/ide-plugin/tests/fixtures/storage/dist/plugin.wasm and b/crates/ide-plugin/tests/fixtures/storage/dist/plugin.wasm differ diff --git a/crates/ide-plugin/tests/fixtures/storage/dist/plugin.wasm.sources b/crates/ide-plugin/tests/fixtures/storage/dist/plugin.wasm.sources index 8ffef6179..21294bdc2 100644 --- a/crates/ide-plugin/tests/fixtures/storage/dist/plugin.wasm.sources +++ b/crates/ide-plugin/tests/fixtures/storage/dist/plugin.wasm.sources @@ -1 +1 @@ -ae9cb4a106aa8fd46662dba07acd0daba2927b7fe53e544cd95348954505bc36 +f76dd5cbd131af57c371c2f44146274426731a3e9609cc6f5aaabbacaecb17a7 diff --git a/crates/ide-plugin/tests/fixtures/symbols/Cargo.lock b/crates/ide-plugin/tests/fixtures/symbols/Cargo.lock new file mode 100644 index 000000000..2ea6e3f00 --- /dev/null +++ b/crates/ide-plugin/tests/fixtures/symbols/Cargo.lock @@ -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-test-symbols" +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" diff --git a/crates/ide-plugin/tests/fixtures/symbols/Cargo.toml b/crates/ide-plugin/tests/fixtures/symbols/Cargo.toml new file mode 100644 index 000000000..2e76f7c85 --- /dev/null +++ b/crates/ide-plugin/tests/fixtures/symbols/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "openidea-test-symbols" +version = "0.1.0" +edition = "2024" +publish = false + +[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" diff --git a/crates/ide-plugin/tests/fixtures/symbols/dist/plugin.wasm b/crates/ide-plugin/tests/fixtures/symbols/dist/plugin.wasm new file mode 100644 index 000000000..6d2c5e8bc Binary files /dev/null and b/crates/ide-plugin/tests/fixtures/symbols/dist/plugin.wasm differ diff --git a/crates/ide-plugin/tests/fixtures/symbols/dist/plugin.wasm.sources b/crates/ide-plugin/tests/fixtures/symbols/dist/plugin.wasm.sources new file mode 100644 index 000000000..88a446fad --- /dev/null +++ b/crates/ide-plugin/tests/fixtures/symbols/dist/plugin.wasm.sources @@ -0,0 +1 @@ +ffe6e931db4d0232005de934fce7090ae9098e9cdfd1f9e8b7f6243abe3d3ae9 diff --git a/crates/ide-plugin/tests/fixtures/symbols/plugin.toml b/crates/ide-plugin/tests/fixtures/symbols/plugin.toml new file mode 100644 index 000000000..801ba982a --- /dev/null +++ b/crates/ide-plugin/tests/fixtures/symbols/plugin.toml @@ -0,0 +1,23 @@ +id = "test.symbols" +name = "Indexed symbols fixture" +version = "0.1.0" +engines.openidea = "^1.3" +wasm = "dist/plugin.wasm" +[capabilities] +required = ["project.read"] + +[[contributes.providers]] +kinds = ["definition"] +languages = ["php", "java"] + +[[contributes.commands]] +id = "symbols" +title = "Query indexed symbols" + +[[contributes.commands]] +id = "remember" +title = "Remember project" + +[[contributes.commands]] +id = "remembered" +title = "Query remembered project" diff --git a/crates/ide-plugin/tests/fixtures/symbols/src/lib.rs b/crates/ide-plugin/tests/fixtures/symbols/src/lib.rs new file mode 100644 index 000000000..226c5e15b --- /dev/null +++ b/crates/ide-plugin/tests/fixtures/symbols/src/lib.rs @@ -0,0 +1,55 @@ +use openidea_plugin::host::{self, Project}; +use openidea_plugin::providers::{Document, Providers}; +use openidea_plugin::{Error, Invocation, Location, Plugin, Position, register_plugin}; + +#[derive(Default)] +struct Fixture { + project: Option, +} + +impl Plugin for Fixture { + fn run_command(&mut self, id: &str, ctx: &Invocation, arg: Option<&str>) -> Result, Error> { + if id == "remember" { + self.project = ctx.project(); + return Ok(None); + } + let current = ctx.project(); + let project = if id == "remembered" { self.project.as_ref() } else { current.as_ref() } + .ok_or_else(|| Error::NotFound("no project".into()))?; + let (query, limit) = arg.unwrap_or("Order|10").rsplit_once('|').ok_or("expected query|limit")?; + let limit = limit.parse().map_err(|_| Error::Invalid("invalid limit".into()))?; + let symbols = project.symbols(query, limit)?; + Ok(Some( + symbols + .into_iter() + .map(|s| { + format!( + "{}\t{}\t{}\t{}\t{}:{}-{}:{}", + s.name, + s.kind, + s.container.unwrap_or_default(), + s.location.path, + s.location.range.start.line, + s.location.range.start.character, + s.location.range.end.line, + s.location.range.end.character + ) + }) + .collect::>() + .join("\n"), + )) + } +} + +impl Providers for Fixture { + fn definition(&mut self, doc: &Document, _: Position) -> Vec { + host::project_of(&doc.path) + .and_then(|project| project.symbols(doc.text.trim(), 10).ok()) + .unwrap_or_default() + .into_iter() + .map(|s| s.location) + .collect() + } +} + +register_plugin!(Fixture, providers); diff --git a/crates/ide-plugin/tests/fixtures/tables/dist/plugin.wasm b/crates/ide-plugin/tests/fixtures/tables/dist/plugin.wasm index fc5b069fe..84391bcc6 100644 Binary files a/crates/ide-plugin/tests/fixtures/tables/dist/plugin.wasm and b/crates/ide-plugin/tests/fixtures/tables/dist/plugin.wasm differ diff --git a/crates/ide-plugin/tests/fixtures/tables/dist/plugin.wasm.sources b/crates/ide-plugin/tests/fixtures/tables/dist/plugin.wasm.sources index 8830ecac1..33c5cbdbd 100644 --- a/crates/ide-plugin/tests/fixtures/tables/dist/plugin.wasm.sources +++ b/crates/ide-plugin/tests/fixtures/tables/dist/plugin.wasm.sources @@ -1 +1 @@ -8a79f2899d88773d7c33175136960727b5ad0aefc5ce5eb363fd20dc4990c60e +8d42a25cd250041ceb6aaafed66ac0bd38ff1717cc450d6175517cd3d01738c9 diff --git a/plugins/README.md b/plugins/README.md index b929d38a1..a9b2c0ca0 100644 --- a/plugins/README.md +++ b/plugins/README.md @@ -281,7 +281,7 @@ machine. Users can revoke optional capabilities in Settings › Plugins. | Capability | Risk | Unlocks today | | ------------------- | ------ | ---------------------------------------------------------------------------------------------------------------------------- | -| `project.read` | low | `Project::root`, `read_file`, `find_files`, `host::projects`, `publish_diagnostics` | +| `project.read` | low | `Project::root`, `read_file`, `find_files`, `symbols`, `host::projects`, `publish_diagnostics` | | `project.write` | medium | `Project::write_file` — creates and replaces text files inside the open projects | | `fs.read:` | high | `host::fs::read_file`, `read_bytes`, `stat`, `read_dir` — files outside the projects, under the grant's directory | | `fs.write:` | high | `host::fs::write_file`, `write_bytes` — files under that path; doesn't imply reading | @@ -439,6 +439,7 @@ then run `openidea-plugin check`. | `host::locale()` | `ui.locale` | The IDE's UI language as a BCP 47 tag (`en`, `fr`, `zh-CN`); no capability. See _Translations_. | | `host::log(level, msg)` | `log` | IDE log, tagged with the plugin id. `eprintln!` output is logged too and shown when the plugin crashes. | | `Project::find_files(glob, limit)` | `workspace.project.find-files` | Project-relative glob (`**/*Mapper.xml`); honours `.gitignore`; at most `limit` paths. | +| `Project::symbols(query, limit)` | `workspace.symbols` | New in 1.3; `project.read`. Fuzzy in-memory search (no LSP): name, camelCase kind, container and UTF-16 location. Queries ≤ 256 UTF-8 bytes; limits capped at 1000. Snapshot notes below. | | `host::publish_diagnostics(path, items)` | `diagnostics.publish` | Replaces this plugin's problems for the file (editor squiggles and the Problems view); an empty list clears them. | | `host::fetch(request)` → `{status, headers, body}` | `net.fetch` | See _Network_. | | `Project::run_process(program, args, stdin)` → `{code, stdout, stderr}` | `process.run` | Only the pinned program of a `process:` grant; runs in the project root of a trusted project; 4 MiB of output; killed when the call is cancelled or times out. | @@ -451,6 +452,20 @@ then run `openidea-plugin check`. | `host::open_location(path, line, column)` | `views.open-location` | Opens a file inside the project in an editor of the invoking window, the caret at `line`/`column` (0-based, UTF-16): a view's links to code. Needs `project.read`. | | `host::setting(key)`, `setting_bool`, `setting_string`, `setting_number` | `settings.get` | The user's value (project over global) or the schema's default. | +`Project::symbols` reads the current in-memory index: results may be partial +while indexing, and locations can lag unsaved edits. Normal saves and filesystem +changes update the index in the background. Results are confined to the project's +root, with normal exclusions; external content roots and updates through escaping +symlinks are excluded. Search honours the current call's cancellation and deadline. +Empty queries and zero limits return no symbols; a closed project returns +`Error::NotFound`. + +Type resolution and member queries remain deferred to the asynchronous API 2.0 +model. Providers can select their project with `host::project_of(&doc.path)` and +call `symbols` synchronously; `crates/ide-plugin/tests/fixtures/symbols` demonstrates +a definition provider. `MockHost::set_symbols(root, symbols)` replaces a test +snapshot; its mock uses case-insensitive substring matching rather than fuzzy ranking. + Both process APIs only add environment variables. A key already in the prepared child environment (including the login-shell `PATH`) fails with an error naming the key; variables are never replaced or removed. Names compare case-insensitively on Windows. diff --git a/plugins/official/ai-completion/dist/plugin.wasm b/plugins/official/ai-completion/dist/plugin.wasm index 45092d655..c31940bed 100644 Binary files a/plugins/official/ai-completion/dist/plugin.wasm and b/plugins/official/ai-completion/dist/plugin.wasm differ diff --git a/plugins/official/ai-completion/dist/plugin.wasm.sources b/plugins/official/ai-completion/dist/plugin.wasm.sources index 3f2de9839..dd5363f3b 100644 --- a/plugins/official/ai-completion/dist/plugin.wasm.sources +++ b/plugins/official/ai-completion/dist/plugin.wasm.sources @@ -1 +1 @@ -7872480dee517db617120a9327d3bbbcc007d31bbf9e6c999ce202f38f0ed478 +977835f3364613876b3f563877d7f0cf36d668226c5c6ac2280473c73d44117d diff --git a/plugins/official/checkstyle/dist/plugin.wasm b/plugins/official/checkstyle/dist/plugin.wasm index 5d8d7d446..77b26d71a 100644 Binary files a/plugins/official/checkstyle/dist/plugin.wasm and b/plugins/official/checkstyle/dist/plugin.wasm differ diff --git a/plugins/official/checkstyle/dist/plugin.wasm.sources b/plugins/official/checkstyle/dist/plugin.wasm.sources index 2e7d19917..ae1a5e838 100644 --- a/plugins/official/checkstyle/dist/plugin.wasm.sources +++ b/plugins/official/checkstyle/dist/plugin.wasm.sources @@ -1 +1 @@ -31f1d88c7e88553eed145fd170485c10e19967eccbf2021ec71fca862d05e952 +55e3306fc2ff9456d79f02b63712c7f83b1fe4e75fec051f5fb96e7fb766066f diff --git a/plugins/official/csv-editor/dist/plugin.wasm b/plugins/official/csv-editor/dist/plugin.wasm index f631fc1d8..dedb8a9b2 100644 Binary files a/plugins/official/csv-editor/dist/plugin.wasm and b/plugins/official/csv-editor/dist/plugin.wasm differ diff --git a/plugins/official/csv-editor/dist/plugin.wasm.sources b/plugins/official/csv-editor/dist/plugin.wasm.sources index 69914ed1b..354594309 100644 --- a/plugins/official/csv-editor/dist/plugin.wasm.sources +++ b/plugins/official/csv-editor/dist/plugin.wasm.sources @@ -1 +1 @@ -6a2db880c49b15d7ce5ba7fda69ac8d1cac04f0412d42b6922550594ef94a7c5 +f582b640e980e4b69e292ec425d0ed8113527119d18d0fa1ea73e7c34f1f89a1 diff --git a/plugins/official/git-toolbox/dist/plugin.wasm b/plugins/official/git-toolbox/dist/plugin.wasm index bfe8a222e..6ab24411e 100644 Binary files a/plugins/official/git-toolbox/dist/plugin.wasm and b/plugins/official/git-toolbox/dist/plugin.wasm differ diff --git a/plugins/official/git-toolbox/dist/plugin.wasm.sources b/plugins/official/git-toolbox/dist/plugin.wasm.sources index a5f6a09d1..cb43fe6a6 100644 --- a/plugins/official/git-toolbox/dist/plugin.wasm.sources +++ b/plugins/official/git-toolbox/dist/plugin.wasm.sources @@ -1 +1 @@ -9299f10c517ee5582312f0f14871e21d36050ee8d621df31d7e6a4126c4ea37e +842de029af0767864b56dc71199a823ca0ea04aec5ece8928dac69340ff7ab7a diff --git a/plugins/official/graphviz/dist/plugin.wasm b/plugins/official/graphviz/dist/plugin.wasm index bc64f8f58..41c2e4a27 100644 Binary files a/plugins/official/graphviz/dist/plugin.wasm and b/plugins/official/graphviz/dist/plugin.wasm differ diff --git a/plugins/official/graphviz/dist/plugin.wasm.sources b/plugins/official/graphviz/dist/plugin.wasm.sources index 19c15d609..2f09b562d 100644 --- a/plugins/official/graphviz/dist/plugin.wasm.sources +++ b/plugins/official/graphviz/dist/plugin.wasm.sources @@ -1 +1 @@ -1f86f15f5e864ec9a91cb31379047eafd4ae8c07ffacb5e1c85955ee07873342 +1dd92a9e139458813f513479cbc0f8187eff217f5dac2b39441537e3039e9dca diff --git a/plugins/official/key-promoter/dist/plugin.wasm b/plugins/official/key-promoter/dist/plugin.wasm index 7de6bde14..ad22fcdbf 100644 Binary files a/plugins/official/key-promoter/dist/plugin.wasm and b/plugins/official/key-promoter/dist/plugin.wasm differ diff --git a/plugins/official/key-promoter/dist/plugin.wasm.sources b/plugins/official/key-promoter/dist/plugin.wasm.sources index 9c0e26954..696c79edd 100644 --- a/plugins/official/key-promoter/dist/plugin.wasm.sources +++ b/plugins/official/key-promoter/dist/plugin.wasm.sources @@ -1 +1 @@ -540e60e34b752eb201ef9cacea44066b4f59a4e36c3d1a468bf208223569a628 +5ce56abe0082fc610deec13f6907cdfd8be7346ce9a1c43c59e1828957bc3274 diff --git a/plugins/official/license-header/dist/plugin.wasm b/plugins/official/license-header/dist/plugin.wasm index 840128652..3f845c15d 100644 Binary files a/plugins/official/license-header/dist/plugin.wasm and b/plugins/official/license-header/dist/plugin.wasm differ diff --git a/plugins/official/license-header/dist/plugin.wasm.sources b/plugins/official/license-header/dist/plugin.wasm.sources index d2e5393cc..8fbbf7217 100644 --- a/plugins/official/license-header/dist/plugin.wasm.sources +++ b/plugins/official/license-header/dist/plugin.wasm.sources @@ -1 +1 @@ -106f4a0b300fdb98fd0bd8f8bc1b3ce2c692a8a7e0107353905d7edbb7c7ce4d +be6dd0b46a0f407f354ae2b67c6304948c71749bc3587dcf81b39dd245afdb48 diff --git a/plugins/official/mybatis/dist/plugin.wasm b/plugins/official/mybatis/dist/plugin.wasm index a6aa869df..1a4045534 100644 Binary files a/plugins/official/mybatis/dist/plugin.wasm and b/plugins/official/mybatis/dist/plugin.wasm differ diff --git a/plugins/official/mybatis/dist/plugin.wasm.sources b/plugins/official/mybatis/dist/plugin.wasm.sources index c26644797..8d0252556 100644 --- a/plugins/official/mybatis/dist/plugin.wasm.sources +++ b/plugins/official/mybatis/dist/plugin.wasm.sources @@ -1 +1 @@ -eba583c669da994dba24a8a16546c9d2cb50fb46db53207207809b20882680e8 +4fe7aec43c3009cd0567e93f2e80c135091269aa0a2fcea3ba00f43a24c5ec56 diff --git a/plugins/official/regex-tester/dist/plugin.wasm b/plugins/official/regex-tester/dist/plugin.wasm index 566e1ebb8..fdf752807 100644 Binary files a/plugins/official/regex-tester/dist/plugin.wasm and b/plugins/official/regex-tester/dist/plugin.wasm differ diff --git a/plugins/official/regex-tester/dist/plugin.wasm.sources b/plugins/official/regex-tester/dist/plugin.wasm.sources index 09ac175c4..b89dabe08 100644 --- a/plugins/official/regex-tester/dist/plugin.wasm.sources +++ b/plugins/official/regex-tester/dist/plugin.wasm.sources @@ -1 +1 @@ -d1385eb9aa4d78bbcbdf98565fdf3b550050388af9248814f5c994b54d605708 +7be4ce274fef38b5d4741646de970afd803fbf5ccffaa49236af1d15c8c870a9 diff --git a/plugins/official/solidity/dist/plugin.wasm b/plugins/official/solidity/dist/plugin.wasm index e6210f2ad..028fcbd86 100644 Binary files a/plugins/official/solidity/dist/plugin.wasm and b/plugins/official/solidity/dist/plugin.wasm differ diff --git a/plugins/official/solidity/dist/plugin.wasm.sources b/plugins/official/solidity/dist/plugin.wasm.sources index 44490bdd2..ac053bcd8 100644 --- a/plugins/official/solidity/dist/plugin.wasm.sources +++ b/plugins/official/solidity/dist/plugin.wasm.sources @@ -1 +1 @@ -8f4c3af7f1f0f6e2001ce161d89e2b83ef700480d6fd5517f46e90437f74639e +0a02c2ec0444e5eccb68b2bfabeaaf7752d1bc2b22bc0f26883a2a35eeac7df7 diff --git a/plugins/official/string-tools/dist/plugin.wasm b/plugins/official/string-tools/dist/plugin.wasm index e67da1f75..5e022a9e2 100644 Binary files a/plugins/official/string-tools/dist/plugin.wasm and b/plugins/official/string-tools/dist/plugin.wasm differ diff --git a/plugins/official/string-tools/dist/plugin.wasm.sources b/plugins/official/string-tools/dist/plugin.wasm.sources index 5771622ca..1599b4632 100644 --- a/plugins/official/string-tools/dist/plugin.wasm.sources +++ b/plugins/official/string-tools/dist/plugin.wasm.sources @@ -1 +1 @@ -2504128882832c282d627946668f10603fe4b165dca8f12a49131934d1a5c09d +10c214d40a6b43bb8b850bfe6d97c1a360ed748b5e95645354b3317806d7a538 diff --git a/sdk/rust/openidea-plugin/src/host.rs b/sdk/rust/openidea-plugin/src/host.rs index ca708f2e2..8f84d945a 100644 --- a/sdk/rust/openidea-plugin/src/host.rs +++ b/sdk/rust/openidea-plugin/src/host.rs @@ -310,6 +310,22 @@ mod imp { t.is_cancelled() } + pub fn project_symbols(p: &ProjectRes, query: &str, limit: u32) -> Result, Error> { + wit::workspace::symbols(p, query, limit) + .map(|symbols| { + symbols + .into_iter() + .map(|s| SymbolInfo { + name: s.name, + kind: s.kind, + container: s.container, + location: Location { path: s.location.path, range: range_from(s.location.range) }, + }) + .collect() + }) + .map_err(error) + } + pub fn project_find(p: &ProjectRes, glob: &str, limit: u32) -> Result, Error> { p.find_files(glob, limit).map_err(error) } @@ -611,6 +627,17 @@ impl Project { imp::project_find(&self.inner, glob, limit) } + /// Fuzzy search of this project's current in-memory index. Needs `project.read`. + /// Never waits for LSP or reads file contents. Only paths inside this project's + /// root are returned, including when the index has external content roots. + /// Search honours the current call's cancellation and deadline. Empty queries return no symbols; + /// queries over 256 UTF-8 bytes are invalid and limits are capped at 1000. + /// Results may be incomplete during indexing or lag unsaved edits. A closed + /// project returns `Error::NotFound`. Locations use UTF-16 columns. + pub fn symbols(&self, query: &str, limit: u32) -> Result, Error> { + imp::project_symbols(&self.inner, query, limit) + } + /// Runs the approved `process:` in this project's root. pub fn run_process(&self, program: &str, args: &[String], stdin: Option<&str>) -> Result { imp::run_process(program, args, Some(&self.inner), stdin) diff --git a/sdk/rust/openidea-plugin/src/mock.rs b/sdk/rust/openidea-plugin/src/mock.rs index 61730e48d..5b7967c7e 100644 --- a/sdk/rust/openidea-plugin/src/mock.rs +++ b/sdk/rust/openidea-plugin/src/mock.rs @@ -80,6 +80,7 @@ struct State { capabilities: Vec, root: Option, other_projects: Vec, + symbols: BTreeMap>, files: BTreeMap, /// Files outside every project, for `fs.read` (absolute paths). @@ -191,6 +192,12 @@ impl MockHost { self.state.borrow_mut().other_projects.push(root.to_owned()); } + /// Replaces a project's indexed symbols. Filtering in the mock is a + /// case-insensitive substring; the real index also supports fuzzy matching. + pub fn set_symbols(&self, root: &str, symbols: Vec) { + self.state.borrow_mut().symbols.insert(root.into(), symbols); + } + /// A project file `read_file` can return (project-relative or absolute path). pub fn add_file(&self, path: &str, text: &str) { self.state.borrow_mut().files.insert(path.to_owned(), text.to_owned()); @@ -818,6 +825,35 @@ pub mod current { }) } + pub fn project_symbols(p: &ProjectRes, query: &str, limit: u32) -> Result, Error> { + with(|s| { + require(s, "project.read")?; + if query.len() > 256 { + return Err(Error::Invalid("symbol query exceeds 256 UTF-8 bytes".into())); + } + if !s.root.iter().chain(&s.other_projects).any(|root| *root == p.root) { + return Err(Error::NotFound("the project is no longer open".into())); + } + let query = query.trim().to_lowercase(); + Ok(s.symbols + .get(&p.root) + .into_iter() + .flatten() + .filter(|symbol| { + !query.is_empty() + && symbol.name.to_lowercase().contains(&query) + && symbol + .location + .path + .strip_prefix(p.root.trim_end_matches('/')) + .is_some_and(|rest| rest.starts_with('/')) + }) + .take(limit.min(1000) as usize) + .cloned() + .collect()) + }) + } + pub fn project_stat(p: &ProjectRes, path: &str) -> Result { with(|s| { require(s, "project.read")?; @@ -1558,6 +1594,39 @@ mod tests { assert!(matches!(project.stat("x"), Err(Error::Denied(_)))); } + #[test] + fn indexed_symbols_are_bounded_scoped_and_capability_checked() { + let mock = MockHost::install(); + let symbol = SymbolInfo { + name: "OrderController".into(), + kind: "class".into(), + container: Some("App".into()), + location: Location { + path: "/project/Order.php".into(), + range: Range::new(Position::new(0, 2), Position::new(0, 17)), + }, + }; + let mut symbols = vec![symbol.clone(); 1001]; + let mut outside = symbol.clone(); + outside.location.path = "/project-other/Order.php".into(); + symbols.insert(0, outside); + mock.set_symbols("/project", symbols); + mock.add_project("/other"); + let project = mock.invocation().project().unwrap(); + assert_eq!(project.symbols("order", u32::MAX).unwrap().len(), 1000); + assert_eq!(project.symbols("order", 1).unwrap(), [symbol]); + assert!(project.symbols("", 1).unwrap().is_empty()); + assert!(project.symbols("order", 0).unwrap().is_empty()); + assert!(matches!(project.symbols(&"é".repeat(129), 1), Err(Error::Invalid(_)))); + assert!(host::projects().unwrap()[1].symbols("order", 1).unwrap().is_empty()); + mock.set_symbols("/project", Vec::new()); + assert!(project.symbols("order", 1).unwrap().is_empty()); + mock.set_project_root(None); + assert!(matches!(project.symbols("order", 1), Err(Error::NotFound(_)))); + let _mock = mock.with_capabilities(&[]); + assert_eq!(project.symbols("order", 1), Err(Error::Denied("project.read".into()))); + } + #[test] fn versioned_edits() { let mock = MockHost::install(); diff --git a/sdk/rust/openidea-plugin/src/types.rs b/sdk/rust/openidea-plugin/src/types.rs index 129947b40..f69089617 100644 --- a/sdk/rust/openidea-plugin/src/types.rs +++ b/sdk/rust/openidea-plugin/src/types.rs @@ -102,6 +102,16 @@ pub struct TextEdit { pub text: String, } +/// A symbol from the host's current in-memory project index. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct SymbolInfo { + pub name: String, + /// Host symbol kind in camelCase (`class`, `method`, `enumMember`, …). + pub kind: String, + pub container: Option, + pub location: Location, +} + /// An editor's document at one version. #[derive(Clone, Debug, PartialEq, Eq)] pub struct Document { diff --git a/sdk/rust/openidea-plugin/wit/workspace.wit b/sdk/rust/openidea-plugin/wit/workspace.wit index baa2edbce..9b2205418 100644 --- a/sdk/rust/openidea-plugin/wit/workspace.wit +++ b/sdk/rust/openidea-plugin/wit/workspace.wit @@ -1,7 +1,7 @@ package openidea:plugin@1.3.0; interface workspace { - use types.{error}; + use types.{error, location}; /// Requests File > Open in this command/event's window (or a new window), /// including the usual project trust prompt. Needs no capability. @@ -30,6 +30,26 @@ interface workspace { modified: option, } + /// A symbol in the host's current in-memory project index (new in 1.3). + record symbol-info { + name: string, + /// Host symbol kind, in camelCase (`class`, `method`, `enumMember`, …). + kind: string, + container: option, + location: location, + } + + /// Fuzzy symbol search (CamelHump and container-qualified queries) over the + /// current index only; never starts or waits for a language server. Results + /// may be incomplete while indexing and ranges reflect the indexed text, + /// not necessarily unsaved edits. Empty queries return no symbols. Queries + /// over 256 UTF-8 bytes are invalid; limit is capped at 1000 (zero is empty). + /// Only paths readable under this project's root are returned; external + /// content roots are excluded. Cancellation returns cancelled; exceeding the + /// current call's deadline fails. A closed project is not-found. + /// Requires `project.read`. New in 1.3. + symbols: func(project: borrow, query: string, limit: u32) -> result, error>; + /// A project open in the IDE. resource project { /// Absolute path of the project root. Requires `project.read`. diff --git a/wit/workspace.wit b/wit/workspace.wit index baa2edbce..9b2205418 100644 --- a/wit/workspace.wit +++ b/wit/workspace.wit @@ -1,7 +1,7 @@ package openidea:plugin@1.3.0; interface workspace { - use types.{error}; + use types.{error, location}; /// Requests File > Open in this command/event's window (or a new window), /// including the usual project trust prompt. Needs no capability. @@ -30,6 +30,26 @@ interface workspace { modified: option, } + /// A symbol in the host's current in-memory project index (new in 1.3). + record symbol-info { + name: string, + /// Host symbol kind, in camelCase (`class`, `method`, `enumMember`, …). + kind: string, + container: option, + location: location, + } + + /// Fuzzy symbol search (CamelHump and container-qualified queries) over the + /// current index only; never starts or waits for a language server. Results + /// may be incomplete while indexing and ranges reflect the indexed text, + /// not necessarily unsaved edits. Empty queries return no symbols. Queries + /// over 256 UTF-8 bytes are invalid; limit is capped at 1000 (zero is empty). + /// Only paths readable under this project's root are returned; external + /// content roots are excluded. Cancellation returns cancelled; exceeding the + /// current call's deadline fails. A closed project is not-found. + /// Requires `project.read`. New in 1.3. + symbols: func(project: borrow, query: string, limit: u32) -> result, error>; + /// A project open in the IDE. resource project { /// Absolute path of the project root. Requires `project.read`.