From 7cedf0b9bec3d36c8e4608904e9f5d3913030609 Mon Sep 17 00:00:00 2001 From: Arnaud Ritti <77437157+arnaud-ritti@users.noreply.github.com> Date: Sat, 7 Nov 2026 20:15:08 +0100 Subject: [PATCH] feat(plugins): expose indexed workspace symbol search (#2040) Closes #1944 API 1.3 lets a plugin search the IDE's workspace symbol index. Results are limited to paths the plugin may read under its `project.read` grant (external content roots and symlink aliases excluded), the search honours the call's cancellation and deadline including lock waits, and ranges use UTF-16. Incremental index updates no longer publish symlink-ancestor aliases. Verified on macOS with the test fixture. Co-authored-by: arnaud-ritti <77437157+arnaud-ritti@users.noreply.github.com> --- crates/ide-host/src/plugins/api.rs | 82 +++++ crates/ide-host/tests/plugin_symbols.rs | 232 ++++++++++++ crates/ide-index/src/lib.rs | 37 ++ crates/ide-index/src/matching.rs | 54 ++- crates/ide-index/src/tests.rs | 77 ++++ crates/ide-index/src/worker.rs | 10 +- crates/ide-plugin/src/runtime.rs | 13 + crates/ide-plugin/src/types.rs | 9 + crates/ide-plugin/src/wit_host.rs | 33 ++ crates/ide-plugin/tests/api_compat.rs | 1 + .../tests/fixtures/commit/dist/plugin.wasm | Bin 97343 -> 97348 bytes .../fixtures/commit/dist/plugin.wasm.sources | 2 +- .../tests/fixtures/endpoints/dist/plugin.wasm | Bin 78636 -> 78631 bytes .../endpoints/dist/plugin.wasm.sources | 2 +- .../fixtures/file-templates/dist/plugin.wasm | Bin 68048 -> 68048 bytes .../file-templates/dist/plugin.wasm.sources | 2 +- .../fixtures/grouped-views/dist/plugin.wasm | Bin 89160 -> 89160 bytes .../grouped-views/dist/plugin.wasm.sources | 2 +- .../tests/fixtures/hello/dist/plugin.wasm | Bin 141555 -> 141569 bytes .../fixtures/hello/dist/plugin.wasm.sources | 2 +- .../tests/fixtures/hover/dist/plugin.wasm | Bin 86785 -> 86790 bytes .../fixtures/hover/dist/plugin.wasm.sources | 2 +- .../tests/fixtures/indexer/dist/plugin.wasm | Bin 79926 -> 79931 bytes .../fixtures/indexer/dist/plugin.wasm.sources | 2 +- .../fixtures/inlay-hints/dist/plugin.wasm | Bin 88681 -> 88691 bytes .../inlay-hints/dist/plugin.wasm.sources | 2 +- .../tests/fixtures/jobs/dist/plugin.wasm | Bin 147785 -> 147779 bytes .../fixtures/jobs/dist/plugin.wasm.sources | 2 +- .../misbehave/dist/plugin.wasm.sources | 2 +- .../fixtures/path-events/dist/plugin.wasm | Bin 91105 -> 91105 bytes .../path-events/dist/plugin.wasm.sources | 2 +- .../fixtures/semantic-tokens/dist/plugin.wasm | Bin 88860 -> 88860 bytes .../semantic-tokens/dist/plugin.wasm.sources | 2 +- .../tests/fixtures/storage/dist/plugin.wasm | Bin 71118 -> 71114 bytes .../fixtures/storage/dist/plugin.wasm.sources | 2 +- .../tests/fixtures/symbols/Cargo.lock | 333 ++++++++++++++++++ .../tests/fixtures/symbols/Cargo.toml | 20 ++ .../tests/fixtures/symbols/dist/plugin.wasm | Bin 0 -> 103930 bytes .../fixtures/symbols/dist/plugin.wasm.sources | 1 + .../tests/fixtures/symbols/plugin.toml | 23 ++ .../tests/fixtures/symbols/src/lib.rs | 55 +++ .../tests/fixtures/tables/dist/plugin.wasm | Bin 116488 -> 116488 bytes .../fixtures/tables/dist/plugin.wasm.sources | 2 +- plugins/README.md | 17 +- .../official/ai-completion/dist/plugin.wasm | Bin 169568 -> 169568 bytes .../ai-completion/dist/plugin.wasm.sources | 2 +- plugins/official/checkstyle/dist/plugin.wasm | Bin 166533 -> 166533 bytes .../checkstyle/dist/plugin.wasm.sources | 2 +- plugins/official/csv-editor/dist/plugin.wasm | Bin 130591 -> 130585 bytes .../csv-editor/dist/plugin.wasm.sources | 2 +- plugins/official/git-toolbox/dist/plugin.wasm | Bin 108493 -> 108497 bytes .../git-toolbox/dist/plugin.wasm.sources | 2 +- plugins/official/graphviz/dist/plugin.wasm | Bin 126110 -> 126110 bytes .../graphviz/dist/plugin.wasm.sources | 2 +- .../official/key-promoter/dist/plugin.wasm | Bin 97368 -> 97364 bytes .../key-promoter/dist/plugin.wasm.sources | 2 +- .../official/license-header/dist/plugin.wasm | Bin 150760 -> 150780 bytes .../license-header/dist/plugin.wasm.sources | 2 +- plugins/official/mybatis/dist/plugin.wasm | Bin 166698 -> 166708 bytes .../official/mybatis/dist/plugin.wasm.sources | 2 +- .../official/regex-tester/dist/plugin.wasm | Bin 932226 -> 932825 bytes .../regex-tester/dist/plugin.wasm.sources | 2 +- plugins/official/solidity/dist/plugin.wasm | Bin 105046 -> 105046 bytes .../solidity/dist/plugin.wasm.sources | 2 +- .../official/string-tools/dist/plugin.wasm | Bin 162800 -> 162796 bytes .../string-tools/dist/plugin.wasm.sources | 2 +- sdk/rust/openidea-plugin/src/host.rs | 27 ++ sdk/rust/openidea-plugin/src/mock.rs | 69 ++++ sdk/rust/openidea-plugin/src/types.rs | 10 + sdk/rust/openidea-plugin/wit/workspace.wit | 22 +- wit/workspace.wit | 22 +- 71 files changed, 1157 insertions(+), 40 deletions(-) create mode 100644 crates/ide-host/tests/plugin_symbols.rs create mode 100644 crates/ide-plugin/tests/fixtures/symbols/Cargo.lock create mode 100644 crates/ide-plugin/tests/fixtures/symbols/Cargo.toml create mode 100644 crates/ide-plugin/tests/fixtures/symbols/dist/plugin.wasm create mode 100644 crates/ide-plugin/tests/fixtures/symbols/dist/plugin.wasm.sources create mode 100644 crates/ide-plugin/tests/fixtures/symbols/plugin.toml create mode 100644 crates/ide-plugin/tests/fixtures/symbols/src/lib.rs 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 30247799c67c7b1dc5b4e4c71968688079e2ec80..87f0133bfbe89fa5b64c1126b87739cdcef61b28 100644 GIT binary patch delta 246 zcmdo0gZ0P{)(syx8Q*XI$l1-w_+WFb@GmyT>ysyFiBImAYLdG6_1DdYW%D+^W@uGl z)@0^zn%D8~{kp#1sY^C%Nl#&Byfb;f+yZ>6D&!w=#E4?)WNU5j$$46eK!p>uP!*!N&RSdNm%znccH9P)N!fBO|KApwXf4@ZuF7~K F3;?Atb=3d> delta 254 zcmX@|gLVH8)(syx8DDMw$l1-wczbiL@GmyTYm+OaW=fu4^RvJG?VM*htqRPV%p6Wz zUj4qZ|HI$8`!;J!Phn=fJvlI6baJnh#^k?p$#@i3$v@y=oI5#MV-jvfli4+knWiyK zW{ee^JV~<}Ss^2e)sr2xy(brGDKbt28Ynk;rl!v1ds-gI>d~C)psn*uz(4F?VMg(p UHzr@_{c8Shy>EMsD&vVT0D#VbBme*a 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 b09c15c53d38758fc52daf0ab9b92be6b782db02..4e4f1e858b985ae9ed2139797b81e274814a5c74 100644 GIT binary patch delta 281 zcmZ4UjAi*VmJR1Q882jH@P370zS4I@vxIdJ?%Nq}Y(DAEwM1ajO!;)70zS4HrZUvYO9NCP9|L7>V>_*6X-%V%SpJGot6ZgPQa2_BV` z|H>9IPMcgHhiV)nilvjK<+Fi`+T~Fdp}GvH$ZKtw=*)GW1L(cBRVYrRE<~4bP=oVxp=vLH;E!{3Kb8VbRo#Sca2SuKN4GV>qY&JeO*BSuImQW-B delta 162 zcmcaGndQP{mJQ1VrLONk_v7KBYwbF%3e1|!98PDt`ageudH3r}hRp{BbC~eSyNj?g yBFi(P>YrRE<~4bP=oVxp=vLH;E!{3~eZH`~SR}uW&(UN%|MJ(Fn~l%SwFUseOj1$+ 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 f5fc8e5d990fe62b858409469dba531388a4b1c4..a679243d79363239bdc749225e267a2a9981025c 100644 GIT binary patch delta 231 zcmX@HgZ0D?)(x!UQfqF^*njKN%5~ouS{0ZznK_)ctvh-0`TGlB7HyUncVxyRKUrQ} zeDg%9vlwbDkj$u%Yhc1-#%3W!HbxYAMie(p_Ez?uT&uJNMG2ZSy_LVc70}UnwtVWc Vm@>0)qsltxmy5Os3Nn880sy_&c7p%_ delta 231 zcmX@HgZ0D?)(x!UQWxjX-}7?T@f&mNTNRi!nK_)EOnNn^|3Sz8>6_)n9hvdSPnH)K z-#k(3EQT5jBr_`H8kq2yu~|rwjS)qj5ycIYy_LNu*D7s6QG(`7Z{=@q1umX%pMFhd V!xoH6lDDF1por$bApsUg{jj z%5sF6k!AVH$=JwnB7$vd*Z8*6q delta 374 zcmZp?#PRte$A$_4#{HWs1-|ew-rAfeYbwFi$vpW$jL76w_G=_t`ZgcAx$E3t)>Z{( zO=b?KJDFZP(#U}U5vQ5_XTp)+)1_^{4T3Z5m87F^mP}>HG8;r6#ilPD57Ai0aSnfqhJeASOo#%|lj*79iULZJGmuw+93>c4iB( g9PgQN)$%uM%+99b+8_4%(*+JPs%_`r&-f`50L|2>O8@`> 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 37642818a3c4a25b0c253ae941564edb86ca0956..b6e97341e29724eb2b1336c32abcf78b0fd8a879 100644 GIT binary patch delta 258 zcmZo%#@e=wb;EiN#ub}4aJ=VWJUV%SU>@V|$^1f7rP^L>y1(S@{?F$bS`}C{nI)X= zcm28Y_11<(Q#S7vO66p{KABfee6p2H6N*Y^O=b=xl~-lV85!pRRZhaAYVr=bVy5X# zlOJe`On%qJHrZXi97Q!FikXw&D|k;JaAaJ=VWJTQ5IU>@VI$^1f7B^&N+I&*7!?}ma_1r|+a38$|O zlNQZbvwYpE&HIE>IT^1_=9d$nY%SA-tdLognFC4Tbs2L;#<@U+lW;4Vyj!lAaoS{k zc@#B_CEgyITEPSYch+l;$|krbZ@Ti(RSEPt>`(y!8e2O;j6_wy*n#MGFUAWlf zw{q3U<}sq$I(d$Q*W|PEij32M7RycMRVYDLjPAra3g@l}WK?$@T6wgBch!@OsHK~q LPuyJf^`H{~LkoOb 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 a55184ae9949aaca83f51306256d06dc5d998e42..365ad903602d0fc0a99ad091239e05d5f1879b75 100644 GIT binary patch delta 254 zcmaF4h4u3m)(z`97?*9{z#-1bIC=63!92#-lg)*uNol?_ttNIDl~zO@%rR=Y4ORul1(T|nKhX?kd3ceB0R!j}T+rk;`)ytcNSj=5Q( OvTD)Z?H||~D}w+QVtok! delta 245 zcmeyoh4tkY)(z`980T!>z#-1b*g5%xU>@Vk$>u^+C1?Jg(f45clox!h3M`t;5>5}d z?$|hQ!oS0Ho1Y6!U}L;Gxk7R#vLa?pW)37pI#OngjB_T>k(HY~Nm69;ROtlViYE)m z6fsR@n#>;{HhGpzHL^lR6jLV~$YwK61sXaDSq-YqKs8>QC(DJu6qspyjB#J!1-^$; Rde0hvwEx@wi=DAD2mtGead7|u 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 640b15a49b8d4b6c4fcb7204dddcc431d71541f7..261df6af6b1d50a13e93b8c646369f6fa131a0d2 100644 GIT binary patch delta 327 zcmX@v#Cf=hbHgn`#*dqC3rg_u{GQ6TftiVcdmbYr)8vgBBAbO3W^pjip1e-upj6MR z_H*rbkAG%uRbbX+=5XrWb@0p23BR8lVc6WId6b{=)8qilE%;Q)TM4l;?O~p7n8PSC z+1-AzJgSKjNG7(n1n@FW?zK|dtmCklmGSW83$APM7(IEqTOHFLrpfm{icQvapNg!K z5!I=a@4A;W?g1L7FxkvqXYxXi1Z2f%{sJoYn*72|w|TPX_Q{@%#fbtuUk_HkkbBO` T8#`fM`nPnXHu%E92hD2KGA? z&`gs+GOe{GfR}M{x0TXnO^3y-j7KI1cqmMM>)M0Q=*g4a>VPH-xvxP{#fa+8$v51~ yfvN&LP*tJ%2B^xbIl*&#f+ypGM1f6TgY{y5O^FnETA_Jt*0*)r1y(Z}PXho(yqj78 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 38d968683c2499fafbef65195ac86747101489f8..afd0a867f4f517cf93e57861f01710f82dd640e5 100644 GIT binary patch delta 190 zcmaEOob};x)(tyFC7&JKu;%*L-dRzt3M`t;5>8kDtax;7&xvQsn=gqPG9gPdYcg{n zNy|uxv*MBVk#%53mS#lJJ$a9!!{nz5`;b+jnZHMI-7A4-3O%K3%}s8F+Ii(XRXnS_ Ky^4o1Jsbec>tNyl delta 190 zcmaEOob};x)(tyFCEJc4zPSC$t>X!;3M`t;5>8Y1HJ*Lda^Qu)=1Zc6OvuvAn#>$X z(lQd_tazk-WF451r5RClPu`>GF!`y%K4cYW=I>El_e!Acu|d`1to{e0@e5X}FaPSuZd?uF-TS}ldGBYo#zW~>w=ZR9EDr_% Deu`uY delta 195 zcmbQUjdjj8)(zK%q`Ggfxw3KP;w7%F3e1|!98T>ouG~C%XZ@D%44Xd+d9vY?50N~{ zh)>>JniWN!5!L+3d9q%U7szbEXGET?`YVBM-EJxGUXP>fCZ@BW?8_+MzLcG@JQx7> C%U+=X 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 9c2511c366d7aa169e11e088473577fdda96724f..b76eea9cc3e2c382f2547f79372adfd228e282ae 100644 GIT binary patch delta 239 zcmX@Nn&s4LmJJ^{7-w$&#L>XPGqH!cor#HoJB^XCZt_Gig~{^-oms9iF)~k{xK(8G zPr)XsTf0_wTzm4MXPGqs1gor#HoJB^XCZt?^%g~{^-of)rB-XI}9`KMr$ z^oQrW`<_1ieR)@Xs{*qoGl$duyLWcoUOex{BZkdWg&s3A&I2lyn><~t1fSB$s^UeA z(>BDmO@=szkRsM$&wrz=zN00`=Z6-?d*ae7kJrhNR7l IudCYu0M1Kp_y7O^ 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 0000000000000000000000000000000000000000..6d2c5e8bc8a8d169441aa24395036c3302e15323 GIT binary patch literal 103930 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 ZQ&Vkz;ZIF9c7?KAcW=T}yXWU1|1WMLIDh~E literal 0 HcmV?d00001 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 fc5b069fe62fdd4c4c355c0f440e3f88fe96bc2b..84391bcc61d367e1e0ed1d10f08e0f51179231a6 100644 GIT binary patch delta 235 zcmeC^WAEr=-!M;7^77L&{qOedS!&d(z^uv4;WYKlluNh1?z|bkdAsC2COp#XWU|@i zP<2Znbhowy@G?$zlv3IpsiMP%$C!CqmW;^KjHr(IplyUAjq1k9AGE!iTXna$>N4)M d5x8vh`QfvVUPhv3uB+BQ-*kPuUl!x87yx6)b{_x$ delta 235 zcmeC^WAEr=-!M;7^8MkBzkV&4ImMt=fmxH8!|B`EhUx3BUO2N4)M d5qO{Ivz|Rw!{D3RgGZ~JW-@K}%VOLW0|1RBa8LjM 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 45092d655907f93b9726acc97a3369d1aafbda70..c31940bedde41fa4e61c8450ec1d03b42cf1d76b 100644 GIT binary patch delta 361 zcmaE`hU>u^t_|k0l1F#1?|prG{@#LC1r|+a38#A-T8?eKedEa1%>lBm?0BTtsRS_M zkrr2rM3H9JWadB^K6#yr-R9%!Ea*y*><24R(v(G4f@I3(2rXe0C!$)ixy7&_k8Y3! zle3K9A$x)m#g&^2O;qt2P-x=a{L6IvFH^=HR|JmUUA-}`uIfXa#KDN$Nu67_AL3$~ G*9`zGuDA>U delta 361 zcmaE`hU>u^t_|k0l8e7RZ9Vwt%@)sA1r|+a38&?2Zcbh|wYR5pbAYTXJ09tEDglgm zq{Y=DQKXqQnK=-KPhO{DxB0j_3%U{{`@u?-G-c71Aepi`LQ5FMiKv!rZZYh~qZ?$w z4BRNwqLWyb7!|{&S!xVAf>jK(?jR%6@tX6QjUnU3I?6x2$w0-!t5Y zt^v(iwwu2gsi0bmX6j~RlWrDtQ;@CQeA8koeiJ&aj8GLYqB&f~)^@V6%{#od$k?8~ z&NyTH3lYX7=IIM07|%@il3)y*K3R(K!uA|VMs>#R$7*8CPze%WKWexNq`Ji9D7C%#6&F|0qdKj?|K$+#*>ex$eXIzt1K< z*Kbu|)?}7&YQK7N@~-{gF7K|NEFpD?asK4jQio90a3HDKd|cX)o$=7*pGx~t6`>iV zzWJxJ6B|(BejSC$Fnd+stNsoCE-a<&LQU delta 315 zcmbRFhkgDZ_6>7*8P{)~%WKWecx3WTi9D8t%#6&_^`scZCJQLDOcqmCn%pi~F7;z= z!>>7;XRfGeRbbX+mT-D-ta<8^r-%MsW|(|lS$(pd)M~~BlV3?4LQ}|rq;T_5X+w6F z!_17VFyoh@sYW(-^Lgb=Hpat~cj+ihj@0TwQ;BTu$y{wRbDF_q!PGOG zjngKuFn*pKlO48MD!Y!A@zds+1)7%yN|(&q{f8}}d*=foh8wa+c(-42X8fHD0Cr$_ A@&Et; delta 257 zcmcb3p6%>;whfPX86RwZ%D&Q*z~} zi<2hww44rVRbbX+=5U&@?7)eKD<@C3-R!H##mqQoa=)6~1n)%=kMQ03w-d7XSbN 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 bc64f8f58bf6611fed58672145c2122530e63ec5..41c2e4a27ec4742e96ecdd265c4e2688047b59c8 100644 GIT binary patch delta 235 zcmbPtlYQPz_6=!LQcV{(yjr7b bNyBHZQRV?<-k6fwra4MA+jn;{K28Jxr&Vot delta 235 zcmbPtlYQPz_6=!LQhN?|-u!!G_0ew(tqRPV%o0wGXOH&$>o|VJY;%KDA|tvy2a^16 zX(uLhc?*R6Lm;8 bNl?0dd|g*rW7@=r?lLRQw(ss@e4GdXTK9B2 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 7de6bde145f3422d833ec867f6d819d0ccf18eeb..ad22fcdbfbbab708aa0dcdaf549c82a219f5d0f8 100644 GIT binary patch delta 215 zcmccdgZ0V})(v+#882?r6Q9%rCTKDE;_uo=kn9VRjmrl zn#>$d|Ca2$du?^^1J}*!GHvWkb08`YE7#&yIXOzDh;i!VvnrF2)i9!%HhH3&{p200 wI!r4e2L4jTV_@?{we1tt7_FHFWG`+wK4Y5uuezXh*|DYvj&8rB$ygHv0PJpLZvX%Q delta 228 zcmccegZ0J_)(v+#8Lw@=$63qC_;7QKus8?PHKxgmsbZ76rJJN?-aonQ`0Qmp*{ur9 zn#>$dlh;jsyK&+5-d_xx)nwY(ndX9&N=-hYT#iTS?dzk)nQr%G5fD7KC_#9)wcJlF$)&JF4OjK?--@E0*pUM!?A*-}`BQD(ZKHj~Wc??O(SONFnqvM@3+b8Zfh zoXEy_W%DEXb1aNoCT~=kDtYq%J znvltF)$AEx0@aC6R?}0SoUR^?+sw^()ps#7u9&Q7CpY=Liq2$FU4zN9?Ie&jFl#b% zpg6*Ia+8iDn%W2hh*O7Q+n;(jF%<{SuMh?XfnU` z4aPYnI`Wc@Da%wQM&{{>Zj2I>ZS3?X2ibZc+rfzHv2%9zlfT&NFs=mJGYMH8Y5;6L zXQw=w@&4vd7hAa)dp1|xSk7_rzqpy&G&y*T@^U_KjqV&jbS$}C*I#B#eL%U_RI^6lWPFsZx2BL 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 a6aa869dff79471dc64a21b99d6c9cf4cbe77296..1a4045534e489251d3238c9871d3abc73942684a 100644 GIT binary patch delta 572 zcmZ2Ajcdy^t_{Upj2kzXaQU#aGBL8VGjBdB*uchkezTjzU2fJ#OpMI+)7PJ46rXIb zGFxhY=bHypKlPuFZ&hH?WR`H6{Hg0+>zwb+Ul=C;x3HaTpc=+Fd2+kzHU-=YTU!EL zd6_2btBP$dQZry={YA75MH;Ia-%Xa&+>GoDW=&=eBxh`XrMZWZap~mc+DFh-Av<<+ zi%uXTu`>2=FSB4=a#LVGi*(FH`+4_;cOHs;G41PG+~XoouKY#yDwmhw3(Yoa$Oz0$h2SCL5@VZ7xwW zU}XGBz}Qla)r@Z^D{5{=wwhU!nFC?<=69NV7@3wZPcNuo6q$TLdp(*`Bv)?k)Cpu{ zoV5A2E)zTB^2zs1_TpAMIl^3h@_uWv$)8N8GQOYOVTR&+R3`w{K4d%zbf);^uO_OK z6I28y=V~fV{%PilWevP?%-&xqp9$uq3ICeO9f zWjqA7=aJPMWVNX7**wGAc{ 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 566e1ebb8e0793034f2e031c1fef1c544c77e566..fdf75280796f7be36aa5264cdf3325aca98bfeab 100644 GIT binary patch delta 36688 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%0D5$bUnEo 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

9g%~fH7#XkQYnHLNe0}gzU4lLs&Ow^aJP?}4K z-&m!*&xh2+v~dbB?T{oeoxKhgnHY~CmVu=I|CqjtC1StE>}R&tX`NuR7vR(_>3jIvvJvQOQj}YqtfQBATAL9Z@BKI1bdZ4nta$~yHY{{=Ka4FB4`eE%5J5& zmToY1GTZ*G5ET>&Vv)Q5HyDtE($c;*rPEN`42|-!JFy#>B(q1ml|`5xX6{iwMv!@2 zsn4SJqJTN`jou5}B%STqtMrEoFwL>5U1%h`+J!2tNGGL?masXu6^87#?Ay*te(m;) zOr=iymCm_fRbvnKD^;?S#0=f}fYK&U($NE$?{gwywK6$(`-Zjnof6D$9?WCz!NB_t z^K&+kuC#u!`#C1L2RYuen18y`&RnZhlT9$ zF=3l!9aFB5BYc3~wCX@Fmt~_eENdx%K{+e1H=NJeR~gD{=q0yE^n53j1{}+_Coz$l zCZwo({$zGl^_1eVGE+`r*4V?2o>FR;+{N(@a`7uuLg58;e}NC={eAFFu#6PvZq}*? zEyY@#R+?F@EkCW`^X{Xp(;07TdfFM-I_U6$vq~v4ll^=a$l>dpOyx1wsMXIY1Mp1` zi@GHE@qZ=I9hb4L#Y*%16~!Ol8L-GRyrVjjUv zy!)F{xQyu`G^d^w3(mm$b^hlSHvD%h?0!Xs>Uprq_-~ZG`l_-dS%2%XQsf1ch+Ao^ zm8aDQ^IuE@UX&!qxoiP1AZV!iFF3Q=%i-i{a>ajMbi+%EGL8YX+-&k9{9e?}mjHS} z(U_Oe)}RRcMX?3xyChpI`OgS*^(8HvUC;rq%*#MwB)hA&F|Wujq1oWX$z*N(@Y!Of zKFrUCle)9?a}L`@U$RW!QpDB;<`MHPX5({1MI-2F1n(O`oCx%^Vz%E%ncN0j%uASq zxl7tgu=~YrMe664sM)zHSYnnd5&jutFJiv|<;`GQ5q}ekTX6?F<5*1ai$>Kbz7NHz zSsXX(*b3{v2ipq2h(ZF#x+QEy(D6SR{Gw4hfX zBN%H0@ka2S2=re{+hBpl>HEvt>f6Y<`IT(fE9BbYaX&B^=>k`^KGr7!F$)bj(eqcf zb>;ezAI;FGRJASQ#!=&jHhzesVRhU4SjBIsZaZa`Si7!F#0Hjg`-(`V13(#+@KpX*3ecR@V{?pyJi(T)yU@Ot)oQQaL~zYU{n*{ zfDT`l<}L7LGh2C?oX`_m+Fs|0&sy0Ikn3!JYg<#ewzW2>VN?&-ecPcE=+mY4wnk_( zxPy)FpV#hSE1B)I!{lc1%84JiO4iwXoou7MPCdoDjq)JF%qN{~e9M1kXB!SBu@+ry zacKBr7u(-xSkHXTHkM-ds%LLo6|5Df_O|8o$51UCW?MiJRvTrzBO@&Fk?k%I?VsBG zWb^Fz?8>CF-s%e*tUBxPmyW|<3LmPRXTdH_ur22#%9%DK);7T(XS(jjL1q!Y&43j` zve33)16LhfY2$~rDy*`7&(~Ogd?lj2*Vwl6Xy7^#{c@eHL^0bU&NSQw9O3EJGX!0_ zxzE{B*gJLQxuMMnOc zXKgbm=r!vnTVK}ZimgUe_Gx~nahl)94k>)$JNK>P8WHQfyHQNvb;Z_+S}vdtu$ z^eeY)Yq^(y@-Mb9uLI)Y&$e*Z?{}N8e(4vK0xylYE2L%OUE4bJE%Khh!8k7#sCT_@ zdxmX?IGb9JP5;eS$ID8%`^IFo%#>4t)#B!;;K;ZnxEg{U*|IpV=4yA;V_Ue6c3F-l z3;cm-B1`nu6U4a`hj#t_Zio7F;k=f7=ZxDw9z2V<3}|Mriq zAUX%s-v2ML+MJ|*YA5Gd8HZY}Om6KGyI!E5`xCjWlv9k1mJ3m%Z|C#{4mFC9{p_5- zT81QV$)`3azTXL()yJW+jGD9v`>ZB)YUxXaTKbNCkzcLpEuk7EGV-fML#$f3=gJb+ zq&R+8gxe3ExMwwK2^Qv5!$Q(c=(yj?DtGcLQBFm<{!W!Ue!nyVVbP~J)sL)?+Edw{ zQ?|k^s(@M${_(B_fOH&tUH~VDW*1Q3g{a~^KWFs(h1Bi}Ij0XTsz&gYO}`^UJBVfKu&pYBdDI%c%n)gvZLM%Yf56<<%ro+#C={N2X5> ziQ*BxMnyGE&a(v1*e_Z<$D~O01bTQnQmxOwKXq19lhKD=)zmh?*|$1QUHfOl=HDpo zuBk3WNxf%n)ng+U*!>2eMKUYeP<_+6TO93_`S;@dGJv1hgobKe5NKCJ^)||^ZKPt6 zs2^&imLvJHp*{wZTcXs;HsDdEIl7pq#dlk%jY)~zHp12K1pa2?Qo=g7)q+{yHL}X? zwN#e?($rS!etuS}V_Pn0YCbZy+wrW=?c#SbRLbcev8~(G?1;i9Re+P9MzBteq zcTulG+38=ruC|rPJU#slwIbgp`n`wx23LaZd#h)_F5z#g@kl-Nrg}|5J6{h_e}Rn7 zAE*utvOdwtzUL(7q-bQZUTBc|8oo9XT&9nCOKpz(2-yC&)lz8i_SxpH4SPIV*r zd7Rn;MrY)Bj5Bzx?F6-M6_fI&&fRc8kjCUV^wc_=zuU;r_0$RK0B)`nnFJ0^)ay)A zCyKqk$!g0I-a~GvX!CA7?>;18p4Lmns)>H6b9AoSUM45?;`7y|eCv7ZLNyY>FALSY zo}S&`8|dhvi`13`2F$RaT=R=cogbNpFmyHKU<8Ku zvmepw6L$7T!HUIxQd^l{42YfQ2X$;VHuEPSV}7D|;U_hf1DSPGZ3ftn2xZcCqb zM{Oy$G)2=Kr`>V6mbTdH9`2R(1k;ExM&IIcvjUs-SmoyY&c|Hw>Fs}2anwIi@Agoo zao2V~1h^tEu4lHvG9RlmF?kI69i*Danmkb(dQ~_S5GIZ3PgGTM zS^_BU%Hbc-uF6vT=Q124|K@Wg>+rYAPq*RxSsfNt7U#8Q>3{#N_8_qRU;jsa!&W*s zIx7tG+|)Wa#*Vviz5saKXR?xW%m+65nlSqgsk9T+S`ek&Qv8k53CJC-(V={RulmxG z&`Mhzv}fMqvm31C(|i7O5ZA#d>tes>r#R!RA8^v&c`Mrs(a~IsRWD4ZBD$+Eh5s&& zWfh@6VZ<*Mr8h9*djlyxYq`YE7pF@wd5jLG=3acLHhZ2JaGm`cOubw3a~N(n3uf&? zXn8a%4%9CVp?qgwtjVgEpzy|BXYZAylkmCj3>U2o&sZ534M(i4i&iZlT!m1alDYo% z7TV#WdWj``%hBvF=gO&%_v@gNB)Dk(^ zxi7(DjrSF`M7BT;?b-cssZxZ*owUDWb9RKaFALkYFk8`Ap=@bw+SW?_r#5Y8IeYum zp^M1(#vZeO-RyMigf|AhDRnWnTiNBhv^3qyS;VeJ^4S+#}~XSvy< zhIA}yeAI}RE-8w_ySsIh(Em8^kK6ii1(XSbZEZyBqss4%DBo#s+8CWV&OU8Shk#($ z8q==DP5&^#IodEBjsz>hH!Q(Aaf%)E05&iRtr@2s^gp90<$hq&gzmSlR)W2geatV- zSY4#BYfT{1NqU2(5cDG7@c#JoJDHuE#Vf(v7JXYY3h%}D>}+$&k0IDv&^;F6F0`Qg z;DcV?l9nv+oL3RoUEywL(EW_q<*xqH50gakVkl`U>1AuhqC|2YV>MdQ$!KCnD_XaV z@YVB+h4^^-IR6pV0?g4x5)NBAV`i`h=}P%m>>%`MO~d6p7ieXex=~-yj^>w00!!#X zcj6k@w>n}h&ahP-DNc##Z937XZIri771*{nEN*W5)^EN)ML-Oys#GE z6?rLv7;YTuqyN;Et|u0AdN&^Q^{>+?esd2_6MX{N8rEIN*2?Zew!ZC7t0D0>Be8rB zk%%3H_C@ybWr#fw`F>|#_n;LOt|UB{E|%q&V(xm)RxH0|QQ2@BB^>9H&h?<_*>^Fr zbvl8UgqJ*oDhyM=Sy_R9+PqGfa6AkQyx_2HMhdM`xAa?RlsH$B?j z&QOsZh+9SfeP6pBr*oqJ^J|Bli};`VDqyyDgyJ?2XFS{3j|TbHc(;~d_^PrjyE)g|kcMaCBKW)suL>hF_?7p-HiXG@n@8Dtu00{%5|22SilOrIZxbe6= z`*I-FSl|A%i+J;li;hr1eV%=*KmC=gGn$SxoBo>D?rye>+mZ{CWo*nq+Cy44)pK-6 zb!7MtqK%!&x$(z2T8_W^v4Q`3J$exR5cgEG$Ad9hEoUX)qCKVMQ$5eeRiS8nw+~w1 z_!iYsV%QL9+%s(95IP3aM&M9-6`OT`4yDZzG#*A{EcX{~lCg=)N7(UUfQh4V!yw0t zSgqlheP*$d!|4Ddo*Yhb;~%T~HZ4QaSogPS7aVi^`fUgi*`|lTL*ohA$qv0sU(dc8 zmem_cTi~G5*pWcwTYck5ObVFLpS=gO*xBTd=q6Y^^**M_klouK({9|yJQ~3&T(d+ElQpdWC-kZJmQrl}ooB;7 zML)jNzxost7EH;9pV3D+7zMx|}81j;jGn(SAVTz9##dr!F%xI(; z<<}FM1kahRAe;4WO5Vh!+U%~X;r5eexCarD#Ac*cas2I);;3k9?yMDj@`r|&oJrF? zBk#>*v0u{m#mwad%GQLiNKt0#3CZmKmo%uTm)s=-e}qyXFMG-v|hk( zoI*er1)V<|?+M&LR-AnlL#qU*t>T~!vx84eVF0Xg=)TT-YB!0 zRt3P1$3s-6vK`}TLq`^e>$341O|LTB1iG*IbNrzhpW&RWNRAkuJfXG>DA<6G8OPd9 zq~Xo~IJukmO4PBcLvvl_koX<@Hp6-jBkr--M)HCR{uii7Z|l4|i=7BTdBToNCXKlktN7ZXf6GVw5F*hSVS`!(`SH*udTZi=Ia}f{0=@8pqy9ltd3Gok1Dxy*7bzZ!8&CP)Z5Ss`sTYCXHT4`{LOCZ2 zCKn&+7h_R#v520^M$V;G3W^Mu6lxQ-fl(~J^rS6)ZyVz=m9PR;LG#(eVO#`zFdB*vf2sURhSe1OBWIHq*L2EIbfIjvrtUr#-unitsz<$$!_bZo{SxK}oyL!?_ zJ-b`0?7oNkOB1KDe>_;wK4WF)(Ys_j3y7no%f|}w!~K}GB?tbLhZMy($`;5@Lt7F{ zTk;b;1LNo*eB+WHN9$V>?c7MjYyX9;0F5cR1?&_I?f5!Xr@d*F%o+9-@7$-uU$OoSkPh3R=4O0+{S@?<}A_ z$Tuuu0j(g7o5p@vKo=uAY9V|XNA;A2a42A1^y4BL`SPaAFNTZb7VEeeb?~(%g5ZmW zs`P#zXWrh6nZUffmnlnV)$rMZ?J*l#{ee<9(p-XjiF#>CL^OQ%I&}xo-d@M1L z;L~l!B?={%(gL_LwdzvZ{N)(VTuOrqrHhGJ43UpmtvkQfw=bnm{_XCOWw6eku}90W z#<^pd9PI7oG*p^0jqP&dEZ8%ax||+?mXBHSDpI7c5Tv-bf;IxwiYuu{OERP~nSHks zbbrRQRkT8*C1xV;4kSm*ai`S|Bg-P4>Y`phx0S_4t%8M_!Pc#!r3wm9OHSkH0lTt_ z;+iy8el-ng|3LJ*790~`3@}-a^|G;k##BMXN>@x4#$B~`4-p2G8{geRteyO6UEG<@ zs>Z*n_ZL@VK0Nea7ple9$J6uV4x9QF?A$y0iLbC^KvZ4JqpbZ}IMD8}s03PZegb?T z;&nbBmu$Px8lntlXgjsKdK z;$;)Q#sFT815KNvpr&H0FbEXeq}Y zjwlo-SC;GwPAZnUy+%y9-jj;NIH?$ob#Bg+iUT+Ep_{>CH&b|S*Ba(F+qfA{q&RkL zGfeBd%x??k@k1qJ(wH=j~|N z!_FhTfZdL74;`M!j)A_RkQ%OsF0mClEmHh}P-XZFW0=|``0m5d4T70kaZY9@blOT9 zH=PwvgGQM+oi$3MfjsPz*{!3}>g*H&5rW?kd0=;WWQ?^fz6`8cnJbsH&S6@+PH*;bGzi=E#Jn=^|&*-Fb7 zjOF7HhK*xuM?8yF+a^lAyA2jt7Ms0|4lVUcz;4X<0A74M7XMkS(RL`sEH-dEeUn^c zd$!Y#b1_}VdLsk4K80QacrNWT;&(!6iuc#~*tFPXjMcDRG>pe*?4s2W>U(xk+)X{5t=|LTp3Khh zAOUyB(%^!}1u{OZ{D~9==Vt&$zQSHwsX~GnE^ecuhvlTe+A^2ow{b6e zew=;3mqr#j&B1buf_H?o6%;o5Utk{%aGlMOr&)RDM4~XISo3|fO8y%>Axy#~0N!+F zvhn+9x5~G8VyFaTrBtf88K53Uz#X06?!0N_@RbnifUx+3NQ~~M6FT1JIbaO&2ZEdz zc+5@kZ5BW2!8`P`kq^Fy3F5ry&Bt;8k}%x)i;<5Ga0Pvlo!?KZ6!=~g1IO?#x*KBc z6?A}hkYc9m!ww#1(`IS%_Go{c>YUwS;dcpTa+o;fpU zO9b6BXh;zYgc9ZlHDuM(r)5x_Vu)u8PGF*rXZKFfg9yGkNrxjSd5SJa@a-u$j^o*x zQ`p0ZXH8FozvJ2P)3B%GS=?z_t-{ldrd|XmT75T@v``!eH`h>Myptv^ivB?R7PD|l zlf(F*(eV;4p3VA!Rtqz-EBWle`SVf!Hm=yvbt$`%c=po|w06)~p`IxJ11_q|Hxzio~;d zXE7m(mO?4kx5(Ic7QT&m_WM~u2v;U8?rkYBOwEa9=S*xi#l!_cSmcj_tq1)GvpJr9^&=e`U?CES3EA7P(4S~^ zZ%#nYoS{F__fhr2PcWk5*_!JD`kCu=2V$NZ0*`|?1Rl+9iedlsCQ43X&2GU-8_(D+ zS_;ATTcX~bTl4^88*dBIdvIGMx_+jK-nMgLb?Ijz*8#s!mzPlj!qlATwfO~GIPom$ z7kW!G2To>J9@ApX`7_Or96%3Y*Y3ap6wmtI#hyw$8*^8nz40#n0u;1uqr?*8kMVvs#`3ScBB;2wqh`aic zD0=FVpz6ZMf~uL1MdH#w(C^Z}(=WYE=OXQv-vw#!{r+;&mU<#cJMjs<1?=AW!veb) zvAS7;l5b=QN>0rZl%#(O259gnJ;_Pu{EIdr(^%KP=v*GX^%u>68J_kxT=`R2(Wlss zn8n_EN~?#==5$35`HI8vo8k@2Ib&a7_fu>#n_g+>0`}}FRy}{QbN_&&AF`ll@E6Wx zanCRh%w+eTL6<*dt)9cun#m?ShqTXR7oKDCcoXa!X_Gg#+Oc(VA(o38pFBSkmfh3#?d-r168cYb32q{@ucZR|{?# zzCpZ)U63?>S7}RGdy6FNDYDj;I1)rf7ZD2%f14H>vfD^e+!+Q;>r@slUPRk%ZCXvN z$j92WA;BhAIW}D<>=*tc{Fx7?sM=J}ah9s}LSOHw+RxI=>B8O59Gdn4c06JhIlF9n}flTQv(GUGT#3Jol z5c}Cr%U8&BFSNXfnX4hiuJFaRqN%m|r2S9NDPXD5N!1a6tO~qFPlIV-z2v&6K5>L{=kI zs~(ib*^xW%P1#PpZEkU=>0?8+#k`$O?#f-I9ovBw(!Lvz^!a}b?tdbo@HkG z?#hQElWJ(sE4;FZsF2^~bQN$FM1>7CwdZyJ8x=(LLaqQ;VOJ67R{eG@Es|&p5`_e8 zWv)8f87;wxC9r38wAaw(u64E3ik4=i>a0p5ZB=&cTqA81b_?1z)*`jrMv)YKd}FPj zTrbOv@+RTD$^PRi=_V*MBl~aUWm;{7G}YRLTCtbb*QYksIuq9aOWcj|pqb{6#m}>5 z+Amt{I?)~6+1p&}0(T11E?4~qADIHMxAvry4&EV>OCdObVdM%#ha()704C*XZsJMBXR zpS9D5IK=4!*nzP5Wmce_7Q+16Yb_o7d2fYzXe39mO6|39RNC7fe4WVdw%3}XFOeOz zAmtd!vIp%Ur$akH9C4EXQsDtj?4V^pfK{4yo zo>5D1IK4$Duy_*d(@7hPcEuNzk2`6Ph}H4pmQ8X0Hz75TAnFisrK?6|Ey19DGAq;# zS}U0?>!$5AM(Djav|ARu5#6;_vXNY>xAqmbD-QM6dUIhb`z9FjG3)-Ob{cf6)JGeM zI!lcJXNZdErTS`v2q+udPg|p<8LgexYxUQ<5iQ+}vUvlvFR?BxI8dumTsZ#3;>!^B zP~q~0^UHl5pP6FHIL1;2YCobG+-NZyLI1aKEW6QQZH|#tgxwsheP*N&jpQ`v=7F_X zIP{mGf#MVye7h7o9Z*C@G|_ZY`47>aqaASwDr~40tD#yUepJ8rFfFmX zb6us!O`;w3W)z<~^oLu=dahw>f6)3&{$QSl#}|(u9=ra*JjdoPP}C>; zAHi!N3%u+2HKaUVE0_;2pqNiZgq2v0dyd-vIg_ihq4yl83Rgp#W|pjuum*qF&_Dc3 z7OREV+ISpzIIuc+>f))#V(&XnMb|fzc~%2F{2zzJpN2ewe^$Ja5f#52<;G^*YB|b` z^ML2O%{YJOPZKUzG9tJ z{~8y)+@-HEN8IJ}33f}z!oO+qZ{vlHIRm7~C*YM8&SuIt?`;U06{GWIx z{#p5-7*TKjbu%s_F5!@pmF zhk1b1V(&fiFY0Tx@f%z4hkq4z_7DGXp^>fhC#aUQsXt6G*3e|-0sAM5$ntNDRoc)j z|L>#!M(+Q^M>QH}X@B}xkA4bVEQ0-m_h)9D@~+^Y)ng5DUp!V>KfK#{9M7{rr;wt> zcrSoDIm`JYEgzozcygvYd0FBU?NbmhcV7$e;j=H7g#WL&Gs#IHh{7-!R>rrI7942> zR2)D>K_nXE7#A)a5M6Tw51>crxUjCk4ZMaio<TByF9)C zMVN;LScDXmz}8+uFT)C~LK!Ns2B~)X>r}Oj)4hZ)>Pd$Gdv$wKIbWyO@11Pmt6;Nl z{_$IQjYUsEqDEbZZBX5Cg;??rJIMcn{SE)5NX@;*QY|&P9(|SUf|fmBe>*;1yx(`p zw~@N*^d&4F60=)ygg%B7-%rP>Jayf^9d4y$!#@Uqj2sEjP}d2XZerMB8u+ L*_X$G3^hLiP)18O delta 36485 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�r%%owx&``6zbYk`W&INO{IDW z1vZmPLK@x8q<0YdsGC$o+ujUKLJr>+Qfb6hZ6O^8kXq`c+qIM~0dM>vDIaRqPapMc zrB{_P>dQ7#do_BC&W=|y7opj$fjM*hiHb^Ha_y6{tFf;u285lnlt8sDsBK$G$r7KA zDdlyTob6tr79~PyMr%o;Dc#j^y&S$a22KMUqB^F3*}<28J;1LUqEgu@;gUgJD2AHg)O zi{wX27pVYxuV@!(0>FkYut7K<(*-7B9@Xlq*AdZGav?OOt5h3Vj_^pm{!j|)COHZk z)nVtAA37@>E?g8c*9A&L{eL}^h!eLsV&{x-uGSz%5u7_0--op84XZ;Rb+;N>hYn?U z&fgYedVFR3C>l#)}lgf4xg z(S}geq2Rt2X#v!@uk;L!uip>KxzEUYwx84%OfAwMCUrH9?k~A~=Wz!=0D0+s1{u@r1Om~6lJaZH> zEFB~@M)uzZ!Mv}gHWAR;d9)}3ZCR})MMxq1q~zVf(hQ5VSwo~M5@#4CIGSZa{48o|vm(uT;MwK2ZZQ%^M{*^QnU7OSt6V1tAOWB2| zG1|hfr6M@fuI-7$=#>R+nniQu5{QX~Ra-DxYRwnmejf*oU!yt3ONWFvm2|F`g6B?> zlq~5M!Q_h8xzQ}z%MEJ0UUJf!jZ#ijVHF2c)k#uF)@m{_Mq4#SI?tzvqozu$v(%Td zCBbxM`kskYzhAXE48!s(sK5$B`UJ22j0t6~Xf;7ToFnbe5O z#FG`$a0P8MX&Ajur(xAPiSNPjuAu_ozQV~j&|vjsmX7fX!lG}bj}5GxxXd+1tpC5M zvE&ACskcdB+3( z%u?fRDJm?E9yQ71*x@k${GD_sYY9ubHK_d-E*i79O2J&f|KEzn+x0;gwYMouMgi)$ zLnrdX6?RbXEOd~!KVT{Apk>^TR2{~9N>6B866Q5V zq!xBs!eZtj>X8g@KcjiQm+UpK6nsYVScx~^Qwp#hbnT2(6+Tejvr>Mxow{{a3Q?D{ zQbP-E!dVG#upgqn=e%gO^ZD$5YhCM_tD*lQh~V-rNE-* z8mqa&xNP1zcxTT4oImb=m_P9?`736jCFyN>VdO#|Ndd2KSw)z_FCR&{U)S*EFQqD3 zDoUOEhDN_8Zxp>buZyNKTW+>y&J5e@2zw2-WpBZjmCCrc$%Q`fiR{IPR^5 ze?nn!>RyPg-0L`n3i#oMz6ESoU(L$)oRK9RHw9Udj)fM+-yjyA!|SCuCw)`MAZC@ zO1GJ!%G#Q1Bg)tWZcO>f5N&aJ+X5DBx(EEr4<@?98_sZhhGpD}w&AL|#S=-RYuIwr zwTiYaX8AQgRI>4NIXbQzm2C|m9mWD*yK1(J)^o?|wo7Iv>Qck@HzWuJnD8(4)}?Q2 z+bUxD;Yw}WHI&&?2i;&*7F5rM!&hcyK;We-c{PXB1I zXIxv`XDIc&t&Oh~2DY=s0IX}D&AspFDv zwru|B=M95w^Ay0wAK7k;fE7Nm-Qlp{7dC9OFV{+ZWos*e%8Vz^`i-_N=Z!bwG@M4$ zdo*K~kl$YXrYJVVl5dDwSjlMX+^} z4aaI%YW+{!a`0#rDn+@w@m)`C?-|=h5osU&WSgphQ7LDMmIe`HHO}Q%p2;|B9_84Z3Fg39NMgV#Ai6UQZ7auG=cWq9Lx9_R)2a4uTSI z*orXB+3DtQwvOncIyVuXPJM6Mrm^+f!<)7>d{uejA5h+v+7Ex&_VE3>s5^R}&%a|^ zhuDU9ZGIdTPN?O^f%`dmnX#iTr4`_4ZWa8C{5C=OoIZTA7EoG%kN; zCC&fJR7sJ?HiEOi>#=PXXm6WlTZO9rO0%^CU0Rc8wnf|uU;hW3U#ZD2Y&j7P3WooO z%D+#Pzp%42RKp=xsF&HKL~qhA^)@|GE?t#`=v7<}5h{&G>pw7Rr3msnhD zkWJ3br|4a>%as9U0Qm0V#tKVoGPSLOCGT3*6qW8~m$M=Ld3Kqv7Y8|kZ!dLr%K7rY zW|1!C8~)h?w;k^a&qNj!(AVYWR8;~gF&A9_M^3o`gs)l-xeV6c2jq~8qFblskjEJy zHx4bZVHATWgR@t|qZF+E;nxa6T>kCP;3Iw=eDm$$06WI^>m)02Bz8 z`70T^U=IkExnPeEmUD;&tdCycpcPIJS!Wkg_vyWTQrnzguEF2hxL-h?Afl2_!{o&> z=m{t;R{>~IT&@5xs<_PeS$-)lFF~J8L*UUqtSWnK>_-x6gF9=eWNrC<=XU-MRG7%W;=LHzx zX5?ZBn_I{@#I?+5CoBp+C1D!( z8D>tcP-}S#bXnWfUcL$&rpC?R|L_VlTZf zUz1=TcK4S5K!>dBqjzMszH$(oqZR8bcM#Aqkik5fr@!0~-(R4V!Ezy>e?D05%s0+D z4#QeK^7Q#g{sB!28!k`gwQLw6FY@kIDc%=tmKjzgp89+uPl5rt`-vP>ApKm0Zwr5% znh3?0GWh4J&`t@bWeR;NHv)x&K9$?R+HLz(ZVT&p;+kBJYJ3J#QYrKoIX{j3OvawF zcJebBLr5~U`a+Ji#AhmHug`MR$*rR7iR8!!866~2^Hxj8EIHJ9&rt@|kSCz>%c z5@@E=^+-91{X*YI$?jagaIS+cKlgX68AQg<`K#tTT8`&NddC>q4e-wx8OzdI!?7|J zW=>L%adP$C=9^b$H^m*$JM;`~90%6MYlp_ky)oiYi3yOkm0HUQ@_2qsGI^rh1dR-i z=Gv-Njh5H?A@$lUxs}L{Yh~xki}{ZBiTQFlfEV+5S#mAlN~Bd@fTHjhzFsKv&B}_4 zktZ4FGCWq9tGOI2-KT&8lAnBN45lj)p z%jM7=D=}@c?z)}SdM=Y&aa*);m5k$MYczSaT$O8Koi*|_8SumgIkyaLyq=(wE^pRB zxDK@Do8=n z+lQ`SsqNk;AA!(nlMc#jMcJ^HH1n8TSnGCF{s_(rojf6T11Nb?2k7xKwB@8+7*Rj# zQChWAGR{j)*SejND+vDSV?C6d7XB#XR4{cq2QW#?^^-hahTAm%l6-*=s3We(@9|^t z5YKlE$sB^r(|$ossdW1nU06#0DmS(1GS6RG45t;pqHe=C)b9T(W5mQcY@o|hRiatWtb^`6lBdCZkNgV^nMNJ|mTP;hB*S;I zrfbXomSw?dT26SW_#zH}OLt3}+{^2FHGIZLDC+%8=BLl z_v$V|9~M(eyq=X(L0wtfJVdM1`s2g57_KCI37dYcq=d6wM1Q1B|_ zT&;0Us0YNSacu?1v`ML>j7G6Zb(A6~wyBPi$6M?~9q@e;`PPL5AEi!pf&U0it*Z>c zBI{pum3Q))Tx2*98-`<#_#z=VC}6;MZlgZ+lsV>E1kK;AD14PQus&o@etXckVY<_Bjsm zyy!*&FNsi&_E=_o!ejK$WbrR%GG~&p0sO|diKt9dB`-TnO`9qcfg0Nj)pO{=h}~i| z3`m@hV`y||B^RHrwLL7A2zw(gz3S_JEE_HF%JOKlnkkZ)su<>1-62A zwpMoGTd`lZ0mGAMOB-d4K}CQzysgronTj!zK5C8G#q)MbsaJ)(1;z} z`5E8t;~yNRVG*|6MQ1a7UjAP{DNJ8u{`vgTUKfW7{+-e+r9dLSylEh4{`&G(1;~nT%D(oK z$$EL0?BmAgE9aBeO{w8ru2;kd?@)Mx&K1PoSNMWM;rErE0;CZiFb<-{J(Qd{5Ej=% z>1h)jKHzft`DkT%Zqmu%ORS7K$$J3v_48_Vew?owE-1@bt14h zzs*efdYuAF?TczQXixfb$03P6?2qD0Xnud#nI-h7zfuK}B?l;)P48fuQ4q&TUJOtI z5S?qFGD`SiGTqr_>p(XLDpRnR_3S(jB312P;L{7D^thv}YT&B14qV8QZEY`9N94paTttD(?!L zCe!H$EH5%|9;!5kNq#mImVB31au^IfM(oc&g3oc1x(`xN?}!P-c!$8o<-m zoh2&use;#E>GG$_2Dl?JpD7*L8v2!kWmMyH-l;A>l)^ii)#(6S~ zHG19L88s?H8DAggBZ`PpzAO>_jc)5a+#GMH@0w6w99LDq=^r=4R}#%B9Vl~PCV+2o zR27~tqtpIs0RJY(Xr-Y4;Z=HZV=;-6+(IDs1(%Elt8`F|*chc5yGAX>C^Z7^@V0^; zG}ig6@f^$F9t)(6V=!ngqtLN%!EVyaF-j;-K$@XiW0jle?ZV>}cmC_V6pr;mu5h@O zf7+LS(Zv$8G2@hmP=Q0^l;Sz>=}R6qEO`j^cmd}V62>c$Lh5oFJ04!qB>H|lBz7D< z8?V%M{K@gUZ2Z%s7pUO`WnaE$_{A5W2+k2ihevD}S5*WSxIoWG)0Bxy;YN>>6Oq9v zV-<(BxWF;-&*s|BdWExlVYgz}%l`5Xrc zLyQ$6{t0&HE$TB}DOuZ~iZ2wUQ>8B(F+LY84E=s32eVeqHq)``5cdm|V+MNsG&Px_ zG%54Ql#z^5gr6);$!bZw>*u5Mkai{)L)F-r-q_z$l+jL#*&-m7Mk z&U?}A1Ji}rjA#Uoru!_Vq3~idt(pb*?i`(-rPRo2bv=IM#OV4vl;5M&q3RnIJA5vD z@TYXZ+_k!HY=E!q_6ltJH-LK88^Pvd;f<%YXAyhBaKFm6Ik zd*;XO*~=-^Yc@upakO!^Qa0yHUdAPa+H@2!rDN#+Y^90%i^1#3)ULmZ?VRFRQ|~$l zBkwqxFh?m}WVB9$DVkIEm%9f=EeWW~ee~yxaT_rYGsV=;nOtt1J#i4Bd z=PGwF*?otUA|+CFy~6h{s|pSro)zlxeVKWp(=d(9GK~zTaRn)T@iv7rPpN6Cs{RpQ z9IWLd0X_{)@6A&pR7+y$=se{In2JgBl?p*Ax)|YW(dHOrgq=6(%zP!R@D05``A78e zL3RKMKN4`5a|iWf8pMa0CzN-AQV%A%%L4eilW6J!r8ADj-&~-S6rN9}fQ8CJ1mhMe zwQvUK?n0zJ!mmj39iw!#V9k6jsb-1H*BD3X1s^h4Nyd?@hwC7l=RB-I#T?WyB>}{szOQ8q%Y1mSw(VJ;I zu~Z4p6{ioJdgu7qbqU(0Jzc6e`MGfWa(F>$RAD)$3^yiY(F}U`e3TMK>z2b}jHTnt zQH%ceK^O(BP}X6xv3teaxS_1nxlwARQU~YR8WC$FzcC4gT7Jn zw@T5QZ~QtMku`8EZ)na~1@o30)MzaaQp{Q`L*1arbxJ9!yH2TVJkRAl zbgBWnlewpAu2-7niPjZ^YaNUnZyatC2F-=*m0H5BRdjy6((R3zT5eE20k`&UPzv$9 zH#aEl9h;3&7>x&(|6nhZ-d}z*E*0M#w({Fcl*B6kDzk)l! zxH+HjBl10W!I%6GzHsWhREu`J0u} z!j>u4jNq5eN;jryb-%})A|zEeGJJ1EvSVE=(mEaxhM|UbZ2>!u8p|EGI5FmV_smBH z%W6s?#}1why!z)E;M!_|d zs$r>SI{9yfqx>5++ln!HA4P6e3IZ(M3IlhP&TNI_e2nsMQ$7hx;Z1=;f(%oe@N&MV=XRy3@O%n&*{*~N z7_`3Lt^{#N+m!*?j`Mmfmm!)QJCyuY^f>oHALo992oZ$fb&yDtkQj@QP(i2%F2Qa> zdLB`a9XM6|oybH;{MPh3WED`g#{E;Z<6(OL?FDJog8s z4ZBEff53X#B^vdE(jw1g-bmOZZh8X1-cbCWn(RD9SAW35*A06513ctY6u4U{=zD=@ z#cu|kq}sccW{r|~&>a~nJ5*leJP+~?;J-)X)DzKvkaI-f!`1xPa-2W&JpMlTP(HGS z3#FVVs7xYO5q{kbL#aQ%%e!WLqR~~ciAop`A5T;&0B+f%+!fday0;IaJemUc1I(aW z`<0NKM|4_zn))*sONI|dj;4tHO6ig_^v>eWB^p><2&yWo@XbKqYYOZ|0pLsBkA@#1 zzXM9S+$T9&?nm%;aG~OJkdxH*fD+(3ogq!N($46ShIcgXfKoR56&?|WSB{{Sf-{-+ z9#A@#xyB`c|NR)+siQl!x7r2n>k(6;4RXQV@k2&fALKAetHg5 zS5kEaz}XnSi0+Le`*Edq@ZWm&5A^X(NBNjX;MWD8XuXds&snxZJZ!oO|4gAFCzYC| zV>TLAASg`W^IQ|<^?1`Zgo#!)VUqhgmH!E{A45-0Vr4ppdY!^@bqsAfrA&7ic*5{A zCdk%2N$G@y6-k)H#nAaA*rpiDep+b<(CakR87sV};Yh?#;%O`v#ZbOv7_1m-ldLoW zpkyU~Zi_G}%nzo=G8Nja21Q;Ne*%0-*bk%2I$AKVo?{h62wi zLjcB{gH*@R+HEmlKuOxcM%GU^W{{^NjhGKqEE&)XRs^?tr zt8x~hq1SccU3nc|a133)t_%#Y2noW->TOrg8%jlQNs1577kLqrT(ESSlQcp zWRKg*T5sK%s0z8GRHunu&lgIV0hd?kK%b*X=vXP1WpD zkqSIk@=}}IN_M26-u&dchgF;yT6zx~4>7dsp5CC;d&*Y`eQ+P*9YeAAl_LPdQ0XD0y)5i~#K4r)14j=cjpb1i_ioR2_P?xD;wm%_(F;wydKV>8%k}F(=4r^A~*fD4PD4&h}sa(zE9LTjzJm z-#WkZJk}#GK0&+tK32Z=R-K8qzaHzBu~eGk@@i)m*7i=*S-U??xe4mlKe5m*MCkLU zIw$8o)j4_Osm{s9&velk`AkXXtZVaJsn5pKtmn!s9xVEgk_7+z@jqB3A4?ryV8>t* zZF-?p%s<(5PWjBjSP;b%=55Bh`^y*DTQygTowKO+OH6Mb%?VQLvDQ zESgF&wF|pV6Pa2Qp?ys42oNl&tq?g-P+RiVQbBFbCeIaB{DLNhh-w))cP&J<37bqF zQJv*)Efr(L#bQAPNo}a6>fQ#H1xHD0bu}thcbeDGZb{|GH*ZL4(d;{M8l1m2>%dG? zaPOjEn>tuF7n4U9REtw9n_7Y{+th4qjds_jzQY_dbUZF&8qiWvL-WTPF_L?$QHAvz z0r=P;NSUmtl`$nxRMY`QO(HY=0Li%h`l=9qfP^#BKAB;>rfh>17k!|rJ<-&Ys@heE zhGoa^+EJ>nT8IbIXXGvX)Q_?0u-i}l3CQW32;Wjd1Z8Mq^$zh zP?G%BKrE-%b)ddPTIK*x_tPy0m~?=K`m4bd?yqLcWiH*~c&dJY9*e#EshPi(DV+S9 z3)OI*iX+RjoTK-#2l}|+-?-TbHyaMmX^r;WAKlD;pibG4zV(@pIPO!7Td%u`kMBpXx&`>*ETn!cQiS|EAsyM)$hz~`oaR3RWA-Rd#{nBcLjTh=#QN>4B z6KG*2^<{Q_dmrPJigRCn&IDRpS$&y5OC%=*hYTGqe^)kSIZ#D?S@Nw}^a9!ORm2>w zoG5U=s`|3}|Db?gJeMoL73j+COwfu|SIaRqYOSs!3Dl#8dQP2TgjP`Pnra6$c~(vJ ztfVe8V)5PUdg_YwP>@^w2)hQ8-D){C$;c9`?RBd?#Tr-5Ag>a0NQd|PnQy) z>=Layx;0W;hFYOFme-CnQrmI4(qA_$+eG!p6sdL-^$#`GLR7Y?+8(m?RZGo`*8Z;p3nv zXIAq=OGxrs%GOHViP_Y#R_f=Vv0-a<1i-e|>HtKBwo!*7G}8dAEo62jm29gv0F^`A zs=?A>w3^D|iPp4*vaO*Lc!tv6YO5weKex5hBd@em8wBsyZJD`ar0Ux|LHdr-0jkwr zEn=CqxFt#f8s8q`yP6iaS4X3I{ZkPY-%%ejt6}*p$K&3gx<)*NDno%AysIjrI%Az_ zN!!1tP6kPr-cxr0i_ZN`oz;z_&HASpjQvSSvpWy@DF_6GOcs)L_T048w#rF+#JFI^muCCrOwkj$$jU@7wgFyg7teMePVci)lJsyyOUGrj^~Me_pi;;6&}__J_U z#&Z>1oD?2c6<0M})#*^G<4h#BOT1ZY;+g;PD)?242k>WwYa2oR&RbK*3|l1C&5~Zv z4D-DFa^qU)bovYjoQJD`kB^r3PsgR~f%OqO7V7jF40r?RID;CHUGVSZ+Ypa&vX76G zPUrM3ND~DAAm5(82qV5dtr7ey2I|QO_Q2H>R});l=)T}z2tS$OAH`S%O_cm$ED_IH z@BuFVXR+u*BdF8J=^Sc?t@OO~Sn?m*ZdjIVA7#nLo5laEVjtsqp6t}C>w)#JoCuG= zWs&g-X*T~J%|1mG|FepHhUd?5%@B-6e!&y*XQh9M=aINL19;q5xcHx>wUPbptkpC; z_r%51r{kJ|%PKn)&-x#l$y++h4DtXEyUZ|$yu61r#lK19Y%|(IGY8?hxU4YYnRlo~ z(tJEG$dJAe&x>#cnQ@B&m*5I9!%G2|;i?51IL_s`R^W2kbeuVTRwBUvtO{4rbk%>r zpl=Y>9alyHSdFk%&l)_(;xo z@hhIk;jbJnj`}+uz@OEz2}aP1b~9`l-MC^9-_z{gfqJiW#=(IiDpw z4B-p7tk(XVCH*48mvCA5FKa#i_AewvUdd4ADxUeDRR`~bYq%_&zwku-S?OGx_@9;j zs}b~pKI%=qZicP=H?o9B`S=v|`3;d4f}420WroeXI-=jH*c1PP><_B?#D6F)Np(1L zWYz2k^wSgnvg}WK{KUUv>5NGKHZfTYe{6;=s?un{Q~$b{JFR)@e=PDTQvZLvWTSF= z{LH^1zP)0QYEdd7Y{O--OUAQ;>;DvA?m7tsQ5asWR&-X-Mex3gh$4cDpb-_O*_@fp+Av5)hn4?? zj*%9Q8^afYaR~b-uoXD)-EoJXO(|nJ3Sal}o>dPfogpBPbCTXXJc^FN6i73LHI3~; z93(FRa*$;cNwgfiIhcn9ScD~bS5fOqVju_CN>}@;Ra<7vgB4hX6s&<1MjFg|*3lW* zfKAB47Uc9tUnOSk2mZafuGJvZG}))8{k|$Rw}}kzE{|ORGyBdTdly@Lw@kZ7Uxa-S zUD9_0mFks|{{- wP!9qnS!6@|J~wzXaGPpv1-40VW1qo!;5P+x`q*`D)CJy4=*Z~Hr!!jl0rwm4pa1{> 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 e6210f2ad3390afa167b4b8ee934148fc1c51ef1..028fcbd862f068a28e7d51a986f7cb9d9ad14240 100644 GIT binary patch delta 271 zcmcb%h3(oFwhiJUQg=Ha{eSuO+Sz*ytqRPV%p6W{5B)pRf9vn{Rhtb(F0$g0pKK^1 zve`gNnw^lEO2uCo_DCSv(`y#Rh{uf0j23LD@{B0%oUCa1ACGpRw0Co>)%I2^#*@qf ccUykUUl%<)E_dPl9~Zu@U$s5Xj`2q{00%vjh5!Hn delta 271 zcmcb%h3(oFwhiJUQk`>7T{ymN#pa6)tqRPV%p6YZA1&-XzOVOK%VtB7i>!F$CmV{0 zY&MXRW+$YkQt=mtJrYRv^qNI6;xS`0qXiqPJR^!bCo5Y1$D%^J{1FWR%(Q-t 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 e67da1f75e31f3c7ddbdb3d250fc2e254772e1c8..5e022a9e2820b34a5c99263c190c852deb3699a7 100644 GIT binary patch delta 225 zcmezHo%79i&JCBj7}ssS!j;d>xN@_LSc4GLU8c#2ej<}^X>?01>znZS>*2fWSz8sD zHJLe_makbpXVLPL*Y?zJZqQ^BVw^j9kFDI~3=PT2yR4(}sGe+KQ^YcjiII6ae?Ftc zxOTIPSc4GLJ*Me}jErKF?`U*Nzuxoz#^U1>H=JW= zRbbX+=5Xq|@#( 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`.