diff --git a/src/lang/inspect/defs.rs b/src/lang/inspect/defs.rs index 6b1b5ec7..c72227e5 100644 --- a/src/lang/inspect/defs.rs +++ b/src/lang/inspect/defs.rs @@ -32,6 +32,7 @@ use tracing::error; use crate::lang::db::{AnalysisDatabase, LsSemanticGroup, LsSyntaxGroup}; use crate::lang::inspect::usages::FindUsages; +use crate::lang::inspect::usages::search_scope::SearchScope; /// Keeps information about the symbol that is being searched for/inspected. /// @@ -144,6 +145,12 @@ impl SymbolDef { } } + /// Builds a search scope for finding usages of this symbol. + pub fn search_scope(&self, db: &AnalysisDatabase) -> SearchScope { + // TODO(mkaput): Narrow down the scope as much as possible for particular symbol kinds. + SearchScope::everything(db) + } + /// Starts a find-usages search for this symbol. pub fn usages<'a>(&'a self, db: &'a AnalysisDatabase) -> FindUsages<'a> { FindUsages::new(self, db) diff --git a/src/lang/inspect/usages.rs b/src/lang/inspect/usages/mod.rs similarity index 83% rename from src/lang/inspect/usages.rs rename to src/lang/inspect/usages/mod.rs index 0ceae66c..20e5c9cf 100644 --- a/src/lang/inspect/usages.rs +++ b/src/lang/inspect/usages/mod.rs @@ -1,9 +1,5 @@ -use std::collections::HashMap; use std::ops::ControlFlow; -use std::sync::Arc; -use cairo_lang_defs::db::DefsGroup; -use cairo_lang_filesystem::db::FilesGroup; use cairo_lang_filesystem::ids::FileId; use cairo_lang_filesystem::span::{TextOffset, TextSpan, TextWidth}; use cairo_lang_syntax::node::ast::TerminalIdentifier; @@ -15,6 +11,8 @@ use smol_str::format_smolstr; use crate::lang::db::{AnalysisDatabase, LsSyntaxGroup}; use crate::lang::inspect::defs::SymbolDef; +pub mod search_scope; + macro_rules! flow { ($expr:expr) => { let ControlFlow::Continue(()) = $expr else { @@ -23,7 +21,6 @@ macro_rules! flow { }; } -// TODO(mkaput): Implement search scopes: for example, variables will never be used in other files. // TODO(mkaput): Deal with `crate` keyword. /// An implementation of the find-usages functionality. /// @@ -61,6 +58,10 @@ impl<'a> FindUsages<'a> { pub fn search(self, sink: &mut dyn FnMut(FoundUsage) -> ControlFlow<(), ()>) { let db = self.db; + // TODO(mkaput): When needed, allow setting search scope externally, via a field in + // FindUsages and set_scope/in_scope methods like RA does. + let search_scope = self.symbol.search_scope(db); + let needle = match self.symbol { // Small optimisation for inline macros: we can be sure that any usages will have a `!` // at the end, so we do not need to search for occurrences without it. @@ -70,9 +71,9 @@ impl<'a> FindUsages<'a> { let finder = Finder::new(needle.as_bytes()); - for (file, text) in Self::scope_files(db) { + for (file, text, search_span) in search_scope.file_contents(db) { // Search occurrences of the symbol's name. - for offset in Self::match_offsets(&finder, &text) { + for offset in Self::match_offsets(&finder, &text, search_span) { if let Some(node) = db.find_syntax_node_at_offset(file, offset) { if let Some(identifier) = TerminalIdentifier::cast_token(db.upcast(), node) { flow!(self.found_identifier(identifier, sink)); @@ -82,27 +83,17 @@ impl<'a> FindUsages<'a> { } } - fn scope_files(db: &AnalysisDatabase) -> impl Iterator)> + '_ { - let mut files = HashMap::new(); - for crate_id in db.crates() { - for &module_id in db.crate_modules(crate_id).iter() { - if let Ok(file_id) = db.module_main_file(module_id) { - if let Some(text) = db.file_content(file_id) { - files.insert(file_id, text); - } - } - } - } - files.into_iter() - } - fn match_offsets<'b>( finder: &'b Finder<'b>, text: &'b str, + search_span: Option, ) -> impl Iterator + use<'b> { finder .find_iter(text.as_bytes()) .map(|offset| TextWidth::at(text, offset).as_offset()) + .filter(move |&offset| { + search_span.is_none_or(|span| span.start <= offset && offset <= span.end) + }) .filter(|offset| { // Reject matches that are not at word boundaries - for example, an identifier // `core` will never be a direct usage of a needle `or`. diff --git a/src/lang/inspect/usages/search_scope.rs b/src/lang/inspect/usages/search_scope.rs new file mode 100644 index 00000000..bbc398ec --- /dev/null +++ b/src/lang/inspect/usages/search_scope.rs @@ -0,0 +1,53 @@ +use std::collections::HashMap; +use std::sync::Arc; + +use cairo_lang_defs::db::DefsGroup; +use cairo_lang_filesystem::db::FilesGroup; +use cairo_lang_filesystem::ids::FileId; +use cairo_lang_filesystem::span::TextSpan; + +use crate::lang::db::AnalysisDatabase; + +#[derive(Clone, Default)] +pub struct SearchScope { + /// A collection of all files constituting this search scope, with optional text spans to + /// narrow down searching ranges. + entries: HashMap>, +} + +impl SearchScope { + /// Builds a new empty search scope. + pub fn empty() -> Self { + Self::default() + } + + /// Builds a search scope spanning an entire set of analysed crates. + #[tracing::instrument(skip_all)] + pub fn everything(db: &AnalysisDatabase) -> Self { + let mut this = Self::empty(); + for crate_id in db.crates() { + for &module_id in db.crate_modules(crate_id).iter() { + if let Ok(file_id) = db.module_main_file(module_id) { + this.entries.insert(file_id, None); + } + } + } + this + } + + /// Creates an iterator over all files and the optional search scope text spans. + pub fn files_and_spans(&self) -> impl Iterator)> + use<'_> { + self.entries.iter().map(|(&file, &span)| (file, span)) + } + + /// Creates an iterator over all files, their contents and the optional search scope text spans. + pub fn file_contents<'a, 'b>( + &'a self, + db: &'b AnalysisDatabase, + ) -> impl Iterator, Option)> + use<'a, 'b> { + self.files_and_spans().filter_map(move |(file, span)| { + let text = db.file_content(file)?; + Some((file, text, span)) + }) + } +}