diff --git a/Cargo.lock b/Cargo.lock index 6899bb63005..634480e5fb8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1025,6 +1025,7 @@ dependencies = [ "itertools 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", "join_to_string 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", + "once_cell 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "proptest 0.9.4 (registry+https://github.com/rust-lang/crates.io-index)", "ra_assists 0.1.0", "ra_cfg 0.1.0", diff --git a/crates/ra_hir/src/from_source.rs b/crates/ra_hir/src/from_source.rs index f80d8eb5f1e..a137aeb9098 100644 --- a/crates/ra_hir/src/from_source.rs +++ b/crates/ra_hir/src/from_source.rs @@ -181,7 +181,6 @@ impl Module { ) -> Option { let decl_id = match src.ast { ModuleSource::Module(ref module) => { - assert!(!module.has_semi()); let ast_id_map = db.ast_id_map(src.file_id); let item_id = ast_id_map.ast_id(module).with_file_id(src.file_id); Some(item_id) diff --git a/crates/ra_ide_api/Cargo.toml b/crates/ra_ide_api/Cargo.toml index 73f39b647cd..f9bf0c686a3 100644 --- a/crates/ra_ide_api/Cargo.toml +++ b/crates/ra_ide_api/Cargo.toml @@ -19,6 +19,7 @@ rustc-hash = "1.0" unicase = "2.2.0" superslice = "1.0.0" rand = { version = "0.7.0", features = ["small_rng"] } +once_cell = "1.2.0" ra_syntax = { path = "../ra_syntax" } ra_text_edit = { path = "../ra_text_edit" } diff --git a/crates/ra_ide_api/src/goto_definition.rs b/crates/ra_ide_api/src/goto_definition.rs index 41a88314fb2..1f3fa6c57be 100644 --- a/crates/ra_ide_api/src/goto_definition.rs +++ b/crates/ra_ide_api/src/goto_definition.rs @@ -10,7 +10,7 @@ use ra_syntax::{ use crate::{ db::RootDatabase, display::ShortLabel, - name_ref_kind::{classify_name_ref, NameRefKind::*}, + references::{classify_name_ref, NameKind::*}, FilePosition, NavigationTarget, RangeInfo, }; @@ -54,13 +54,11 @@ pub(crate) fn reference_definition( ) -> ReferenceResult { use self::ReferenceResult::*; - let analyzer = hir::SourceAnalyzer::new(db, file_id, name_ref.syntax(), None); - - match classify_name_ref(db, &analyzer, name_ref) { + let name_kind = classify_name_ref(db, file_id, &name_ref).map(|d| d.kind); + match name_kind { Some(Macro(mac)) => return Exact(NavigationTarget::from_macro_def(db, mac)), - Some(FieldAccess(field)) => return Exact(NavigationTarget::from_field(db, field)), + Some(Field(field)) => return Exact(NavigationTarget::from_field(db, field)), Some(AssocItem(assoc)) => return Exact(NavigationTarget::from_assoc_item(db, assoc)), - Some(Method(func)) => return Exact(NavigationTarget::from_def_source(db, func)), Some(Def(def)) => match NavigationTarget::from_def(db, def) { Some(nav) => return Exact(nav), None => return Approximate(vec![]), @@ -70,7 +68,7 @@ pub(crate) fn reference_definition( return Exact(NavigationTarget::from_adt_def(db, def_id)); } } - Some(Pat(pat)) => return Exact(NavigationTarget::from_pat(db, file_id, pat)), + Some(Pat((_, pat))) => return Exact(NavigationTarget::from_pat(db, file_id, pat)), Some(SelfParam(par)) => return Exact(NavigationTarget::from_self_param(file_id, par)), Some(GenericParam(_)) => { // FIXME: go to the generic param def diff --git a/crates/ra_ide_api/src/hover.rs b/crates/ra_ide_api/src/hover.rs index 24b161c5c11..ba328efa140 100644 --- a/crates/ra_ide_api/src/hover.rs +++ b/crates/ra_ide_api/src/hover.rs @@ -14,7 +14,7 @@ use crate::{ description_from_symbol, docs_from_symbol, macro_label, rust_code_markup, rust_code_markup_with_doc, ShortLabel, }, - name_ref_kind::{classify_name_ref, NameRefKind::*}, + references::{classify_name_ref, NameKind::*}, FilePosition, FileRange, RangeInfo, }; @@ -99,17 +99,14 @@ pub(crate) fn hover(db: &RootDatabase, position: FilePosition) -> Option(file.syntax(), position.offset) { - let analyzer = hir::SourceAnalyzer::new(db, position.file_id, name_ref.syntax(), None); - let mut no_fallback = false; - - match classify_name_ref(db, &analyzer, &name_ref) { - Some(Method(it)) => res.extend(from_def_source(db, it)), + let name_kind = classify_name_ref(db, position.file_id, &name_ref).map(|d| d.kind); + match name_kind { Some(Macro(it)) => { let src = it.source(db); res.extend(hover_text(src.ast.doc_comment_text(), Some(macro_label(&src.ast)))); } - Some(FieldAccess(it)) => { + Some(Field(it)) => { let src = it.source(db); if let hir::FieldSource::Named(it) = src.ast { res.extend(hover_text(it.doc_comment_text(), it.short_label())); diff --git a/crates/ra_ide_api/src/lib.rs b/crates/ra_ide_api/src/lib.rs index f7fd42f6529..19669a7f021 100644 --- a/crates/ra_ide_api/src/lib.rs +++ b/crates/ra_ide_api/src/lib.rs @@ -19,7 +19,6 @@ mod feature_flags; mod status; mod completion; mod runnables; -mod name_ref_kind; mod goto_definition; mod goto_type_definition; mod extend_selection; diff --git a/crates/ra_ide_api/src/name_ref_kind.rs b/crates/ra_ide_api/src/name_ref_kind.rs deleted file mode 100644 index 149585971e9..00000000000 --- a/crates/ra_ide_api/src/name_ref_kind.rs +++ /dev/null @@ -1,98 +0,0 @@ -//! FIXME: write short doc here - -use hir::Either; -use ra_syntax::{ast, AstNode, AstPtr}; -use test_utils::tested_by; - -use crate::db::RootDatabase; - -pub enum NameRefKind { - Method(hir::Function), - Macro(hir::MacroDef), - FieldAccess(hir::StructField), - AssocItem(hir::AssocItem), - Def(hir::ModuleDef), - SelfType(hir::Ty), - Pat(AstPtr), - SelfParam(AstPtr), - GenericParam(u32), -} - -pub(crate) fn classify_name_ref( - db: &RootDatabase, - analyzer: &hir::SourceAnalyzer, - name_ref: &ast::NameRef, -) -> Option { - use NameRefKind::*; - - // Check if it is a method - if let Some(method_call) = name_ref.syntax().parent().and_then(ast::MethodCallExpr::cast) { - tested_by!(goto_definition_works_for_methods); - if let Some(func) = analyzer.resolve_method_call(&method_call) { - return Some(Method(func)); - } - } - - // It could be a macro call - if let Some(macro_call) = name_ref - .syntax() - .parent() - .and_then(|node| node.parent()) - .and_then(|node| node.parent()) - .and_then(ast::MacroCall::cast) - { - tested_by!(goto_definition_works_for_macros); - if let Some(mac) = analyzer.resolve_macro_call(db, ¯o_call) { - return Some(Macro(mac)); - } - } - - // It could also be a field access - if let Some(field_expr) = name_ref.syntax().parent().and_then(ast::FieldExpr::cast) { - tested_by!(goto_definition_works_for_fields); - if let Some(field) = analyzer.resolve_field(&field_expr) { - return Some(FieldAccess(field)); - }; - } - - // It could also be a named field - if let Some(field_expr) = name_ref.syntax().parent().and_then(ast::RecordField::cast) { - tested_by!(goto_definition_works_for_record_fields); - - let record_lit = field_expr.syntax().ancestors().find_map(ast::RecordLit::cast); - - if let Some(ty) = record_lit.and_then(|lit| analyzer.type_of(db, &lit.into())) { - if let Some((hir::Adt::Struct(s), _)) = ty.as_adt() { - let hir_path = hir::Path::from_name_ref(name_ref); - let hir_name = hir_path.as_ident().unwrap(); - - if let Some(field) = s.field(db, hir_name) { - return Some(FieldAccess(field)); - } - } - } - } - - // General case, a path or a local: - if let Some(path) = name_ref.syntax().ancestors().find_map(ast::Path::cast) { - if let Some(resolved) = analyzer.resolve_path(db, &path) { - return match resolved { - hir::PathResolution::Def(def) => Some(Def(def)), - hir::PathResolution::LocalBinding(Either::A(pat)) => Some(Pat(pat)), - hir::PathResolution::LocalBinding(Either::B(par)) => Some(SelfParam(par)), - hir::PathResolution::GenericParam(par) => { - // FIXME: get generic param def - Some(GenericParam(par)) - } - hir::PathResolution::Macro(def) => Some(Macro(def)), - hir::PathResolution::SelfType(impl_block) => { - let ty = impl_block.target_ty(db); - Some(SelfType(ty)) - } - hir::PathResolution::AssocItem(assoc) => Some(AssocItem(assoc)), - }; - } - } - - None -} diff --git a/crates/ra_ide_api/src/references.rs b/crates/ra_ide_api/src/references.rs index 4247c6d9085..f35d835acf1 100644 --- a/crates/ra_ide_api/src/references.rs +++ b/crates/ra_ide_api/src/references.rs @@ -1,13 +1,29 @@ -//! FIXME: write short doc here +//! This module implements a reference search. +//! First, the element at the cursor position must be either an `ast::Name` +//! or `ast::NameRef`. If it's a `ast::NameRef`, at the classification step we +//! try to resolve the direct tree parent of this element, otherwise we +//! already have a definition and just need to get its HIR together with +//! some information that is needed for futher steps of searching. +//! After that, we collect files that might contain references and look +//! for text occurrences of the identifier. If there's an `ast::NameRef` +//! at the index that the match starts at and its tree parent is +//! resolved to the search element definition, we get a reference. -use hir::{Either, ModuleSource}; +mod classify; +mod name_definition; +mod rename; +mod search_scope; + +use once_cell::unsync::Lazy; use ra_db::{SourceDatabase, SourceDatabaseExt}; -use ra_syntax::{algo::find_node_at_offset, ast, AstNode, SourceFile, SyntaxNode}; -use relative_path::{RelativePath, RelativePathBuf}; +use ra_syntax::{algo::find_node_at_offset, ast, AstNode, SourceFile, SyntaxNode, TextUnit}; -use crate::{ - db::RootDatabase, FileId, FilePosition, FileRange, FileSystemEdit, NavigationTarget, RangeInfo, - SourceChange, SourceFileEdit, TextRange, +use crate::{db::RootDatabase, FilePosition, FileRange, NavigationTarget, RangeInfo}; + +pub(crate) use self::{ + classify::{classify_name, classify_name_ref}, + name_definition::{NameDefinition, NameKind}, + rename::rename, }; #[derive(Debug, Clone)] @@ -52,161 +68,82 @@ pub(crate) fn find_all_refs( position: FilePosition, ) -> Option> { let parse = db.parse(position.file_id); - let RangeInfo { range, info: (binding, analyzer) } = find_binding(db, &parse.tree(), position)?; - let declaration = NavigationTarget::from_bind_pat(position.file_id, &binding); + let syntax = parse.tree().syntax().clone(); + let RangeInfo { range, info: (name, def) } = find_name(db, &syntax, position)?; - let references = analyzer - .find_all_refs(&binding) - .into_iter() - .map(move |ref_desc| FileRange { file_id: position.file_id, range: ref_desc.range }) - .collect::>(); + let declaration = match def.kind { + NameKind::Macro(mac) => NavigationTarget::from_macro_def(db, mac), + NameKind::Field(field) => NavigationTarget::from_field(db, field), + NameKind::AssocItem(assoc) => NavigationTarget::from_assoc_item(db, assoc), + NameKind::Def(def) => NavigationTarget::from_def(db, def)?, + NameKind::SelfType(ref ty) => match ty.as_adt() { + Some((def_id, _)) => NavigationTarget::from_adt_def(db, def_id), + None => return None, + }, + NameKind::Pat((_, pat)) => NavigationTarget::from_pat(db, position.file_id, pat), + NameKind::SelfParam(par) => NavigationTarget::from_self_param(position.file_id, par), + NameKind::GenericParam(_) => return None, + }; - return Some(RangeInfo::new(range, ReferenceSearchResult { declaration, references })); + let references = process_definition(db, def, name); - fn find_binding<'a>( - db: &RootDatabase, - source_file: &SourceFile, - position: FilePosition, - ) -> Option> { - let syntax = source_file.syntax(); - if let Some(binding) = find_node_at_offset::(syntax, position.offset) { - let range = binding.syntax().text_range(); - let analyzer = hir::SourceAnalyzer::new(db, position.file_id, binding.syntax(), None); - return Some(RangeInfo::new(range, (binding, analyzer))); - }; - let name_ref = find_node_at_offset::(syntax, position.offset)?; - let range = name_ref.syntax().text_range(); - let analyzer = hir::SourceAnalyzer::new(db, position.file_id, name_ref.syntax(), None); - let resolved = analyzer.resolve_local_name(&name_ref)?; - if let Either::A(ptr) = resolved.ptr() { - if let ast::Pat::BindPat(binding) = ptr.to_node(source_file.syntax()) { - return Some(RangeInfo::new(range, (binding, analyzer))); - } - } - None - } + Some(RangeInfo::new(range, ReferenceSearchResult { declaration, references })) } -pub(crate) fn rename( +fn find_name<'a>( db: &RootDatabase, - position: FilePosition, - new_name: &str, -) -> Option> { - let parse = db.parse(position.file_id); - if let Some((ast_name, ast_module)) = - find_name_and_module_at_offset(parse.tree().syntax(), position) - { - let range = ast_name.syntax().text_range(); - rename_mod(db, &ast_name, &ast_module, position, new_name) - .map(|info| RangeInfo::new(range, info)) - } else { - rename_reference(db, position, new_name) - } -} - -fn find_name_and_module_at_offset( syntax: &SyntaxNode, position: FilePosition, -) -> Option<(ast::Name, ast::Module)> { - let ast_name = find_node_at_offset::(syntax, position.offset)?; - let ast_module = ast::Module::cast(ast_name.syntax().parent()?)?; - Some((ast_name, ast_module)) -} - -fn source_edit_from_file_id_range( - file_id: FileId, - range: TextRange, - new_name: &str, -) -> SourceFileEdit { - SourceFileEdit { - file_id, - edit: { - let mut builder = ra_text_edit::TextEditBuilder::default(); - builder.replace(range, new_name.into()); - builder.finish() - }, +) -> Option> { + if let Some(name) = find_node_at_offset::(&syntax, position.offset) { + let def = classify_name(db, position.file_id, &name)?; + let range = name.syntax().text_range(); + return Some(RangeInfo::new(range, (name.text().to_string(), def))); } + let name_ref = find_node_at_offset::(&syntax, position.offset)?; + let def = classify_name_ref(db, position.file_id, &name_ref)?; + let range = name_ref.syntax().text_range(); + Some(RangeInfo::new(range, (name_ref.text().to_string(), def))) } -fn rename_mod( - db: &RootDatabase, - ast_name: &ast::Name, - ast_module: &ast::Module, - position: FilePosition, - new_name: &str, -) -> Option { - let mut source_file_edits = Vec::new(); - let mut file_system_edits = Vec::new(); - let module_src = hir::Source { file_id: position.file_id.into(), ast: ast_module.clone() }; - if let Some(module) = hir::Module::from_declaration(db, module_src) { - let src = module.definition_source(db); - let file_id = src.file_id.original_file(db); - match src.ast { - ModuleSource::SourceFile(..) => { - let mod_path: RelativePathBuf = db.file_relative_path(file_id); - // mod is defined in path/to/dir/mod.rs - let dst_path = if mod_path.file_stem() == Some("mod") { - mod_path - .parent() - .and_then(|p| p.parent()) - .or_else(|| Some(RelativePath::new(""))) - .map(|p| p.join(new_name).join("mod.rs")) - } else { - Some(mod_path.with_file_name(new_name).with_extension("rs")) - }; - if let Some(path) = dst_path { - let move_file = FileSystemEdit::MoveFile { - src: file_id, - dst_source_root: db.file_source_root(position.file_id), - dst_path: path, - }; - file_system_edits.push(move_file); +fn process_definition(db: &RootDatabase, def: NameDefinition, name: String) -> Vec { + let pat = name.as_str(); + let scope = def.search_scope(db); + let mut refs = vec![]; + + for (file_id, search_range) in scope { + let text = db.file_text(file_id); + let parse = Lazy::new(|| SourceFile::parse(&text)); + + for (idx, _) in text.match_indices(pat) { + let offset = TextUnit::from_usize(idx); + + if let Some(name_ref) = + find_node_at_offset::(parse.tree().syntax(), offset) + { + let range = name_ref.syntax().text_range(); + if let Some(search_range) = search_range { + if !range.is_subrange(&search_range) { + continue; + } + } + if let Some(d) = classify_name_ref(db, file_id, &name_ref) { + if d == def { + refs.push(FileRange { file_id, range }); + } } } - ModuleSource::Module(..) => {} } } - - let edit = SourceFileEdit { - file_id: position.file_id, - edit: { - let mut builder = ra_text_edit::TextEditBuilder::default(); - builder.replace(ast_name.syntax().text_range(), new_name.into()); - builder.finish() - }, - }; - source_file_edits.push(edit); - - Some(SourceChange::from_edits("rename", source_file_edits, file_system_edits)) -} - -fn rename_reference( - db: &RootDatabase, - position: FilePosition, - new_name: &str, -) -> Option> { - let RangeInfo { range, info: refs } = find_all_refs(db, position)?; - - let edit = refs - .into_iter() - .map(|range| source_edit_from_file_id_range(range.file_id, range.range, new_name)) - .collect::>(); - - if edit.is_empty() { - return None; - } - - Some(RangeInfo::new(range, SourceChange::source_file_edits("rename", edit))) + refs } #[cfg(test)] mod tests { use crate::{ - mock_analysis::analysis_and_position, mock_analysis::single_file_with_position, FileId, + mock_analysis::analysis_and_position, mock_analysis::single_file_with_position, ReferenceSearchResult, }; - use insta::assert_debug_snapshot; - use test_utils::assert_eq_text; #[test] fn test_find_all_refs_for_local() { @@ -249,211 +186,144 @@ mod tests { assert_eq!(refs.len(), 2); } + #[test] + fn test_find_all_refs_field_name() { + let code = r#" + //- /lib.rs + struct Foo { + pub spam<|>: u32, + } + + fn main(s: Foo) { + let f = s.spam; + } + "#; + + let refs = get_all_refs(code); + assert_eq!(refs.len(), 2); + } + + #[test] + fn test_find_all_refs_impl_item_name() { + let code = r#" + //- /lib.rs + struct Foo; + impl Foo { + fn f<|>(&self) { } + } + "#; + + let refs = get_all_refs(code); + assert_eq!(refs.len(), 1); + } + + #[test] + fn test_find_all_refs_enum_var_name() { + let code = r#" + //- /lib.rs + enum Foo { + A, + B<|>, + C, + } + "#; + + let refs = get_all_refs(code); + assert_eq!(refs.len(), 1); + } + + #[test] + fn test_find_all_refs_two_modules() { + let code = r#" + //- /lib.rs + pub mod foo; + pub mod bar; + + fn f() { + let i = foo::Foo { n: 5 }; + } + + //- /foo.rs + use crate::bar; + + pub struct Foo { + pub n: u32, + } + + fn f() { + let i = bar::Bar { n: 5 }; + } + + //- /bar.rs + use crate::foo; + + pub struct Bar { + pub n: u32, + } + + fn f() { + let i = foo::Foo<|> { n: 5 }; + } + "#; + + let (analysis, pos) = analysis_and_position(code); + let refs = analysis.find_all_refs(pos).unwrap().unwrap(); + assert_eq!(refs.len(), 3); + } + + // `mod foo;` is not in the results because `foo` is an `ast::Name`. + // So, there are two references: the first one is a definition of the `foo` module, + // which is the whole `foo.rs`, and the second one is in `use foo::Foo`. + #[test] + fn test_find_all_refs_decl_module() { + let code = r#" + //- /lib.rs + mod foo<|>; + + use foo::Foo; + + fn f() { + let i = Foo { n: 5 }; + } + + //- /foo.rs + pub struct Foo { + pub n: u32, + } + "#; + + let (analysis, pos) = analysis_and_position(code); + let refs = analysis.find_all_refs(pos).unwrap().unwrap(); + assert_eq!(refs.len(), 2); + } + + #[test] + fn test_find_all_refs_super_mod_vis() { + let code = r#" + //- /lib.rs + mod foo; + + //- /foo.rs + mod some; + use some::Foo; + + fn f() { + let i = Foo { n: 5 }; + } + + //- /foo/some.rs + pub(super) struct Foo<|> { + pub n: u32, + } + "#; + + let (analysis, pos) = analysis_and_position(code); + let refs = analysis.find_all_refs(pos).unwrap().unwrap(); + assert_eq!(refs.len(), 3); + } + fn get_all_refs(text: &str) -> ReferenceSearchResult { let (analysis, position) = single_file_with_position(text); analysis.find_all_refs(position).unwrap().unwrap() } - - #[test] - fn test_rename_for_local() { - test_rename( - r#" - fn main() { - let mut i = 1; - let j = 1; - i = i<|> + j; - - { - i = 0; - } - - i = 5; - }"#, - "k", - r#" - fn main() { - let mut k = 1; - let j = 1; - k = k + j; - - { - k = 0; - } - - k = 5; - }"#, - ); - } - - #[test] - fn test_rename_for_param_inside() { - test_rename( - r#" - fn foo(i : u32) -> u32 { - i<|> - }"#, - "j", - r#" - fn foo(j : u32) -> u32 { - j - }"#, - ); - } - - #[test] - fn test_rename_refs_for_fn_param() { - test_rename( - r#" - fn foo(i<|> : u32) -> u32 { - i - }"#, - "new_name", - r#" - fn foo(new_name : u32) -> u32 { - new_name - }"#, - ); - } - - #[test] - fn test_rename_for_mut_param() { - test_rename( - r#" - fn foo(mut i<|> : u32) -> u32 { - i - }"#, - "new_name", - r#" - fn foo(mut new_name : u32) -> u32 { - new_name - }"#, - ); - } - - #[test] - fn test_rename_mod() { - let (analysis, position) = analysis_and_position( - " - //- /lib.rs - mod bar; - - //- /bar.rs - mod foo<|>; - - //- /bar/foo.rs - // emtpy - ", - ); - let new_name = "foo2"; - let source_change = analysis.rename(position, new_name).unwrap(); - assert_debug_snapshot!(&source_change, -@r###" - Some( - RangeInfo { - range: [4; 7), - info: SourceChange { - label: "rename", - source_file_edits: [ - SourceFileEdit { - file_id: FileId( - 2, - ), - edit: TextEdit { - atoms: [ - AtomTextEdit { - delete: [4; 7), - insert: "foo2", - }, - ], - }, - }, - ], - file_system_edits: [ - MoveFile { - src: FileId( - 3, - ), - dst_source_root: SourceRootId( - 0, - ), - dst_path: "bar/foo2.rs", - }, - ], - cursor_position: None, - }, - }, - ) - "###); - } - - #[test] - fn test_rename_mod_in_dir() { - let (analysis, position) = analysis_and_position( - " - //- /lib.rs - mod fo<|>o; - //- /foo/mod.rs - // emtpy - ", - ); - let new_name = "foo2"; - let source_change = analysis.rename(position, new_name).unwrap(); - assert_debug_snapshot!(&source_change, - @r###" - Some( - RangeInfo { - range: [4; 7), - info: SourceChange { - label: "rename", - source_file_edits: [ - SourceFileEdit { - file_id: FileId( - 1, - ), - edit: TextEdit { - atoms: [ - AtomTextEdit { - delete: [4; 7), - insert: "foo2", - }, - ], - }, - }, - ], - file_system_edits: [ - MoveFile { - src: FileId( - 2, - ), - dst_source_root: SourceRootId( - 0, - ), - dst_path: "foo2/mod.rs", - }, - ], - cursor_position: None, - }, - }, - ) - "### - ); - } - - fn test_rename(text: &str, new_name: &str, expected: &str) { - let (analysis, position) = single_file_with_position(text); - let source_change = analysis.rename(position, new_name).unwrap(); - let mut text_edit_builder = ra_text_edit::TextEditBuilder::default(); - let mut file_id: Option = None; - if let Some(change) = source_change { - for edit in change.info.source_file_edits { - file_id = Some(edit.file_id); - for atom in edit.edit.as_atoms() { - text_edit_builder.replace(atom.delete, atom.insert.clone()); - } - } - } - let result = - text_edit_builder.finish().apply(&*analysis.file_text(file_id.unwrap()).unwrap()); - assert_eq_text!(expected, &*result); - } } diff --git a/crates/ra_ide_api/src/references/classify.rs b/crates/ra_ide_api/src/references/classify.rs new file mode 100644 index 00000000000..c8daff9b1bd --- /dev/null +++ b/crates/ra_ide_api/src/references/classify.rs @@ -0,0 +1,179 @@ +//! Functions that are used to classify an element from its definition or reference. + +use hir::{Either, FromSource, Module, ModuleSource, Path, PathResolution, Source, SourceAnalyzer}; +use ra_db::FileId; +use ra_syntax::{ast, match_ast, AstNode, AstPtr}; +use test_utils::tested_by; + +use super::{ + name_definition::{from_assoc_item, from_module_def, from_pat, from_struct_field}, + NameDefinition, NameKind, +}; +use crate::db::RootDatabase; + +pub(crate) fn classify_name( + db: &RootDatabase, + file_id: FileId, + name: &ast::Name, +) -> Option { + let parent = name.syntax().parent()?; + let file_id = file_id.into(); + + // FIXME: add ast::MacroCall(it) + match_ast! { + match parent { + ast::BindPat(it) => { + from_pat(db, file_id, AstPtr::new(&it)) + }, + ast::RecordFieldDef(it) => { + let ast = hir::FieldSource::Named(it); + let src = hir::Source { file_id, ast }; + let field = hir::StructField::from_source(db, src)?; + Some(from_struct_field(db, field)) + }, + ast::Module(it) => { + let def = { + if !it.has_semi() { + let ast = hir::ModuleSource::Module(it); + let src = hir::Source { file_id, ast }; + hir::Module::from_definition(db, src) + } else { + let src = hir::Source { file_id, ast: it }; + hir::Module::from_declaration(db, src) + } + }?; + Some(from_module_def(db, def.into(), None)) + }, + ast::StructDef(it) => { + let src = hir::Source { file_id, ast: it }; + let def = hir::Struct::from_source(db, src)?; + Some(from_module_def(db, def.into(), None)) + }, + ast::EnumDef(it) => { + let src = hir::Source { file_id, ast: it }; + let def = hir::Enum::from_source(db, src)?; + Some(from_module_def(db, def.into(), None)) + }, + ast::TraitDef(it) => { + let src = hir::Source { file_id, ast: it }; + let def = hir::Trait::from_source(db, src)?; + Some(from_module_def(db, def.into(), None)) + }, + ast::StaticDef(it) => { + let src = hir::Source { file_id, ast: it }; + let def = hir::Static::from_source(db, src)?; + Some(from_module_def(db, def.into(), None)) + }, + ast::EnumVariant(it) => { + let src = hir::Source { file_id, ast: it }; + let def = hir::EnumVariant::from_source(db, src)?; + Some(from_module_def(db, def.into(), None)) + }, + ast::FnDef(it) => { + let src = hir::Source { file_id, ast: it }; + let def = hir::Function::from_source(db, src)?; + if parent.parent().and_then(ast::ItemList::cast).is_some() { + Some(from_assoc_item(db, def.into())) + } else { + Some(from_module_def(db, def.into(), None)) + } + }, + ast::ConstDef(it) => { + let src = hir::Source { file_id, ast: it }; + let def = hir::Const::from_source(db, src)?; + if parent.parent().and_then(ast::ItemList::cast).is_some() { + Some(from_assoc_item(db, def.into())) + } else { + Some(from_module_def(db, def.into(), None)) + } + }, + ast::TypeAliasDef(it) => { + let src = hir::Source { file_id, ast: it }; + let def = hir::TypeAlias::from_source(db, src)?; + if parent.parent().and_then(ast::ItemList::cast).is_some() { + Some(from_assoc_item(db, def.into())) + } else { + Some(from_module_def(db, def.into(), None)) + } + }, + _ => None, + } + } +} + +pub(crate) fn classify_name_ref( + db: &RootDatabase, + file_id: FileId, + name_ref: &ast::NameRef, +) -> Option { + use PathResolution::*; + + let parent = name_ref.syntax().parent()?; + let analyzer = SourceAnalyzer::new(db, file_id, name_ref.syntax(), None); + + if let Some(method_call) = ast::MethodCallExpr::cast(parent.clone()) { + tested_by!(goto_definition_works_for_methods); + if let Some(func) = analyzer.resolve_method_call(&method_call) { + return Some(from_assoc_item(db, func.into())); + } + } + + if let Some(field_expr) = ast::FieldExpr::cast(parent.clone()) { + tested_by!(goto_definition_works_for_fields); + if let Some(field) = analyzer.resolve_field(&field_expr) { + return Some(from_struct_field(db, field)); + } + } + + if let Some(record_field) = ast::RecordField::cast(parent.clone()) { + tested_by!(goto_definition_works_for_record_fields); + if let Some(record_lit) = record_field.syntax().ancestors().find_map(ast::RecordLit::cast) { + let variant_def = analyzer.resolve_record_literal(&record_lit)?; + let hir_path = Path::from_name_ref(name_ref); + let hir_name = hir_path.as_ident()?; + let field = variant_def.field(db, hir_name)?; + return Some(from_struct_field(db, field)); + } + } + + let ast = ModuleSource::from_child_node(db, file_id, &parent); + let file_id = file_id.into(); + // FIXME: find correct container and visibility for each case + let container = Module::from_definition(db, Source { file_id, ast })?; + let visibility = None; + + if let Some(macro_call) = parent.ancestors().find_map(ast::MacroCall::cast) { + tested_by!(goto_definition_works_for_macros); + if let Some(macro_def) = analyzer.resolve_macro_call(db, ¯o_call) { + let kind = NameKind::Macro(macro_def); + return Some(NameDefinition { kind, container, visibility }); + } + } + + let path = name_ref.syntax().ancestors().find_map(ast::Path::cast)?; + let resolved = analyzer.resolve_path(db, &path)?; + match resolved { + Def(def) => Some(from_module_def(db, def, Some(container))), + AssocItem(item) => Some(from_assoc_item(db, item)), + LocalBinding(Either::A(pat)) => from_pat(db, file_id, pat), + LocalBinding(Either::B(par)) => { + let kind = NameKind::SelfParam(par); + Some(NameDefinition { kind, container, visibility }) + } + GenericParam(par) => { + // FIXME: get generic param def + let kind = NameKind::GenericParam(par); + Some(NameDefinition { kind, container, visibility }) + } + Macro(def) => { + let kind = NameKind::Macro(def); + Some(NameDefinition { kind, container, visibility }) + } + SelfType(impl_block) => { + let ty = impl_block.target_ty(db); + let kind = NameKind::SelfType(ty); + let container = impl_block.module(); + Some(NameDefinition { kind, container, visibility }) + } + } +} diff --git a/crates/ra_ide_api/src/references/name_definition.rs b/crates/ra_ide_api/src/references/name_definition.rs new file mode 100644 index 00000000000..4580bc78995 --- /dev/null +++ b/crates/ra_ide_api/src/references/name_definition.rs @@ -0,0 +1,113 @@ +//! `NameDefinition` keeps information about the element we want to search references for. +//! The element is represented by `NameKind`. It's located inside some `container` and +//! has a `visibility`, which defines a search scope. +//! Note that the reference search is possible for not all of the classified items. + +use hir::{ + db::AstDatabase, Adt, AssocItem, DefWithBody, FromSource, HasSource, HirFileId, MacroDef, + Module, ModuleDef, StructField, Ty, VariantDef, +}; +use ra_syntax::{ast, ast::VisibilityOwner, match_ast, AstNode, AstPtr}; + +use crate::db::RootDatabase; + +#[derive(Debug, PartialEq, Eq)] +pub enum NameKind { + Macro(MacroDef), + Field(StructField), + AssocItem(AssocItem), + Def(ModuleDef), + SelfType(Ty), + Pat((DefWithBody, AstPtr)), + SelfParam(AstPtr), + GenericParam(u32), +} + +#[derive(PartialEq, Eq)] +pub(crate) struct NameDefinition { + pub visibility: Option, + pub container: Module, + pub kind: NameKind, +} + +pub(super) fn from_pat( + db: &RootDatabase, + file_id: HirFileId, + pat: AstPtr, +) -> Option { + let root = db.parse_or_expand(file_id)?; + let def = pat.to_node(&root).syntax().ancestors().find_map(|node| { + match_ast! { + match node { + ast::FnDef(it) => { + let src = hir::Source { file_id, ast: it }; + Some(hir::Function::from_source(db, src)?.into()) + }, + ast::ConstDef(it) => { + let src = hir::Source { file_id, ast: it }; + Some(hir::Const::from_source(db, src)?.into()) + }, + ast::StaticDef(it) => { + let src = hir::Source { file_id, ast: it }; + Some(hir::Static::from_source(db, src)?.into()) + }, + _ => None, + } + } + })?; + let kind = NameKind::Pat((def, pat)); + let container = def.module(db); + Some(NameDefinition { kind, container, visibility: None }) +} + +pub(super) fn from_assoc_item(db: &RootDatabase, item: AssocItem) -> NameDefinition { + let container = item.module(db); + let visibility = match item { + AssocItem::Function(f) => f.source(db).ast.visibility(), + AssocItem::Const(c) => c.source(db).ast.visibility(), + AssocItem::TypeAlias(a) => a.source(db).ast.visibility(), + }; + let kind = NameKind::AssocItem(item); + NameDefinition { kind, container, visibility } +} + +pub(super) fn from_struct_field(db: &RootDatabase, field: StructField) -> NameDefinition { + let kind = NameKind::Field(field); + let parent = field.parent_def(db); + let container = parent.module(db); + let visibility = match parent { + VariantDef::Struct(s) => s.source(db).ast.visibility(), + VariantDef::EnumVariant(e) => e.source(db).ast.parent_enum().visibility(), + }; + NameDefinition { kind, container, visibility } +} + +pub(super) fn from_module_def( + db: &RootDatabase, + def: ModuleDef, + module: Option, +) -> NameDefinition { + let kind = NameKind::Def(def); + let (container, visibility) = match def { + ModuleDef::Module(it) => { + let container = it.parent(db).or_else(|| Some(it)).unwrap(); + let visibility = it.declaration_source(db).and_then(|s| s.ast.visibility()); + (container, visibility) + } + ModuleDef::EnumVariant(it) => { + let container = it.module(db); + let visibility = it.source(db).ast.parent_enum().visibility(); + (container, visibility) + } + ModuleDef::Function(it) => (it.module(db), it.source(db).ast.visibility()), + ModuleDef::Const(it) => (it.module(db), it.source(db).ast.visibility()), + ModuleDef::Static(it) => (it.module(db), it.source(db).ast.visibility()), + ModuleDef::Trait(it) => (it.module(db), it.source(db).ast.visibility()), + ModuleDef::TypeAlias(it) => (it.module(db), it.source(db).ast.visibility()), + ModuleDef::Adt(Adt::Struct(it)) => (it.module(db), it.source(db).ast.visibility()), + ModuleDef::Adt(Adt::Union(it)) => (it.module(db), it.source(db).ast.visibility()), + ModuleDef::Adt(Adt::Enum(it)) => (it.module(db), it.source(db).ast.visibility()), + ModuleDef::BuiltinType(..) => (module.unwrap(), None), + }; + NameDefinition { kind, container, visibility } +} diff --git a/crates/ra_ide_api/src/references/rename.rs b/crates/ra_ide_api/src/references/rename.rs new file mode 100644 index 00000000000..0e2e088e0a9 --- /dev/null +++ b/crates/ra_ide_api/src/references/rename.rs @@ -0,0 +1,469 @@ +//! FIXME: write short doc here + +use hir::ModuleSource; +use ra_db::{SourceDatabase, SourceDatabaseExt}; +use ra_syntax::{algo::find_node_at_offset, ast, AstNode, SyntaxNode}; +use relative_path::{RelativePath, RelativePathBuf}; + +use crate::{ + db::RootDatabase, FileId, FilePosition, FileSystemEdit, RangeInfo, SourceChange, + SourceFileEdit, TextRange, +}; + +use super::find_all_refs; + +pub(crate) fn rename( + db: &RootDatabase, + position: FilePosition, + new_name: &str, +) -> Option> { + let parse = db.parse(position.file_id); + if let Some((ast_name, ast_module)) = + find_name_and_module_at_offset(parse.tree().syntax(), position) + { + let range = ast_name.syntax().text_range(); + rename_mod(db, &ast_name, &ast_module, position, new_name) + .map(|info| RangeInfo::new(range, info)) + } else { + rename_reference(db, position, new_name) + } +} + +fn find_name_and_module_at_offset( + syntax: &SyntaxNode, + position: FilePosition, +) -> Option<(ast::Name, ast::Module)> { + let ast_name = find_node_at_offset::(syntax, position.offset)?; + let ast_module = ast::Module::cast(ast_name.syntax().parent()?)?; + Some((ast_name, ast_module)) +} + +fn source_edit_from_file_id_range( + file_id: FileId, + range: TextRange, + new_name: &str, +) -> SourceFileEdit { + SourceFileEdit { + file_id, + edit: { + let mut builder = ra_text_edit::TextEditBuilder::default(); + builder.replace(range, new_name.into()); + builder.finish() + }, + } +} + +fn rename_mod( + db: &RootDatabase, + ast_name: &ast::Name, + ast_module: &ast::Module, + position: FilePosition, + new_name: &str, +) -> Option { + let mut source_file_edits = Vec::new(); + let mut file_system_edits = Vec::new(); + let module_src = hir::Source { file_id: position.file_id.into(), ast: ast_module.clone() }; + if let Some(module) = hir::Module::from_declaration(db, module_src) { + let src = module.definition_source(db); + let file_id = src.file_id.original_file(db); + match src.ast { + ModuleSource::SourceFile(..) => { + let mod_path: RelativePathBuf = db.file_relative_path(file_id); + // mod is defined in path/to/dir/mod.rs + let dst_path = if mod_path.file_stem() == Some("mod") { + mod_path + .parent() + .and_then(|p| p.parent()) + .or_else(|| Some(RelativePath::new(""))) + .map(|p| p.join(new_name).join("mod.rs")) + } else { + Some(mod_path.with_file_name(new_name).with_extension("rs")) + }; + if let Some(path) = dst_path { + let move_file = FileSystemEdit::MoveFile { + src: file_id, + dst_source_root: db.file_source_root(position.file_id), + dst_path: path, + }; + file_system_edits.push(move_file); + } + } + ModuleSource::Module(..) => {} + } + } + + let edit = SourceFileEdit { + file_id: position.file_id, + edit: { + let mut builder = ra_text_edit::TextEditBuilder::default(); + builder.replace(ast_name.syntax().text_range(), new_name.into()); + builder.finish() + }, + }; + source_file_edits.push(edit); + + Some(SourceChange::from_edits("rename", source_file_edits, file_system_edits)) +} + +fn rename_reference( + db: &RootDatabase, + position: FilePosition, + new_name: &str, +) -> Option> { + let RangeInfo { range, info: refs } = find_all_refs(db, position)?; + + let edit = refs + .into_iter() + .map(|range| source_edit_from_file_id_range(range.file_id, range.range, new_name)) + .collect::>(); + + if edit.is_empty() { + return None; + } + + Some(RangeInfo::new(range, SourceChange::source_file_edits("rename", edit))) +} + +#[cfg(test)] +mod tests { + use crate::{ + mock_analysis::analysis_and_position, mock_analysis::single_file_with_position, FileId, + ReferenceSearchResult, + }; + use insta::assert_debug_snapshot; + use test_utils::assert_eq_text; + + #[test] + fn test_find_all_refs_for_local() { + let code = r#" + fn main() { + let mut i = 1; + let j = 1; + i = i<|> + j; + + { + i = 0; + } + + i = 5; + }"#; + + let refs = get_all_refs(code); + assert_eq!(refs.len(), 5); + } + + #[test] + fn test_find_all_refs_for_param_inside() { + let code = r#" + fn foo(i : u32) -> u32 { + i<|> + }"#; + + let refs = get_all_refs(code); + assert_eq!(refs.len(), 2); + } + + #[test] + fn test_find_all_refs_for_fn_param() { + let code = r#" + fn foo(i<|> : u32) -> u32 { + i + }"#; + + let refs = get_all_refs(code); + assert_eq!(refs.len(), 2); + } + + #[test] + fn test_find_all_refs_field_name() { + let code = r#" + //- /lib.rs + struct Foo { + pub spam<|>: u32, + } + + fn main(s: Foo) { + let f = s.spam; + } + "#; + + let refs = get_all_refs(code); + assert_eq!(refs.len(), 2); + } + + #[test] + fn test_find_all_refs_impl_item_name() { + let code = r#" + //- /lib.rs + struct Foo; + impl Foo { + fn f<|>(&self) { } + } + "#; + + let refs = get_all_refs(code); + assert_eq!(refs.len(), 1); + } + + #[test] + fn test_find_all_refs_enum_var_name() { + let code = r#" + //- /lib.rs + enum Foo { + A, + B<|>, + C, + } + "#; + + let refs = get_all_refs(code); + assert_eq!(refs.len(), 1); + } + + #[test] + fn test_find_all_refs_modules() { + let code = r#" + //- /lib.rs + pub mod foo; + pub mod bar; + + fn f() { + let i = foo::Foo { n: 5 }; + } + + //- /foo.rs + use crate::bar; + + pub struct Foo { + pub n: u32, + } + + fn f() { + let i = bar::Bar { n: 5 }; + } + + //- /bar.rs + use crate::foo; + + pub struct Bar { + pub n: u32, + } + + fn f() { + let i = foo::Foo<|> { n: 5 }; + } + "#; + + let (analysis, pos) = analysis_and_position(code); + let refs = analysis.find_all_refs(pos).unwrap().unwrap(); + assert_eq!(refs.len(), 3); + } + + fn get_all_refs(text: &str) -> ReferenceSearchResult { + let (analysis, position) = single_file_with_position(text); + analysis.find_all_refs(position).unwrap().unwrap() + } + + #[test] + fn test_rename_for_local() { + test_rename( + r#" + fn main() { + let mut i = 1; + let j = 1; + i = i<|> + j; + + { + i = 0; + } + + i = 5; + }"#, + "k", + r#" + fn main() { + let mut k = 1; + let j = 1; + k = k + j; + + { + k = 0; + } + + k = 5; + }"#, + ); + } + + #[test] + fn test_rename_for_param_inside() { + test_rename( + r#" + fn foo(i : u32) -> u32 { + i<|> + }"#, + "j", + r#" + fn foo(j : u32) -> u32 { + j + }"#, + ); + } + + #[test] + fn test_rename_refs_for_fn_param() { + test_rename( + r#" + fn foo(i<|> : u32) -> u32 { + i + }"#, + "new_name", + r#" + fn foo(new_name : u32) -> u32 { + new_name + }"#, + ); + } + + #[test] + fn test_rename_for_mut_param() { + test_rename( + r#" + fn foo(mut i<|> : u32) -> u32 { + i + }"#, + "new_name", + r#" + fn foo(mut new_name : u32) -> u32 { + new_name + }"#, + ); + } + + #[test] + fn test_rename_mod() { + let (analysis, position) = analysis_and_position( + " + //- /lib.rs + mod bar; + + //- /bar.rs + mod foo<|>; + + //- /bar/foo.rs + // emtpy + ", + ); + let new_name = "foo2"; + let source_change = analysis.rename(position, new_name).unwrap(); + assert_debug_snapshot!(&source_change, +@r###" + Some( + RangeInfo { + range: [4; 7), + info: SourceChange { + label: "rename", + source_file_edits: [ + SourceFileEdit { + file_id: FileId( + 2, + ), + edit: TextEdit { + atoms: [ + AtomTextEdit { + delete: [4; 7), + insert: "foo2", + }, + ], + }, + }, + ], + file_system_edits: [ + MoveFile { + src: FileId( + 3, + ), + dst_source_root: SourceRootId( + 0, + ), + dst_path: "bar/foo2.rs", + }, + ], + cursor_position: None, + }, + }, + ) + "###); + } + + #[test] + fn test_rename_mod_in_dir() { + let (analysis, position) = analysis_and_position( + " + //- /lib.rs + mod fo<|>o; + //- /foo/mod.rs + // emtpy + ", + ); + let new_name = "foo2"; + let source_change = analysis.rename(position, new_name).unwrap(); + assert_debug_snapshot!(&source_change, + @r###" + Some( + RangeInfo { + range: [4; 7), + info: SourceChange { + label: "rename", + source_file_edits: [ + SourceFileEdit { + file_id: FileId( + 1, + ), + edit: TextEdit { + atoms: [ + AtomTextEdit { + delete: [4; 7), + insert: "foo2", + }, + ], + }, + }, + ], + file_system_edits: [ + MoveFile { + src: FileId( + 2, + ), + dst_source_root: SourceRootId( + 0, + ), + dst_path: "foo2/mod.rs", + }, + ], + cursor_position: None, + }, + }, + ) + "### + ); + } + + fn test_rename(text: &str, new_name: &str, expected: &str) { + let (analysis, position) = single_file_with_position(text); + let source_change = analysis.rename(position, new_name).unwrap(); + let mut text_edit_builder = ra_text_edit::TextEditBuilder::default(); + let mut file_id: Option = None; + if let Some(change) = source_change { + for edit in change.info.source_file_edits { + file_id = Some(edit.file_id); + for atom in edit.edit.as_atoms() { + text_edit_builder.replace(atom.delete, atom.insert.clone()); + } + } + } + let result = + text_edit_builder.finish().apply(&*analysis.file_text(file_id.unwrap()).unwrap()); + assert_eq_text!(expected, &*result); + } +} diff --git a/crates/ra_ide_api/src/references/search_scope.rs b/crates/ra_ide_api/src/references/search_scope.rs new file mode 100644 index 00000000000..5cb69b8fcd1 --- /dev/null +++ b/crates/ra_ide_api/src/references/search_scope.rs @@ -0,0 +1,92 @@ +//! Generally, `search_scope` returns files that might contain references for the element. +//! For `pub(crate)` things it's a crate, for `pub` things it's a crate and dependant crates. +//! In some cases, the location of the references is known to within a `TextRange`, +//! e.g. for things like local variables. + +use hir::{DefWithBody, HasSource, ModuleSource}; +use ra_db::{FileId, SourceDatabase, SourceDatabaseExt}; +use ra_syntax::{AstNode, TextRange}; +use rustc_hash::FxHashSet; + +use crate::db::RootDatabase; + +use super::{NameDefinition, NameKind}; + +impl NameDefinition { + pub(crate) fn search_scope(&self, db: &RootDatabase) -> FxHashSet<(FileId, Option)> { + let module_src = self.container.definition_source(db); + let file_id = module_src.file_id.original_file(db); + + if let NameKind::Pat((def, _)) = self.kind { + let mut res = FxHashSet::default(); + let range = match def { + DefWithBody::Function(f) => f.source(db).ast.syntax().text_range(), + DefWithBody::Const(c) => c.source(db).ast.syntax().text_range(), + DefWithBody::Static(s) => s.source(db).ast.syntax().text_range(), + }; + res.insert((file_id, Some(range))); + return res; + } + + let vis = + self.visibility.as_ref().map(|v| v.syntax().to_string()).unwrap_or("".to_string()); + + if vis.as_str() == "pub(super)" { + if let Some(parent_module) = self.container.parent(db) { + let mut files = FxHashSet::default(); + let parent_src = parent_module.definition_source(db); + let file_id = parent_src.file_id.original_file(db); + + match parent_src.ast { + ModuleSource::Module(m) => { + let range = Some(m.syntax().text_range()); + files.insert((file_id, range)); + } + ModuleSource::SourceFile(_) => { + files.insert((file_id, None)); + files.extend(parent_module.children(db).map(|m| { + let src = m.definition_source(db); + (src.file_id.original_file(db), None) + })); + } + } + return files; + } + } + + if vis.as_str() != "" { + let source_root_id = db.file_source_root(file_id); + let source_root = db.source_root(source_root_id); + let mut files = + source_root.walk().map(|id| (id.into(), None)).collect::>(); + + // FIXME: add "pub(in path)" + + if vis.as_str() == "pub(crate)" { + return files; + } + if vis.as_str() == "pub" { + let krate = self.container.krate(db).unwrap(); + let crate_graph = db.crate_graph(); + for crate_id in crate_graph.iter() { + let mut crate_deps = crate_graph.dependencies(crate_id); + if crate_deps.any(|dep| dep.crate_id() == krate.crate_id()) { + let root_file = crate_graph.crate_root(crate_id); + let source_root_id = db.file_source_root(root_file); + let source_root = db.source_root(source_root_id); + files.extend(source_root.walk().map(|id| (id.into(), None))); + } + } + return files; + } + } + + let mut res = FxHashSet::default(); + let range = match module_src.ast { + ModuleSource::Module(m) => Some(m.syntax().text_range()), + ModuleSource::SourceFile(_) => None, + }; + res.insert((file_id, range)); + res + } +} diff --git a/crates/ra_ide_api/src/syntax_highlighting.rs b/crates/ra_ide_api/src/syntax_highlighting.rs index 1d290387c41..33f3cacebee 100644 --- a/crates/ra_ide_api/src/syntax_highlighting.rs +++ b/crates/ra_ide_api/src/syntax_highlighting.rs @@ -14,7 +14,7 @@ use ra_syntax::{ use crate::{ db::RootDatabase, - name_ref_kind::{classify_name_ref, NameRefKind::*}, + references::{classify_name_ref, NameKind::*}, FileId, }; @@ -101,12 +101,10 @@ pub(crate) fn highlight(db: &RootDatabase, file_id: FileId) -> Vec "function", + let name_kind = classify_name_ref(db, file_id, &name_ref).map(|d| d.kind); + match name_kind { Some(Macro(_)) => "macro", - Some(FieldAccess(_)) => "field", + Some(Field(_)) => "field", Some(AssocItem(hir::AssocItem::Function(_))) => "function", Some(AssocItem(hir::AssocItem::Const(_))) => "constant", Some(AssocItem(hir::AssocItem::TypeAlias(_))) => "type", @@ -120,7 +118,7 @@ pub(crate) fn highlight(db: &RootDatabase, file_id: FileId) -> Vec "type", Some(Def(hir::ModuleDef::BuiltinType(_))) => "type", Some(SelfType(_)) => "type", - Some(Pat(ptr)) => { + Some(Pat((_, ptr))) => { let pat = ptr.to_node(&root); if let Some(name) = pat.name() { let text = name.text(); @@ -130,6 +128,8 @@ pub(crate) fn highlight(db: &RootDatabase, file_id: FileId) -> Vec Result>> { let position = params.text_document_position.try_conv_with(&world)?; - let line_index = world.analysis().file_line_index(position.file_id)?; let refs = match world.analysis().find_all_refs(position)? { None => return Ok(None), @@ -490,13 +489,19 @@ pub fn handle_references( let locations = if params.context.include_declaration { refs.into_iter() - .filter_map(|r| to_location(r.file_id, r.range, &world, &line_index).ok()) + .filter_map(|r| { + let line_index = world.analysis().file_line_index(r.file_id).ok()?; + to_location(r.file_id, r.range, &world, &line_index).ok() + }) .collect() } else { // Only iterate over the references if include_declaration was false refs.references() .iter() - .filter_map(|r| to_location(r.file_id, r.range, &world, &line_index).ok()) + .filter_map(|r| { + let line_index = world.analysis().file_line_index(r.file_id).ok()?; + to_location(r.file_id, r.range, &world, &line_index).ok() + }) .collect() }; @@ -746,6 +751,7 @@ pub fn handle_document_highlight( Ok(Some( refs.into_iter() + .filter(|r| r.file_id == file_id) .map(|r| DocumentHighlight { range: r.range.conv_with(&line_index), kind: None }) .collect(), ))