aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--crates/ra_ide/src/extend_selection.rs123
1 files changed, 109 insertions, 14 deletions
diff --git a/crates/ra_ide/src/extend_selection.rs b/crates/ra_ide/src/extend_selection.rs
index 1ec41a117..c6f558021 100644
--- a/crates/ra_ide/src/extend_selection.rs
+++ b/crates/ra_ide/src/extend_selection.rs
@@ -4,20 +4,27 @@ use ra_db::SourceDatabase;
4use ra_syntax::{ 4use ra_syntax::{
5 algo::find_covering_element, 5 algo::find_covering_element,
6 ast::{self, AstNode, AstToken}, 6 ast::{self, AstNode, AstToken},
7 Direction, NodeOrToken, 7 Direction, NodeOrToken, SyntaxElement,
8 SyntaxKind::{self, *}, 8 SyntaxKind::{self, *},
9 SyntaxNode, SyntaxToken, TextRange, TextUnit, TokenAtOffset, T, 9 SyntaxNode, SyntaxToken, TextRange, TextUnit, TokenAtOffset, T,
10}; 10};
11 11
12use crate::{db::RootDatabase, FileRange}; 12use crate::{db::RootDatabase, FileRange};
13use hir::{db::AstDatabase, InFile};
14use itertools::Itertools;
13 15
14// FIXME: restore macro support 16// FIXME: restore macro support
15pub(crate) fn extend_selection(db: &RootDatabase, frange: FileRange) -> TextRange { 17pub(crate) fn extend_selection(db: &RootDatabase, frange: FileRange) -> TextRange {
16 let parse = db.parse(frange.file_id); 18 let src = db.parse(frange.file_id).tree();
17 try_extend_selection(parse.tree().syntax(), frange.range).unwrap_or(frange.range) 19 let root = InFile::new(frange.file_id.into(), src.syntax());
20 try_extend_selection(db, root, frange.range).unwrap_or(frange.range)
18} 21}
19 22
20fn try_extend_selection(root: &SyntaxNode, range: TextRange) -> Option<TextRange> { 23fn try_extend_selection(
24 db: &RootDatabase,
25 root: InFile<&SyntaxNode>,
26 range: TextRange,
27) -> Option<TextRange> {
21 let string_kinds = [COMMENT, STRING, RAW_STRING, BYTE_STRING, RAW_BYTE_STRING]; 28 let string_kinds = [COMMENT, STRING, RAW_STRING, BYTE_STRING, RAW_BYTE_STRING];
22 let list_kinds = [ 29 let list_kinds = [
23 RECORD_FIELD_PAT_LIST, 30 RECORD_FIELD_PAT_LIST,
@@ -40,9 +47,9 @@ fn try_extend_selection(root: &SyntaxNode, range: TextRange) -> Option<TextRange
40 47
41 if range.is_empty() { 48 if range.is_empty() {
42 let offset = range.start(); 49 let offset = range.start();
43 let mut leaves = root.token_at_offset(offset); 50 let mut leaves = root.value.token_at_offset(offset);
44 if leaves.clone().all(|it| it.kind() == WHITESPACE) { 51 if leaves.clone().all(|it| it.kind() == WHITESPACE) {
45 return Some(extend_ws(root, leaves.next()?, offset)); 52 return Some(extend_ws(root.value, leaves.next()?, offset));
46 } 53 }
47 let leaf_range = match leaves { 54 let leaf_range = match leaves {
48 TokenAtOffset::None => return None, 55 TokenAtOffset::None => return None,
@@ -58,7 +65,7 @@ fn try_extend_selection(root: &SyntaxNode, range: TextRange) -> Option<TextRange
58 }; 65 };
59 return Some(leaf_range); 66 return Some(leaf_range);
60 }; 67 };
61 let node = match find_covering_element(root, range) { 68 let node = match find_covering_element(root.value, range) {
62 NodeOrToken::Token(token) => { 69 NodeOrToken::Token(token) => {
63 if token.text_range() != range { 70 if token.text_range() != range {
64 return Some(token.text_range()); 71 return Some(token.text_range());
@@ -72,6 +79,16 @@ fn try_extend_selection(root: &SyntaxNode, range: TextRange) -> Option<TextRange
72 } 79 }
73 NodeOrToken::Node(node) => node, 80 NodeOrToken::Node(node) => node,
74 }; 81 };
82
83 // if we are in single token_tree, we maybe live in macro or attr
84 if node.kind() == TOKEN_TREE {
85 if let Some(macro_call) = node.ancestors().find_map(ast::MacroCall::cast) {
86 if let Some(range) = extend_tokens_from_range(db, &root, macro_call, range) {
87 return Some(range);
88 }
89 }
90 }
91
75 if node.text_range() != range { 92 if node.text_range() != range {
76 return Some(node.text_range()); 93 return Some(node.text_range());
77 } 94 }
@@ -88,6 +105,67 @@ fn try_extend_selection(root: &SyntaxNode, range: TextRange) -> Option<TextRange
88 node.parent().map(|it| it.text_range()) 105 node.parent().map(|it| it.text_range())
89} 106}
90 107
108fn extend_tokens_from_range(
109 db: &RootDatabase,
110 root: &InFile<&SyntaxNode>,
111 macro_call: ast::MacroCall,
112 original_range: TextRange,
113) -> Option<TextRange> {
114 let analyzer = hir::SourceAnalyzer::new(db, root.clone(), None);
115 let expansion = analyzer.expand(db, root.with_value(&macro_call))?;
116
117 // compute original mapped token range
118 let range = macro_call
119 .syntax()
120 .descendants_with_tokens()
121 .filter_map(|n| match n {
122 NodeOrToken::Token(token) if token.text_range().is_subrange(&original_range) => {
123 expansion
124 .map_token_down(db, root.with_value(&token))
125 .map(|node| node.value.text_range())
126 }
127 _ => None,
128 })
129 .fold1(|x, y| union_range(x, y))?;
130
131 let src = db.parse_or_expand(expansion.file_id())?;
132 let parent = shallow_node(&find_covering_element(&src, range))?.parent()?;
133
134 // compute parent mapped token range
135 let range = macro_call
136 .syntax()
137 .descendants_with_tokens()
138 .filter_map(|n| match n {
139 NodeOrToken::Token(token) => {
140 expansion.map_token_down(db, root.with_value(&token)).and_then(|node| {
141 if node.value.text_range().is_subrange(&parent.text_range()) {
142 Some(token.text_range())
143 } else {
144 None
145 }
146 })
147 }
148 _ => None,
149 })
150 .fold1(|x, y| union_range(x, y))?;
151
152 if original_range.is_subrange(&range) && original_range != range {
153 Some(range)
154 } else {
155 None
156 }
157}
158
159fn union_range(range: TextRange, r: TextRange) -> TextRange {
160 let start = range.start().min(r.start());
161 let end = range.end().max(r.end());
162 TextRange::from_to(start, end)
163}
164
165fn shallow_node(node: &SyntaxElement) -> Option<SyntaxNode> {
166 node.ancestors().take_while(|n| n.text_range() == node.text_range()).last()
167}
168
91fn extend_single_word_in_comment_or_string( 169fn extend_single_word_in_comment_or_string(
92 leaf: &SyntaxToken, 170 leaf: &SyntaxToken,
93 offset: TextUnit, 171 offset: TextUnit,
@@ -227,18 +305,19 @@ fn adj_comments(comment: &ast::Comment, dir: Direction) -> ast::Comment {
227 305
228#[cfg(test)] 306#[cfg(test)]
229mod tests { 307mod tests {
230 use ra_syntax::{AstNode, SourceFile};
231 use test_utils::extract_offset;
232
233 use super::*; 308 use super::*;
309 use crate::mock_analysis::single_file;
310 use test_utils::extract_offset;
234 311
235 fn do_check(before: &str, afters: &[&str]) { 312 fn do_check(before: &str, afters: &[&str]) {
236 let (cursor, before) = extract_offset(before); 313 let (cursor, before) = extract_offset(before);
237 let parse = SourceFile::parse(&before); 314 let (analysis, file_id) = single_file(&before);
238 let mut range = TextRange::offset_len(cursor, 0.into()); 315 let range = TextRange::offset_len(cursor, 0.into());
316 let mut frange = FileRange { file_id: file_id, range };
317
239 for &after in afters { 318 for &after in afters {
240 range = try_extend_selection(parse.tree().syntax(), range).unwrap(); 319 frange.range = analysis.extend_selection(frange).unwrap();
241 let actual = &before[range]; 320 let actual = &before[frange.range];
242 assert_eq!(after, actual); 321 assert_eq!(after, actual);
243 } 322 }
244 } 323 }
@@ -503,4 +582,20 @@ fn main() { let var = (
503 ], 582 ],
504 ); 583 );
505 } 584 }
585
586 #[test]
587 fn extend_selection_inside_macros() {
588 do_check(
589 r#"macro_rules! foo { ($item:item) => {$item} }
590 foo!{fn hello(na<|>me:usize){}}"#,
591 &[
592 "name",
593 "name:usize",
594 "(name:usize)",
595 "fn hello(name:usize){}",
596 "{fn hello(name:usize){}}",
597 "foo!{fn hello(name:usize){}}",
598 ],
599 );
600 }
506} 601}