diff options
-rw-r--r-- | crates/ra_ide/src/extend_selection.rs | 76 |
1 files changed, 49 insertions, 27 deletions
diff --git a/crates/ra_ide/src/extend_selection.rs b/crates/ra_ide/src/extend_selection.rs index 9b6cc15e8..a9ad4b476 100644 --- a/crates/ra_ide/src/extend_selection.rs +++ b/crates/ra_ide/src/extend_selection.rs | |||
@@ -9,21 +9,22 @@ use ra_syntax::{ | |||
9 | SyntaxNode, SyntaxToken, TextRange, TextUnit, TokenAtOffset, T, | 9 | SyntaxNode, SyntaxToken, TextRange, TextUnit, TokenAtOffset, T, |
10 | }; | 10 | }; |
11 | 11 | ||
12 | use crate::{db::RootDatabase, FileRange}; | 12 | use crate::{db::RootDatabase, expand::descend_into_macros, FileId, FileRange}; |
13 | use hir::{db::AstDatabase, InFile}; | 13 | use hir::db::AstDatabase; |
14 | use itertools::Itertools; | 14 | use itertools::Itertools; |
15 | 15 | ||
16 | pub(crate) fn extend_selection(db: &RootDatabase, frange: FileRange) -> TextRange { | 16 | pub(crate) fn extend_selection(db: &RootDatabase, frange: FileRange) -> TextRange { |
17 | let src = db.parse(frange.file_id).tree(); | 17 | let src = db.parse(frange.file_id).tree(); |
18 | let root = InFile::new(frange.file_id.into(), src.syntax()); | 18 | try_extend_selection(db, src.syntax(), frange).unwrap_or(frange.range) |
19 | try_extend_selection(db, root, frange.range).unwrap_or(frange.range) | ||
20 | } | 19 | } |
21 | 20 | ||
22 | fn try_extend_selection( | 21 | fn try_extend_selection( |
23 | db: &RootDatabase, | 22 | db: &RootDatabase, |
24 | root: InFile<&SyntaxNode>, | 23 | root: &SyntaxNode, |
25 | range: TextRange, | 24 | frange: FileRange, |
26 | ) -> Option<TextRange> { | 25 | ) -> Option<TextRange> { |
26 | let range = frange.range; | ||
27 | |||
27 | 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]; |
28 | let list_kinds = [ | 29 | let list_kinds = [ |
29 | RECORD_FIELD_PAT_LIST, | 30 | RECORD_FIELD_PAT_LIST, |
@@ -46,9 +47,9 @@ fn try_extend_selection( | |||
46 | 47 | ||
47 | if range.is_empty() { | 48 | if range.is_empty() { |
48 | let offset = range.start(); | 49 | let offset = range.start(); |
49 | let mut leaves = root.value.token_at_offset(offset); | 50 | let mut leaves = root.token_at_offset(offset); |
50 | if leaves.clone().all(|it| it.kind() == WHITESPACE) { | 51 | if leaves.clone().all(|it| it.kind() == WHITESPACE) { |
51 | return Some(extend_ws(root.value, leaves.next()?, offset)); | 52 | return Some(extend_ws(root, leaves.next()?, offset)); |
52 | } | 53 | } |
53 | let leaf_range = match leaves { | 54 | let leaf_range = match leaves { |
54 | TokenAtOffset::None => return None, | 55 | TokenAtOffset::None => return None, |
@@ -64,7 +65,7 @@ fn try_extend_selection( | |||
64 | }; | 65 | }; |
65 | return Some(leaf_range); | 66 | return Some(leaf_range); |
66 | }; | 67 | }; |
67 | let node = match find_covering_element(root.value, range) { | 68 | let node = match find_covering_element(root, range) { |
68 | NodeOrToken::Token(token) => { | 69 | NodeOrToken::Token(token) => { |
69 | if token.text_range() != range { | 70 | if token.text_range() != range { |
70 | return Some(token.text_range()); | 71 | return Some(token.text_range()); |
@@ -82,7 +83,7 @@ fn try_extend_selection( | |||
82 | // if we are in single token_tree, we maybe live in macro or attr | 83 | // if we are in single token_tree, we maybe live in macro or attr |
83 | if node.kind() == TOKEN_TREE { | 84 | if node.kind() == TOKEN_TREE { |
84 | if let Some(macro_call) = node.ancestors().find_map(ast::MacroCall::cast) { | 85 | if let Some(macro_call) = node.ancestors().find_map(ast::MacroCall::cast) { |
85 | if let Some(range) = extend_tokens_from_range(db, &root, macro_call, range) { | 86 | if let Some(range) = extend_tokens_from_range(db, frange.file_id, macro_call, range) { |
86 | return Some(range); | 87 | return Some(range); |
87 | } | 88 | } |
88 | } | 89 | } |
@@ -105,48 +106,52 @@ fn try_extend_selection( | |||
105 | 106 | ||
106 | fn extend_tokens_from_range( | 107 | fn extend_tokens_from_range( |
107 | db: &RootDatabase, | 108 | db: &RootDatabase, |
108 | root: &InFile<&SyntaxNode>, | 109 | file_id: FileId, |
109 | macro_call: ast::MacroCall, | 110 | macro_call: ast::MacroCall, |
110 | original_range: TextRange, | 111 | original_range: TextRange, |
111 | ) -> Option<TextRange> { | 112 | ) -> Option<TextRange> { |
112 | let analyzer = hir::SourceAnalyzer::new(db, root.clone(), None); | ||
113 | let expansion = analyzer.expand(db, root.with_value(¯o_call))?; | ||
114 | |||
115 | // compute original mapped token range | 113 | // compute original mapped token range |
114 | let mut expanded = None; | ||
116 | let range = macro_call | 115 | let range = macro_call |
117 | .syntax() | 116 | .syntax() |
118 | .descendants_with_tokens() | 117 | .descendants_with_tokens() |
119 | .filter_map(|n| match n { | 118 | .filter_map(|n| match n { |
120 | NodeOrToken::Token(token) if token.text_range().is_subrange(&original_range) => { | 119 | NodeOrToken::Token(token) if token.text_range().is_subrange(&original_range) => { |
121 | expansion | 120 | let node = descend_into_macros(db, file_id, token); |
122 | .map_token_down(db, root.with_value(&token)) | 121 | match node.file_id { |
123 | .map(|node| node.value.text_range()) | 122 | it if it == file_id.into() => None, |
123 | it if expanded.is_none() || expanded == Some(it) => { | ||
124 | expanded = Some(it.into()); | ||
125 | Some(node.value.text_range()) | ||
126 | } | ||
127 | _ => None, | ||
128 | } | ||
124 | } | 129 | } |
125 | _ => None, | 130 | _ => None, |
126 | }) | 131 | }) |
127 | .fold1(|x, y| union_range(x, y))?; | 132 | .fold1(|x, y| union_range(x, y))?; |
128 | 133 | ||
129 | let src = db.parse_or_expand(expansion.file_id())?; | 134 | let expanded = expanded?; |
135 | let src = db.parse_or_expand(expanded)?; | ||
130 | let parent = shallowest_node(&find_covering_element(&src, range))?.parent()?; | 136 | let parent = shallowest_node(&find_covering_element(&src, range))?.parent()?; |
131 | |||
132 | // compute parent mapped token range | 137 | // compute parent mapped token range |
133 | let range = macro_call | 138 | let range = macro_call |
134 | .syntax() | 139 | .syntax() |
135 | .descendants_with_tokens() | 140 | .descendants_with_tokens() |
136 | .filter_map(|n| match n { | 141 | .filter_map(|n| match n { |
137 | NodeOrToken::Token(token) => { | 142 | NodeOrToken::Token(token) => { |
138 | expansion.map_token_down(db, root.with_value(&token)).and_then(|node| { | 143 | let node = descend_into_macros(db, file_id, token.clone()); |
139 | if node.value.text_range().is_subrange(&parent.text_range()) { | 144 | if node.file_id == expanded |
140 | Some(token.text_range()) | 145 | && node.value.text_range().is_subrange(&parent.text_range()) |
141 | } else { | 146 | { |
142 | None | 147 | Some(token.text_range()) |
143 | } | 148 | } else { |
144 | }) | 149 | None |
150 | } | ||
145 | } | 151 | } |
146 | _ => None, | 152 | _ => None, |
147 | }) | 153 | }) |
148 | .fold1(|x, y| union_range(x, y))?; | 154 | .fold1(|x, y| union_range(x, y))?; |
149 | |||
150 | if original_range.is_subrange(&range) && original_range != range { | 155 | if original_range.is_subrange(&range) && original_range != range { |
151 | Some(range) | 156 | Some(range) |
152 | } else { | 157 | } else { |
@@ -597,4 +602,21 @@ fn main() { let var = ( | |||
597 | ], | 602 | ], |
598 | ); | 603 | ); |
599 | } | 604 | } |
605 | |||
606 | #[test] | ||
607 | fn extend_selection_inside_recur_macros() { | ||
608 | do_check( | ||
609 | r#" macro_rules! foo2 { ($item:item) => {$item} } | ||
610 | macro_rules! foo { ($item:item) => {foo2!($item);} } | ||
611 | foo!{fn hello(na<|>me:usize){}}"#, | ||
612 | &[ | ||
613 | "name", | ||
614 | "name:usize", | ||
615 | "(name:usize)", | ||
616 | "fn hello(name:usize){}", | ||
617 | "{fn hello(name:usize){}}", | ||
618 | "foo!{fn hello(name:usize){}}", | ||
619 | ], | ||
620 | ); | ||
621 | } | ||
600 | } | 622 | } |