From d993f329a01deb3cdc011c3eb1dfd859302fec04 Mon Sep 17 00:00:00 2001 From: Jeremy Kolb Date: Sat, 4 Jan 2020 17:46:01 -0500 Subject: Basic DocumentHighlightKind support for assignments --- crates/ra_ide/src/lib.rs | 2 +- crates/ra_ide/src/references.rs | 73 ++++++++++++++++++++++++++++++++++++++--- 2 files changed, 69 insertions(+), 6 deletions(-) (limited to 'crates/ra_ide/src') diff --git a/crates/ra_ide/src/lib.rs b/crates/ra_ide/src/lib.rs index 7b187eba3..837315ca7 100644 --- a/crates/ra_ide/src/lib.rs +++ b/crates/ra_ide/src/lib.rs @@ -75,7 +75,7 @@ pub use crate::{ inlay_hints::{InlayHint, InlayKind}, line_index::{LineCol, LineIndex}, line_index_utils::translate_offset_with_edit, - references::{Reference, ReferenceKind, ReferenceSearchResult, SearchScope}, + references::{Reference, ReferenceAccess, ReferenceKind, ReferenceSearchResult, SearchScope}, runnables::{Runnable, RunnableKind}, source_change::{FileSystemEdit, SourceChange, SourceFileEdit}, syntax_highlighting::HighlightedRange, diff --git a/crates/ra_ide/src/references.rs b/crates/ra_ide/src/references.rs index 5a3ec4eb9..b9d8a6b1e 100644 --- a/crates/ra_ide/src/references.rs +++ b/crates/ra_ide/src/references.rs @@ -19,8 +19,8 @@ use once_cell::unsync::Lazy; use ra_db::{SourceDatabase, SourceDatabaseExt}; use ra_prof::profile; use ra_syntax::{ - algo::find_node_at_offset, ast, AstNode, SourceFile, SyntaxKind, SyntaxNode, TextUnit, - TokenAtOffset, + algo::find_node_at_offset, ast, match_ast, AstNode, SourceFile, SyntaxKind, SyntaxNode, + TextUnit, TokenAtOffset, }; use crate::{ @@ -46,6 +46,7 @@ pub struct ReferenceSearchResult { pub struct Reference { pub file_range: FileRange, pub kind: ReferenceKind, + pub access: Option, } #[derive(Debug, Clone, PartialEq)] @@ -54,6 +55,12 @@ pub enum ReferenceKind { Other, } +#[derive(Debug, Clone, PartialEq)] +pub enum ReferenceAccess { + Read, + Write, +} + impl ReferenceSearchResult { pub fn declaration(&self) -> &NavigationTarget { &self.declaration @@ -72,7 +79,7 @@ impl ReferenceSearchResult { } // allow turning ReferenceSearchResult into an iterator -// over FileRanges +// over References impl IntoIterator for ReferenceSearchResult { type Item = Reference; type IntoIter = std::vec::IntoIter; @@ -85,6 +92,7 @@ impl IntoIterator for ReferenceSearchResult { range: self.declaration.range(), }, kind: self.declaration_kind, + access: None, }); v.append(&mut self.references); v.into_iter() @@ -201,7 +209,13 @@ fn process_definition( } else { ReferenceKind::Other }; - refs.push(Reference { file_range: FileRange { file_id, range }, kind }); + let access = access_mode(d.kind, &name_ref); + + refs.push(Reference { + file_range: FileRange { file_id, range }, + kind, + access, + }); } } } @@ -210,11 +224,46 @@ fn process_definition( refs } +fn access_mode(kind: NameKind, name_ref: &ast::NameRef) -> Option { + match kind { + NameKind::Local(_) | NameKind::Field(_) => { + //LetExpr or BinExpr + name_ref.syntax().ancestors().find_map(|node| { + match_ast! { + match (node) { + ast::BinExpr(expr) => { + match expr.op_kind() { + Some(kind) if kind.is_assignment() => { + if let Some(lhs) = expr.lhs() { + if lhs.syntax().text_range() == name_ref.syntax().text_range() { + return Some(ReferenceAccess::Write); + } + } + + if let Some(rhs) = expr.rhs() { + if rhs.syntax().text_range().is_subrange(&name_ref.syntax().text_range()) { + return Some(ReferenceAccess::Read); + } + } + }, + _ => { return Some(ReferenceAccess::Read) }, + } + None + }, + _ => {None} + } + } + }) + } + _ => None, + } +} + #[cfg(test)] mod tests { use crate::{ mock_analysis::{analysis_and_position, single_file_with_position, MockAnalysis}, - Reference, ReferenceKind, ReferenceSearchResult, SearchScope, + Reference, ReferenceAccess, ReferenceKind, ReferenceSearchResult, SearchScope, }; #[test] @@ -515,6 +564,20 @@ mod tests { ); } + #[test] + fn test_basic_highlight_read() { + let code = r#" + fn foo() { + let i<|> = 0; + i = i + 1; + }"#; + + let refs = get_all_refs(code); + assert_eq!(refs.len(), 3); + assert_eq!(refs.references[0].access, Some(ReferenceAccess::Write)); + assert_eq!(refs.references[1].access, Some(ReferenceAccess::Read)); + } + fn get_all_refs(text: &str) -> ReferenceSearchResult { let (analysis, position) = single_file_with_position(text); analysis.find_all_refs(position, None).unwrap().unwrap() -- cgit v1.2.3 From 6c89d86ade392ddd4088ecdb444ea7bd002a3cff Mon Sep 17 00:00:00 2001 From: Jeremy Kolb Date: Sat, 4 Jan 2020 19:25:29 -0500 Subject: Tweaks --- crates/ra_ide/src/references.rs | 48 ++++++++++++++++++++++++++++++----------- 1 file changed, 35 insertions(+), 13 deletions(-) (limited to 'crates/ra_ide/src') diff --git a/crates/ra_ide/src/references.rs b/crates/ra_ide/src/references.rs index b9d8a6b1e..7d31ef6bd 100644 --- a/crates/ra_ide/src/references.rs +++ b/crates/ra_ide/src/references.rs @@ -232,22 +232,26 @@ fn access_mode(kind: NameKind, name_ref: &ast::NameRef) -> Option { - match expr.op_kind() { - Some(kind) if kind.is_assignment() => { - if let Some(lhs) = expr.lhs() { - if lhs.syntax().text_range() == name_ref.syntax().text_range() { - return Some(ReferenceAccess::Write); - } + if expr.op_kind()?.is_assignment() { + // If the variable or field ends on the LHS's end then it's a Write (covers fields and locals). + // FIXME: This is not terribly accurate. + if let Some(lhs) = expr.lhs() { + if lhs.syntax().text_range().end() == name_ref.syntax().text_range().end() { + return Some(ReferenceAccess::Write); + } else if name_ref.syntax().text_range().is_subrange(&lhs.syntax().text_range()) { + return Some(ReferenceAccess::Read); } + } - if let Some(rhs) = expr.rhs() { - if rhs.syntax().text_range().is_subrange(&name_ref.syntax().text_range()) { - return Some(ReferenceAccess::Read); - } + // If the variable is on the RHS then it's a Read. + if let Some(rhs) = expr.rhs() { + if name_ref.syntax().text_range().is_subrange(&rhs.syntax().text_range()) { + return Some(ReferenceAccess::Read); } - }, - _ => { return Some(ReferenceAccess::Read) }, + } } + + // Cannot determine access None }, _ => {None} @@ -565,7 +569,7 @@ mod tests { } #[test] - fn test_basic_highlight_read() { + fn test_basic_highlight_read_write() { let code = r#" fn foo() { let i<|> = 0; @@ -578,6 +582,24 @@ mod tests { assert_eq!(refs.references[1].access, Some(ReferenceAccess::Read)); } + #[test] + fn test_basic_highlight_field_read_write() { + let code = r#" + struct S { + f: u32, + } + + fn foo() { + let mut s = S{f: 0}; + s.f<|> = 0; + }"#; + + let refs = get_all_refs(code); + assert_eq!(refs.len(), 3); + //assert_eq!(refs.references[0].access, Some(ReferenceAccess::Write)); + assert_eq!(refs.references[1].access, Some(ReferenceAccess::Write)); + } + fn get_all_refs(text: &str) -> ReferenceSearchResult { let (analysis, position) = single_file_with_position(text); analysis.find_all_refs(position, None).unwrap().unwrap() -- cgit v1.2.3 From cc96ddfe695c2b3d2e9f28c3c5205a83a99a8ac3 Mon Sep 17 00:00:00 2001 From: kjeremy Date: Thu, 9 Jan 2020 16:01:43 -0500 Subject: Simplify and update tests to account for access --- crates/ra_ide/src/references.rs | 101 +++++++++++++++++++++------------------- 1 file changed, 53 insertions(+), 48 deletions(-) (limited to 'crates/ra_ide/src') diff --git a/crates/ra_ide/src/references.rs b/crates/ra_ide/src/references.rs index 7d31ef6bd..5b8ed370c 100644 --- a/crates/ra_ide/src/references.rs +++ b/crates/ra_ide/src/references.rs @@ -55,7 +55,7 @@ pub enum ReferenceKind { Other, } -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Copy, Clone, PartialEq)] pub enum ReferenceAccess { Read, Write, @@ -225,49 +225,41 @@ fn process_definition( } fn access_mode(kind: NameKind, name_ref: &ast::NameRef) -> Option { + // Only Locals and Fields have accesses for now. match kind { - NameKind::Local(_) | NameKind::Field(_) => { - //LetExpr or BinExpr - name_ref.syntax().ancestors().find_map(|node| { - match_ast! { - match (node) { - ast::BinExpr(expr) => { - if expr.op_kind()?.is_assignment() { - // If the variable or field ends on the LHS's end then it's a Write (covers fields and locals). - // FIXME: This is not terribly accurate. - if let Some(lhs) = expr.lhs() { - if lhs.syntax().text_range().end() == name_ref.syntax().text_range().end() { - return Some(ReferenceAccess::Write); - } else if name_ref.syntax().text_range().is_subrange(&lhs.syntax().text_range()) { - return Some(ReferenceAccess::Read); - } - } - - // If the variable is on the RHS then it's a Read. - if let Some(rhs) = expr.rhs() { - if name_ref.syntax().text_range().is_subrange(&rhs.syntax().text_range()) { - return Some(ReferenceAccess::Read); - } - } - } + NameKind::Local(_) | NameKind::Field(_) => {} + _ => return None, + }; - // Cannot determine access - None - }, - _ => {None} + let mode = name_ref.syntax().ancestors().find_map(|node| { + match_ast! { + match (node) { + ast::BinExpr(expr) => { + if expr.op_kind()?.is_assignment() { + // If the variable or field ends on the LHS's end then it's a Write (covers fields and locals). + // FIXME: This is not terribly accurate. + if let Some(lhs) = expr.lhs() { + if lhs.syntax().text_range().end() == name_ref.syntax().text_range().end() { + return Some(ReferenceAccess::Write); + } + } } - } - }) + return Some(ReferenceAccess::Read); + }, + _ => {None} + } } - _ => None, - } + }); + + // Default Locals and Fields to read + mode.or(Some(ReferenceAccess::Read)) } #[cfg(test)] mod tests { use crate::{ mock_analysis::{analysis_and_position, single_file_with_position, MockAnalysis}, - Reference, ReferenceAccess, ReferenceKind, ReferenceSearchResult, SearchScope, + Reference, ReferenceKind, ReferenceSearchResult, SearchScope, }; #[test] @@ -314,10 +306,10 @@ mod tests { "i BIND_PAT FileId(1) [33; 34)", ReferenceKind::Other, &[ - "FileId(1) [67; 68) Other", - "FileId(1) [71; 72) Other", - "FileId(1) [101; 102) Other", - "FileId(1) [127; 128) Other", + "FileId(1) [67; 68) Other Write", + "FileId(1) [71; 72) Other Read", + "FileId(1) [101; 102) Other Write", + "FileId(1) [127; 128) Other Write", ], ); } @@ -334,7 +326,7 @@ mod tests { refs, "i BIND_PAT FileId(1) [12; 13)", ReferenceKind::Other, - &["FileId(1) [38; 39) Other"], + &["FileId(1) [38; 39) Other Read"], ); } @@ -350,7 +342,7 @@ mod tests { refs, "i BIND_PAT FileId(1) [12; 13)", ReferenceKind::Other, - &["FileId(1) [38; 39) Other"], + &["FileId(1) [38; 39) Other Read"], ); } @@ -372,7 +364,7 @@ mod tests { refs, "spam RECORD_FIELD_DEF FileId(1) [66; 79) [70; 74)", ReferenceKind::Other, - &["FileId(1) [152; 156) Other"], + &["FileId(1) [152; 156) Other Read"], ); } @@ -577,9 +569,12 @@ mod tests { }"#; let refs = get_all_refs(code); - assert_eq!(refs.len(), 3); - assert_eq!(refs.references[0].access, Some(ReferenceAccess::Write)); - assert_eq!(refs.references[1].access, Some(ReferenceAccess::Read)); + check_result( + refs, + "i BIND_PAT FileId(1) [36; 37)", + ReferenceKind::Other, + &["FileId(1) [55; 56) Other Write", "FileId(1) [59; 60) Other Read"], + ); } #[test] @@ -595,9 +590,12 @@ mod tests { }"#; let refs = get_all_refs(code); - assert_eq!(refs.len(), 3); - //assert_eq!(refs.references[0].access, Some(ReferenceAccess::Write)); - assert_eq!(refs.references[1].access, Some(ReferenceAccess::Write)); + check_result( + refs, + "f RECORD_FIELD_DEF FileId(1) [32; 38) [32; 33)", + ReferenceKind::Other, + &["FileId(1) [96; 97) Other Read", "FileId(1) [117; 118) Other Write"], + ); } fn get_all_refs(text: &str) -> ReferenceSearchResult { @@ -620,7 +618,14 @@ mod tests { impl Reference { fn debug_render(&self) -> String { - format!("{:?} {:?} {:?}", self.file_range.file_id, self.file_range.range, self.kind) + let mut s = format!( + "{:?} {:?} {:?}", + self.file_range.file_id, self.file_range.range, self.kind + ); + if let Some(access) = self.access { + s.push_str(&format!(" {:?}", access)); + } + s } fn assert_match(&self, expected: &str) { -- cgit v1.2.3 From 49fd6a5228bfb2200611e6a9a9b24d01368bb279 Mon Sep 17 00:00:00 2001 From: kjeremy Date: Thu, 9 Jan 2020 16:27:10 -0500 Subject: Split Declaration out into it's own type --- crates/ra_ide/src/lib.rs | 4 +- crates/ra_ide/src/references.rs | 100 +++++++++++++++++++++------------------- 2 files changed, 55 insertions(+), 49 deletions(-) (limited to 'crates/ra_ide/src') diff --git a/crates/ra_ide/src/lib.rs b/crates/ra_ide/src/lib.rs index 837315ca7..4d8deb21c 100644 --- a/crates/ra_ide/src/lib.rs +++ b/crates/ra_ide/src/lib.rs @@ -75,7 +75,9 @@ pub use crate::{ inlay_hints::{InlayHint, InlayKind}, line_index::{LineCol, LineIndex}, line_index_utils::translate_offset_with_edit, - references::{Reference, ReferenceAccess, ReferenceKind, ReferenceSearchResult, SearchScope}, + references::{ + Declaration, Reference, ReferenceAccess, ReferenceKind, ReferenceSearchResult, SearchScope, + }, runnables::{Runnable, RunnableKind}, source_change::{FileSystemEdit, SourceChange, SourceFileEdit}, syntax_highlighting::HighlightedRange, diff --git a/crates/ra_ide/src/references.rs b/crates/ra_ide/src/references.rs index 5b8ed370c..2d20de1d0 100644 --- a/crates/ra_ide/src/references.rs +++ b/crates/ra_ide/src/references.rs @@ -37,11 +37,17 @@ pub use self::search_scope::SearchScope; #[derive(Debug, Clone)] pub struct ReferenceSearchResult { - declaration: NavigationTarget, - declaration_kind: ReferenceKind, + declaration: Declaration, references: Vec, } +#[derive(Debug, Clone)] +pub struct Declaration { + pub nav: NavigationTarget, + pub kind: ReferenceKind, + pub access: Option, +} + #[derive(Debug, Clone)] pub struct Reference { pub file_range: FileRange, @@ -62,10 +68,14 @@ pub enum ReferenceAccess { } impl ReferenceSearchResult { - pub fn declaration(&self) -> &NavigationTarget { + pub fn declaration(&self) -> &Declaration { &self.declaration } + pub fn decl_target(&self) -> &NavigationTarget { + &self.declaration.nav + } + pub fn references(&self) -> &[Reference] { &self.references } @@ -88,11 +98,11 @@ impl IntoIterator for ReferenceSearchResult { let mut v = Vec::with_capacity(self.len()); v.push(Reference { file_range: FileRange { - file_id: self.declaration.file_id(), - range: self.declaration.range(), + file_id: self.declaration.nav.file_id(), + range: self.declaration.nav.range(), }, - kind: self.declaration_kind, - access: None, + kind: self.declaration.kind, + access: self.declaration.access, }); v.append(&mut self.references); v.into_iter() @@ -139,15 +149,14 @@ pub(crate) fn find_all_refs( } }; + let declaration = Declaration { nav: declaration, kind: ReferenceKind::Other, access: None }; + let references = process_definition(db, def, name, search_scope) .into_iter() .filter(|r| search_kind == ReferenceKind::Other || search_kind == r.kind) .collect(); - Some(RangeInfo::new( - range, - ReferenceSearchResult { declaration, references, declaration_kind: ReferenceKind::Other }, - )) + Some(RangeInfo::new(range, ReferenceSearchResult { declaration, references })) } fn find_name<'a>( @@ -259,7 +268,7 @@ fn access_mode(kind: NameKind, name_ref: &ast::NameRef) -> Option String { + let mut s = format!("{} {:?}", self.nav.debug_render(), self.kind); + if let Some(access) = self.access { + s.push_str(&format!(" {:?}", access)); + } + s + } + + fn assert_match(&self, expected: &str) { + let actual = self.debug_render(); + test_utils::assert_eq_text!(expected.trim(), actual.trim(),); + } + } + impl Reference { fn debug_render(&self) -> String { let mut s = format!( -- cgit v1.2.3 From 32540abcb3c22cbd62662a255fc59972e0c5a06b Mon Sep 17 00:00:00 2001 From: kjeremy Date: Fri, 10 Jan 2020 14:56:58 -0500 Subject: Add AccessMode to decls --- crates/ra_ide/src/references.rs | 65 ++++++++++++++++++++++++++++++++++++----- 1 file changed, 57 insertions(+), 8 deletions(-) (limited to 'crates/ra_ide/src') diff --git a/crates/ra_ide/src/references.rs b/crates/ra_ide/src/references.rs index 2d20de1d0..4e52e0e7b 100644 --- a/crates/ra_ide/src/references.rs +++ b/crates/ra_ide/src/references.rs @@ -19,8 +19,9 @@ use once_cell::unsync::Lazy; use ra_db::{SourceDatabase, SourceDatabaseExt}; use ra_prof::profile; use ra_syntax::{ - algo::find_node_at_offset, ast, match_ast, AstNode, SourceFile, SyntaxKind, SyntaxNode, - TextUnit, TokenAtOffset, + algo::find_node_at_offset, + ast::{self, NameOwner}, + match_ast, AstNode, SourceFile, SyntaxKind, SyntaxNode, TextRange, TextUnit, TokenAtOffset, }; use crate::{ @@ -149,7 +150,13 @@ pub(crate) fn find_all_refs( } }; - let declaration = Declaration { nav: declaration, kind: ReferenceKind::Other, access: None }; + let decl_range = declaration.range(); + + let declaration = Declaration { + nav: declaration, + kind: ReferenceKind::Other, + access: decl_access(&def.kind, &name, &syntax, decl_range), + }; let references = process_definition(db, def, name, search_scope) .into_iter() @@ -218,12 +225,11 @@ fn process_definition( } else { ReferenceKind::Other }; - let access = access_mode(d.kind, &name_ref); refs.push(Reference { file_range: FileRange { file_id, range }, kind, - access, + access: reference_access(&d.kind, &name_ref), }); } } @@ -233,7 +239,34 @@ fn process_definition( refs } -fn access_mode(kind: NameKind, name_ref: &ast::NameRef) -> Option { +fn decl_access( + kind: &NameKind, + name: &str, + syntax: &SyntaxNode, + range: TextRange, +) -> Option { + match kind { + NameKind::Local(_) | NameKind::Field(_) => {} + _ => return None, + }; + + let stmt = find_node_at_offset::(syntax, range.start())?; + if let Some(_) = stmt.initializer() { + let pat = stmt.pat()?; + match pat { + ast::Pat::BindPat(it) => { + if it.name()?.text().as_str() == name { + return Some(ReferenceAccess::Write); + } + } + _ => {} + } + } + + None +} + +fn reference_access(kind: &NameKind, name_ref: &ast::NameRef) -> Option { // Only Locals and Fields have accesses for now. match kind { NameKind::Local(_) | NameKind::Field(_) => {} @@ -311,7 +344,7 @@ mod tests { let refs = get_all_refs(code); check_result( refs, - "i BIND_PAT FileId(1) [33; 34) Other", + "i BIND_PAT FileId(1) [33; 34) Other Write", &[ "FileId(1) [67; 68) Other Write", "FileId(1) [71; 72) Other Read", @@ -569,7 +602,7 @@ mod tests { let refs = get_all_refs(code); check_result( refs, - "i BIND_PAT FileId(1) [36; 37) Other", + "i BIND_PAT FileId(1) [36; 37) Other Write", &["FileId(1) [55; 56) Other Write", "FileId(1) [59; 60) Other Read"], ); } @@ -594,6 +627,22 @@ mod tests { ); } + #[test] + fn test_basic_highlight_decl_no_write() { + let code = r#" + fn foo() { + let i<|>; + i = 1; + }"#; + + let refs = get_all_refs(code); + check_result( + refs, + "i BIND_PAT FileId(1) [36; 37) Other", + &["FileId(1) [51; 52) Other Write"], + ); + } + fn get_all_refs(text: &str) -> ReferenceSearchResult { let (analysis, position) = single_file_with_position(text); analysis.find_all_refs(position, None).unwrap().unwrap() -- cgit v1.2.3