aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorbors[bot] <bors[bot]@users.noreply.github.com>2019-03-20 20:53:45 +0000
committerbors[bot] <bors[bot]@users.noreply.github.com>2019-03-20 20:53:45 +0000
commit8b61ea127c596e4305acffa0d9180e48bd57f6c4 (patch)
tree748877a47b9a94d65d40037fbcf0d6be375569ce
parent90ff3ba64133c1bcae9d49709c4dd704ae59b1ee (diff)
parentb931a472c4465e553f23c8b0e0e754b7b06169dd (diff)
Merge #1008
1008: Move extend selection to ra_ide_api r=matklad a=matklad Co-authored-by: Aleksey Kladov <[email protected]>
-rw-r--r--Cargo.lock1
-rw-r--r--crates/ra_cli/Cargo.toml1
-rw-r--r--crates/ra_cli/src/main.rs22
-rw-r--r--crates/ra_ide_api/src/extend_selection.rs377
-rw-r--r--crates/ra_ide_api/src/lib.rs17
-rw-r--r--crates/ra_ide_api_light/src/extend_selection.rs369
-rw-r--r--crates/ra_ide_api_light/src/lib.rs34
7 files changed, 420 insertions, 401 deletions
diff --git a/Cargo.lock b/Cargo.lock
index ad9f6970b..eef6fa621 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -945,6 +945,7 @@ dependencies = [
945 "ra_batch 0.1.0", 945 "ra_batch 0.1.0",
946 "ra_db 0.1.0", 946 "ra_db 0.1.0",
947 "ra_hir 0.1.0", 947 "ra_hir 0.1.0",
948 "ra_ide_api 0.1.0",
948 "ra_ide_api_light 0.1.0", 949 "ra_ide_api_light 0.1.0",
949 "ra_syntax 0.1.0", 950 "ra_syntax 0.1.0",
950 "tools 0.1.0", 951 "tools 0.1.0",
diff --git a/crates/ra_cli/Cargo.toml b/crates/ra_cli/Cargo.toml
index ff30bf0b3..4c666f556 100644
--- a/crates/ra_cli/Cargo.toml
+++ b/crates/ra_cli/Cargo.toml
@@ -13,6 +13,7 @@ flexi_logger = "0.11.0"
13indicatif = "0.11.0" 13indicatif = "0.11.0"
14 14
15ra_syntax = { path = "../ra_syntax" } 15ra_syntax = { path = "../ra_syntax" }
16ra_ide_api = { path = "../ra_ide_api" }
16ra_ide_api_light = { path = "../ra_ide_api_light" } 17ra_ide_api_light = { path = "../ra_ide_api_light" }
17tools = { path = "../tools" } 18tools = { path = "../tools" }
18ra_batch = { path = "../ra_batch" } 19ra_batch = { path = "../ra_batch" }
diff --git a/crates/ra_cli/src/main.rs b/crates/ra_cli/src/main.rs
index 294f4b8af..5285f1f28 100644
--- a/crates/ra_cli/src/main.rs
+++ b/crates/ra_cli/src/main.rs
@@ -4,7 +4,8 @@ use std::{fs, io::Read, path::Path, time::Instant};
4 4
5use clap::{App, Arg, SubCommand}; 5use clap::{App, Arg, SubCommand};
6use join_to_string::join; 6use join_to_string::join;
7use ra_ide_api_light::{extend_selection, file_structure}; 7use ra_ide_api::{Analysis, FileRange};
8use ra_ide_api_light::file_structure;
8use ra_syntax::{SourceFile, TextRange, TreeArc, AstNode}; 9use ra_syntax::{SourceFile, TextRange, TreeArc, AstNode};
9use tools::collect_tests; 10use tools::collect_tests;
10use flexi_logger::Logger; 11use flexi_logger::Logger;
@@ -59,8 +60,8 @@ fn main() -> Result<()> {
59 ("extend-selection", Some(matches)) => { 60 ("extend-selection", Some(matches)) => {
60 let start: u32 = matches.value_of("start").unwrap().parse()?; 61 let start: u32 = matches.value_of("start").unwrap().parse()?;
61 let end: u32 = matches.value_of("end").unwrap().parse()?; 62 let end: u32 = matches.value_of("end").unwrap().parse()?;
62 let file = file()?; 63 let text = read_stdin()?;
63 let sels = selections(&file, start, end); 64 let sels = selections(text, start, end);
64 println!("{}", sels) 65 println!("{}", sels)
65 } 66 }
66 ("analysis-stats", Some(matches)) => { 67 ("analysis-stats", Some(matches)) => {
@@ -98,12 +99,17 @@ fn render_test(file: &Path, line: usize) -> Result<(String, String)> {
98 Ok((test.text, tree)) 99 Ok((test.text, tree))
99} 100}
100 101
101fn selections(file: &SourceFile, start: u32, end: u32) -> String { 102fn selections(text: String, start: u32, end: u32) -> String {
103 let (analysis, file_id) = Analysis::from_single_file(text);
102 let mut ranges = Vec::new(); 104 let mut ranges = Vec::new();
103 let mut cur = Some(TextRange::from_to((start - 1).into(), (end - 1).into())); 105 let mut range = TextRange::from_to((start - 1).into(), (end - 1).into());
104 while let Some(r) = cur { 106 loop {
105 ranges.push(r); 107 ranges.push(range);
106 cur = extend_selection(file.syntax(), r); 108 let next = analysis.extend_selection(FileRange { file_id, range }).unwrap();
109 if range == next {
110 break;
111 }
112 range = next;
107 } 113 }
108 let ranges = ranges 114 let ranges = ranges
109 .iter() 115 .iter()
diff --git a/crates/ra_ide_api/src/extend_selection.rs b/crates/ra_ide_api/src/extend_selection.rs
index d23290b74..63879a0b5 100644
--- a/crates/ra_ide_api/src/extend_selection.rs
+++ b/crates/ra_ide_api/src/extend_selection.rs
@@ -1,13 +1,378 @@
1use ra_db::SourceDatabase; 1use ra_db::SourceDatabase;
2use ra_syntax::AstNode; 2use ra_syntax::{
3 3 Direction, SyntaxNode, TextRange, TextUnit, AstNode,
4use crate::{ 4 algo::{find_covering_node, find_leaf_at_offset, LeafAtOffset},
5 TextRange, FileRange, 5 SyntaxKind::*,
6 db::RootDatabase,
7}; 6};
8 7
8use crate::{FileRange, db::RootDatabase};
9
9// FIXME: restore macro support 10// FIXME: restore macro support
10pub(crate) fn extend_selection(db: &RootDatabase, frange: FileRange) -> TextRange { 11pub(crate) fn extend_selection(db: &RootDatabase, frange: FileRange) -> TextRange {
11 let source_file = db.parse(frange.file_id); 12 let source_file = db.parse(frange.file_id);
12 ra_ide_api_light::extend_selection(source_file.syntax(), frange.range).unwrap_or(frange.range) 13 try_extend_selection(source_file.syntax(), frange.range).unwrap_or(frange.range)
14}
15
16fn try_extend_selection(root: &SyntaxNode, range: TextRange) -> Option<TextRange> {
17 let string_kinds = [COMMENT, STRING, RAW_STRING, BYTE_STRING, RAW_BYTE_STRING];
18 let list_kinds = [
19 FIELD_PAT_LIST,
20 MATCH_ARM_LIST,
21 NAMED_FIELD_DEF_LIST,
22 POS_FIELD_DEF_LIST,
23 NAMED_FIELD_LIST,
24 ENUM_VARIANT_LIST,
25 USE_TREE_LIST,
26 TYPE_PARAM_LIST,
27 TYPE_ARG_LIST,
28 PARAM_LIST,
29 ARG_LIST,
30 ARRAY_EXPR,
31 ];
32
33 if range.is_empty() {
34 let offset = range.start();
35 let mut leaves = find_leaf_at_offset(root, offset);
36 if leaves.clone().all(|it| it.kind() == WHITESPACE) {
37 return Some(extend_ws(root, leaves.next()?, offset));
38 }
39 let leaf_range = match leaves {
40 LeafAtOffset::None => return None,
41 LeafAtOffset::Single(l) => {
42 if string_kinds.contains(&l.kind()) {
43 extend_single_word_in_comment_or_string(l, offset).unwrap_or_else(|| l.range())
44 } else {
45 l.range()
46 }
47 }
48 LeafAtOffset::Between(l, r) => pick_best(l, r).range(),
49 };
50 return Some(leaf_range);
51 };
52 let node = find_covering_node(root, range);
53
54 // Using shallowest node with same range allows us to traverse siblings.
55 let node = node.ancestors().take_while(|n| n.range() == node.range()).last().unwrap();
56
57 if range == node.range() {
58 if string_kinds.contains(&node.kind()) {
59 if let Some(range) = extend_comments(node) {
60 return Some(range);
61 }
62 }
63
64 if node.parent().map(|n| list_kinds.contains(&n.kind())) == Some(true) {
65 if let Some(range) = extend_list_item(node) {
66 return Some(range);
67 }
68 }
69 }
70
71 match node.ancestors().skip_while(|n| n.range() == range).next() {
72 None => None,
73 Some(parent) => Some(parent.range()),
74 }
75}
76
77fn extend_single_word_in_comment_or_string(
78 leaf: &SyntaxNode,
79 offset: TextUnit,
80) -> Option<TextRange> {
81 let text: &str = leaf.leaf_text()?;
82 let cursor_position: u32 = (offset - leaf.range().start()).into();
83
84 let (before, after) = text.split_at(cursor_position as usize);
85
86 fn non_word_char(c: char) -> bool {
87 !(c.is_alphanumeric() || c == '_')
88 }
89
90 let start_idx = before.rfind(non_word_char)? as u32;
91 let end_idx = after.find(non_word_char).unwrap_or(after.len()) as u32;
92
93 let from: TextUnit = (start_idx + 1).into();
94 let to: TextUnit = (cursor_position + end_idx).into();
95
96 let range = TextRange::from_to(from, to);
97 if range.is_empty() {
98 None
99 } else {
100 Some(range + leaf.range().start())
101 }
102}
103
104fn extend_ws(root: &SyntaxNode, ws: &SyntaxNode, offset: TextUnit) -> TextRange {
105 let ws_text = ws.leaf_text().unwrap();
106 let suffix = TextRange::from_to(offset, ws.range().end()) - ws.range().start();
107 let prefix = TextRange::from_to(ws.range().start(), offset) - ws.range().start();
108 let ws_suffix = &ws_text.as_str()[suffix];
109 let ws_prefix = &ws_text.as_str()[prefix];
110 if ws_text.contains('\n') && !ws_suffix.contains('\n') {
111 if let Some(node) = ws.next_sibling() {
112 let start = match ws_prefix.rfind('\n') {
113 Some(idx) => ws.range().start() + TextUnit::from((idx + 1) as u32),
114 None => node.range().start(),
115 };
116 let end = if root.text().char_at(node.range().end()) == Some('\n') {
117 node.range().end() + TextUnit::of_char('\n')
118 } else {
119 node.range().end()
120 };
121 return TextRange::from_to(start, end);
122 }
123 }
124 ws.range()
125}
126
127fn pick_best<'a>(l: &'a SyntaxNode, r: &'a SyntaxNode) -> &'a SyntaxNode {
128 return if priority(r) > priority(l) { r } else { l };
129 fn priority(n: &SyntaxNode) -> usize {
130 match n.kind() {
131 WHITESPACE => 0,
132 IDENT | SELF_KW | SUPER_KW | CRATE_KW | LIFETIME => 2,
133 _ => 1,
134 }
135 }
136}
137
138/// Extend list item selection to include nearby comma and whitespace.
139fn extend_list_item(node: &SyntaxNode) -> Option<TextRange> {
140 fn is_single_line_ws(node: &SyntaxNode) -> bool {
141 node.kind() == WHITESPACE && !node.leaf_text().unwrap().contains('\n')
142 }
143
144 fn nearby_comma(node: &SyntaxNode, dir: Direction) -> Option<&SyntaxNode> {
145 node.siblings(dir)
146 .skip(1)
147 .skip_while(|node| is_single_line_ws(node))
148 .next()
149 .filter(|node| node.kind() == COMMA)
150 }
151
152 if let Some(comma_node) = nearby_comma(node, Direction::Prev) {
153 return Some(TextRange::from_to(comma_node.range().start(), node.range().end()));
154 }
155
156 if let Some(comma_node) = nearby_comma(node, Direction::Next) {
157 // Include any following whitespace when comma if after list item.
158 let final_node = comma_node
159 .siblings(Direction::Next)
160 .skip(1)
161 .next()
162 .filter(|node| is_single_line_ws(node))
163 .unwrap_or(comma_node);
164
165 return Some(TextRange::from_to(node.range().start(), final_node.range().end()));
166 }
167
168 return None;
169}
170
171fn extend_comments(node: &SyntaxNode) -> Option<TextRange> {
172 let prev = adj_comments(node, Direction::Prev);
173 let next = adj_comments(node, Direction::Next);
174 if prev != next {
175 Some(TextRange::from_to(prev.range().start(), next.range().end()))
176 } else {
177 None
178 }
179}
180
181fn adj_comments(node: &SyntaxNode, dir: Direction) -> &SyntaxNode {
182 let mut res = node;
183 for node in node.siblings(dir) {
184 match node.kind() {
185 COMMENT => res = node,
186 WHITESPACE if !node.leaf_text().unwrap().as_str().contains("\n\n") => (),
187 _ => break,
188 }
189 }
190 res
191}
192
193#[cfg(test)]
194mod tests {
195 use ra_syntax::{SourceFile, AstNode};
196 use test_utils::extract_offset;
197
198 use super::*;
199
200 fn do_check(before: &str, afters: &[&str]) {
201 let (cursor, before) = extract_offset(before);
202 let file = SourceFile::parse(&before);
203 let mut range = TextRange::offset_len(cursor, 0.into());
204 for &after in afters {
205 range = try_extend_selection(file.syntax(), range).unwrap();
206 let actual = &before[range];
207 assert_eq!(after, actual);
208 }
209 }
210
211 #[test]
212 fn test_extend_selection_arith() {
213 do_check(r#"fn foo() { <|>1 + 1 }"#, &["1", "1 + 1", "{ 1 + 1 }"]);
214 }
215
216 #[test]
217 fn test_extend_selection_list() {
218 do_check(r#"fn foo(<|>x: i32) {}"#, &["x", "x: i32"]);
219 do_check(r#"fn foo(<|>x: i32, y: i32) {}"#, &["x", "x: i32", "x: i32, "]);
220 do_check(r#"fn foo(<|>x: i32,y: i32) {}"#, &["x", "x: i32", "x: i32,"]);
221 do_check(r#"fn foo(x: i32, <|>y: i32) {}"#, &["y", "y: i32", ", y: i32"]);
222 do_check(r#"fn foo(x: i32, <|>y: i32, ) {}"#, &["y", "y: i32", ", y: i32"]);
223 do_check(r#"fn foo(x: i32,<|>y: i32) {}"#, &["y", "y: i32", ",y: i32"]);
224
225 do_check(r#"const FOO: [usize; 2] = [ 22<|> , 33];"#, &["22", "22 , "]);
226 do_check(r#"const FOO: [usize; 2] = [ 22 , 33<|>];"#, &["33", ", 33"]);
227 do_check(r#"const FOO: [usize; 2] = [ 22 , 33<|> ,];"#, &["33", ", 33"]);
228
229 do_check(
230 r#"
231const FOO: [usize; 2] = [
232 22,
233 <|>33,
234]"#,
235 &["33", "33,"],
236 );
237
238 do_check(
239 r#"
240const FOO: [usize; 2] = [
241 22
242 , 33<|>,
243]"#,
244 &["33", ", 33"],
245 );
246 }
247
248 #[test]
249 fn test_extend_selection_start_of_the_line() {
250 do_check(
251 r#"
252impl S {
253<|> fn foo() {
254
255 }
256}"#,
257 &[" fn foo() {\n\n }\n"],
258 );
259 }
260
261 #[test]
262 fn test_extend_selection_doc_comments() {
263 do_check(
264 r#"
265struct A;
266
267/// bla
268/// bla
269struct B {
270 <|>
271}
272 "#,
273 &["\n \n", "{\n \n}", "/// bla\n/// bla\nstruct B {\n \n}"],
274 )
275 }
276
277 #[test]
278 fn test_extend_selection_comments() {
279 do_check(
280 r#"
281fn bar(){}
282
283// fn foo() {
284// 1 + <|>1
285// }
286
287// fn foo(){}
288 "#,
289 &["1", "// 1 + 1", "// fn foo() {\n// 1 + 1\n// }"],
290 );
291
292 do_check(
293 r#"
294// #[derive(Debug, Clone, Copy, PartialEq, Eq)]
295// pub enum Direction {
296// <|> Next,
297// Prev
298// }
299"#,
300 &[
301 "// Next,",
302 "// #[derive(Debug, Clone, Copy, PartialEq, Eq)]\n// pub enum Direction {\n// Next,\n// Prev\n// }",
303 ],
304 );
305
306 do_check(
307 r#"
308/*
309foo
310_bar1<|>*/
311 "#,
312 &["_bar1", "/*\nfoo\n_bar1*/"],
313 );
314
315 do_check(
316 r#"
317//!<|>foo_2 bar
318 "#,
319 &["foo_2", "//!foo_2 bar"],
320 );
321
322 do_check(
323 r#"
324/<|>/foo bar
325 "#,
326 &["//foo bar"],
327 );
328 }
329
330 #[test]
331 fn test_extend_selection_prefer_idents() {
332 do_check(
333 r#"
334fn main() { foo<|>+bar;}
335 "#,
336 &["foo", "foo+bar"],
337 );
338 do_check(
339 r#"
340fn main() { foo+<|>bar;}
341 "#,
342 &["bar", "foo+bar"],
343 );
344 }
345
346 #[test]
347 fn test_extend_selection_prefer_lifetimes() {
348 do_check(r#"fn foo<<|>'a>() {}"#, &["'a", "<'a>"]);
349 do_check(r#"fn foo<'a<|>>() {}"#, &["'a", "<'a>"]);
350 }
351
352 #[test]
353 fn test_extend_selection_select_first_word() {
354 do_check(r#"// foo bar b<|>az quxx"#, &["baz", "// foo bar baz quxx"]);
355 do_check(
356 r#"
357impl S {
358 fn foo() {
359 // hel<|>lo world
360 }
361}
362 "#,
363 &["hello", "// hello world"],
364 );
365 }
366
367 #[test]
368 fn test_extend_selection_string() {
369 do_check(
370 r#"
371fn bar(){}
372
373" fn f<|>oo() {"
374 "#,
375 &["foo", "\" fn foo() {\""],
376 );
377 }
13} 378}
diff --git a/crates/ra_ide_api/src/lib.rs b/crates/ra_ide_api/src/lib.rs
index b8a4adbce..81ca57035 100644
--- a/crates/ra_ide_api/src/lib.rs
+++ b/crates/ra_ide_api/src/lib.rs
@@ -211,6 +211,23 @@ pub struct Analysis {
211// API, the API should in theory be usable as a library, or via a different 211// API, the API should in theory be usable as a library, or via a different
212// protocol. 212// protocol.
213impl Analysis { 213impl Analysis {
214 // Creates an analysis instance for a single file, without any extenal
215 // dependencies, stdlib support or ability to apply changes. See
216 // `AnalysisHost` for creating a fully-featured analysis.
217 pub fn from_single_file(text: String) -> (Analysis, FileId) {
218 let mut host = AnalysisHost::default();
219 let source_root = SourceRootId(0);
220 let mut change = AnalysisChange::new();
221 change.add_root(source_root, true);
222 let mut crate_graph = CrateGraph::default();
223 let file_id = FileId(0);
224 crate_graph.add_crate_root(file_id, Edition::Edition2018);
225 change.add_file(source_root, file_id, "main.rs".into(), Arc::new(text));
226 change.set_crate_graph(crate_graph);
227 host.apply_change(change);
228 (host.analysis(), file_id)
229 }
230
214 /// Debug info about the current state of the analysis 231 /// Debug info about the current state of the analysis
215 pub fn status(&self) -> String { 232 pub fn status(&self) -> String {
216 status::status(&*self.db) 233 status::status(&*self.db)
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}
diff --git a/crates/ra_ide_api_light/src/lib.rs b/crates/ra_ide_api_light/src/lib.rs
index 43cdd6ea4..ca13eb018 100644
--- a/crates/ra_ide_api_light/src/lib.rs
+++ b/crates/ra_ide_api_light/src/lib.rs
@@ -3,7 +3,6 @@
3//! This usually means functions which take syntax tree as an input and produce 3//! This usually means functions which take syntax tree as an input and produce
4//! an edit or some auxiliary info. 4//! an edit or some auxiliary info.
5 5
6mod extend_selection;
7mod folding_ranges; 6mod folding_ranges;
8mod line_index; 7mod line_index;
9mod line_index_utils; 8mod line_index_utils;
@@ -14,15 +13,16 @@ mod join_lines;
14mod typing; 13mod typing;
15mod diagnostics; 14mod diagnostics;
16 15
17#[derive(Debug)] 16use rustc_hash::FxHashSet;
18pub struct LocalEdit { 17use ra_text_edit::TextEditBuilder;
19 pub label: String, 18use ra_syntax::{
20 pub edit: ra_text_edit::TextEdit, 19 SourceFile, SyntaxNode, TextRange, TextUnit, Direction,
21 pub cursor_position: Option<TextUnit>, 20 algo::find_leaf_at_offset,
22} 21 SyntaxKind::{self, *},
22 ast::{self, AstNode},
23};
23 24
24pub use self::{ 25pub use crate::{
25 extend_selection::extend_selection,
26 folding_ranges::{folding_ranges, Fold, FoldKind}, 26 folding_ranges::{folding_ranges, Fold, FoldKind},
27 line_index::{LineCol, LineIndex}, 27 line_index::{LineCol, LineIndex},
28 line_index_utils::translate_offset_with_edit, 28 line_index_utils::translate_offset_with_edit,
@@ -30,16 +30,14 @@ pub use self::{
30 diagnostics::diagnostics, 30 diagnostics::diagnostics,
31 join_lines::join_lines, 31 join_lines::join_lines,
32 typing::{on_enter, on_dot_typed, on_eq_typed}, 32 typing::{on_enter, on_dot_typed, on_eq_typed},
33
34}; 33};
35use ra_text_edit::TextEditBuilder; 34
36use ra_syntax::{ 35#[derive(Debug)]
37 SourceFile, SyntaxNode, TextRange, TextUnit, Direction, 36pub struct LocalEdit {
38 SyntaxKind::{self, *}, 37 pub label: String,
39 ast::{self, AstNode}, 38 pub edit: ra_text_edit::TextEdit,
40 algo::find_leaf_at_offset, 39 pub cursor_position: Option<TextUnit>,
41}; 40}
42use rustc_hash::FxHashSet;
43 41
44#[derive(Debug)] 42#[derive(Debug)]
45pub struct HighlightedRange { 43pub struct HighlightedRange {