aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEdwin Cheng <[email protected]>2020-01-07 16:22:08 +0000
committerEdwin Cheng <[email protected]>2020-01-12 12:25:58 +0000
commitb30e6a7b56942f31cbd7b9fdb78925bf5b65b247 (patch)
treef7b7cca49841cd219a8b7060a091367cff68ec60
parent07f4171b1803f562118671255d73b97f20d24e07 (diff)
Handle extend selection in recursive macro
-rw-r--r--crates/ra_ide/src/extend_selection.rs76
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
12use crate::{db::RootDatabase, FileRange}; 12use crate::{db::RootDatabase, expand::descend_into_macros, FileId, FileRange};
13use hir::{db::AstDatabase, InFile}; 13use hir::db::AstDatabase;
14use itertools::Itertools; 14use itertools::Itertools;
15 15
16pub(crate) fn extend_selection(db: &RootDatabase, frange: FileRange) -> TextRange { 16pub(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
22fn try_extend_selection( 21fn 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
106fn extend_tokens_from_range( 107fn 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(&macro_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}