aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_ide_api_light/src/extend_selection.rs
diff options
context:
space:
mode:
authorAleksey Kladov <[email protected]>2019-03-20 20:52:55 +0000
committerAleksey Kladov <[email protected]>2019-03-20 20:52:55 +0000
commitb931a472c4465e553f23c8b0e0e754b7b06169dd (patch)
tree748877a47b9a94d65d40037fbcf0d6be375569ce /crates/ra_ide_api_light/src/extend_selection.rs
parent3eb56f7a6aab1e9814cb901c8cd90c2b4b3138af (diff)
move extend selection from ra_ide_api_light to ra_ide_api
Diffstat (limited to 'crates/ra_ide_api_light/src/extend_selection.rs')
-rw-r--r--crates/ra_ide_api_light/src/extend_selection.rs369
1 files changed, 0 insertions, 369 deletions
diff --git a/crates/ra_ide_api_light/src/extend_selection.rs b/crates/ra_ide_api_light/src/extend_selection.rs
deleted file mode 100644
index 28d62f290..000000000
--- a/crates/ra_ide_api_light/src/extend_selection.rs
+++ /dev/null
@@ -1,369 +0,0 @@
1use ra_syntax::{
2 Direction, SyntaxNode, TextRange, TextUnit,
3 algo::{find_covering_node, find_leaf_at_offset, LeafAtOffset},
4 SyntaxKind::*,
5};
6
7pub fn extend_selection(root: &SyntaxNode, range: TextRange) -> Option<TextRange> {
8 let string_kinds = [COMMENT, STRING, RAW_STRING, BYTE_STRING, RAW_BYTE_STRING];
9 let list_kinds = [
10 FIELD_PAT_LIST,
11 MATCH_ARM_LIST,
12 NAMED_FIELD_DEF_LIST,
13 POS_FIELD_DEF_LIST,
14 NAMED_FIELD_LIST,
15 ENUM_VARIANT_LIST,
16 USE_TREE_LIST,
17 TYPE_PARAM_LIST,
18 TYPE_ARG_LIST,
19 PARAM_LIST,
20 ARG_LIST,
21 ARRAY_EXPR,
22 ];
23
24 if range.is_empty() {
25 let offset = range.start();
26 let mut leaves = find_leaf_at_offset(root, offset);
27 if leaves.clone().all(|it| it.kind() == WHITESPACE) {
28 return Some(extend_ws(root, leaves.next()?, offset));
29 }
30 let leaf_range = match leaves {
31 LeafAtOffset::None => return None,
32 LeafAtOffset::Single(l) => {
33 if string_kinds.contains(&l.kind()) {
34 extend_single_word_in_comment_or_string(l, offset).unwrap_or_else(|| l.range())
35 } else {
36 l.range()
37 }
38 }
39 LeafAtOffset::Between(l, r) => pick_best(l, r).range(),
40 };
41 return Some(leaf_range);
42 };
43 let node = find_covering_node(root, range);
44
45 // Using shallowest node with same range allows us to traverse siblings.
46 let node = node.ancestors().take_while(|n| n.range() == node.range()).last().unwrap();
47
48 if range == node.range() {
49 if string_kinds.contains(&node.kind()) {
50 if let Some(range) = extend_comments(node) {
51 return Some(range);
52 }
53 }
54
55 if node.parent().map(|n| list_kinds.contains(&n.kind())) == Some(true) {
56 if let Some(range) = extend_list_item(node) {
57 return Some(range);
58 }
59 }
60 }
61
62 match node.ancestors().skip_while(|n| n.range() == range).next() {
63 None => None,
64 Some(parent) => Some(parent.range()),
65 }
66}
67
68fn extend_single_word_in_comment_or_string(
69 leaf: &SyntaxNode,
70 offset: TextUnit,
71) -> Option<TextRange> {
72 let text: &str = leaf.leaf_text()?;
73 let cursor_position: u32 = (offset - leaf.range().start()).into();
74
75 let (before, after) = text.split_at(cursor_position as usize);
76
77 fn non_word_char(c: char) -> bool {
78 !(c.is_alphanumeric() || c == '_')
79 }
80
81 let start_idx = before.rfind(non_word_char)? as u32;
82 let end_idx = after.find(non_word_char).unwrap_or(after.len()) as u32;
83
84 let from: TextUnit = (start_idx + 1).into();
85 let to: TextUnit = (cursor_position + end_idx).into();
86
87 let range = TextRange::from_to(from, to);
88 if range.is_empty() {
89 None
90 } else {
91 Some(range + leaf.range().start())
92 }
93}
94
95fn extend_ws(root: &SyntaxNode, ws: &SyntaxNode, offset: TextUnit) -> TextRange {
96 let ws_text = ws.leaf_text().unwrap();
97 let suffix = TextRange::from_to(offset, ws.range().end()) - ws.range().start();
98 let prefix = TextRange::from_to(ws.range().start(), offset) - ws.range().start();
99 let ws_suffix = &ws_text.as_str()[suffix];
100 let ws_prefix = &ws_text.as_str()[prefix];
101 if ws_text.contains('\n') && !ws_suffix.contains('\n') {
102 if let Some(node) = ws.next_sibling() {
103 let start = match ws_prefix.rfind('\n') {
104 Some(idx) => ws.range().start() + TextUnit::from((idx + 1) as u32),
105 None => node.range().start(),
106 };
107 let end = if root.text().char_at(node.range().end()) == Some('\n') {
108 node.range().end() + TextUnit::of_char('\n')
109 } else {
110 node.range().end()
111 };
112 return TextRange::from_to(start, end);
113 }
114 }
115 ws.range()
116}
117
118fn pick_best<'a>(l: &'a SyntaxNode, r: &'a SyntaxNode) -> &'a SyntaxNode {
119 return if priority(r) > priority(l) { r } else { l };
120 fn priority(n: &SyntaxNode) -> usize {
121 match n.kind() {
122 WHITESPACE => 0,
123 IDENT | SELF_KW | SUPER_KW | CRATE_KW | LIFETIME => 2,
124 _ => 1,
125 }
126 }
127}
128
129/// Extend list item selection to include nearby comma and whitespace.
130fn extend_list_item(node: &SyntaxNode) -> Option<TextRange> {
131 fn is_single_line_ws(node: &SyntaxNode) -> bool {
132 node.kind() == WHITESPACE && !node.leaf_text().unwrap().contains('\n')
133 }
134
135 fn nearby_comma(node: &SyntaxNode, dir: Direction) -> Option<&SyntaxNode> {
136 node.siblings(dir)
137 .skip(1)
138 .skip_while(|node| is_single_line_ws(node))
139 .next()
140 .filter(|node| node.kind() == COMMA)
141 }
142
143 if let Some(comma_node) = nearby_comma(node, Direction::Prev) {
144 return Some(TextRange::from_to(comma_node.range().start(), node.range().end()));
145 }
146
147 if let Some(comma_node) = nearby_comma(node, Direction::Next) {
148 // Include any following whitespace when comma if after list item.
149 let final_node = comma_node
150 .siblings(Direction::Next)
151 .skip(1)
152 .next()
153 .filter(|node| is_single_line_ws(node))
154 .unwrap_or(comma_node);
155
156 return Some(TextRange::from_to(node.range().start(), final_node.range().end()));
157 }
158
159 return None;
160}
161
162fn extend_comments(node: &SyntaxNode) -> Option<TextRange> {
163 let prev = adj_comments(node, Direction::Prev);
164 let next = adj_comments(node, Direction::Next);
165 if prev != next {
166 Some(TextRange::from_to(prev.range().start(), next.range().end()))
167 } else {
168 None
169 }
170}
171
172fn adj_comments(node: &SyntaxNode, dir: Direction) -> &SyntaxNode {
173 let mut res = node;
174 for node in node.siblings(dir) {
175 match node.kind() {
176 COMMENT => res = node,
177 WHITESPACE if !node.leaf_text().unwrap().as_str().contains("\n\n") => (),
178 _ => break,
179 }
180 }
181 res
182}
183
184#[cfg(test)]
185mod tests {
186 use ra_syntax::{SourceFile, AstNode};
187 use test_utils::extract_offset;
188
189 use super::*;
190
191 fn do_check(before: &str, afters: &[&str]) {
192 let (cursor, before) = extract_offset(before);
193 let file = SourceFile::parse(&before);
194 let mut range = TextRange::offset_len(cursor, 0.into());
195 for &after in afters {
196 range = extend_selection(file.syntax(), range).unwrap();
197 let actual = &before[range];
198 assert_eq!(after, actual);
199 }
200 }
201
202 #[test]
203 fn test_extend_selection_arith() {
204 do_check(r#"fn foo() { <|>1 + 1 }"#, &["1", "1 + 1", "{ 1 + 1 }"]);
205 }
206
207 #[test]
208 fn test_extend_selection_list() {
209 do_check(r#"fn foo(<|>x: i32) {}"#, &["x", "x: i32"]);
210 do_check(r#"fn foo(<|>x: i32, y: i32) {}"#, &["x", "x: i32", "x: i32, "]);
211 do_check(r#"fn foo(<|>x: i32,y: i32) {}"#, &["x", "x: i32", "x: i32,"]);
212 do_check(r#"fn foo(x: i32, <|>y: i32) {}"#, &["y", "y: i32", ", y: i32"]);
213 do_check(r#"fn foo(x: i32, <|>y: i32, ) {}"#, &["y", "y: i32", ", y: i32"]);
214 do_check(r#"fn foo(x: i32,<|>y: i32) {}"#, &["y", "y: i32", ",y: i32"]);
215
216 do_check(r#"const FOO: [usize; 2] = [ 22<|> , 33];"#, &["22", "22 , "]);
217 do_check(r#"const FOO: [usize; 2] = [ 22 , 33<|>];"#, &["33", ", 33"]);
218 do_check(r#"const FOO: [usize; 2] = [ 22 , 33<|> ,];"#, &["33", ", 33"]);
219
220 do_check(
221 r#"
222const FOO: [usize; 2] = [
223 22,
224 <|>33,
225]"#,
226 &["33", "33,"],
227 );
228
229 do_check(
230 r#"
231const FOO: [usize; 2] = [
232 22
233 , 33<|>,
234]"#,
235 &["33", ", 33"],
236 );
237 }
238
239 #[test]
240 fn test_extend_selection_start_of_the_line() {
241 do_check(
242 r#"
243impl S {
244<|> fn foo() {
245
246 }
247}"#,
248 &[" fn foo() {\n\n }\n"],
249 );
250 }
251
252 #[test]
253 fn test_extend_selection_doc_comments() {
254 do_check(
255 r#"
256struct A;
257
258/// bla
259/// bla
260struct B {
261 <|>
262}
263 "#,
264 &["\n \n", "{\n \n}", "/// bla\n/// bla\nstruct B {\n \n}"],
265 )
266 }
267
268 #[test]
269 fn test_extend_selection_comments() {
270 do_check(
271 r#"
272fn bar(){}
273
274// fn foo() {
275// 1 + <|>1
276// }
277
278// fn foo(){}
279 "#,
280 &["1", "// 1 + 1", "// fn foo() {\n// 1 + 1\n// }"],
281 );
282
283 do_check(
284 r#"
285// #[derive(Debug, Clone, Copy, PartialEq, Eq)]
286// pub enum Direction {
287// <|> Next,
288// Prev
289// }
290"#,
291 &[
292 "// Next,",
293 "// #[derive(Debug, Clone, Copy, PartialEq, Eq)]\n// pub enum Direction {\n// Next,\n// Prev\n// }",
294 ],
295 );
296
297 do_check(
298 r#"
299/*
300foo
301_bar1<|>*/
302 "#,
303 &["_bar1", "/*\nfoo\n_bar1*/"],
304 );
305
306 do_check(
307 r#"
308//!<|>foo_2 bar
309 "#,
310 &["foo_2", "//!foo_2 bar"],
311 );
312
313 do_check(
314 r#"
315/<|>/foo bar
316 "#,
317 &["//foo bar"],
318 );
319 }
320
321 #[test]
322 fn test_extend_selection_prefer_idents() {
323 do_check(
324 r#"
325fn main() { foo<|>+bar;}
326 "#,
327 &["foo", "foo+bar"],
328 );
329 do_check(
330 r#"
331fn main() { foo+<|>bar;}
332 "#,
333 &["bar", "foo+bar"],
334 );
335 }
336
337 #[test]
338 fn test_extend_selection_prefer_lifetimes() {
339 do_check(r#"fn foo<<|>'a>() {}"#, &["'a", "<'a>"]);
340 do_check(r#"fn foo<'a<|>>() {}"#, &["'a", "<'a>"]);
341 }
342
343 #[test]
344 fn test_extend_selection_select_first_word() {
345 do_check(r#"// foo bar b<|>az quxx"#, &["baz", "// foo bar baz quxx"]);
346 do_check(
347 r#"
348impl S {
349 fn foo() {
350 // hel<|>lo world
351 }
352}
353 "#,
354 &["hello", "// hello world"],
355 );
356 }
357
358 #[test]
359 fn test_extend_selection_string() {
360 do_check(
361 r#"
362fn bar(){}
363
364" fn f<|>oo() {"
365 "#,
366 &["foo", "\" fn foo() {\""],
367 );
368 }
369}