@@ -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
12
use crate :: { db:: RootDatabase , FileRange } ;
13
+ use hir:: { db:: AstDatabase , InFile } ;
14
+ use itertools:: Itertools ;
13
15
14
16
// FIXME: restore macro support
15
17
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 )
18
+ let src = db. parse ( frange. file_id ) . tree ( ) ;
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
20
- fn try_extend_selection ( root : & SyntaxNode , range : TextRange ) -> Option < TextRange > {
23
+ fn try_extend_selection (
24
+ db : & RootDatabase ,
25
+ root : InFile < & SyntaxNode > ,
26
+ range : TextRange ,
27
+ ) -> Option < TextRange > {
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 ,
@@ -40,9 +47,9 @@ fn try_extend_selection(root: &SyntaxNode, range: TextRange) -> Option<TextRange
40
47
41
48
if range. is_empty ( ) {
42
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
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
54
let leaf_range = match leaves {
48
55
TokenAtOffset :: None => return None ,
@@ -58,7 +65,7 @@ fn try_extend_selection(root: &SyntaxNode, range: TextRange) -> Option<TextRange
58
65
} ;
59
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
69
NodeOrToken :: Token ( token) => {
63
70
if token. text_range ( ) != range {
64
71
return Some ( token. text_range ( ) ) ;
@@ -72,6 +79,16 @@ 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, & root, 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
}
@@ -88,6 +105,67 @@ fn try_extend_selection(root: &SyntaxNode, range: TextRange) -> Option<TextRange
88
105
node. parent ( ) . map ( |it| it. text_range ( ) )
89
106
}
90
107
108
+ fn 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
+
159
+ fn 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
+
165
+ fn shallow_node ( node : & SyntaxElement ) -> Option < SyntaxNode > {
166
+ node. ancestors ( ) . take_while ( |n| n. text_range ( ) == node. text_range ( ) ) . last ( )
167
+ }
168
+
91
169
fn extend_single_word_in_comment_or_string (
92
170
leaf : & SyntaxToken ,
93
171
offset : TextUnit ,
@@ -227,18 +305,19 @@ fn adj_comments(comment: &ast::Comment, dir: Direction) -> ast::Comment {
227
305
228
306
#[ cfg( test) ]
229
307
mod tests {
230
- use ra_syntax:: { AstNode , SourceFile } ;
231
- use test_utils:: extract_offset;
232
-
233
308
use super :: * ;
309
+ use crate :: mock_analysis:: single_file;
310
+ use test_utils:: extract_offset;
234
311
235
312
fn do_check ( before : & str , afters : & [ & str ] ) {
236
313
let ( cursor, before) = extract_offset ( before) ;
237
- let parse = SourceFile :: parse ( & before) ;
238
- let mut range = TextRange :: offset_len ( cursor, 0 . into ( ) ) ;
314
+ let ( analysis, file_id) = single_file ( & before) ;
315
+ let range = TextRange :: offset_len ( cursor, 0 . into ( ) ) ;
316
+ let mut frange = FileRange { file_id : file_id, range } ;
317
+
239
318
for & after in afters {
240
- range = try_extend_selection ( parse . tree ( ) . syntax ( ) , range ) . unwrap ( ) ;
241
- let actual = & before[ range] ;
319
+ frange . range = analysis . extend_selection ( frange ) . unwrap ( ) ;
320
+ let actual = & before[ frange . range ] ;
242
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
}
0 commit comments