aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Cargo.lock1
-rw-r--r--crates/ra_hir/src/lib.rs2
-rw-r--r--crates/ra_hir/src/marks.rs82
-rw-r--r--crates/ra_hir/src/module_tree.rs6
-rw-r--r--crates/ra_hir/src/nameres/tests.rs29
-rw-r--r--crates/ra_ide_api/src/completion/complete_dot.rs2
-rw-r--r--crates/ra_ide_api/src/completion/complete_path.rs14
-rw-r--r--crates/ra_ide_api/src/completion/completion_context.rs14
-rw-r--r--crates/ra_ide_api/src/completion/completion_item.rs2
-rw-r--r--crates/ra_ide_api_light/src/assists/change_visibility.rs69
-rw-r--r--crates/ra_ide_api_light/src/assists/replace_if_let_with_match.rs26
-rw-r--r--crates/ra_ide_api_light/src/formatting.rs42
-rw-r--r--crates/ra_ide_api_light/src/join_lines.rs451
-rw-r--r--crates/ra_ide_api_light/src/lib.rs8
-rw-r--r--crates/ra_ide_api_light/src/typing.rs490
-rw-r--r--crates/ra_lsp_server/Cargo.toml1
-rw-r--r--crates/ra_lsp_server/src/project_model.rs56
-rw-r--r--crates/ra_syntax/src/grammar/items/nominal.rs4
-rw-r--r--crates/ra_syntax/tests/data/parser/inline/ok/0114_tuple_struct_where.rs2
-rw-r--r--crates/ra_syntax/tests/data/parser/inline/ok/0114_tuple_struct_where.txt62
20 files changed, 809 insertions, 554 deletions
diff --git a/Cargo.lock b/Cargo.lock
index dba94aa6b..45f1feb7e 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -733,6 +733,7 @@ dependencies = [
733 "languageserver-types 0.53.1 (registry+https://github.com/rust-lang/crates.io-index)", 733 "languageserver-types 0.53.1 (registry+https://github.com/rust-lang/crates.io-index)",
734 "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", 734 "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
735 "parking_lot 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)", 735 "parking_lot 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)",
736 "ra_arena 0.1.0",
736 "ra_ide_api 0.1.0", 737 "ra_ide_api 0.1.0",
737 "ra_syntax 0.1.0", 738 "ra_syntax 0.1.0",
738 "ra_text_edit 0.1.0", 739 "ra_text_edit 0.1.0",
diff --git a/crates/ra_hir/src/lib.rs b/crates/ra_hir/src/lib.rs
index 74957ffc9..1aca2f067 100644
--- a/crates/ra_hir/src/lib.rs
+++ b/crates/ra_hir/src/lib.rs
@@ -17,6 +17,8 @@ macro_rules! ctry {
17pub mod db; 17pub mod db;
18#[cfg(test)] 18#[cfg(test)]
19mod mock; 19mod mock;
20#[macro_use]
21mod marks;
20mod query_definitions; 22mod query_definitions;
21mod path; 23mod path;
22pub mod source_binder; 24pub mod source_binder;
diff --git a/crates/ra_hir/src/marks.rs b/crates/ra_hir/src/marks.rs
new file mode 100644
index 000000000..05430b975
--- /dev/null
+++ b/crates/ra_hir/src/marks.rs
@@ -0,0 +1,82 @@
1//! This module implements manually tracked test coverage, which useful for
2//! quickly finding a test responsible for testing a particular bit of code.
3//!
4//! See https://matklad.github.io/2018/06/18/a-trick-for-test-maintenance.html
5//! for details, but the TL;DR is that you write your test as
6//!
7//! ```no-run
8//! #[test]
9//! fn test_foo() {
10//! covers!(test_foo);
11//! }
12//! ```
13//!
14//! and in the code under test you write
15//!
16//! ```no-run
17//! fn foo() {
18//! if some_condition() {
19//! tested_by!(test_foo);
20//! }
21//! }
22//! ```
23//!
24//! This module then checks that executing the test indeed covers the specified
25//! function. This is useful if you come back to the `foo` function ten years
26//! later and wonder where the test are: now you can grep for `test_foo`.
27
28#[macro_export]
29macro_rules! tested_by {
30 ($ident:ident) => {
31 #[cfg(test)]
32 {
33 crate::marks::marks::$ident.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
34 }
35 };
36}
37
38#[macro_export]
39macro_rules! covers {
40 ($ident:ident) => {
41 let _checker = crate::marks::marks::MarkChecker::new(&crate::marks::marks::$ident);
42 };
43}
44
45#[cfg(test)]
46pub(crate) mod marks {
47 use std::sync::atomic::{AtomicUsize, Ordering};
48
49 pub(crate) struct MarkChecker {
50 mark: &'static AtomicUsize,
51 value_on_entry: usize,
52 }
53
54 impl MarkChecker {
55 pub(crate) fn new(mark: &'static AtomicUsize) -> MarkChecker {
56 let value_on_entry = mark.load(Ordering::SeqCst);
57 MarkChecker {
58 mark,
59 value_on_entry,
60 }
61 }
62 }
63
64 impl Drop for MarkChecker {
65 fn drop(&mut self) {
66 if std::thread::panicking() {
67 return;
68 }
69 let value_on_exit = self.mark.load(Ordering::SeqCst);
70 assert!(value_on_exit > self.value_on_entry, "mark was not hit")
71 }
72 }
73
74 macro_rules! mark {
75 ($ident:ident) => {
76 #[allow(bad_style)]
77 pub(crate) static $ident: AtomicUsize = AtomicUsize::new(0);
78 };
79 }
80
81 mark!(name_res_works_for_broken_modules);
82}
diff --git a/crates/ra_hir/src/module_tree.rs b/crates/ra_hir/src/module_tree.rs
index d2c92f150..50383c6d8 100644
--- a/crates/ra_hir/src/module_tree.rs
+++ b/crates/ra_hir/src/module_tree.rs
@@ -14,7 +14,7 @@ use ra_arena::{Arena, RawId, impl_arena_id};
14use crate::{Name, AsName, HirDatabase, SourceItemId, HirFileId, Problem, SourceFileItems, ModuleSource}; 14use crate::{Name, AsName, HirDatabase, SourceItemId, HirFileId, Problem, SourceFileItems, ModuleSource};
15 15
16impl ModuleSource { 16impl ModuleSource {
17 pub fn from_source_item_id( 17 pub(crate) fn from_source_item_id(
18 db: &impl HirDatabase, 18 db: &impl HirDatabase,
19 source_item_id: SourceItemId, 19 source_item_id: SourceItemId,
20 ) -> ModuleSource { 20 ) -> ModuleSource {
@@ -217,6 +217,10 @@ fn modules(root: &impl ast::ModuleItemOwner) -> impl Iterator<Item = (Name, &ast
217 }) 217 })
218 .filter_map(|module| { 218 .filter_map(|module| {
219 let name = module.name()?.as_name(); 219 let name = module.name()?.as_name();
220 if !module.has_semi() && module.item_list().is_none() {
221 tested_by!(name_res_works_for_broken_modules);
222 return None;
223 }
220 Some((name, module)) 224 Some((name, module))
221 }) 225 })
222} 226}
diff --git a/crates/ra_hir/src/nameres/tests.rs b/crates/ra_hir/src/nameres/tests.rs
index 17de54b44..ba9fcb3d1 100644
--- a/crates/ra_hir/src/nameres/tests.rs
+++ b/crates/ra_hir/src/nameres/tests.rs
@@ -137,6 +137,35 @@ fn re_exports() {
137} 137}
138 138
139#[test] 139#[test]
140fn name_res_works_for_broken_modules() {
141 covers!(name_res_works_for_broken_modules);
142 let (item_map, module_id) = item_map(
143 "
144 //- /lib.rs
145 mod foo // no `;`, no body
146
147 use self::foo::Baz;
148 <|>
149
150 //- /foo/mod.rs
151 pub mod bar;
152
153 pub use self::bar::Baz;
154
155 //- /foo/bar.rs
156 pub struct Baz;
157 ",
158 );
159 check_module_item_map(
160 &item_map,
161 module_id,
162 "
163 Baz: _
164 ",
165 );
166}
167
168#[test]
140fn item_map_contains_items_from_expansions() { 169fn item_map_contains_items_from_expansions() {
141 let (item_map, module_id) = item_map( 170 let (item_map, module_id) = item_map(
142 " 171 "
diff --git a/crates/ra_ide_api/src/completion/complete_dot.rs b/crates/ra_ide_api/src/completion/complete_dot.rs
index 65bba6dc7..80d0b1663 100644
--- a/crates/ra_ide_api/src/completion/complete_dot.rs
+++ b/crates/ra_ide_api/src/completion/complete_dot.rs
@@ -16,7 +16,7 @@ pub(super) fn complete_dot(acc: &mut Completions, ctx: &CompletionContext) -> Ca
16 None => return Ok(()), 16 None => return Ok(()),
17 }; 17 };
18 let receiver_ty = infer_result[expr].clone(); 18 let receiver_ty = infer_result[expr].clone();
19 if !ctx.is_method_call { 19 if !ctx.is_call {
20 complete_fields(acc, ctx, receiver_ty)?; 20 complete_fields(acc, ctx, receiver_ty)?;
21 } 21 }
22 Ok(()) 22 Ok(())
diff --git a/crates/ra_ide_api/src/completion/complete_path.rs b/crates/ra_ide_api/src/completion/complete_path.rs
index 9bfec88d0..4860db629 100644
--- a/crates/ra_ide_api/src/completion/complete_path.rs
+++ b/crates/ra_ide_api/src/completion/complete_path.rs
@@ -126,4 +126,18 @@ mod tests {
126 "foo", 126 "foo",
127 ) 127 )
128 } 128 }
129
130 #[test]
131 fn dont_render_function_parens_if_already_call() {
132 check_reference_completion(
133 "
134 //- /lib.rs
135 fn frobnicate() {}
136 fn main() {
137 frob<|>();
138 }
139 ",
140 "main;frobnicate",
141 )
142 }
129} 143}
diff --git a/crates/ra_ide_api/src/completion/completion_context.rs b/crates/ra_ide_api/src/completion/completion_context.rs
index 01786bb69..113f6c070 100644
--- a/crates/ra_ide_api/src/completion/completion_context.rs
+++ b/crates/ra_ide_api/src/completion/completion_context.rs
@@ -32,8 +32,8 @@ pub(super) struct CompletionContext<'a> {
32 pub(super) is_new_item: bool, 32 pub(super) is_new_item: bool,
33 /// The receiver if this is a field or method access, i.e. writing something.<|> 33 /// The receiver if this is a field or method access, i.e. writing something.<|>
34 pub(super) dot_receiver: Option<&'a ast::Expr>, 34 pub(super) dot_receiver: Option<&'a ast::Expr>,
35 /// If this is a method call in particular, i.e. the () are already there. 35 /// If this is a call (method or function) in particular, i.e. the () are already there.
36 pub(super) is_method_call: bool, 36 pub(super) is_call: bool,
37} 37}
38 38
39impl<'a> CompletionContext<'a> { 39impl<'a> CompletionContext<'a> {
@@ -60,7 +60,7 @@ impl<'a> CompletionContext<'a> {
60 can_be_stmt: false, 60 can_be_stmt: false,
61 is_new_item: false, 61 is_new_item: false,
62 dot_receiver: None, 62 dot_receiver: None,
63 is_method_call: false, 63 is_call: false,
64 }; 64 };
65 ctx.fill(original_file, position.offset); 65 ctx.fill(original_file, position.offset);
66 Ok(Some(ctx)) 66 Ok(Some(ctx))
@@ -172,6 +172,12 @@ impl<'a> CompletionContext<'a> {
172 } 172 }
173 } 173 }
174 } 174 }
175 self.is_call = path
176 .syntax()
177 .parent()
178 .and_then(ast::PathExpr::cast)
179 .and_then(|it| it.syntax().parent().and_then(ast::CallExpr::cast))
180 .is_some()
175 } 181 }
176 if let Some(field_expr) = ast::FieldExpr::cast(parent) { 182 if let Some(field_expr) = ast::FieldExpr::cast(parent) {
177 // The receiver comes before the point of insertion of the fake 183 // The receiver comes before the point of insertion of the fake
@@ -187,7 +193,7 @@ impl<'a> CompletionContext<'a> {
187 .expr() 193 .expr()
188 .map(|e| e.syntax().range()) 194 .map(|e| e.syntax().range())
189 .and_then(|r| find_node_with_range(original_file.syntax(), r)); 195 .and_then(|r| find_node_with_range(original_file.syntax(), r));
190 self.is_method_call = true; 196 self.is_call = true;
191 } 197 }
192 } 198 }
193} 199}
diff --git a/crates/ra_ide_api/src/completion/completion_item.rs b/crates/ra_ide_api/src/completion/completion_item.rs
index 334449fae..6a9770429 100644
--- a/crates/ra_ide_api/src/completion/completion_item.rs
+++ b/crates/ra_ide_api/src/completion/completion_item.rs
@@ -165,7 +165,7 @@ impl Builder {
165 165
166 fn from_function(mut self, ctx: &CompletionContext, function: hir::Function) -> Builder { 166 fn from_function(mut self, ctx: &CompletionContext, function: hir::Function) -> Builder {
167 // If not an import, add parenthesis automatically. 167 // If not an import, add parenthesis automatically.
168 if ctx.use_item_syntax.is_none() { 168 if ctx.use_item_syntax.is_none() && !ctx.is_call {
169 if function.signature(ctx.db).args().is_empty() { 169 if function.signature(ctx.db).args().is_empty() {
170 self.snippet = Some(format!("{}()$0", self.label)); 170 self.snippet = Some(format!("{}()$0", self.label));
171 } else { 171 } else {
diff --git a/crates/ra_ide_api_light/src/assists/change_visibility.rs b/crates/ra_ide_api_light/src/assists/change_visibility.rs
index 89729e2c2..6e8bc2632 100644
--- a/crates/ra_ide_api_light/src/assists/change_visibility.rs
+++ b/crates/ra_ide_api_light/src/assists/change_visibility.rs
@@ -1,7 +1,7 @@
1use ra_syntax::{ 1use ra_syntax::{
2 AstNode, 2 AstNode, SyntaxNode, TextUnit,
3 ast::{self, VisibilityOwner, NameOwner}, 3 ast::{self, VisibilityOwner, NameOwner},
4 SyntaxKind::{VISIBILITY, FN_KW, MOD_KW, STRUCT_KW, ENUM_KW, TRAIT_KW, FN_DEF, MODULE, STRUCT_DEF, ENUM_DEF, TRAIT_DEF, IDENT}, 4 SyntaxKind::{VISIBILITY, FN_KW, MOD_KW, STRUCT_KW, ENUM_KW, TRAIT_KW, FN_DEF, MODULE, STRUCT_DEF, ENUM_DEF, TRAIT_DEF, IDENT, WHITESPACE, COMMENT, ATTR},
5}; 5};
6 6
7use crate::assists::{AssistCtx, Assist}; 7use crate::assists::{AssistCtx, Assist};
@@ -30,14 +30,14 @@ fn add_vis(ctx: AssistCtx) -> Option<Assist> {
30 if parent.children().any(|child| child.kind() == VISIBILITY) { 30 if parent.children().any(|child| child.kind() == VISIBILITY) {
31 return None; 31 return None;
32 } 32 }
33 parent.range().start() 33 vis_offset(parent)
34 } else { 34 } else {
35 let ident = ctx.leaf_at_offset().find(|leaf| leaf.kind() == IDENT)?; 35 let ident = ctx.leaf_at_offset().find(|leaf| leaf.kind() == IDENT)?;
36 let field = ident.ancestors().find_map(ast::NamedFieldDef::cast)?; 36 let field = ident.ancestors().find_map(ast::NamedFieldDef::cast)?;
37 if field.name()?.syntax().range() != ident.range() && field.visibility().is_some() { 37 if field.name()?.syntax().range() != ident.range() && field.visibility().is_some() {
38 return None; 38 return None;
39 } 39 }
40 field.syntax().range().start() 40 vis_offset(field.syntax())
41 }; 41 };
42 42
43 ctx.build("make pub(crate)", |edit| { 43 ctx.build("make pub(crate)", |edit| {
@@ -46,14 +46,31 @@ fn add_vis(ctx: AssistCtx) -> Option<Assist> {
46 }) 46 })
47} 47}
48 48
49fn vis_offset(node: &SyntaxNode) -> TextUnit {
50 node.children()
51 .skip_while(|it| match it.kind() {
52 WHITESPACE | COMMENT | ATTR => true,
53 _ => false,
54 })
55 .next()
56 .map(|it| it.range().start())
57 .unwrap_or(node.range().start())
58}
59
49fn change_vis(ctx: AssistCtx, vis: &ast::Visibility) -> Option<Assist> { 60fn change_vis(ctx: AssistCtx, vis: &ast::Visibility) -> Option<Assist> {
50 if vis.syntax().text() != "pub" { 61 if vis.syntax().text() == "pub" {
51 return None; 62 return ctx.build("chage to pub(crate)", |edit| {
63 edit.replace(vis.syntax().range(), "pub(crate)");
64 edit.set_cursor(vis.syntax().range().start());
65 });
52 } 66 }
53 ctx.build("chage to pub(crate)", |edit| { 67 if vis.syntax().text() == "pub(crate)" {
54 edit.replace(vis.syntax().range(), "pub(crate)"); 68 return ctx.build("chage to pub", |edit| {
55 edit.set_cursor(vis.syntax().range().start()); 69 edit.replace(vis.syntax().range(), "pub");
56 }) 70 edit.set_cursor(vis.syntax().range().start());
71 });
72 }
73 None
57} 74}
58 75
59#[cfg(test)] 76#[cfg(test)]
@@ -113,4 +130,36 @@ mod tests {
113 "<|>pub(crate) fn foo() {}", 130 "<|>pub(crate) fn foo() {}",
114 ) 131 )
115 } 132 }
133
134 #[test]
135 fn change_visibility_pub_crate_to_pub() {
136 check_assist(
137 change_visibility,
138 "<|>pub(crate) fn foo() {}",
139 "<|>pub fn foo() {}",
140 )
141 }
142
143 #[test]
144 fn change_visibility_handles_comment_attrs() {
145 check_assist(
146 change_visibility,
147 "
148 /// docs
149
150 // comments
151
152 #[derive(Debug)]
153 <|>struct Foo;
154 ",
155 "
156 /// docs
157
158 // comments
159
160 #[derive(Debug)]
161 <|>pub(crate) struct Foo;
162 ",
163 )
164 }
116} 165}
diff --git a/crates/ra_ide_api_light/src/assists/replace_if_let_with_match.rs b/crates/ra_ide_api_light/src/assists/replace_if_let_with_match.rs
index 30c371480..d64c34d54 100644
--- a/crates/ra_ide_api_light/src/assists/replace_if_let_with_match.rs
+++ b/crates/ra_ide_api_light/src/assists/replace_if_let_with_match.rs
@@ -1,9 +1,9 @@
1use ra_syntax::{ 1use ra_syntax::{AstNode, ast};
2 AstNode, SyntaxKind::{L_CURLY, R_CURLY, WHITESPACE},
3 ast,
4};
5 2
6use crate::assists::{AssistCtx, Assist}; 3use crate::{
4 assists::{AssistCtx, Assist},
5 formatting::extract_trivial_expression,
6};
7 7
8pub fn replace_if_let_with_match(ctx: AssistCtx) -> Option<Assist> { 8pub fn replace_if_let_with_match(ctx: AssistCtx) -> Option<Assist> {
9 let if_expr: &ast::IfExpr = ctx.node_at_offset()?; 9 let if_expr: &ast::IfExpr = ctx.node_at_offset()?;
@@ -39,26 +39,12 @@ fn build_match_expr(
39} 39}
40 40
41fn format_arm(block: &ast::Block) -> String { 41fn format_arm(block: &ast::Block) -> String {
42 match extract_expression(block) { 42 match extract_trivial_expression(block) {
43 None => block.syntax().text().to_string(), 43 None => block.syntax().text().to_string(),
44 Some(e) => format!("{},", e.syntax().text()), 44 Some(e) => format!("{},", e.syntax().text()),
45 } 45 }
46} 46}
47 47
48fn extract_expression(block: &ast::Block) -> Option<&ast::Expr> {
49 let expr = block.expr()?;
50 let non_trivial_children = block.syntax().children().filter(|it| {
51 !(it == &expr.syntax()
52 || it.kind() == L_CURLY
53 || it.kind() == R_CURLY
54 || it.kind() == WHITESPACE)
55 });
56 if non_trivial_children.count() > 0 {
57 return None;
58 }
59 Some(expr)
60}
61
62#[cfg(test)] 48#[cfg(test)]
63mod tests { 49mod tests {
64 use super::*; 50 use super::*;
diff --git a/crates/ra_ide_api_light/src/formatting.rs b/crates/ra_ide_api_light/src/formatting.rs
new file mode 100644
index 000000000..1f3769209
--- /dev/null
+++ b/crates/ra_ide_api_light/src/formatting.rs
@@ -0,0 +1,42 @@
1use ra_syntax::{
2 ast, AstNode,
3 SyntaxNode, SyntaxKind::*,
4};
5
6pub(crate) fn extract_trivial_expression(block: &ast::Block) -> Option<&ast::Expr> {
7 let expr = block.expr()?;
8 if expr.syntax().text().contains('\n') {
9 return None;
10 }
11 let non_trivial_children = block.syntax().children().filter(|it| match it.kind() {
12 WHITESPACE | L_CURLY | R_CURLY => false,
13 _ => it != &expr.syntax(),
14 });
15 if non_trivial_children.count() > 0 {
16 return None;
17 }
18 Some(expr)
19}
20
21pub(crate) fn compute_ws(left: &SyntaxNode, right: &SyntaxNode) -> &'static str {
22 match left.kind() {
23 L_PAREN | L_BRACK => return "",
24 L_CURLY => {
25 if let USE_TREE = right.kind() {
26 return "";
27 }
28 }
29 _ => (),
30 }
31 match right.kind() {
32 R_PAREN | R_BRACK => return "",
33 R_CURLY => {
34 if let USE_TREE = left.kind() {
35 return "";
36 }
37 }
38 DOT => return "",
39 _ => (),
40 }
41 " "
42}
diff --git a/crates/ra_ide_api_light/src/join_lines.rs b/crates/ra_ide_api_light/src/join_lines.rs
new file mode 100644
index 000000000..ab7c5b4b5
--- /dev/null
+++ b/crates/ra_ide_api_light/src/join_lines.rs
@@ -0,0 +1,451 @@
1use itertools::Itertools;
2use ra_syntax::{
3 SourceFile, TextRange, TextUnit, AstNode, SyntaxNode,
4 SyntaxKind::{self, WHITESPACE, COMMA, R_CURLY, R_PAREN, R_BRACK},
5 algo::find_covering_node,
6 ast,
7};
8
9use crate::{
10 LocalEdit, TextEditBuilder,
11 formatting::{compute_ws, extract_trivial_expression},
12};
13
14pub fn join_lines(file: &SourceFile, range: TextRange) -> LocalEdit {
15 let range = if range.is_empty() {
16 let syntax = file.syntax();
17 let text = syntax.text().slice(range.start()..);
18 let pos = match text.find('\n') {
19 None => {
20 return LocalEdit {
21 label: "join lines".to_string(),
22 edit: TextEditBuilder::default().finish(),
23 cursor_position: None,
24 };
25 }
26 Some(pos) => pos,
27 };
28 TextRange::offset_len(range.start() + pos, TextUnit::of_char('\n'))
29 } else {
30 range
31 };
32
33 let node = find_covering_node(file.syntax(), range);
34 let mut edit = TextEditBuilder::default();
35 for node in node.descendants() {
36 let text = match node.leaf_text() {
37 Some(text) => text,
38 None => continue,
39 };
40 let range = match range.intersection(&node.range()) {
41 Some(range) => range,
42 None => continue,
43 } - node.range().start();
44 for (pos, _) in text[range].bytes().enumerate().filter(|&(_, b)| b == b'\n') {
45 let pos: TextUnit = (pos as u32).into();
46 let off = node.range().start() + range.start() + pos;
47 if !edit.invalidates_offset(off) {
48 remove_newline(&mut edit, node, text.as_str(), off);
49 }
50 }
51 }
52
53 LocalEdit {
54 label: "join lines".to_string(),
55 edit: edit.finish(),
56 cursor_position: None,
57 }
58}
59
60fn remove_newline(
61 edit: &mut TextEditBuilder,
62 node: &SyntaxNode,
63 node_text: &str,
64 offset: TextUnit,
65) {
66 if node.kind() != WHITESPACE || node_text.bytes().filter(|&b| b == b'\n').count() != 1 {
67 // The node is either the first or the last in the file
68 let suff = &node_text[TextRange::from_to(
69 offset - node.range().start() + TextUnit::of_char('\n'),
70 TextUnit::of_str(node_text),
71 )];
72 let spaces = suff.bytes().take_while(|&b| b == b' ').count();
73
74 edit.replace(
75 TextRange::offset_len(offset, ((spaces + 1) as u32).into()),
76 " ".to_string(),
77 );
78 return;
79 }
80
81 // Special case that turns something like:
82 //
83 // ```
84 // my_function({<|>
85 // <some-expr>
86 // })
87 // ```
88 //
89 // into `my_function(<some-expr>)`
90 if join_single_expr_block(edit, node).is_some() {
91 return;
92 }
93 // ditto for
94 //
95 // ```
96 // use foo::{<|>
97 // bar
98 // };
99 // ```
100 if join_single_use_tree(edit, node).is_some() {
101 return;
102 }
103
104 // The node is between two other nodes
105 let prev = node.prev_sibling().unwrap();
106 let next = node.next_sibling().unwrap();
107 if is_trailing_comma(prev.kind(), next.kind()) {
108 // Removes: trailing comma, newline (incl. surrounding whitespace)
109 edit.delete(TextRange::from_to(prev.range().start(), node.range().end()));
110 } else if prev.kind() == COMMA && next.kind() == R_CURLY {
111 // Removes: comma, newline (incl. surrounding whitespace)
112 let space = if let Some(left) = prev.prev_sibling() {
113 compute_ws(left, next)
114 } else {
115 " "
116 };
117 edit.replace(
118 TextRange::from_to(prev.range().start(), node.range().end()),
119 space.to_string(),
120 );
121 } else if let (Some(_), Some(next)) = (ast::Comment::cast(prev), ast::Comment::cast(next)) {
122 // Removes: newline (incl. surrounding whitespace), start of the next comment
123 edit.delete(TextRange::from_to(
124 node.range().start(),
125 next.syntax().range().start() + TextUnit::of_str(next.prefix()),
126 ));
127 } else {
128 // Remove newline but add a computed amount of whitespace characters
129 edit.replace(node.range(), compute_ws(prev, next).to_string());
130 }
131}
132
133fn join_single_expr_block(edit: &mut TextEditBuilder, node: &SyntaxNode) -> Option<()> {
134 let block = ast::Block::cast(node.parent()?)?;
135 let block_expr = ast::BlockExpr::cast(block.syntax().parent()?)?;
136 let expr = extract_trivial_expression(block)?;
137 edit.replace(
138 block_expr.syntax().range(),
139 expr.syntax().text().to_string(),
140 );
141 Some(())
142}
143
144fn join_single_use_tree(edit: &mut TextEditBuilder, node: &SyntaxNode) -> Option<()> {
145 let use_tree_list = ast::UseTreeList::cast(node.parent()?)?;
146 let (tree,) = use_tree_list.use_trees().collect_tuple()?;
147 edit.replace(
148 use_tree_list.syntax().range(),
149 tree.syntax().text().to_string(),
150 );
151 Some(())
152}
153
154fn is_trailing_comma(left: SyntaxKind, right: SyntaxKind) -> bool {
155 match (left, right) {
156 (COMMA, R_PAREN) | (COMMA, R_BRACK) => true,
157 _ => false,
158 }
159}
160
161#[cfg(test)]
162mod tests {
163 use crate::test_utils::{assert_eq_text, check_action, extract_range};
164
165 use super::*;
166
167 fn check_join_lines(before: &str, after: &str) {
168 check_action(before, after, |file, offset| {
169 let range = TextRange::offset_len(offset, 0.into());
170 let res = join_lines(file, range);
171 Some(res)
172 })
173 }
174
175 #[test]
176 fn test_join_lines_comma() {
177 check_join_lines(
178 r"
179fn foo() {
180 <|>foo(1,
181 )
182}
183",
184 r"
185fn foo() {
186 <|>foo(1)
187}
188",
189 );
190 }
191
192 #[test]
193 fn test_join_lines_lambda_block() {
194 check_join_lines(
195 r"
196pub fn reparse(&self, edit: &AtomTextEdit) -> File {
197 <|>self.incremental_reparse(edit).unwrap_or_else(|| {
198 self.full_reparse(edit)
199 })
200}
201",
202 r"
203pub fn reparse(&self, edit: &AtomTextEdit) -> File {
204 <|>self.incremental_reparse(edit).unwrap_or_else(|| self.full_reparse(edit))
205}
206",
207 );
208 }
209
210 #[test]
211 fn test_join_lines_block() {
212 check_join_lines(
213 r"
214fn foo() {
215 foo(<|>{
216 92
217 })
218}",
219 r"
220fn foo() {
221 foo(<|>92)
222}",
223 );
224 }
225
226 #[test]
227 fn test_join_lines_use_items_left() {
228 // No space after the '{'
229 check_join_lines(
230 r"
231<|>use ra_syntax::{
232 TextUnit, TextRange,
233};",
234 r"
235<|>use ra_syntax::{TextUnit, TextRange,
236};",
237 );
238 }
239
240 #[test]
241 fn test_join_lines_use_items_right() {
242 // No space after the '}'
243 check_join_lines(
244 r"
245use ra_syntax::{
246<|> TextUnit, TextRange
247};",
248 r"
249use ra_syntax::{
250<|> TextUnit, TextRange};",
251 );
252 }
253
254 #[test]
255 fn test_join_lines_use_items_right_comma() {
256 // No space after the '}'
257 check_join_lines(
258 r"
259use ra_syntax::{
260<|> TextUnit, TextRange,
261};",
262 r"
263use ra_syntax::{
264<|> TextUnit, TextRange};",
265 );
266 }
267
268 #[test]
269 fn test_join_lines_use_tree() {
270 check_join_lines(
271 r"
272use ra_syntax::{
273 algo::<|>{
274 find_leaf_at_offset,
275 },
276 ast,
277};",
278 r"
279use ra_syntax::{
280 algo::<|>find_leaf_at_offset,
281 ast,
282};",
283 );
284 }
285
286 #[test]
287 fn test_join_lines_normal_comments() {
288 check_join_lines(
289 r"
290fn foo() {
291 // Hello<|>
292 // world!
293}
294",
295 r"
296fn foo() {
297 // Hello<|> world!
298}
299",
300 );
301 }
302
303 #[test]
304 fn test_join_lines_doc_comments() {
305 check_join_lines(
306 r"
307fn foo() {
308 /// Hello<|>
309 /// world!
310}
311",
312 r"
313fn foo() {
314 /// Hello<|> world!
315}
316",
317 );
318 }
319
320 #[test]
321 fn test_join_lines_mod_comments() {
322 check_join_lines(
323 r"
324fn foo() {
325 //! Hello<|>
326 //! world!
327}
328",
329 r"
330fn foo() {
331 //! Hello<|> world!
332}
333",
334 );
335 }
336
337 #[test]
338 fn test_join_lines_multiline_comments_1() {
339 check_join_lines(
340 r"
341fn foo() {
342 // Hello<|>
343 /* world! */
344}
345",
346 r"
347fn foo() {
348 // Hello<|> world! */
349}
350",
351 );
352 }
353
354 #[test]
355 fn test_join_lines_multiline_comments_2() {
356 check_join_lines(
357 r"
358fn foo() {
359 // The<|>
360 /* quick
361 brown
362 fox! */
363}
364",
365 r"
366fn foo() {
367 // The<|> quick
368 brown
369 fox! */
370}
371",
372 );
373 }
374
375 fn check_join_lines_sel(before: &str, after: &str) {
376 let (sel, before) = extract_range(before);
377 let file = SourceFile::parse(&before);
378 let result = join_lines(&file, sel);
379 let actual = result.edit.apply(&before);
380 assert_eq_text!(after, &actual);
381 }
382
383 #[test]
384 fn test_join_lines_selection_fn_args() {
385 check_join_lines_sel(
386 r"
387fn foo() {
388 <|>foo(1,
389 2,
390 3,
391 <|>)
392}
393 ",
394 r"
395fn foo() {
396 foo(1, 2, 3)
397}
398 ",
399 );
400 }
401
402 #[test]
403 fn test_join_lines_selection_struct() {
404 check_join_lines_sel(
405 r"
406struct Foo <|>{
407 f: u32,
408}<|>
409 ",
410 r"
411struct Foo { f: u32 }
412 ",
413 );
414 }
415
416 #[test]
417 fn test_join_lines_selection_dot_chain() {
418 check_join_lines_sel(
419 r"
420fn foo() {
421 join(<|>type_params.type_params()
422 .filter_map(|it| it.name())
423 .map(|it| it.text())<|>)
424}",
425 r"
426fn foo() {
427 join(type_params.type_params().filter_map(|it| it.name()).map(|it| it.text()))
428}",
429 );
430 }
431
432 #[test]
433 fn test_join_lines_selection_lambda_block_body() {
434 check_join_lines_sel(
435 r"
436pub fn handle_find_matching_brace() {
437 params.offsets
438 .map(|offset| <|>{
439 world.analysis().matching_brace(&file, offset).unwrap_or(offset)
440 }<|>)
441 .collect();
442}",
443 r"
444pub fn handle_find_matching_brace() {
445 params.offsets
446 .map(|offset| world.analysis().matching_brace(&file, offset).unwrap_or(offset))
447 .collect();
448}",
449 );
450 }
451}
diff --git a/crates/ra_ide_api_light/src/lib.rs b/crates/ra_ide_api_light/src/lib.rs
index 40638eda8..bc9bee752 100644
--- a/crates/ra_ide_api_light/src/lib.rs
+++ b/crates/ra_ide_api_light/src/lib.rs
@@ -11,8 +11,10 @@ mod line_index_utils;
11mod structure; 11mod structure;
12#[cfg(test)] 12#[cfg(test)]
13mod test_utils; 13mod test_utils;
14mod join_lines;
14mod typing; 15mod typing;
15mod diagnostics; 16mod diagnostics;
17pub(crate) mod formatting;
16 18
17pub use self::{ 19pub use self::{
18 assists::LocalEdit, 20 assists::LocalEdit,
@@ -21,8 +23,10 @@ pub use self::{
21 line_index::{LineCol, LineIndex}, 23 line_index::{LineCol, LineIndex},
22 line_index_utils::translate_offset_with_edit, 24 line_index_utils::translate_offset_with_edit,
23 structure::{file_structure, StructureNode}, 25 structure::{file_structure, StructureNode},
24 typing::{join_lines, on_enter, on_dot_typed, on_eq_typed}, 26 diagnostics::diagnostics,
25 diagnostics::diagnostics 27 join_lines::join_lines,
28 typing::{on_enter, on_dot_typed, on_eq_typed},
29
26}; 30};
27use ra_text_edit::TextEditBuilder; 31use ra_text_edit::TextEditBuilder;
28use ra_syntax::{ 32use ra_syntax::{
diff --git a/crates/ra_ide_api_light/src/typing.rs b/crates/ra_ide_api_light/src/typing.rs
index d8177f245..5726209cc 100644
--- a/crates/ra_ide_api_light/src/typing.rs
+++ b/crates/ra_ide_api_light/src/typing.rs
@@ -1,62 +1,12 @@
1use std::mem;
2
3use itertools::Itertools;
4use ra_syntax::{ 1use ra_syntax::{
5 algo::{find_node_at_offset, find_covering_node, find_leaf_at_offset, LeafAtOffset}, 2 algo::{find_node_at_offset, find_leaf_at_offset, LeafAtOffset},
6 ast, 3 ast,
7 AstNode, Direction, SourceFile, SyntaxKind, 4 AstNode, Direction, SourceFile, SyntaxKind::*,
8 SyntaxKind::*, 5 SyntaxNode, TextUnit,
9 SyntaxNode, TextRange, TextUnit,
10}; 6};
11 7
12use crate::{LocalEdit, TextEditBuilder}; 8use crate::{LocalEdit, TextEditBuilder};
13 9
14pub fn join_lines(file: &SourceFile, range: TextRange) -> LocalEdit {
15 let range = if range.is_empty() {
16 let syntax = file.syntax();
17 let text = syntax.text().slice(range.start()..);
18 let pos = match text.find('\n') {
19 None => {
20 return LocalEdit {
21 label: "join lines".to_string(),
22 edit: TextEditBuilder::default().finish(),
23 cursor_position: None,
24 };
25 }
26 Some(pos) => pos,
27 };
28 TextRange::offset_len(range.start() + pos, TextUnit::of_char('\n'))
29 } else {
30 range
31 };
32
33 let node = find_covering_node(file.syntax(), range);
34 let mut edit = TextEditBuilder::default();
35 for node in node.descendants() {
36 let text = match node.leaf_text() {
37 Some(text) => text,
38 None => continue,
39 };
40 let range = match range.intersection(&node.range()) {
41 Some(range) => range,
42 None => continue,
43 } - node.range().start();
44 for (pos, _) in text[range].bytes().enumerate().filter(|&(_, b)| b == b'\n') {
45 let pos: TextUnit = (pos as u32).into();
46 let off = node.range().start() + range.start() + pos;
47 if !edit.invalidates_offset(off) {
48 remove_newline(&mut edit, node, text.as_str(), off);
49 }
50 }
51 }
52
53 LocalEdit {
54 label: "join lines".to_string(),
55 edit: edit.finish(),
56 cursor_position: None,
57 }
58}
59
60pub fn on_enter(file: &SourceFile, offset: TextUnit) -> Option<LocalEdit> { 10pub fn on_enter(file: &SourceFile, offset: TextUnit) -> Option<LocalEdit> {
61 let comment = find_leaf_at_offset(file.syntax(), offset) 11 let comment = find_leaf_at_offset(file.syntax(), offset)
62 .left_biased() 12 .left_biased()
@@ -184,441 +134,11 @@ fn last_line_indent_in_whitespace(ws: &str) -> &str {
184 ws.split('\n').last().unwrap_or("") 134 ws.split('\n').last().unwrap_or("")
185} 135}
186 136
187fn remove_newline(
188 edit: &mut TextEditBuilder,
189 node: &SyntaxNode,
190 node_text: &str,
191 offset: TextUnit,
192) {
193 if node.kind() != WHITESPACE || node_text.bytes().filter(|&b| b == b'\n').count() != 1 {
194 // The node is either the first or the last in the file
195 let suff = &node_text[TextRange::from_to(
196 offset - node.range().start() + TextUnit::of_char('\n'),
197 TextUnit::of_str(node_text),
198 )];
199 let spaces = suff.bytes().take_while(|&b| b == b' ').count();
200
201 edit.replace(
202 TextRange::offset_len(offset, ((spaces + 1) as u32).into()),
203 " ".to_string(),
204 );
205 return;
206 }
207
208 // Special case that turns something like:
209 //
210 // ```
211 // my_function({<|>
212 // <some-expr>
213 // })
214 // ```
215 //
216 // into `my_function(<some-expr>)`
217 if join_single_expr_block(edit, node).is_some() {
218 return;
219 }
220 // ditto for
221 //
222 // ```
223 // use foo::{<|>
224 // bar
225 // };
226 // ```
227 if join_single_use_tree(edit, node).is_some() {
228 return;
229 }
230
231 // The node is between two other nodes
232 let prev = node.prev_sibling().unwrap();
233 let next = node.next_sibling().unwrap();
234 if is_trailing_comma(prev.kind(), next.kind()) {
235 // Removes: trailing comma, newline (incl. surrounding whitespace)
236 edit.delete(TextRange::from_to(prev.range().start(), node.range().end()));
237 } else if prev.kind() == COMMA && next.kind() == R_CURLY {
238 // Removes: comma, newline (incl. surrounding whitespace)
239 let space = if let Some(left) = prev.prev_sibling() {
240 compute_ws(left, next)
241 } else {
242 " "
243 };
244 edit.replace(
245 TextRange::from_to(prev.range().start(), node.range().end()),
246 space.to_string(),
247 );
248 } else if let (Some(_), Some(next)) = (ast::Comment::cast(prev), ast::Comment::cast(next)) {
249 // Removes: newline (incl. surrounding whitespace), start of the next comment
250 edit.delete(TextRange::from_to(
251 node.range().start(),
252 next.syntax().range().start() + TextUnit::of_str(next.prefix()),
253 ));
254 } else {
255 // Remove newline but add a computed amount of whitespace characters
256 edit.replace(node.range(), compute_ws(prev, next).to_string());
257 }
258}
259
260fn is_trailing_comma(left: SyntaxKind, right: SyntaxKind) -> bool {
261 match (left, right) {
262 (COMMA, R_PAREN) | (COMMA, R_BRACK) => true,
263 _ => false,
264 }
265}
266
267fn join_single_expr_block(edit: &mut TextEditBuilder, node: &SyntaxNode) -> Option<()> {
268 let block = ast::Block::cast(node.parent()?)?;
269 let block_expr = ast::BlockExpr::cast(block.syntax().parent()?)?;
270 let expr = single_expr(block)?;
271 edit.replace(
272 block_expr.syntax().range(),
273 expr.syntax().text().to_string(),
274 );
275 Some(())
276}
277
278fn single_expr(block: &ast::Block) -> Option<&ast::Expr> {
279 let mut res = None;
280 for child in block.syntax().children() {
281 if let Some(expr) = ast::Expr::cast(child) {
282 if expr.syntax().text().contains('\n') {
283 return None;
284 }
285 if mem::replace(&mut res, Some(expr)).is_some() {
286 return None;
287 }
288 } else {
289 match child.kind() {
290 WHITESPACE | L_CURLY | R_CURLY => (),
291 _ => return None,
292 }
293 }
294 }
295 res
296}
297
298fn join_single_use_tree(edit: &mut TextEditBuilder, node: &SyntaxNode) -> Option<()> {
299 let use_tree_list = ast::UseTreeList::cast(node.parent()?)?;
300 let (tree,) = use_tree_list.use_trees().collect_tuple()?;
301 edit.replace(
302 use_tree_list.syntax().range(),
303 tree.syntax().text().to_string(),
304 );
305 Some(())
306}
307
308fn compute_ws(left: &SyntaxNode, right: &SyntaxNode) -> &'static str {
309 match left.kind() {
310 L_PAREN | L_BRACK => return "",
311 L_CURLY => {
312 if let USE_TREE = right.kind() {
313 return "";
314 }
315 }
316 _ => (),
317 }
318 match right.kind() {
319 R_PAREN | R_BRACK => return "",
320 R_CURLY => {
321 if let USE_TREE = left.kind() {
322 return "";
323 }
324 }
325 DOT => return "",
326 _ => (),
327 }
328 " "
329}
330
331#[cfg(test)] 137#[cfg(test)]
332mod tests { 138mod tests {
333 use super::*; 139 use crate::test_utils::{add_cursor, assert_eq_text, extract_offset};
334 use crate::test_utils::{
335 add_cursor, assert_eq_text, check_action, extract_offset, extract_range,
336};
337
338 fn check_join_lines(before: &str, after: &str) {
339 check_action(before, after, |file, offset| {
340 let range = TextRange::offset_len(offset, 0.into());
341 let res = join_lines(file, range);
342 Some(res)
343 })
344 }
345 140
346 #[test] 141 use super::*;
347 fn test_join_lines_comma() {
348 check_join_lines(
349 r"
350fn foo() {
351 <|>foo(1,
352 )
353}
354",
355 r"
356fn foo() {
357 <|>foo(1)
358}
359",
360 );
361 }
362
363 #[test]
364 fn test_join_lines_lambda_block() {
365 check_join_lines(
366 r"
367pub fn reparse(&self, edit: &AtomTextEdit) -> File {
368 <|>self.incremental_reparse(edit).unwrap_or_else(|| {
369 self.full_reparse(edit)
370 })
371}
372",
373 r"
374pub fn reparse(&self, edit: &AtomTextEdit) -> File {
375 <|>self.incremental_reparse(edit).unwrap_or_else(|| self.full_reparse(edit))
376}
377",
378 );
379 }
380
381 #[test]
382 fn test_join_lines_block() {
383 check_join_lines(
384 r"
385fn foo() {
386 foo(<|>{
387 92
388 })
389}",
390 r"
391fn foo() {
392 foo(<|>92)
393}",
394 );
395 }
396
397 #[test]
398 fn test_join_lines_use_items_left() {
399 // No space after the '{'
400 check_join_lines(
401 r"
402<|>use ra_syntax::{
403 TextUnit, TextRange,
404};",
405 r"
406<|>use ra_syntax::{TextUnit, TextRange,
407};",
408 );
409 }
410
411 #[test]
412 fn test_join_lines_use_items_right() {
413 // No space after the '}'
414 check_join_lines(
415 r"
416use ra_syntax::{
417<|> TextUnit, TextRange
418};",
419 r"
420use ra_syntax::{
421<|> TextUnit, TextRange};",
422 );
423 }
424
425 #[test]
426 fn test_join_lines_use_items_right_comma() {
427 // No space after the '}'
428 check_join_lines(
429 r"
430use ra_syntax::{
431<|> TextUnit, TextRange,
432};",
433 r"
434use ra_syntax::{
435<|> TextUnit, TextRange};",
436 );
437 }
438
439 #[test]
440 fn test_join_lines_use_tree() {
441 check_join_lines(
442 r"
443use ra_syntax::{
444 algo::<|>{
445 find_leaf_at_offset,
446 },
447 ast,
448};",
449 r"
450use ra_syntax::{
451 algo::<|>find_leaf_at_offset,
452 ast,
453};",
454 );
455 }
456
457 #[test]
458 fn test_join_lines_normal_comments() {
459 check_join_lines(
460 r"
461fn foo() {
462 // Hello<|>
463 // world!
464}
465",
466 r"
467fn foo() {
468 // Hello<|> world!
469}
470",
471 );
472 }
473
474 #[test]
475 fn test_join_lines_doc_comments() {
476 check_join_lines(
477 r"
478fn foo() {
479 /// Hello<|>
480 /// world!
481}
482",
483 r"
484fn foo() {
485 /// Hello<|> world!
486}
487",
488 );
489 }
490
491 #[test]
492 fn test_join_lines_mod_comments() {
493 check_join_lines(
494 r"
495fn foo() {
496 //! Hello<|>
497 //! world!
498}
499",
500 r"
501fn foo() {
502 //! Hello<|> world!
503}
504",
505 );
506 }
507
508 #[test]
509 fn test_join_lines_multiline_comments_1() {
510 check_join_lines(
511 r"
512fn foo() {
513 // Hello<|>
514 /* world! */
515}
516",
517 r"
518fn foo() {
519 // Hello<|> world! */
520}
521",
522 );
523 }
524
525 #[test]
526 fn test_join_lines_multiline_comments_2() {
527 check_join_lines(
528 r"
529fn foo() {
530 // The<|>
531 /* quick
532 brown
533 fox! */
534}
535",
536 r"
537fn foo() {
538 // The<|> quick
539 brown
540 fox! */
541}
542",
543 );
544 }
545
546 fn check_join_lines_sel(before: &str, after: &str) {
547 let (sel, before) = extract_range(before);
548 let file = SourceFile::parse(&before);
549 let result = join_lines(&file, sel);
550 let actual = result.edit.apply(&before);
551 assert_eq_text!(after, &actual);
552 }
553
554 #[test]
555 fn test_join_lines_selection_fn_args() {
556 check_join_lines_sel(
557 r"
558fn foo() {
559 <|>foo(1,
560 2,
561 3,
562 <|>)
563}
564 ",
565 r"
566fn foo() {
567 foo(1, 2, 3)
568}
569 ",
570 );
571 }
572
573 #[test]
574 fn test_join_lines_selection_struct() {
575 check_join_lines_sel(
576 r"
577struct Foo <|>{
578 f: u32,
579}<|>
580 ",
581 r"
582struct Foo { f: u32 }
583 ",
584 );
585 }
586
587 #[test]
588 fn test_join_lines_selection_dot_chain() {
589 check_join_lines_sel(
590 r"
591fn foo() {
592 join(<|>type_params.type_params()
593 .filter_map(|it| it.name())
594 .map(|it| it.text())<|>)
595}",
596 r"
597fn foo() {
598 join(type_params.type_params().filter_map(|it| it.name()).map(|it| it.text()))
599}",
600 );
601 }
602
603 #[test]
604 fn test_join_lines_selection_lambda_block_body() {
605 check_join_lines_sel(
606 r"
607pub fn handle_find_matching_brace() {
608 params.offsets
609 .map(|offset| <|>{
610 world.analysis().matching_brace(&file, offset).unwrap_or(offset)
611 }<|>)
612 .collect();
613}",
614 r"
615pub fn handle_find_matching_brace() {
616 params.offsets
617 .map(|offset| world.analysis().matching_brace(&file, offset).unwrap_or(offset))
618 .collect();
619}",
620 );
621 }
622 142
623 #[test] 143 #[test]
624 fn test_on_eq_typed() { 144 fn test_on_eq_typed() {
diff --git a/crates/ra_lsp_server/Cargo.toml b/crates/ra_lsp_server/Cargo.toml
index 296fae34f..b4a7b3388 100644
--- a/crates/ra_lsp_server/Cargo.toml
+++ b/crates/ra_lsp_server/Cargo.toml
@@ -30,6 +30,7 @@ thread_worker = { path = "../thread_worker" }
30ra_syntax = { path = "../ra_syntax" } 30ra_syntax = { path = "../ra_syntax" }
31ra_text_edit = { path = "../ra_text_edit" } 31ra_text_edit = { path = "../ra_text_edit" }
32ra_ide_api = { path = "../ra_ide_api" } 32ra_ide_api = { path = "../ra_ide_api" }
33ra_arena = { path = "../ra_arena" }
33gen_lsp_server = { path = "../gen_lsp_server" } 34gen_lsp_server = { path = "../gen_lsp_server" }
34ra_vfs = { path = "../ra_vfs" } 35ra_vfs = { path = "../ra_vfs" }
35 36
diff --git a/crates/ra_lsp_server/src/project_model.rs b/crates/ra_lsp_server/src/project_model.rs
index ff9befe46..9f429c9a1 100644
--- a/crates/ra_lsp_server/src/project_model.rs
+++ b/crates/ra_lsp_server/src/project_model.rs
@@ -2,6 +2,7 @@ use std::path::{Path, PathBuf};
2 2
3use cargo_metadata::{metadata_run, CargoOpt}; 3use cargo_metadata::{metadata_run, CargoOpt};
4use ra_syntax::SmolStr; 4use ra_syntax::SmolStr;
5use ra_arena::{Arena, RawId, impl_arena_id};
5use rustc_hash::FxHashMap; 6use rustc_hash::FxHashMap;
6use failure::{format_err, bail}; 7use failure::{format_err, bail};
7use thread_worker::{WorkerHandle, Worker}; 8use thread_worker::{WorkerHandle, Worker};
@@ -17,14 +18,17 @@ use crate::Result;
17/// concepts. 18/// concepts.
18#[derive(Debug, Clone)] 19#[derive(Debug, Clone)]
19pub struct CargoWorkspace { 20pub struct CargoWorkspace {
20 packages: Vec<PackageData>, 21 packages: Arena<Package, PackageData>,
21 targets: Vec<TargetData>, 22 targets: Arena<Target, TargetData>,
22} 23}
23 24
24#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] 25#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
25pub struct Package(usize); 26pub struct Package(RawId);
27impl_arena_id!(Package);
28
26#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] 29#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
27pub struct Target(usize); 30pub struct Target(RawId);
31impl_arena_id!(Target);
28 32
29#[derive(Debug, Clone)] 33#[derive(Debug, Clone)]
30struct PackageData { 34struct PackageData {
@@ -61,38 +65,38 @@ pub enum TargetKind {
61 65
62impl Package { 66impl Package {
63 pub fn name(self, ws: &CargoWorkspace) -> &str { 67 pub fn name(self, ws: &CargoWorkspace) -> &str {
64 ws.pkg(self).name.as_str() 68 ws.packages[self].name.as_str()
65 } 69 }
66 pub fn root(self, ws: &CargoWorkspace) -> &Path { 70 pub fn root(self, ws: &CargoWorkspace) -> &Path {
67 ws.pkg(self).manifest.parent().unwrap() 71 ws.packages[self].manifest.parent().unwrap()
68 } 72 }
69 pub fn targets<'a>(self, ws: &'a CargoWorkspace) -> impl Iterator<Item = Target> + 'a { 73 pub fn targets<'a>(self, ws: &'a CargoWorkspace) -> impl Iterator<Item = Target> + 'a {
70 ws.pkg(self).targets.iter().cloned() 74 ws.packages[self].targets.iter().cloned()
71 } 75 }
72 #[allow(unused)] 76 #[allow(unused)]
73 pub fn is_member(self, ws: &CargoWorkspace) -> bool { 77 pub fn is_member(self, ws: &CargoWorkspace) -> bool {
74 ws.pkg(self).is_member 78 ws.packages[self].is_member
75 } 79 }
76 pub fn dependencies<'a>( 80 pub fn dependencies<'a>(
77 self, 81 self,
78 ws: &'a CargoWorkspace, 82 ws: &'a CargoWorkspace,
79 ) -> impl Iterator<Item = &'a PackageDependency> + 'a { 83 ) -> impl Iterator<Item = &'a PackageDependency> + 'a {
80 ws.pkg(self).dependencies.iter() 84 ws.packages[self].dependencies.iter()
81 } 85 }
82} 86}
83 87
84impl Target { 88impl Target {
85 pub fn package(self, ws: &CargoWorkspace) -> Package { 89 pub fn package(self, ws: &CargoWorkspace) -> Package {
86 ws.tgt(self).pkg 90 ws.targets[self].pkg
87 } 91 }
88 pub fn name(self, ws: &CargoWorkspace) -> &str { 92 pub fn name(self, ws: &CargoWorkspace) -> &str {
89 ws.tgt(self).name.as_str() 93 ws.targets[self].name.as_str()
90 } 94 }
91 pub fn root(self, ws: &CargoWorkspace) -> &Path { 95 pub fn root(self, ws: &CargoWorkspace) -> &Path {
92 ws.tgt(self).root.as_path() 96 ws.targets[self].root.as_path()
93 } 97 }
94 pub fn kind(self, ws: &CargoWorkspace) -> TargetKind { 98 pub fn kind(self, ws: &CargoWorkspace) -> TargetKind {
95 ws.tgt(self).kind 99 ws.targets[self].kind
96 } 100 }
97} 101}
98 102
@@ -106,25 +110,24 @@ impl CargoWorkspace {
106 ) 110 )
107 .map_err(|e| format_err!("cargo metadata failed: {}", e))?; 111 .map_err(|e| format_err!("cargo metadata failed: {}", e))?;
108 let mut pkg_by_id = FxHashMap::default(); 112 let mut pkg_by_id = FxHashMap::default();
109 let mut packages = Vec::new(); 113 let mut packages = Arena::default();
110 let mut targets = Vec::new(); 114 let mut targets = Arena::default();
111 115
112 let ws_members = &meta.workspace_members; 116 let ws_members = &meta.workspace_members;
113 117
114 for meta_pkg in meta.packages { 118 for meta_pkg in meta.packages {
115 let pkg = Package(packages.len());
116 let is_member = ws_members.contains(&meta_pkg.id); 119 let is_member = ws_members.contains(&meta_pkg.id);
117 pkg_by_id.insert(meta_pkg.id.clone(), pkg); 120 let pkg = packages.alloc(PackageData {
118 let mut pkg_data = PackageData {
119 name: meta_pkg.name.into(), 121 name: meta_pkg.name.into(),
120 manifest: meta_pkg.manifest_path.clone(), 122 manifest: meta_pkg.manifest_path.clone(),
121 targets: Vec::new(), 123 targets: Vec::new(),
122 is_member, 124 is_member,
123 dependencies: Vec::new(), 125 dependencies: Vec::new(),
124 }; 126 });
127 let pkg_data = &mut packages[pkg];
128 pkg_by_id.insert(meta_pkg.id.clone(), pkg);
125 for meta_tgt in meta_pkg.targets { 129 for meta_tgt in meta_pkg.targets {
126 let tgt = Target(targets.len()); 130 let tgt = targets.alloc(TargetData {
127 targets.push(TargetData {
128 pkg, 131 pkg,
129 name: meta_tgt.name.into(), 132 name: meta_tgt.name.into(),
130 root: meta_tgt.src_path.clone(), 133 root: meta_tgt.src_path.clone(),
@@ -132,7 +135,6 @@ impl CargoWorkspace {
132 }); 135 });
133 pkg_data.targets.push(tgt); 136 pkg_data.targets.push(tgt);
134 } 137 }
135 packages.push(pkg_data)
136 } 138 }
137 let resolve = meta.resolve.expect("metadata executed with deps"); 139 let resolve = meta.resolve.expect("metadata executed with deps");
138 for node in resolve.nodes { 140 for node in resolve.nodes {
@@ -142,26 +144,20 @@ impl CargoWorkspace {
142 name: dep_node.name.into(), 144 name: dep_node.name.into(),
143 pkg: pkg_by_id[&dep_node.pkg], 145 pkg: pkg_by_id[&dep_node.pkg],
144 }; 146 };
145 packages[source.0].dependencies.push(dep); 147 packages[source].dependencies.push(dep);
146 } 148 }
147 } 149 }
148 150
149 Ok(CargoWorkspace { packages, targets }) 151 Ok(CargoWorkspace { packages, targets })
150 } 152 }
151 pub fn packages<'a>(&'a self) -> impl Iterator<Item = Package> + 'a { 153 pub fn packages<'a>(&'a self) -> impl Iterator<Item = Package> + 'a {
152 (0..self.packages.len()).map(Package) 154 self.packages.iter().map(|(id, _pkg)| id)
153 } 155 }
154 pub fn target_by_root(&self, root: &Path) -> Option<Target> { 156 pub fn target_by_root(&self, root: &Path) -> Option<Target> {
155 self.packages() 157 self.packages()
156 .filter_map(|pkg| pkg.targets(self).find(|it| it.root(self) == root)) 158 .filter_map(|pkg| pkg.targets(self).find(|it| it.root(self) == root))
157 .next() 159 .next()
158 } 160 }
159 fn pkg(&self, pkg: Package) -> &PackageData {
160 &self.packages[pkg.0]
161 }
162 fn tgt(&self, tgt: Target) -> &TargetData {
163 &self.targets[tgt.0]
164 }
165} 161}
166 162
167fn find_cargo_toml(path: &Path) -> Result<PathBuf> { 163fn find_cargo_toml(path: &Path) -> Result<PathBuf> {
diff --git a/crates/ra_syntax/src/grammar/items/nominal.rs b/crates/ra_syntax/src/grammar/items/nominal.rs
index 8d02ad555..495462ca7 100644
--- a/crates/ra_syntax/src/grammar/items/nominal.rs
+++ b/crates/ra_syntax/src/grammar/items/nominal.rs
@@ -29,6 +29,10 @@ pub(super) fn struct_def(p: &mut Parser, kind: SyntaxKind) {
29 L_CURLY => named_field_def_list(p), 29 L_CURLY => named_field_def_list(p),
30 L_PAREN if kind == STRUCT_KW => { 30 L_PAREN if kind == STRUCT_KW => {
31 pos_field_list(p); 31 pos_field_list(p);
32 // test tuple_struct_where
33 // struct Test<T>(T) where T: Clone;
34 // struct Test<T>(T);
35 type_params::opt_where_clause(p);
32 p.expect(SEMI); 36 p.expect(SEMI);
33 } 37 }
34 _ if kind == STRUCT_KW => { 38 _ if kind == STRUCT_KW => {
diff --git a/crates/ra_syntax/tests/data/parser/inline/ok/0114_tuple_struct_where.rs b/crates/ra_syntax/tests/data/parser/inline/ok/0114_tuple_struct_where.rs
new file mode 100644
index 000000000..ddd59016d
--- /dev/null
+++ b/crates/ra_syntax/tests/data/parser/inline/ok/0114_tuple_struct_where.rs
@@ -0,0 +1,2 @@
1struct Test<T>(T) where T: Clone;
2struct Test<T>(T);
diff --git a/crates/ra_syntax/tests/data/parser/inline/ok/0114_tuple_struct_where.txt b/crates/ra_syntax/tests/data/parser/inline/ok/0114_tuple_struct_where.txt
new file mode 100644
index 000000000..b7de83072
--- /dev/null
+++ b/crates/ra_syntax/tests/data/parser/inline/ok/0114_tuple_struct_where.txt
@@ -0,0 +1,62 @@
1SOURCE_FILE@[0; 53)
2 STRUCT_DEF@[0; 33)
3 STRUCT_KW@[0; 6)
4 WHITESPACE@[6; 7)
5 NAME@[7; 11)
6 IDENT@[7; 11) "Test"
7 TYPE_PARAM_LIST@[11; 14)
8 L_ANGLE@[11; 12)
9 TYPE_PARAM@[12; 13)
10 NAME@[12; 13)
11 IDENT@[12; 13) "T"
12 R_ANGLE@[13; 14)
13 POS_FIELD_LIST@[14; 17)
14 L_PAREN@[14; 15)
15 POS_FIELD@[15; 16)
16 PATH_TYPE@[15; 16)
17 PATH@[15; 16)
18 PATH_SEGMENT@[15; 16)
19 NAME_REF@[15; 16)
20 IDENT@[15; 16) "T"
21 R_PAREN@[16; 17)
22 WHITESPACE@[17; 18)
23 WHERE_CLAUSE@[18; 32)
24 WHERE_KW@[18; 23)
25 WHITESPACE@[23; 24)
26 WHERE_PRED@[24; 32)
27 PATH_TYPE@[24; 25)
28 PATH@[24; 25)
29 PATH_SEGMENT@[24; 25)
30 NAME_REF@[24; 25)
31 IDENT@[24; 25) "T"
32 COLON@[25; 26)
33 WHITESPACE@[26; 27)
34 PATH_TYPE@[27; 32)
35 PATH@[27; 32)
36 PATH_SEGMENT@[27; 32)
37 NAME_REF@[27; 32)
38 IDENT@[27; 32) "Clone"
39 SEMI@[32; 33)
40 WHITESPACE@[33; 34)
41 STRUCT_DEF@[34; 52)
42 STRUCT_KW@[34; 40)
43 WHITESPACE@[40; 41)
44 NAME@[41; 45)
45 IDENT@[41; 45) "Test"
46 TYPE_PARAM_LIST@[45; 48)
47 L_ANGLE@[45; 46)
48 TYPE_PARAM@[46; 47)
49 NAME@[46; 47)
50 IDENT@[46; 47) "T"
51 R_ANGLE@[47; 48)
52 POS_FIELD_LIST@[48; 51)
53 L_PAREN@[48; 49)
54 POS_FIELD@[49; 50)
55 PATH_TYPE@[49; 50)
56 PATH@[49; 50)
57 PATH_SEGMENT@[49; 50)
58 NAME_REF@[49; 50)
59 IDENT@[49; 50) "T"
60 R_PAREN@[50; 51)
61 SEMI@[51; 52)
62 WHITESPACE@[52; 53)