diff options
author | bors[bot] <bors[bot]@users.noreply.github.com> | 2019-01-08 19:48:48 +0000 |
---|---|---|
committer | bors[bot] <bors[bot]@users.noreply.github.com> | 2019-01-08 19:48:48 +0000 |
commit | 46f74e33ca53a7897e9020d3de75cc76a6b89d79 (patch) | |
tree | 2bc001c8ecf58b49ac9a0da1f20d5644ce29fb3a /crates/ra_ide_api_light/src/extend_selection.rs | |
parent | 4f4f7933b1b7ff34f8633b1686b18b2d1b994c47 (diff) | |
parent | 0c62b1bb7a49bf527780ce1f8cade5eb4fbfdb2d (diff) |
Merge #471
471: rename crates to match reality r=matklad a=matklad
Co-authored-by: Aleksey Kladov <[email protected]>
Diffstat (limited to 'crates/ra_ide_api_light/src/extend_selection.rs')
-rw-r--r-- | crates/ra_ide_api_light/src/extend_selection.rs | 281 |
1 files changed, 281 insertions, 0 deletions
diff --git a/crates/ra_ide_api_light/src/extend_selection.rs b/crates/ra_ide_api_light/src/extend_selection.rs new file mode 100644 index 000000000..08cae5a51 --- /dev/null +++ b/crates/ra_ide_api_light/src/extend_selection.rs | |||
@@ -0,0 +1,281 @@ | |||
1 | use ra_syntax::{ | ||
2 | Direction, SyntaxNode, TextRange, TextUnit, | ||
3 | algo::{find_covering_node, find_leaf_at_offset, LeafAtOffset}, | ||
4 | SyntaxKind::*, | ||
5 | }; | ||
6 | |||
7 | pub fn extend_selection(root: &SyntaxNode, range: TextRange) -> Option<TextRange> { | ||
8 | let string_kinds = [COMMENT, STRING, RAW_STRING, BYTE_STRING, RAW_BYTE_STRING]; | ||
9 | if range.is_empty() { | ||
10 | let offset = range.start(); | ||
11 | let mut leaves = find_leaf_at_offset(root, offset); | ||
12 | if leaves.clone().all(|it| it.kind() == WHITESPACE) { | ||
13 | return Some(extend_ws(root, leaves.next()?, offset)); | ||
14 | } | ||
15 | let leaf_range = match leaves { | ||
16 | LeafAtOffset::None => return None, | ||
17 | LeafAtOffset::Single(l) => { | ||
18 | if string_kinds.contains(&l.kind()) { | ||
19 | extend_single_word_in_comment_or_string(l, offset).unwrap_or_else(|| l.range()) | ||
20 | } else { | ||
21 | l.range() | ||
22 | } | ||
23 | } | ||
24 | LeafAtOffset::Between(l, r) => pick_best(l, r).range(), | ||
25 | }; | ||
26 | return Some(leaf_range); | ||
27 | }; | ||
28 | let node = find_covering_node(root, range); | ||
29 | if string_kinds.contains(&node.kind()) && range == node.range() { | ||
30 | if let Some(range) = extend_comments(node) { | ||
31 | return Some(range); | ||
32 | } | ||
33 | } | ||
34 | |||
35 | match node.ancestors().skip_while(|n| n.range() == range).next() { | ||
36 | None => None, | ||
37 | Some(parent) => Some(parent.range()), | ||
38 | } | ||
39 | } | ||
40 | |||
41 | fn extend_single_word_in_comment_or_string( | ||
42 | leaf: &SyntaxNode, | ||
43 | offset: TextUnit, | ||
44 | ) -> Option<TextRange> { | ||
45 | let text: &str = leaf.leaf_text()?; | ||
46 | let cursor_position: u32 = (offset - leaf.range().start()).into(); | ||
47 | |||
48 | let (before, after) = text.split_at(cursor_position as usize); | ||
49 | |||
50 | fn non_word_char(c: char) -> bool { | ||
51 | !(c.is_alphanumeric() || c == '_') | ||
52 | } | ||
53 | |||
54 | let start_idx = before.rfind(non_word_char)? as u32; | ||
55 | let end_idx = after.find(non_word_char).unwrap_or(after.len()) as u32; | ||
56 | |||
57 | let from: TextUnit = (start_idx + 1).into(); | ||
58 | let to: TextUnit = (cursor_position + end_idx).into(); | ||
59 | |||
60 | let range = TextRange::from_to(from, to); | ||
61 | if range.is_empty() { | ||
62 | None | ||
63 | } else { | ||
64 | Some(range + leaf.range().start()) | ||
65 | } | ||
66 | } | ||
67 | |||
68 | fn extend_ws(root: &SyntaxNode, ws: &SyntaxNode, offset: TextUnit) -> TextRange { | ||
69 | let ws_text = ws.leaf_text().unwrap(); | ||
70 | let suffix = TextRange::from_to(offset, ws.range().end()) - ws.range().start(); | ||
71 | let prefix = TextRange::from_to(ws.range().start(), offset) - ws.range().start(); | ||
72 | let ws_suffix = &ws_text.as_str()[suffix]; | ||
73 | let ws_prefix = &ws_text.as_str()[prefix]; | ||
74 | if ws_text.contains('\n') && !ws_suffix.contains('\n') { | ||
75 | if let Some(node) = ws.next_sibling() { | ||
76 | let start = match ws_prefix.rfind('\n') { | ||
77 | Some(idx) => ws.range().start() + TextUnit::from((idx + 1) as u32), | ||
78 | None => node.range().start(), | ||
79 | }; | ||
80 | let end = if root.text().char_at(node.range().end()) == Some('\n') { | ||
81 | node.range().end() + TextUnit::of_char('\n') | ||
82 | } else { | ||
83 | node.range().end() | ||
84 | }; | ||
85 | return TextRange::from_to(start, end); | ||
86 | } | ||
87 | } | ||
88 | ws.range() | ||
89 | } | ||
90 | |||
91 | fn pick_best<'a>(l: &'a SyntaxNode, r: &'a SyntaxNode) -> &'a SyntaxNode { | ||
92 | return if priority(r) > priority(l) { r } else { l }; | ||
93 | fn priority(n: &SyntaxNode) -> usize { | ||
94 | match n.kind() { | ||
95 | WHITESPACE => 0, | ||
96 | IDENT | SELF_KW | SUPER_KW | CRATE_KW | LIFETIME => 2, | ||
97 | _ => 1, | ||
98 | } | ||
99 | } | ||
100 | } | ||
101 | |||
102 | fn extend_comments(node: &SyntaxNode) -> Option<TextRange> { | ||
103 | let prev = adj_comments(node, Direction::Prev); | ||
104 | let next = adj_comments(node, Direction::Next); | ||
105 | if prev != next { | ||
106 | Some(TextRange::from_to(prev.range().start(), next.range().end())) | ||
107 | } else { | ||
108 | None | ||
109 | } | ||
110 | } | ||
111 | |||
112 | fn adj_comments(node: &SyntaxNode, dir: Direction) -> &SyntaxNode { | ||
113 | let mut res = node; | ||
114 | for node in node.siblings(dir) { | ||
115 | match node.kind() { | ||
116 | COMMENT => res = node, | ||
117 | WHITESPACE if !node.leaf_text().unwrap().as_str().contains("\n\n") => (), | ||
118 | _ => break, | ||
119 | } | ||
120 | } | ||
121 | res | ||
122 | } | ||
123 | |||
124 | #[cfg(test)] | ||
125 | mod tests { | ||
126 | use ra_syntax::{SourceFile, AstNode}; | ||
127 | use test_utils::extract_offset; | ||
128 | |||
129 | use super::*; | ||
130 | |||
131 | fn do_check(before: &str, afters: &[&str]) { | ||
132 | let (cursor, before) = extract_offset(before); | ||
133 | let file = SourceFile::parse(&before); | ||
134 | let mut range = TextRange::offset_len(cursor, 0.into()); | ||
135 | for &after in afters { | ||
136 | range = extend_selection(file.syntax(), range).unwrap(); | ||
137 | let actual = &before[range]; | ||
138 | assert_eq!(after, actual); | ||
139 | } | ||
140 | } | ||
141 | |||
142 | #[test] | ||
143 | fn test_extend_selection_arith() { | ||
144 | do_check(r#"fn foo() { <|>1 + 1 }"#, &["1", "1 + 1", "{ 1 + 1 }"]); | ||
145 | } | ||
146 | |||
147 | #[test] | ||
148 | fn test_extend_selection_start_of_the_lind() { | ||
149 | do_check( | ||
150 | r#" | ||
151 | impl S { | ||
152 | <|> fn foo() { | ||
153 | |||
154 | } | ||
155 | }"#, | ||
156 | &[" fn foo() {\n\n }\n"], | ||
157 | ); | ||
158 | } | ||
159 | |||
160 | #[test] | ||
161 | fn test_extend_selection_doc_comments() { | ||
162 | do_check( | ||
163 | r#" | ||
164 | struct A; | ||
165 | |||
166 | /// bla | ||
167 | /// bla | ||
168 | struct B { | ||
169 | <|> | ||
170 | } | ||
171 | "#, | ||
172 | &[ | ||
173 | "\n \n", | ||
174 | "{\n \n}", | ||
175 | "/// bla\n/// bla\nstruct B {\n \n}", | ||
176 | ], | ||
177 | ) | ||
178 | } | ||
179 | |||
180 | #[test] | ||
181 | fn test_extend_selection_comments() { | ||
182 | do_check( | ||
183 | r#" | ||
184 | fn bar(){} | ||
185 | |||
186 | // fn foo() { | ||
187 | // 1 + <|>1 | ||
188 | // } | ||
189 | |||
190 | // fn foo(){} | ||
191 | "#, | ||
192 | &["1", "// 1 + 1", "// fn foo() {\n// 1 + 1\n// }"], | ||
193 | ); | ||
194 | |||
195 | do_check( | ||
196 | r#" | ||
197 | // #[derive(Debug, Clone, Copy, PartialEq, Eq)] | ||
198 | // pub enum Direction { | ||
199 | // <|> Next, | ||
200 | // Prev | ||
201 | // } | ||
202 | "#, | ||
203 | &[ | ||
204 | "// Next,", | ||
205 | "// #[derive(Debug, Clone, Copy, PartialEq, Eq)]\n// pub enum Direction {\n// Next,\n// Prev\n// }", | ||
206 | ], | ||
207 | ); | ||
208 | |||
209 | do_check( | ||
210 | r#" | ||
211 | /* | ||
212 | foo | ||
213 | _bar1<|>*/ | ||
214 | "#, | ||
215 | &["_bar1", "/*\nfoo\n_bar1*/"], | ||
216 | ); | ||
217 | |||
218 | do_check( | ||
219 | r#" | ||
220 | //!<|>foo_2 bar | ||
221 | "#, | ||
222 | &["foo_2", "//!foo_2 bar"], | ||
223 | ); | ||
224 | |||
225 | do_check( | ||
226 | r#" | ||
227 | /<|>/foo bar | ||
228 | "#, | ||
229 | &["//foo bar"], | ||
230 | ); | ||
231 | } | ||
232 | |||
233 | #[test] | ||
234 | fn test_extend_selection_prefer_idents() { | ||
235 | do_check( | ||
236 | r#" | ||
237 | fn main() { foo<|>+bar;} | ||
238 | "#, | ||
239 | &["foo", "foo+bar"], | ||
240 | ); | ||
241 | do_check( | ||
242 | r#" | ||
243 | fn main() { foo+<|>bar;} | ||
244 | "#, | ||
245 | &["bar", "foo+bar"], | ||
246 | ); | ||
247 | } | ||
248 | |||
249 | #[test] | ||
250 | fn test_extend_selection_prefer_lifetimes() { | ||
251 | do_check(r#"fn foo<<|>'a>() {}"#, &["'a", "<'a>"]); | ||
252 | do_check(r#"fn foo<'a<|>>() {}"#, &["'a", "<'a>"]); | ||
253 | } | ||
254 | |||
255 | #[test] | ||
256 | fn test_extend_selection_select_first_word() { | ||
257 | do_check(r#"// foo bar b<|>az quxx"#, &["baz", "// foo bar baz quxx"]); | ||
258 | do_check( | ||
259 | r#" | ||
260 | impl S { | ||
261 | fn foo() { | ||
262 | // hel<|>lo world | ||
263 | } | ||
264 | } | ||
265 | "#, | ||
266 | &["hello", "// hello world"], | ||
267 | ); | ||
268 | } | ||
269 | |||
270 | #[test] | ||
271 | fn test_extend_selection_string() { | ||
272 | do_check( | ||
273 | r#" | ||
274 | fn bar(){} | ||
275 | |||
276 | " fn f<|>oo() {" | ||
277 | "#, | ||
278 | &["foo", "\" fn foo() {\""], | ||
279 | ); | ||
280 | } | ||
281 | } | ||