@@ -4,20 +4,27 @@ use ra_db::SourceDatabase;
4
4
use ra_syntax:: {
5
5
algo:: find_covering_element,
6
6
ast:: { self , AstNode , AstToken } ,
7
- Direction , NodeOrToken ,
7
+ Direction , NodeOrToken , SyntaxElement ,
8
8
SyntaxKind :: { self , * } ,
9
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 ;
14
+ use std:: iter:: successors;
13
15
14
- // FIXME: restore macro support
15
16
pub ( crate ) fn extend_selection ( db : & RootDatabase , frange : FileRange ) -> TextRange {
16
- let parse = db. parse ( frange. file_id ) ;
17
- try_extend_selection ( parse . tree ( ) . syntax ( ) , frange. range ) . unwrap_or ( frange. range )
17
+ let src = db. parse ( frange. file_id ) . tree ( ) ;
18
+ try_extend_selection ( db , src . syntax ( ) , frange) . unwrap_or ( frange. range )
18
19
}
19
20
20
- fn try_extend_selection ( root : & SyntaxNode , range : TextRange ) -> Option < TextRange > {
21
+ fn try_extend_selection (
22
+ db : & RootDatabase ,
23
+ root : & SyntaxNode ,
24
+ frange : FileRange ,
25
+ ) -> Option < TextRange > {
26
+ let range = frange. range ;
27
+
21
28
let string_kinds = [ COMMENT , STRING , RAW_STRING , BYTE_STRING , RAW_BYTE_STRING ] ;
22
29
let list_kinds = [
23
30
RECORD_FIELD_PAT_LIST ,
@@ -72,12 +79,21 @@ fn try_extend_selection(root: &SyntaxNode, range: TextRange) -> Option<TextRange
72
79
}
73
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, frange. file_id , macro_call, range) {
87
+ return Some ( range) ;
88
+ }
89
+ }
90
+ }
91
+
75
92
if node. text_range ( ) != range {
76
93
return Some ( node. text_range ( ) ) ;
77
94
}
78
95
79
- // Using shallowest node with same range allows us to traverse siblings.
80
- let node = node. ancestors ( ) . take_while ( |n| n. text_range ( ) == node. text_range ( ) ) . last ( ) . unwrap ( ) ;
96
+ let node = shallowest_node ( & node. into ( ) ) . unwrap ( ) ;
81
97
82
98
if node. parent ( ) . map ( |n| list_kinds. contains ( & n. kind ( ) ) ) == Some ( true ) {
83
99
if let Some ( range) = extend_list_item ( & node) {
@@ -88,6 +104,94 @@ fn try_extend_selection(root: &SyntaxNode, range: TextRange) -> Option<TextRange
88
104
node. parent ( ) . map ( |it| it. text_range ( ) )
89
105
}
90
106
107
+ fn extend_tokens_from_range (
108
+ db : & RootDatabase ,
109
+ file_id : FileId ,
110
+ macro_call : ast:: MacroCall ,
111
+ original_range : TextRange ,
112
+ ) -> Option < TextRange > {
113
+ let src = find_covering_element ( & macro_call. syntax ( ) , original_range) ;
114
+ let ( first_token, last_token) = match src {
115
+ NodeOrToken :: Node ( it) => ( it. first_token ( ) ?, it. last_token ( ) ?) ,
116
+ NodeOrToken :: Token ( it) => ( it. clone ( ) , it) ,
117
+ } ;
118
+
119
+ let mut first_token = skip_whitespace ( first_token, Direction :: Next ) ?;
120
+ let mut last_token = skip_whitespace ( last_token, Direction :: Prev ) ?;
121
+
122
+ while !first_token. text_range ( ) . is_subrange ( & original_range) {
123
+ first_token = skip_whitespace ( first_token. next_token ( ) ?, Direction :: Next ) ?;
124
+ }
125
+ while !last_token. text_range ( ) . is_subrange ( & original_range) {
126
+ last_token = skip_whitespace ( last_token. prev_token ( ) ?, Direction :: Prev ) ?;
127
+ }
128
+
129
+ // compute original mapped token range
130
+ let expanded = {
131
+ let first_node = descend_into_macros ( db, file_id, first_token. clone ( ) ) ;
132
+ let first_node = first_node. map ( |it| it. text_range ( ) ) ;
133
+
134
+ let last_node = descend_into_macros ( db, file_id, last_token. clone ( ) ) ;
135
+ if last_node. file_id == file_id. into ( ) || first_node. file_id != last_node. file_id {
136
+ return None ;
137
+ }
138
+ first_node. map ( |it| union_range ( it, last_node. value . text_range ( ) ) )
139
+ } ;
140
+
141
+ // Compute parent node range
142
+ let src = db. parse_or_expand ( expanded. file_id ) ?;
143
+ let parent = shallowest_node ( & find_covering_element ( & src, expanded. value ) ) ?. parent ( ) ?;
144
+
145
+ let validate = |token : SyntaxToken | {
146
+ let node = descend_into_macros ( db, file_id, token. clone ( ) ) ;
147
+ if node. file_id == expanded. file_id
148
+ && node. value . text_range ( ) . is_subrange ( & parent. text_range ( ) )
149
+ {
150
+ Some ( token)
151
+ } else {
152
+ None
153
+ }
154
+ } ;
155
+
156
+ // Find the first and last text range under expanded parent
157
+ let first = successors ( Some ( first_token) , |token| {
158
+ validate ( skip_whitespace ( token. prev_token ( ) ?, Direction :: Prev ) ?)
159
+ } )
160
+ . last ( ) ?;
161
+ let last = successors ( Some ( last_token) , |token| {
162
+ validate ( skip_whitespace ( token. next_token ( ) ?, Direction :: Next ) ?)
163
+ } )
164
+ . last ( ) ?;
165
+
166
+ let range = union_range ( first. text_range ( ) , last. text_range ( ) ) ;
167
+ if original_range. is_subrange ( & range) && original_range != range {
168
+ Some ( range)
169
+ } else {
170
+ None
171
+ }
172
+ }
173
+
174
+ fn skip_whitespace ( mut token : SyntaxToken , direction : Direction ) -> Option < SyntaxToken > {
175
+ while token. kind ( ) == WHITESPACE {
176
+ token = match direction {
177
+ Direction :: Next => token. next_token ( ) ?,
178
+ Direction :: Prev => token. prev_token ( ) ?,
179
+ }
180
+ }
181
+ Some ( token)
182
+ }
183
+
184
+ fn union_range ( range : TextRange , r : TextRange ) -> TextRange {
185
+ let start = range. start ( ) . min ( r. start ( ) ) ;
186
+ let end = range. end ( ) . max ( r. end ( ) ) ;
187
+ TextRange :: from_to ( start, end)
188
+ }
189
+
190
+ /// Find the shallowest node with same range, which allows us to traverse siblings.
191
+ fn shallowest_node ( node : & SyntaxElement ) -> Option < SyntaxNode > {
192
+ node. ancestors ( ) . take_while ( |n| n. text_range ( ) == node. text_range ( ) ) . last ( )
193
+ }
194
+
91
195
fn extend_single_word_in_comment_or_string (
92
196
leaf : & SyntaxToken ,
93
197
offset : TextUnit ,
@@ -227,18 +331,19 @@ fn adj_comments(comment: &ast::Comment, dir: Direction) -> ast::Comment {
227
331
228
332
#[ cfg( test) ]
229
333
mod tests {
230
- use ra_syntax:: { AstNode , SourceFile } ;
231
- use test_utils:: extract_offset;
232
-
233
334
use super :: * ;
335
+ use crate :: mock_analysis:: single_file;
336
+ use test_utils:: extract_offset;
234
337
235
338
fn do_check ( before : & str , afters : & [ & str ] ) {
236
339
let ( cursor, before) = extract_offset ( before) ;
237
- let parse = SourceFile :: parse ( & before) ;
238
- let mut range = TextRange :: offset_len ( cursor, 0 . into ( ) ) ;
340
+ let ( analysis, file_id) = single_file ( & before) ;
341
+ let range = TextRange :: offset_len ( cursor, 0 . into ( ) ) ;
342
+ let mut frange = FileRange { file_id : file_id, range } ;
343
+
239
344
for & after in afters {
240
- range = try_extend_selection ( parse . tree ( ) . syntax ( ) , range ) . unwrap ( ) ;
241
- let actual = & before[ range] ;
345
+ frange . range = analysis . extend_selection ( frange ) . unwrap ( ) ;
346
+ let actual = & before[ frange . range ] ;
242
347
assert_eq ! ( after, actual) ;
243
348
}
244
349
}
@@ -503,4 +608,37 @@ fn main() { let var = (
503
608
] ,
504
609
) ;
505
610
}
611
+
612
+ #[ test]
613
+ fn extend_selection_inside_macros ( ) {
614
+ do_check (
615
+ r#"macro_rules! foo { ($item:item) => {$item} }
616
+ foo!{fn hello(na<|>me:usize){}}"# ,
617
+ & [
618
+ "name" ,
619
+ "name:usize" ,
620
+ "(name:usize)" ,
621
+ "fn hello(name:usize){}" ,
622
+ "{fn hello(name:usize){}}" ,
623
+ "foo!{fn hello(name:usize){}}" ,
624
+ ] ,
625
+ ) ;
626
+ }
627
+
628
+ #[ test]
629
+ fn extend_selection_inside_recur_macros ( ) {
630
+ do_check (
631
+ r#" macro_rules! foo2 { ($item:item) => {$item} }
632
+ macro_rules! foo { ($item:item) => {foo2!($item);} }
633
+ foo!{fn hello(na<|>me:usize){}}"# ,
634
+ & [
635
+ "name" ,
636
+ "name:usize" ,
637
+ "(name:usize)" ,
638
+ "fn hello(name:usize){}" ,
639
+ "{fn hello(name:usize){}}" ,
640
+ "foo!{fn hello(name:usize){}}" ,
641
+ ] ,
642
+ ) ;
643
+ }
506
644
}
0 commit comments