aboutsummaryrefslogtreecommitdiff
path: root/crates
diff options
context:
space:
mode:
Diffstat (limited to 'crates')
-rw-r--r--crates/ra_ide/src/extend_selection.rs91
1 files changed, 47 insertions, 44 deletions
diff --git a/crates/ra_ide/src/extend_selection.rs b/crates/ra_ide/src/extend_selection.rs
index 8048c7be9..9b6bbe82d 100644
--- a/crates/ra_ide/src/extend_selection.rs
+++ b/crates/ra_ide/src/extend_selection.rs
@@ -11,7 +11,7 @@ use ra_syntax::{
11 11
12use crate::{db::RootDatabase, expand::descend_into_macros, FileId, FileRange}; 12use crate::{db::RootDatabase, expand::descend_into_macros, FileId, FileRange};
13use hir::db::AstDatabase; 13use hir::db::AstDatabase;
14use itertools::Itertools; 14use std::iter::successors;
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();
@@ -110,46 +110,28 @@ fn extend_tokens_from_range(
110 macro_call: ast::MacroCall, 110 macro_call: ast::MacroCall,
111 original_range: TextRange, 111 original_range: TextRange,
112) -> Option<TextRange> { 112) -> Option<TextRange> {
113 // Find all non-whitespace tokens under MacroCall 113 let src = find_covering_element(&macro_call.syntax(), original_range);
114 let all_tokens: Vec<_> = macro_call 114 let (first_token, last_token) = match src {
115 .syntax() 115 NodeOrToken::Node(it) => (it.first_token()?, it.last_token()?),
116 .descendants_with_tokens() 116 NodeOrToken::Token(it) => (it.clone(), it),
117 .filter_map(|n| { 117 };
118 let token = n.as_token()?; 118
119 if token.kind() == WHITESPACE { 119 let mut first_token = skip_whitespace(first_token, Direction::Next)?;
120 None 120 let mut last_token = skip_whitespace(last_token, Direction::Prev)?;
121 } else {
122 Some(token.clone())
123 }
124 })
125 .sorted_by(|a, b| Ord::cmp(&a.text_range().start(), &b.text_range().start()))
126 .collect();
127
128 // Get all indices which is in original range
129 let indices: Vec<_> =
130 all_tokens
131 .iter()
132 .enumerate()
133 .filter_map(|(i, token)| {
134 if token.text_range().is_subrange(&original_range) {
135 Some(i)
136 } else {
137 None
138 }
139 })
140 .collect();
141 121
142 // The first and last token index in original_range 122 while !first_token.text_range().is_subrange(&original_range) {
143 // Note that the indices is sorted 123 first_token = skip_whitespace(first_token.next_token()?, Direction::Next)?;
144 let first_idx = *indices.first()?; 124 }
145 let last_idx = *indices.last()?; 125 while !last_token.text_range().is_subrange(&original_range) {
126 last_token = skip_whitespace(last_token.prev_token()?, Direction::Prev)?;
127 }
146 128
147 // compute original mapped token range 129 // compute original mapped token range
148 let expanded = { 130 let expanded = {
149 let first_node = descend_into_macros(db, file_id, all_tokens[first_idx].clone()); 131 let first_node = descend_into_macros(db, file_id, first_token.clone());
150 let first_node = first_node.map(|it| it.text_range()); 132 let first_node = first_node.map(|it| it.text_range());
151 133
152 let last_node = descend_into_macros(db, file_id, all_tokens[last_idx].clone()); 134 let last_node = descend_into_macros(db, file_id, last_token.clone());
153 if last_node.file_id == file_id.into() || first_node.file_id != last_node.file_id { 135 if last_node.file_id == file_id.into() || first_node.file_id != last_node.file_id {
154 return None; 136 return None;
155 } 137 }
@@ -160,20 +142,28 @@ fn extend_tokens_from_range(
160 let src = db.parse_or_expand(expanded.file_id)?; 142 let src = db.parse_or_expand(expanded.file_id)?;
161 let parent = shallowest_node(&find_covering_element(&src, expanded.value))?.parent()?; 143 let parent = shallowest_node(&find_covering_element(&src, expanded.value))?.parent()?;
162 144
163 let validate = |&idx: &usize| { 145 let validate = |token: SyntaxToken| {
164 let token: &SyntaxToken = &all_tokens[idx];
165 let node = descend_into_macros(db, file_id, token.clone()); 146 let node = descend_into_macros(db, file_id, token.clone());
166 147 if node.file_id == expanded.file_id
167 node.file_id == expanded.file_id
168 && node.value.text_range().is_subrange(&parent.text_range()) 148 && node.value.text_range().is_subrange(&parent.text_range())
149 {
150 Some(token)
151 } else {
152 None
153 }
169 }; 154 };
170 155
171 // Find the first and last text range under expanded parent 156 // Find the first and last text range under expanded parent
172 let first = (0..=first_idx).rev().take_while(validate).last()?; 157 let first = successors(Some(first_token), |token| {
173 let last = (last_idx..all_tokens.len()).take_while(validate).last()?; 158 validate(skip_whitespace(token.prev_token()?, Direction::Prev)?)
174 159 })
175 let range = union_range(all_tokens[first].text_range(), all_tokens[last].text_range()); 160 .last()?;
176 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());
177 if original_range.is_subrange(&range) && original_range != range { 167 if original_range.is_subrange(&range) && original_range != range {
178 Some(range) 168 Some(range)
179 } else { 169 } else {
@@ -181,6 +171,19 @@ fn extend_tokens_from_range(
181 } 171 }
182} 172}
183 173
174fn skip_whitespace(
175 mut token: SyntaxToken,
176 direction: Direction,
177) -> Option<SyntaxToken> {
178 while token.kind() == WHITESPACE {
179 token = match direction {
180 Direction::Next => token.next_token()?,
181 Direction::Prev => token.prev_token()?,
182 }
183 }
184 Some(token)
185}
186
184fn union_range(range: TextRange, r: TextRange) -> TextRange { 187fn union_range(range: TextRange, r: TextRange) -> TextRange {
185 let start = range.start().min(r.start()); 188 let start = range.start().min(r.start());
186 let end = range.end().max(r.end()); 189 let end = range.end().max(r.end());