aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_ide_api
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ra_ide_api')
-rw-r--r--crates/ra_ide_api/Cargo.toml5
-rw-r--r--crates/ra_ide_api/src/call_info.rs183
-rw-r--r--crates/ra_ide_api/src/change.rs2
-rw-r--r--crates/ra_ide_api/src/completion/complete_path.rs4
-rw-r--r--crates/ra_ide_api/src/completion/complete_postfix.rs6
-rw-r--r--crates/ra_ide_api/src/completion/completion_item.rs12
-rw-r--r--crates/ra_ide_api/src/completion/presentation.rs74
-rw-r--r--crates/ra_ide_api/src/db.rs1
-rw-r--r--crates/ra_ide_api/src/diagnostics.rs9
-rw-r--r--crates/ra_ide_api/src/display/function_signature.rs112
-rw-r--r--crates/ra_ide_api/src/extend_selection.rs82
-rw-r--r--crates/ra_ide_api/src/impls.rs4
-rw-r--r--crates/ra_ide_api/src/lib.rs129
-rw-r--r--crates/ra_ide_api/src/parent_module.rs5
-rw-r--r--crates/ra_ide_api/src/references/rename.rs24
-rw-r--r--crates/ra_ide_api/src/references/search_scope.rs7
-rw-r--r--crates/ra_ide_api/src/runnables.rs14
-rw-r--r--crates/ra_ide_api/src/source_change.rs119
-rw-r--r--crates/ra_ide_api/src/typing.rs359
19 files changed, 772 insertions, 379 deletions
diff --git a/crates/ra_ide_api/Cargo.toml b/crates/ra_ide_api/Cargo.toml
index f66f0a6ba..bf6ef12f3 100644
--- a/crates/ra_ide_api/Cargo.toml
+++ b/crates/ra_ide_api/Cargo.toml
@@ -27,10 +27,13 @@ ra_db = { path = "../ra_db" }
27ra_cfg = { path = "../ra_cfg" } 27ra_cfg = { path = "../ra_cfg" }
28ra_fmt = { path = "../ra_fmt" } 28ra_fmt = { path = "../ra_fmt" }
29ra_prof = { path = "../ra_prof" } 29ra_prof = { path = "../ra_prof" }
30hir = { path = "../ra_hir", package = "ra_hir" }
31test_utils = { path = "../test_utils" } 30test_utils = { path = "../test_utils" }
32ra_assists = { path = "../ra_assists" } 31ra_assists = { path = "../ra_assists" }
33 32
33# ra_ide_api should depend only on the top-level `hir` package. if you need
34# something from some `hir_xxx` subpackage, reexport the API via `hir`.
35hir = { path = "../ra_hir", package = "ra_hir" }
36
34[dev-dependencies] 37[dev-dependencies]
35insta = "0.12.0" 38insta = "0.12.0"
36 39
diff --git a/crates/ra_ide_api/src/call_info.rs b/crates/ra_ide_api/src/call_info.rs
index 7d18be483..3572825b5 100644
--- a/crates/ra_ide_api/src/call_info.rs
+++ b/crates/ra_ide_api/src/call_info.rs
@@ -2,9 +2,9 @@
2 2
3use ra_db::SourceDatabase; 3use ra_db::SourceDatabase;
4use ra_syntax::{ 4use ra_syntax::{
5 algo::find_node_at_offset, 5 algo::ancestors_at_offset,
6 ast::{self, ArgListOwner}, 6 ast::{self, ArgListOwner},
7 AstNode, SyntaxNode, TextUnit, 7 match_ast, AstNode, SyntaxNode, TextUnit,
8}; 8};
9use test_utils::tested_by; 9use test_utils::tested_by;
10 10
@@ -20,24 +20,30 @@ pub(crate) fn call_info(db: &RootDatabase, position: FilePosition) -> Option<Cal
20 let name_ref = calling_node.name_ref()?; 20 let name_ref = calling_node.name_ref()?;
21 21
22 let analyzer = hir::SourceAnalyzer::new(db, position.file_id, name_ref.syntax(), None); 22 let analyzer = hir::SourceAnalyzer::new(db, position.file_id, name_ref.syntax(), None);
23 let function = match &calling_node { 23 let (mut call_info, has_self) = match &calling_node {
24 FnCallNode::CallExpr(expr) => { 24 FnCallNode::CallExpr(expr) => {
25 //FIXME: apply subst 25 //FIXME: apply subst
26 let (callable_def, _subst) = analyzer.type_of(db, &expr.expr()?)?.as_callable()?; 26 let (callable_def, _subst) = analyzer.type_of(db, &expr.expr()?)?.as_callable()?;
27 match callable_def { 27 match callable_def {
28 hir::CallableDef::Function(it) => it, 28 hir::CallableDef::Function(it) => {
29 //FIXME: handle other callables 29 (CallInfo::with_fn(db, it), it.data(db).has_self_param())
30 _ => return None, 30 }
31 hir::CallableDef::Struct(it) => (CallInfo::with_struct(db, it)?, false),
32 hir::CallableDef::EnumVariant(it) => (CallInfo::with_enum_variant(db, it)?, false),
31 } 33 }
32 } 34 }
33 FnCallNode::MethodCallExpr(expr) => analyzer.resolve_method_call(&expr)?, 35 FnCallNode::MethodCallExpr(expr) => {
36 let function = analyzer.resolve_method_call(&expr)?;
37 (CallInfo::with_fn(db, function), function.data(db).has_self_param())
38 }
39 FnCallNode::MacroCallExpr(expr) => {
40 let macro_def = analyzer.resolve_macro_call(db, &expr)?;
41 (CallInfo::with_macro(db, macro_def)?, false)
42 }
34 }; 43 };
35 44
36 let mut call_info = CallInfo::new(db, function);
37
38 // If we have a calling expression let's find which argument we are on 45 // If we have a calling expression let's find which argument we are on
39 let num_params = call_info.parameters().len(); 46 let num_params = call_info.parameters().len();
40 let has_self = function.data(db).has_self_param();
41 47
42 if num_params == 1 { 48 if num_params == 1 {
43 if !has_self { 49 if !has_self {
@@ -75,20 +81,25 @@ pub(crate) fn call_info(db: &RootDatabase, position: FilePosition) -> Option<Cal
75 Some(call_info) 81 Some(call_info)
76} 82}
77 83
84#[derive(Debug)]
78enum FnCallNode { 85enum FnCallNode {
79 CallExpr(ast::CallExpr), 86 CallExpr(ast::CallExpr),
80 MethodCallExpr(ast::MethodCallExpr), 87 MethodCallExpr(ast::MethodCallExpr),
88 MacroCallExpr(ast::MacroCall),
81} 89}
82 90
83impl FnCallNode { 91impl FnCallNode {
84 fn with_node(syntax: &SyntaxNode, offset: TextUnit) -> Option<FnCallNode> { 92 fn with_node(syntax: &SyntaxNode, offset: TextUnit) -> Option<FnCallNode> {
85 if let Some(expr) = find_node_at_offset::<ast::CallExpr>(syntax, offset) { 93 ancestors_at_offset(syntax, offset).find_map(|node| {
86 return Some(FnCallNode::CallExpr(expr)); 94 match_ast! {
87 } 95 match node {
88 if let Some(expr) = find_node_at_offset::<ast::MethodCallExpr>(syntax, offset) { 96 ast::CallExpr(it) => { Some(FnCallNode::CallExpr(it)) },
89 return Some(FnCallNode::MethodCallExpr(expr)); 97 ast::MethodCallExpr(it) => { Some(FnCallNode::MethodCallExpr(it)) },
90 } 98 ast::MacroCall(it) => { Some(FnCallNode::MacroCallExpr(it)) },
91 None 99 _ => { None },
100 }
101 }
102 })
92 } 103 }
93 104
94 fn name_ref(&self) -> Option<ast::NameRef> { 105 fn name_ref(&self) -> Option<ast::NameRef> {
@@ -101,6 +112,8 @@ impl FnCallNode {
101 FnCallNode::MethodCallExpr(call_expr) => { 112 FnCallNode::MethodCallExpr(call_expr) => {
102 call_expr.syntax().children().filter_map(ast::NameRef::cast).nth(0) 113 call_expr.syntax().children().filter_map(ast::NameRef::cast).nth(0)
103 } 114 }
115
116 FnCallNode::MacroCallExpr(call_expr) => call_expr.path()?.segment()?.name_ref(),
104 } 117 }
105 } 118 }
106 119
@@ -108,17 +121,36 @@ impl FnCallNode {
108 match self { 121 match self {
109 FnCallNode::CallExpr(expr) => expr.arg_list(), 122 FnCallNode::CallExpr(expr) => expr.arg_list(),
110 FnCallNode::MethodCallExpr(expr) => expr.arg_list(), 123 FnCallNode::MethodCallExpr(expr) => expr.arg_list(),
124 FnCallNode::MacroCallExpr(_) => None,
111 } 125 }
112 } 126 }
113} 127}
114 128
115impl CallInfo { 129impl CallInfo {
116 fn new(db: &RootDatabase, function: hir::Function) -> Self { 130 fn with_fn(db: &RootDatabase, function: hir::Function) -> Self {
117 let signature = FunctionSignature::from_hir(db, function); 131 let signature = FunctionSignature::from_hir(db, function);
118 132
119 CallInfo { signature, active_parameter: None } 133 CallInfo { signature, active_parameter: None }
120 } 134 }
121 135
136 fn with_struct(db: &RootDatabase, st: hir::Struct) -> Option<Self> {
137 let signature = FunctionSignature::from_struct(db, st)?;
138
139 Some(CallInfo { signature, active_parameter: None })
140 }
141
142 fn with_enum_variant(db: &RootDatabase, variant: hir::EnumVariant) -> Option<Self> {
143 let signature = FunctionSignature::from_enum_variant(db, variant)?;
144
145 Some(CallInfo { signature, active_parameter: None })
146 }
147
148 fn with_macro(db: &RootDatabase, macro_def: hir::MacroDef) -> Option<Self> {
149 let signature = FunctionSignature::from_macro(db, macro_def)?;
150
151 Some(CallInfo { signature, active_parameter: None })
152 }
153
122 fn parameters(&self) -> &[String] { 154 fn parameters(&self) -> &[String] {
123 &self.signature.parameters 155 &self.signature.parameters
124 } 156 }
@@ -415,6 +447,7 @@ pub fn foo(mut r: WriteHandler<()>) {
415"#, 447"#,
416 ); 448 );
417 449
450 assert_eq!(info.label(), "fn finished(&mut self, ctx: &mut Self::Context)".to_string());
418 assert_eq!(info.parameters(), ["&mut self", "ctx: &mut Self::Context"]); 451 assert_eq!(info.parameters(), ["&mut self", "ctx: &mut Self::Context"]);
419 assert_eq!(info.active_parameter, Some(1)); 452 assert_eq!(info.active_parameter, Some(1));
420 assert_eq!( 453 assert_eq!(
@@ -438,4 +471,118 @@ By default this method stops actor's `Context`."#
438 let call_info = analysis.call_info(position).unwrap(); 471 let call_info = analysis.call_info(position).unwrap();
439 assert!(call_info.is_none()); 472 assert!(call_info.is_none());
440 } 473 }
474
475 #[test]
476 fn test_nested_method_in_lamba() {
477 let info = call_info(
478 r#"struct Foo;
479
480impl Foo {
481 fn bar(&self, _: u32) { }
482}
483
484fn bar(_: u32) { }
485
486fn main() {
487 let foo = Foo;
488 std::thread::spawn(move || foo.bar(<|>));
489}"#,
490 );
491
492 assert_eq!(info.parameters(), ["&self", "_: u32"]);
493 assert_eq!(info.active_parameter, Some(1));
494 assert_eq!(info.label(), "fn bar(&self, _: u32)");
495 }
496
497 #[test]
498 fn works_for_tuple_structs() {
499 let info = call_info(
500 r#"
501/// A cool tuple struct
502struct TS(u32, i32);
503fn main() {
504 let s = TS(0, <|>);
505}"#,
506 );
507
508 assert_eq!(info.label(), "struct TS(u32, i32) -> TS");
509 assert_eq!(info.doc().map(|it| it.into()), Some("A cool tuple struct".to_string()));
510 assert_eq!(info.active_parameter, Some(1));
511 }
512
513 #[test]
514 #[should_panic]
515 fn cant_call_named_structs() {
516 let _ = call_info(
517 r#"
518struct TS { x: u32, y: i32 }
519fn main() {
520 let s = TS(<|>);
521}"#,
522 );
523 }
524
525 #[test]
526 fn works_for_enum_variants() {
527 let info = call_info(
528 r#"
529enum E {
530 /// A Variant
531 A(i32),
532 /// Another
533 B,
534 /// And C
535 C { a: i32, b: i32 }
536}
537
538fn main() {
539 let a = E::A(<|>);
540}
541 "#,
542 );
543
544 assert_eq!(info.label(), "E::A(0: i32)");
545 assert_eq!(info.doc().map(|it| it.into()), Some("A Variant".to_string()));
546 assert_eq!(info.active_parameter, Some(0));
547 }
548
549 #[test]
550 #[should_panic]
551 fn cant_call_enum_records() {
552 let _ = call_info(
553 r#"
554enum E {
555 /// A Variant
556 A(i32),
557 /// Another
558 B,
559 /// And C
560 C { a: i32, b: i32 }
561}
562
563fn main() {
564 let a = E::C(<|>);
565}
566 "#,
567 );
568 }
569
570 #[test]
571 fn fn_signature_for_macro() {
572 let info = call_info(
573 r#"
574/// empty macro
575macro_rules! foo {
576 () => {}
577}
578
579fn f() {
580 foo!(<|>);
581}
582 "#,
583 );
584
585 assert_eq!(info.label(), "foo!()");
586 assert_eq!(info.doc().map(|it| it.into()), Some("empty macro".to_string()));
587 }
441} 588}
diff --git a/crates/ra_ide_api/src/change.rs b/crates/ra_ide_api/src/change.rs
index 050249c0e..39c5946c7 100644
--- a/crates/ra_ide_api/src/change.rs
+++ b/crates/ra_ide_api/src/change.rs
@@ -43,7 +43,7 @@ impl fmt::Debug for AnalysisChange {
43 if !self.libraries_added.is_empty() { 43 if !self.libraries_added.is_empty() {
44 d.field("libraries_added", &self.libraries_added.len()); 44 d.field("libraries_added", &self.libraries_added.len());
45 } 45 }
46 if !self.crate_graph.is_some() { 46 if !self.crate_graph.is_none() {
47 d.field("crate_graph", &self.crate_graph); 47 d.field("crate_graph", &self.crate_graph);
48 } 48 }
49 d.finish() 49 d.finish()
diff --git a/crates/ra_ide_api/src/completion/complete_path.rs b/crates/ra_ide_api/src/completion/complete_path.rs
index 23dece73c..a58fdc036 100644
--- a/crates/ra_ide_api/src/completion/complete_path.rs
+++ b/crates/ra_ide_api/src/completion/complete_path.rs
@@ -50,7 +50,7 @@ pub(super) fn complete_path(acc: &mut Completions, ctx: &CompletionContext) {
50 hir::ModuleDef::TypeAlias(a) => a.ty(ctx.db), 50 hir::ModuleDef::TypeAlias(a) => a.ty(ctx.db),
51 _ => unreachable!(), 51 _ => unreachable!(),
52 }; 52 };
53 let krate = ctx.module.and_then(|m| m.krate(ctx.db)); 53 let krate = ctx.module.map(|m| m.krate());
54 if let Some(krate) = krate { 54 if let Some(krate) = krate {
55 ty.iterate_impl_items(ctx.db, krate, |item| { 55 ty.iterate_impl_items(ctx.db, krate, |item| {
56 match item { 56 match item {
@@ -67,7 +67,7 @@ pub(super) fn complete_path(acc: &mut Completions, ctx: &CompletionContext) {
67 }); 67 });
68 } 68 }
69 } 69 }
70 _ => return, 70 _ => {}
71 }; 71 };
72} 72}
73 73
diff --git a/crates/ra_ide_api/src/completion/complete_postfix.rs b/crates/ra_ide_api/src/completion/complete_postfix.rs
index 555cecb73..60ed3518b 100644
--- a/crates/ra_ide_api/src/completion/complete_postfix.rs
+++ b/crates/ra_ide_api/src/completion/complete_postfix.rs
@@ -9,16 +9,14 @@ use crate::{
9}; 9};
10use hir::{Ty, TypeCtor}; 10use hir::{Ty, TypeCtor};
11use ra_syntax::{ast::AstNode, TextRange, TextUnit}; 11use ra_syntax::{ast::AstNode, TextRange, TextUnit};
12use ra_text_edit::TextEditBuilder; 12use ra_text_edit::TextEdit;
13 13
14fn postfix_snippet(ctx: &CompletionContext, label: &str, detail: &str, snippet: &str) -> Builder { 14fn postfix_snippet(ctx: &CompletionContext, label: &str, detail: &str, snippet: &str) -> Builder {
15 let edit = { 15 let edit = {
16 let receiver_range = 16 let receiver_range =
17 ctx.dot_receiver.as_ref().expect("no receiver available").syntax().text_range(); 17 ctx.dot_receiver.as_ref().expect("no receiver available").syntax().text_range();
18 let delete_range = TextRange::from_to(receiver_range.start(), ctx.source_range().end()); 18 let delete_range = TextRange::from_to(receiver_range.start(), ctx.source_range().end());
19 let mut builder = TextEditBuilder::default(); 19 TextEdit::replace(delete_range, snippet.to_string())
20 builder.replace(delete_range, snippet.to_string());
21 builder.finish()
22 }; 20 };
23 CompletionItem::new(CompletionKind::Postfix, ctx.source_range(), label) 21 CompletionItem::new(CompletionKind::Postfix, ctx.source_range(), label)
24 .detail(detail) 22 .detail(detail)
diff --git a/crates/ra_ide_api/src/completion/completion_item.rs b/crates/ra_ide_api/src/completion/completion_item.rs
index 3e6933bc1..5c9c44704 100644
--- a/crates/ra_ide_api/src/completion/completion_item.rs
+++ b/crates/ra_ide_api/src/completion/completion_item.rs
@@ -4,7 +4,7 @@ use std::fmt;
4 4
5use hir::Documentation; 5use hir::Documentation;
6use ra_syntax::TextRange; 6use ra_syntax::TextRange;
7use ra_text_edit::{TextEdit, TextEditBuilder}; 7use ra_text_edit::TextEdit;
8 8
9/// `CompletionItem` describes a single completion variant in the editor pop-up. 9/// `CompletionItem` describes a single completion variant in the editor pop-up.
10/// It is basically a POD with various properties. To construct a 10/// It is basically a POD with various properties. To construct a
@@ -192,12 +192,10 @@ impl Builder {
192 let label = self.label; 192 let label = self.label;
193 let text_edit = match self.text_edit { 193 let text_edit = match self.text_edit {
194 Some(it) => it, 194 Some(it) => it,
195 None => { 195 None => TextEdit::replace(
196 let mut builder = TextEditBuilder::default(); 196 self.source_range,
197 builder 197 self.insert_text.unwrap_or_else(|| label.clone()),
198 .replace(self.source_range, self.insert_text.unwrap_or_else(|| label.clone())); 198 ),
199 builder.finish()
200 }
201 }; 199 };
202 200
203 CompletionItem { 201 CompletionItem {
diff --git a/crates/ra_ide_api/src/completion/presentation.rs b/crates/ra_ide_api/src/completion/presentation.rs
index aed4ce6d4..65bb639ed 100644
--- a/crates/ra_ide_api/src/completion/presentation.rs
+++ b/crates/ra_ide_api/src/completion/presentation.rs
@@ -136,7 +136,7 @@ impl Completions {
136 for (idx, s) in docs.match_indices(&macro_name) { 136 for (idx, s) in docs.match_indices(&macro_name) {
137 let (before, after) = (&docs[..idx], &docs[idx + s.len()..]); 137 let (before, after) = (&docs[..idx], &docs[idx + s.len()..]);
138 // Ensure to match the full word 138 // Ensure to match the full word
139 if after.starts_with("!") 139 if after.starts_with('!')
140 && before 140 && before
141 .chars() 141 .chars()
142 .rev() 142 .rev()
@@ -164,27 +164,32 @@ impl Completions {
164 name: Option<String>, 164 name: Option<String>,
165 macro_: hir::MacroDef, 165 macro_: hir::MacroDef,
166 ) { 166 ) {
167 let name = match name {
168 Some(it) => it,
169 None => return,
170 };
171
167 let ast_node = macro_.source(ctx.db).ast; 172 let ast_node = macro_.source(ctx.db).ast;
168 if let Some(name) = name { 173 let detail = macro_label(&ast_node);
169 let detail = macro_label(&ast_node); 174
175 let docs = macro_.docs(ctx.db);
176 let macro_declaration = format!("{}!", name);
177
178 let mut builder =
179 CompletionItem::new(CompletionKind::Reference, ctx.source_range(), &macro_declaration)
180 .kind(CompletionItemKind::Macro)
181 .set_documentation(docs.clone())
182 .detail(detail);
170 183
171 let docs = macro_.docs(ctx.db); 184 builder = if ctx.use_item_syntax.is_some() {
185 builder.insert_text(name)
186 } else {
172 let macro_braces_to_insert = 187 let macro_braces_to_insert =
173 self.guess_macro_braces(&name, docs.as_ref().map_or("", |s| s.as_str())); 188 self.guess_macro_braces(&name, docs.as_ref().map_or("", |s| s.as_str()));
174 let macro_declaration = name + "!"; 189 builder.insert_snippet(macro_declaration + macro_braces_to_insert)
175 190 };
176 let builder = CompletionItem::new(
177 CompletionKind::Reference,
178 ctx.source_range(),
179 &macro_declaration,
180 )
181 .kind(CompletionItemKind::Macro)
182 .set_documentation(docs)
183 .detail(detail)
184 .insert_snippet(macro_declaration + macro_braces_to_insert);
185 191
186 self.add(builder); 192 self.add(builder);
187 }
188 } 193 }
189 194
190 fn add_function_with_name( 195 fn add_function_with_name(
@@ -220,7 +225,7 @@ impl Completions {
220 } else { 225 } else {
221 (format!("{}($0)", data.name()), format!("{}(…)", name)) 226 (format!("{}($0)", data.name()), format!("{}(…)", name))
222 }; 227 };
223 builder = builder.lookup_by(name.clone()).label(label).insert_snippet(snippet); 228 builder = builder.lookup_by(name).label(label).insert_snippet(snippet);
224 } 229 }
225 230
226 self.add(builder) 231 self.add(builder)
@@ -281,10 +286,11 @@ fn has_non_default_type_params(def: hir::GenericDef, db: &db::RootDatabase) -> b
281 286
282#[cfg(test)] 287#[cfg(test)]
283mod tests { 288mod tests {
284 use crate::completion::{do_completion, CompletionItem, CompletionKind};
285 use insta::assert_debug_snapshot; 289 use insta::assert_debug_snapshot;
286 use test_utils::covers; 290 use test_utils::covers;
287 291
292 use crate::completion::{do_completion, CompletionItem, CompletionKind};
293
288 fn do_reference_completion(code: &str) -> Vec<CompletionItem> { 294 fn do_reference_completion(code: &str) -> Vec<CompletionItem> {
289 do_completion(code, CompletionKind::Reference) 295 do_completion(code, CompletionKind::Reference)
290 } 296 }
@@ -576,4 +582,34 @@ mod tests {
576 "### 582 "###
577 ); 583 );
578 } 584 }
585
586 #[test]
587 fn dont_insert_macro_call_braces_in_use() {
588 assert_debug_snapshot!(
589 do_reference_completion(
590 r"
591 //- /main.rs
592 use foo::<|>;
593
594 //- /foo/lib.rs
595 #[macro_export]
596 macro_rules frobnicate {
597 () => ()
598 }
599 "
600 ),
601 @r###"
602 [
603 CompletionItem {
604 label: "frobnicate!",
605 source_range: [9; 9),
606 delete: [9; 9),
607 insert: "frobnicate",
608 kind: Macro,
609 detail: "#[macro_export]\nmacro_rules! frobnicate",
610 },
611 ]
612 "###
613 )
614 }
579} 615}
diff --git a/crates/ra_ide_api/src/db.rs b/crates/ra_ide_api/src/db.rs
index 9146b647a..785e71808 100644
--- a/crates/ra_ide_api/src/db.rs
+++ b/crates/ra_ide_api/src/db.rs
@@ -23,6 +23,7 @@ use crate::{
23 hir::db::InternDatabaseStorage, 23 hir::db::InternDatabaseStorage,
24 hir::db::AstDatabaseStorage, 24 hir::db::AstDatabaseStorage,
25 hir::db::DefDatabaseStorage, 25 hir::db::DefDatabaseStorage,
26 hir::db::DefDatabase2Storage,
26 hir::db::HirDatabaseStorage 27 hir::db::HirDatabaseStorage
27)] 28)]
28#[derive(Debug)] 29#[derive(Debug)]
diff --git a/crates/ra_ide_api/src/diagnostics.rs b/crates/ra_ide_api/src/diagnostics.rs
index 8743a3a79..1f1f5cd74 100644
--- a/crates/ra_ide_api/src/diagnostics.rs
+++ b/crates/ra_ide_api/src/diagnostics.rs
@@ -85,10 +85,9 @@ pub(crate) fn diagnostics(db: &RootDatabase, file_id: FileId) -> Vec<Diagnostic>
85 }) 85 })
86 .on::<hir::diagnostics::MissingOkInTailExpr, _>(|d| { 86 .on::<hir::diagnostics::MissingOkInTailExpr, _>(|d| {
87 let node = d.ast(db); 87 let node = d.ast(db);
88 let mut builder = TextEditBuilder::default();
89 let replacement = format!("Ok({})", node.syntax()); 88 let replacement = format!("Ok({})", node.syntax());
90 builder.replace(node.syntax().text_range(), replacement); 89 let edit = TextEdit::replace(node.syntax().text_range(), replacement);
91 let fix = SourceChange::source_file_edit_from("wrap with ok", file_id, builder.finish()); 90 let fix = SourceChange::source_file_edit_from("wrap with ok", file_id, edit);
92 res.borrow_mut().push(Diagnostic { 91 res.borrow_mut().push(Diagnostic {
93 range: d.highlight_range(), 92 range: d.highlight_range(),
94 message: d.message(), 93 message: d.message(),
@@ -152,9 +151,7 @@ fn text_edit_for_remove_unnecessary_braces_with_self_in_use_statement(
152 let start = use_tree_list_node.prev_sibling_or_token()?.text_range().start(); 151 let start = use_tree_list_node.prev_sibling_or_token()?.text_range().start();
153 let end = use_tree_list_node.text_range().end(); 152 let end = use_tree_list_node.text_range().end();
154 let range = TextRange::from_to(start, end); 153 let range = TextRange::from_to(start, end);
155 let mut edit_builder = TextEditBuilder::default(); 154 return Some(TextEdit::delete(range));
156 edit_builder.delete(range);
157 return Some(edit_builder.finish());
158 } 155 }
159 None 156 None
160} 157}
diff --git a/crates/ra_ide_api/src/display/function_signature.rs b/crates/ra_ide_api/src/display/function_signature.rs
index 43f022ccd..9075ca443 100644
--- a/crates/ra_ide_api/src/display/function_signature.rs
+++ b/crates/ra_ide_api/src/display/function_signature.rs
@@ -2,7 +2,7 @@
2 2
3use std::fmt::{self, Display}; 3use std::fmt::{self, Display};
4 4
5use hir::{Docs, Documentation, HasSource}; 5use hir::{Docs, Documentation, HasSource, HirDisplay};
6use join_to_string::join; 6use join_to_string::join;
7use ra_syntax::ast::{self, AstNode, NameOwner, VisibilityOwner}; 7use ra_syntax::ast::{self, AstNode, NameOwner, VisibilityOwner};
8use std::convert::From; 8use std::convert::From;
@@ -12,9 +12,18 @@ use crate::{
12 display::{generic_parameters, where_predicates}, 12 display::{generic_parameters, where_predicates},
13}; 13};
14 14
15#[derive(Debug)]
16pub enum CallableKind {
17 Function,
18 StructConstructor,
19 VariantConstructor,
20 Macro,
21}
22
15/// Contains information about a function signature 23/// Contains information about a function signature
16#[derive(Debug)] 24#[derive(Debug)]
17pub struct FunctionSignature { 25pub struct FunctionSignature {
26 pub kind: CallableKind,
18 /// Optional visibility 27 /// Optional visibility
19 pub visibility: Option<String>, 28 pub visibility: Option<String>,
20 /// Name of the function 29 /// Name of the function
@@ -42,6 +51,99 @@ impl FunctionSignature {
42 let ast_node = function.source(db).ast; 51 let ast_node = function.source(db).ast;
43 FunctionSignature::from(&ast_node).with_doc_opt(doc) 52 FunctionSignature::from(&ast_node).with_doc_opt(doc)
44 } 53 }
54
55 pub(crate) fn from_struct(db: &db::RootDatabase, st: hir::Struct) -> Option<Self> {
56 let node: ast::StructDef = st.source(db).ast;
57 match node.kind() {
58 ast::StructKind::Named(_) => return None,
59 _ => (),
60 };
61
62 let params = st
63 .fields(db)
64 .into_iter()
65 .map(|field: hir::StructField| {
66 let ty = field.ty(db);
67 format!("{}", ty.display(db))
68 })
69 .collect();
70
71 Some(
72 FunctionSignature {
73 kind: CallableKind::StructConstructor,
74 visibility: node.visibility().map(|n| n.syntax().text().to_string()),
75 name: node.name().map(|n| n.text().to_string()),
76 ret_type: node.name().map(|n| n.text().to_string()),
77 parameters: params,
78 generic_parameters: generic_parameters(&node),
79 where_predicates: where_predicates(&node),
80 doc: None,
81 }
82 .with_doc_opt(st.docs(db)),
83 )
84 }
85
86 pub(crate) fn from_enum_variant(
87 db: &db::RootDatabase,
88 variant: hir::EnumVariant,
89 ) -> Option<Self> {
90 let node: ast::EnumVariant = variant.source(db).ast;
91 match node.kind() {
92 ast::StructKind::Named(_) | ast::StructKind::Unit => return None,
93 _ => (),
94 };
95
96 let parent_name = match variant.parent_enum(db).name(db) {
97 Some(name) => name.to_string(),
98 None => "missing".into(),
99 };
100
101 let name = format!("{}::{}", parent_name, variant.name(db).unwrap());
102
103 let params = variant
104 .fields(db)
105 .into_iter()
106 .map(|field: hir::StructField| {
107 let name = field.name(db);
108 let ty = field.ty(db);
109 format!("{}: {}", name, ty.display(db))
110 })
111 .collect();
112
113 Some(
114 FunctionSignature {
115 kind: CallableKind::VariantConstructor,
116 visibility: None,
117 name: Some(name),
118 ret_type: None,
119 parameters: params,
120 generic_parameters: vec![],
121 where_predicates: vec![],
122 doc: None,
123 }
124 .with_doc_opt(variant.docs(db)),
125 )
126 }
127
128 pub(crate) fn from_macro(db: &db::RootDatabase, macro_def: hir::MacroDef) -> Option<Self> {
129 let node: ast::MacroCall = macro_def.source(db).ast;
130
131 let params = vec![];
132
133 Some(
134 FunctionSignature {
135 kind: CallableKind::Macro,
136 visibility: None,
137 name: node.name().map(|n| n.text().to_string()),
138 ret_type: None,
139 parameters: params,
140 generic_parameters: vec![],
141 where_predicates: vec![],
142 doc: None,
143 }
144 .with_doc_opt(macro_def.docs(db)),
145 )
146 }
45} 147}
46 148
47impl From<&'_ ast::FnDef> for FunctionSignature { 149impl From<&'_ ast::FnDef> for FunctionSignature {
@@ -59,6 +161,7 @@ impl From<&'_ ast::FnDef> for FunctionSignature {
59 } 161 }
60 162
61 FunctionSignature { 163 FunctionSignature {
164 kind: CallableKind::Function,
62 visibility: node.visibility().map(|n| n.syntax().text().to_string()), 165 visibility: node.visibility().map(|n| n.syntax().text().to_string()),
63 name: node.name().map(|n| n.text().to_string()), 166 name: node.name().map(|n| n.text().to_string()),
64 ret_type: node 167 ret_type: node
@@ -81,7 +184,12 @@ impl Display for FunctionSignature {
81 } 184 }
82 185
83 if let Some(name) = &self.name { 186 if let Some(name) = &self.name {
84 write!(f, "fn {}", name)?; 187 match self.kind {
188 CallableKind::Function => write!(f, "fn {}", name)?,
189 CallableKind::StructConstructor => write!(f, "struct {}", name)?,
190 CallableKind::VariantConstructor => write!(f, "{}", name)?,
191 CallableKind::Macro => write!(f, "{}!", name)?,
192 }
85 } 193 }
86 194
87 if !self.generic_parameters.is_empty() { 195 if !self.generic_parameters.is_empty() {
diff --git a/crates/ra_ide_api/src/extend_selection.rs b/crates/ra_ide_api/src/extend_selection.rs
index 602757e92..4b7bfc0b1 100644
--- a/crates/ra_ide_api/src/extend_selection.rs
+++ b/crates/ra_ide_api/src/extend_selection.rs
@@ -5,7 +5,7 @@ use ra_syntax::{
5 algo::find_covering_element, 5 algo::find_covering_element,
6 ast::{self, AstNode, AstToken}, 6 ast::{self, AstNode, AstToken},
7 Direction, NodeOrToken, 7 Direction, NodeOrToken,
8 SyntaxKind::*, 8 SyntaxKind::{self, *},
9 SyntaxNode, SyntaxToken, TextRange, TextUnit, TokenAtOffset, T, 9 SyntaxNode, SyntaxToken, TextRange, TextUnit, TokenAtOffset, T,
10}; 10};
11 11
@@ -29,10 +29,12 @@ fn try_extend_selection(root: &SyntaxNode, range: TextRange) -> Option<TextRange
29 USE_TREE_LIST, 29 USE_TREE_LIST,
30 TYPE_PARAM_LIST, 30 TYPE_PARAM_LIST,
31 TYPE_ARG_LIST, 31 TYPE_ARG_LIST,
32 TYPE_BOUND_LIST,
32 PARAM_LIST, 33 PARAM_LIST,
33 ARG_LIST, 34 ARG_LIST,
34 ARRAY_EXPR, 35 ARRAY_EXPR,
35 TUPLE_EXPR, 36 TUPLE_EXPR,
37 WHERE_CLAUSE,
36 ]; 38 ];
37 39
38 if range.is_empty() { 40 if range.is_empty() {
@@ -146,13 +148,17 @@ fn pick_best<'a>(l: SyntaxToken, r: SyntaxToken) -> SyntaxToken {
146 } 148 }
147} 149}
148 150
149/// Extend list item selection to include nearby comma and whitespace. 151/// Extend list item selection to include nearby delimiter and whitespace.
150fn extend_list_item(node: &SyntaxNode) -> Option<TextRange> { 152fn extend_list_item(node: &SyntaxNode) -> Option<TextRange> {
151 fn is_single_line_ws(node: &SyntaxToken) -> bool { 153 fn is_single_line_ws(node: &SyntaxToken) -> bool {
152 node.kind() == WHITESPACE && !node.text().contains('\n') 154 node.kind() == WHITESPACE && !node.text().contains('\n')
153 } 155 }
154 156
155 fn nearby_comma(node: &SyntaxNode, dir: Direction) -> Option<SyntaxToken> { 157 fn nearby_delimiter(
158 delimiter_kind: SyntaxKind,
159 node: &SyntaxNode,
160 dir: Direction,
161 ) -> Option<SyntaxToken> {
156 node.siblings_with_tokens(dir) 162 node.siblings_with_tokens(dir)
157 .skip(1) 163 .skip(1)
158 .skip_while(|node| match node { 164 .skip_while(|node| match node {
@@ -161,19 +167,26 @@ fn extend_list_item(node: &SyntaxNode) -> Option<TextRange> {
161 }) 167 })
162 .next() 168 .next()
163 .and_then(|it| it.into_token()) 169 .and_then(|it| it.into_token())
164 .filter(|node| node.kind() == T![,]) 170 .filter(|node| node.kind() == delimiter_kind)
165 } 171 }
166 172
167 if let Some(comma_node) = nearby_comma(node, Direction::Prev) { 173 let delimiter = match node.kind() {
168 return Some(TextRange::from_to(comma_node.text_range().start(), node.text_range().end())); 174 TYPE_BOUND => T![+],
175 _ => T![,],
176 };
177 if let Some(delimiter_node) = nearby_delimiter(delimiter, node, Direction::Prev) {
178 return Some(TextRange::from_to(
179 delimiter_node.text_range().start(),
180 node.text_range().end(),
181 ));
169 } 182 }
170 if let Some(comma_node) = nearby_comma(node, Direction::Next) { 183 if let Some(delimiter_node) = nearby_delimiter(delimiter, node, Direction::Next) {
171 // Include any following whitespace when comma if after list item. 184 // Include any following whitespace when delimiter is after list item.
172 let final_node = comma_node 185 let final_node = delimiter_node
173 .next_sibling_or_token() 186 .next_sibling_or_token()
174 .and_then(|it| it.into_token()) 187 .and_then(|it| it.into_token())
175 .filter(|node| is_single_line_ws(node)) 188 .filter(|node| is_single_line_ws(node))
176 .unwrap_or(comma_node); 189 .unwrap_or(delimiter_node);
177 190
178 return Some(TextRange::from_to(node.text_range().start(), final_node.text_range().end())); 191 return Some(TextRange::from_to(node.text_range().start(), final_node.text_range().end()));
179 } 192 }
@@ -387,4 +400,53 @@ fn bar(){}
387 &["foo", "\" fn foo() {\""], 400 &["foo", "\" fn foo() {\""],
388 ); 401 );
389 } 402 }
403
404 #[test]
405 fn test_extend_trait_bounds_list_in_where_clause() {
406 do_check(
407 r#"
408fn foo<R>()
409 where
410 R: req::Request + 'static,
411 R::Params: DeserializeOwned<|> + panic::UnwindSafe + 'static,
412 R::Result: Serialize + 'static,
413"#,
414 &[
415 "DeserializeOwned",
416 "DeserializeOwned + ",
417 "DeserializeOwned + panic::UnwindSafe + 'static",
418 "R::Params: DeserializeOwned + panic::UnwindSafe + 'static",
419 "R::Params: DeserializeOwned + panic::UnwindSafe + 'static,",
420 ],
421 );
422 do_check(r#"fn foo<T>() where T: <|>Copy"#, &["Copy"]);
423 do_check(r#"fn foo<T>() where T: <|>Copy + Display"#, &["Copy", "Copy + "]);
424 do_check(r#"fn foo<T>() where T: <|>Copy +Display"#, &["Copy", "Copy +"]);
425 do_check(r#"fn foo<T>() where T: <|>Copy+Display"#, &["Copy", "Copy+"]);
426 do_check(r#"fn foo<T>() where T: Copy + <|>Display"#, &["Display", "+ Display"]);
427 do_check(r#"fn foo<T>() where T: Copy + <|>Display + Sync"#, &["Display", "+ Display"]);
428 do_check(r#"fn foo<T>() where T: Copy +<|>Display"#, &["Display", "+Display"]);
429 }
430
431 #[test]
432 fn test_extend_trait_bounds_list_inline() {
433 do_check(r#"fn foo<T: <|>Copy>() {}"#, &["Copy"]);
434 do_check(r#"fn foo<T: <|>Copy + Display>() {}"#, &["Copy", "Copy + "]);
435 do_check(r#"fn foo<T: <|>Copy +Display>() {}"#, &["Copy", "Copy +"]);
436 do_check(r#"fn foo<T: <|>Copy+Display>() {}"#, &["Copy", "Copy+"]);
437 do_check(r#"fn foo<T: Copy + <|>Display>() {}"#, &["Display", "+ Display"]);
438 do_check(r#"fn foo<T: Copy + <|>Display + Sync>() {}"#, &["Display", "+ Display"]);
439 do_check(r#"fn foo<T: Copy +<|>Display>() {}"#, &["Display", "+Display"]);
440 do_check(
441 r#"fn foo<T: Copy<|> + Display, U: Copy>() {}"#,
442 &[
443 "Copy",
444 "Copy + ",
445 "Copy + Display",
446 "T: Copy + Display",
447 "T: Copy + Display, ",
448 "<T: Copy + Display, U: Copy>",
449 ],
450 );
451 }
390} 452}
diff --git a/crates/ra_ide_api/src/impls.rs b/crates/ra_ide_api/src/impls.rs
index 7fc1b1efa..b899ed3a5 100644
--- a/crates/ra_ide_api/src/impls.rs
+++ b/crates/ra_ide_api/src/impls.rs
@@ -51,7 +51,7 @@ fn impls_for_def(
51 } 51 }
52 }; 52 };
53 53
54 let krate = module.krate(db)?; 54 let krate = module.krate();
55 let impls = db.impls_in_crate(krate); 55 let impls = db.impls_in_crate(krate);
56 56
57 Some( 57 Some(
@@ -72,7 +72,7 @@ fn impls_for_trait(
72 let src = hir::Source { file_id: position.file_id.into(), ast: node.clone() }; 72 let src = hir::Source { file_id: position.file_id.into(), ast: node.clone() };
73 let tr = hir::Trait::from_source(db, src)?; 73 let tr = hir::Trait::from_source(db, src)?;
74 74
75 let krate = module.krate(db)?; 75 let krate = module.krate();
76 let impls = db.impls_in_crate(krate); 76 let impls = db.impls_in_crate(krate);
77 77
78 Some( 78 Some(
diff --git a/crates/ra_ide_api/src/lib.rs b/crates/ra_ide_api/src/lib.rs
index 0832229fd..d0188da44 100644
--- a/crates/ra_ide_api/src/lib.rs
+++ b/crates/ra_ide_api/src/lib.rs
@@ -14,6 +14,7 @@ mod db;
14pub mod mock_analysis; 14pub mod mock_analysis;
15mod symbol_index; 15mod symbol_index;
16mod change; 16mod change;
17mod source_change;
17mod feature_flags; 18mod feature_flags;
18 19
19mod status; 20mod status;
@@ -54,8 +55,6 @@ use ra_db::{
54 CheckCanceled, FileLoader, SourceDatabase, 55 CheckCanceled, FileLoader, SourceDatabase,
55}; 56};
56use ra_syntax::{SourceFile, TextRange, TextUnit}; 57use ra_syntax::{SourceFile, TextRange, TextUnit};
57use ra_text_edit::TextEdit;
58use relative_path::RelativePathBuf;
59 58
60use crate::{db::LineIndexDatabase, symbol_index::FileSymbol}; 59use crate::{db::LineIndexDatabase, symbol_index::FileSymbol};
61 60
@@ -73,6 +72,7 @@ pub use crate::{
73 line_index_utils::translate_offset_with_edit, 72 line_index_utils::translate_offset_with_edit,
74 references::{ReferenceSearchResult, SearchScope}, 73 references::{ReferenceSearchResult, SearchScope},
75 runnables::{Runnable, RunnableKind}, 74 runnables::{Runnable, RunnableKind},
75 source_change::{FileSystemEdit, SourceChange, SourceFileEdit},
76 syntax_highlighting::HighlightedRange, 76 syntax_highlighting::HighlightedRange,
77}; 77};
78 78
@@ -84,99 +84,6 @@ pub use ra_db::{
84pub type Cancelable<T> = Result<T, Canceled>; 84pub type Cancelable<T> = Result<T, Canceled>;
85 85
86#[derive(Debug)] 86#[derive(Debug)]
87pub struct SourceChange {
88 pub label: String,
89 pub source_file_edits: Vec<SourceFileEdit>,
90 pub file_system_edits: Vec<FileSystemEdit>,
91 pub cursor_position: Option<FilePosition>,
92}
93
94impl SourceChange {
95 /// Creates a new SourceChange with the given label
96 /// from the edits.
97 pub(crate) fn from_edits<L: Into<String>>(
98 label: L,
99 source_file_edits: Vec<SourceFileEdit>,
100 file_system_edits: Vec<FileSystemEdit>,
101 ) -> Self {
102 SourceChange {
103 label: label.into(),
104 source_file_edits,
105 file_system_edits,
106 cursor_position: None,
107 }
108 }
109
110 /// Creates a new SourceChange with the given label,
111 /// containing only the given `SourceFileEdits`.
112 pub(crate) fn source_file_edits<L: Into<String>>(label: L, edits: Vec<SourceFileEdit>) -> Self {
113 SourceChange {
114 label: label.into(),
115 source_file_edits: edits,
116 file_system_edits: vec![],
117 cursor_position: None,
118 }
119 }
120
121 /// Creates a new SourceChange with the given label,
122 /// containing only the given `FileSystemEdits`.
123 pub(crate) fn file_system_edits<L: Into<String>>(label: L, edits: Vec<FileSystemEdit>) -> Self {
124 SourceChange {
125 label: label.into(),
126 source_file_edits: vec![],
127 file_system_edits: edits,
128 cursor_position: None,
129 }
130 }
131
132 /// Creates a new SourceChange with the given label,
133 /// containing only a single `SourceFileEdit`.
134 pub(crate) fn source_file_edit<L: Into<String>>(label: L, edit: SourceFileEdit) -> Self {
135 SourceChange::source_file_edits(label, vec![edit])
136 }
137
138 /// Creates a new SourceChange with the given label
139 /// from the given `FileId` and `TextEdit`
140 pub(crate) fn source_file_edit_from<L: Into<String>>(
141 label: L,
142 file_id: FileId,
143 edit: TextEdit,
144 ) -> Self {
145 SourceChange::source_file_edit(label, SourceFileEdit { file_id, edit })
146 }
147
148 /// Creates a new SourceChange with the given label
149 /// from the given `FileId` and `TextEdit`
150 pub(crate) fn file_system_edit<L: Into<String>>(label: L, edit: FileSystemEdit) -> Self {
151 SourceChange::file_system_edits(label, vec![edit])
152 }
153
154 /// Sets the cursor position to the given `FilePosition`
155 pub(crate) fn with_cursor(mut self, cursor_position: FilePosition) -> Self {
156 self.cursor_position = Some(cursor_position);
157 self
158 }
159
160 /// Sets the cursor position to the given `FilePosition`
161 pub(crate) fn with_cursor_opt(mut self, cursor_position: Option<FilePosition>) -> Self {
162 self.cursor_position = cursor_position;
163 self
164 }
165}
166
167#[derive(Debug)]
168pub struct SourceFileEdit {
169 pub file_id: FileId,
170 pub edit: TextEdit,
171}
172
173#[derive(Debug)]
174pub enum FileSystemEdit {
175 CreateFile { source_root: SourceRootId, path: RelativePathBuf },
176 MoveFile { src: FileId, dst_source_root: SourceRootId, dst_path: RelativePathBuf },
177}
178
179#[derive(Debug)]
180pub struct Diagnostic { 87pub struct Diagnostic {
181 pub message: String, 88 pub message: String,
182 pub range: TextRange, 89 pub range: TextRange,
@@ -407,24 +314,20 @@ impl Analysis {
407 self.with_db(|db| typing::on_enter(&db, position)) 314 self.with_db(|db| typing::on_enter(&db, position))
408 } 315 }
409 316
410 /// Returns an edit which should be applied after `=` was typed. Primarily, 317 /// Returns an edit which should be applied after a character was typed.
411 /// this works when adding `let =`. 318 ///
412 // FIXME: use a snippet completion instead of this hack here. 319 /// This is useful for some on-the-fly fixups, like adding `;` to `let =`
413 pub fn on_eq_typed(&self, position: FilePosition) -> Cancelable<Option<SourceChange>> { 320 /// automatically.
414 self.with_db(|db| { 321 pub fn on_char_typed(
415 let parse = db.parse(position.file_id); 322 &self,
416 let file = parse.tree(); 323 position: FilePosition,
417 let edit = typing::on_eq_typed(&file, position.offset)?; 324 char_typed: char,
418 Some(SourceChange::source_file_edit( 325 ) -> Cancelable<Option<SourceChange>> {
419 "add semicolon", 326 // Fast path to not even parse the file.
420 SourceFileEdit { edit, file_id: position.file_id }, 327 if !typing::TRIGGER_CHARS.contains(char_typed) {
421 )) 328 return Ok(None);
422 }) 329 }
423 } 330 self.with_db(|db| typing::on_char_typed(&db, position, char_typed))
424
425 /// Returns an edit which should be applied when a dot ('.') is typed on a blank line, indenting the line appropriately.
426 pub fn on_dot_typed(&self, position: FilePosition) -> Cancelable<Option<SourceChange>> {
427 self.with_db(|db| typing::on_dot_typed(&db, position))
428 } 331 }
429 332
430 /// Returns a tree representation of symbols in the file. Useful to draw a 333 /// Returns a tree representation of symbols in the file. Useful to draw a
diff --git a/crates/ra_ide_api/src/parent_module.rs b/crates/ra_ide_api/src/parent_module.rs
index 566509849..4c57566e2 100644
--- a/crates/ra_ide_api/src/parent_module.rs
+++ b/crates/ra_ide_api/src/parent_module.rs
@@ -27,10 +27,7 @@ pub(crate) fn crate_for(db: &RootDatabase, file_id: FileId) -> Vec<CrateId> {
27 Some(it) => it, 27 Some(it) => it,
28 None => return Vec::new(), 28 None => return Vec::new(),
29 }; 29 };
30 let krate = match module.krate(db) { 30 let krate = module.krate();
31 Some(it) => it,
32 None => return Vec::new(),
33 };
34 vec![krate.crate_id()] 31 vec![krate.crate_id()]
35} 32}
36 33
diff --git a/crates/ra_ide_api/src/references/rename.rs b/crates/ra_ide_api/src/references/rename.rs
index ee6e73e1b..a8783d7a0 100644
--- a/crates/ra_ide_api/src/references/rename.rs
+++ b/crates/ra_ide_api/src/references/rename.rs
@@ -3,6 +3,7 @@
3use hir::ModuleSource; 3use hir::ModuleSource;
4use ra_db::{SourceDatabase, SourceDatabaseExt}; 4use ra_db::{SourceDatabase, SourceDatabaseExt};
5use ra_syntax::{algo::find_node_at_offset, ast, AstNode, SyntaxNode}; 5use ra_syntax::{algo::find_node_at_offset, ast, AstNode, SyntaxNode};
6use ra_text_edit::TextEdit;
6use relative_path::{RelativePath, RelativePathBuf}; 7use relative_path::{RelativePath, RelativePathBuf};
7 8
8use crate::{ 9use crate::{
@@ -43,14 +44,7 @@ fn source_edit_from_file_id_range(
43 range: TextRange, 44 range: TextRange,
44 new_name: &str, 45 new_name: &str,
45) -> SourceFileEdit { 46) -> SourceFileEdit {
46 SourceFileEdit { 47 SourceFileEdit { file_id, edit: TextEdit::replace(range, new_name.into()) }
47 file_id,
48 edit: {
49 let mut builder = ra_text_edit::TextEditBuilder::default();
50 builder.replace(range, new_name.into());
51 builder.finish()
52 },
53 }
54} 48}
55 49
56fn rename_mod( 50fn rename_mod(
@@ -94,11 +88,7 @@ fn rename_mod(
94 88
95 let edit = SourceFileEdit { 89 let edit = SourceFileEdit {
96 file_id: position.file_id, 90 file_id: position.file_id,
97 edit: { 91 edit: TextEdit::replace(ast_name.syntax().text_range(), new_name.into()),
98 let mut builder = ra_text_edit::TextEditBuilder::default();
99 builder.replace(ast_name.syntax().text_range(), new_name.into());
100 builder.finish()
101 },
102 }; 92 };
103 source_file_edits.push(edit); 93 source_file_edits.push(edit);
104 94
@@ -126,12 +116,14 @@ fn rename_reference(
126 116
127#[cfg(test)] 117#[cfg(test)]
128mod tests { 118mod tests {
119 use insta::assert_debug_snapshot;
120 use ra_text_edit::TextEditBuilder;
121 use test_utils::assert_eq_text;
122
129 use crate::{ 123 use crate::{
130 mock_analysis::analysis_and_position, mock_analysis::single_file_with_position, FileId, 124 mock_analysis::analysis_and_position, mock_analysis::single_file_with_position, FileId,
131 ReferenceSearchResult, 125 ReferenceSearchResult,
132 }; 126 };
133 use insta::assert_debug_snapshot;
134 use test_utils::assert_eq_text;
135 127
136 #[test] 128 #[test]
137 fn test_find_all_refs_for_local() { 129 fn test_find_all_refs_for_local() {
@@ -452,7 +444,7 @@ mod tests {
452 fn test_rename(text: &str, new_name: &str, expected: &str) { 444 fn test_rename(text: &str, new_name: &str, expected: &str) {
453 let (analysis, position) = single_file_with_position(text); 445 let (analysis, position) = single_file_with_position(text);
454 let source_change = analysis.rename(position, new_name).unwrap(); 446 let source_change = analysis.rename(position, new_name).unwrap();
455 let mut text_edit_builder = ra_text_edit::TextEditBuilder::default(); 447 let mut text_edit_builder = TextEditBuilder::default();
456 let mut file_id: Option<FileId> = None; 448 let mut file_id: Option<FileId> = None;
457 if let Some(change) = source_change { 449 if let Some(change) = source_change {
458 for edit in change.info.source_file_edits { 450 for edit in change.info.source_file_edits {
diff --git a/crates/ra_ide_api/src/references/search_scope.rs b/crates/ra_ide_api/src/references/search_scope.rs
index b6eb248b7..f2789e0b2 100644
--- a/crates/ra_ide_api/src/references/search_scope.rs
+++ b/crates/ra_ide_api/src/references/search_scope.rs
@@ -111,8 +111,7 @@ impl NameDefinition {
111 if vis.as_str() != "" { 111 if vis.as_str() != "" {
112 let source_root_id = db.file_source_root(file_id); 112 let source_root_id = db.file_source_root(file_id);
113 let source_root = db.source_root(source_root_id); 113 let source_root = db.source_root(source_root_id);
114 let mut res = 114 let mut res = source_root.walk().map(|id| (id, None)).collect::<FxHashMap<_, _>>();
115 source_root.walk().map(|id| (id.into(), None)).collect::<FxHashMap<_, _>>();
116 115
117 // FIXME: add "pub(in path)" 116 // FIXME: add "pub(in path)"
118 117
@@ -120,7 +119,7 @@ impl NameDefinition {
120 return SearchScope::new(res); 119 return SearchScope::new(res);
121 } 120 }
122 if vis.as_str() == "pub" { 121 if vis.as_str() == "pub" {
123 let krate = self.container.krate(db).unwrap(); 122 let krate = self.container.krate();
124 let crate_graph = db.crate_graph(); 123 let crate_graph = db.crate_graph();
125 for crate_id in crate_graph.iter() { 124 for crate_id in crate_graph.iter() {
126 let mut crate_deps = crate_graph.dependencies(crate_id); 125 let mut crate_deps = crate_graph.dependencies(crate_id);
@@ -128,7 +127,7 @@ impl NameDefinition {
128 let root_file = crate_graph.crate_root(crate_id); 127 let root_file = crate_graph.crate_root(crate_id);
129 let source_root_id = db.file_source_root(root_file); 128 let source_root_id = db.file_source_root(root_file);
130 let source_root = db.source_root(source_root_id); 129 let source_root = db.source_root(source_root_id);
131 res.extend(source_root.walk().map(|id| (id.into(), None))); 130 res.extend(source_root.walk().map(|id| (id, None)));
132 } 131 }
133 } 132 }
134 return SearchScope::new(res); 133 return SearchScope::new(res);
diff --git a/crates/ra_ide_api/src/runnables.rs b/crates/ra_ide_api/src/runnables.rs
index 910883da7..1b5c8deea 100644
--- a/crates/ra_ide_api/src/runnables.rs
+++ b/crates/ra_ide_api/src/runnables.rs
@@ -4,7 +4,7 @@ use itertools::Itertools;
4use ra_db::SourceDatabase; 4use ra_db::SourceDatabase;
5use ra_syntax::{ 5use ra_syntax::{
6 ast::{self, AstNode, AttrsOwner, ModuleItemOwner, NameOwner}, 6 ast::{self, AstNode, AttrsOwner, ModuleItemOwner, NameOwner},
7 SyntaxNode, TextRange, 7 match_ast, SyntaxNode, TextRange,
8}; 8};
9 9
10use crate::{db::RootDatabase, FileId}; 10use crate::{db::RootDatabase, FileId};
@@ -29,12 +29,12 @@ pub(crate) fn runnables(db: &RootDatabase, file_id: FileId) -> Vec<Runnable> {
29} 29}
30 30
31fn runnable(db: &RootDatabase, file_id: FileId, item: SyntaxNode) -> Option<Runnable> { 31fn runnable(db: &RootDatabase, file_id: FileId, item: SyntaxNode) -> Option<Runnable> {
32 if let Some(fn_def) = ast::FnDef::cast(item.clone()) { 32 match_ast! {
33 runnable_fn(fn_def) 33 match item {
34 } else if let Some(m) = ast::Module::cast(item) { 34 ast::FnDef(it) => { runnable_fn(it) },
35 runnable_mod(db, file_id, m) 35 ast::Module(it) => { runnable_mod(db, file_id, it) },
36 } else { 36 _ => { None },
37 None 37 }
38 } 38 }
39} 39}
40 40
diff --git a/crates/ra_ide_api/src/source_change.rs b/crates/ra_ide_api/src/source_change.rs
new file mode 100644
index 000000000..4e63bbf6f
--- /dev/null
+++ b/crates/ra_ide_api/src/source_change.rs
@@ -0,0 +1,119 @@
1//! This modules defines type to represent changes to the source code, that flow
2//! from the server to the client.
3//!
4//! It can be viewed as a dual for `AnalysisChange`.
5
6use ra_text_edit::TextEdit;
7use relative_path::RelativePathBuf;
8
9use crate::{FileId, FilePosition, SourceRootId, TextUnit};
10
11#[derive(Debug)]
12pub struct SourceChange {
13 pub label: String,
14 pub source_file_edits: Vec<SourceFileEdit>,
15 pub file_system_edits: Vec<FileSystemEdit>,
16 pub cursor_position: Option<FilePosition>,
17}
18
19impl SourceChange {
20 /// Creates a new SourceChange with the given label
21 /// from the edits.
22 pub(crate) fn from_edits<L: Into<String>>(
23 label: L,
24 source_file_edits: Vec<SourceFileEdit>,
25 file_system_edits: Vec<FileSystemEdit>,
26 ) -> Self {
27 SourceChange {
28 label: label.into(),
29 source_file_edits,
30 file_system_edits,
31 cursor_position: None,
32 }
33 }
34
35 /// Creates a new SourceChange with the given label,
36 /// containing only the given `SourceFileEdits`.
37 pub(crate) fn source_file_edits<L: Into<String>>(label: L, edits: Vec<SourceFileEdit>) -> Self {
38 SourceChange {
39 label: label.into(),
40 source_file_edits: edits,
41 file_system_edits: vec![],
42 cursor_position: None,
43 }
44 }
45
46 /// Creates a new SourceChange with the given label,
47 /// containing only the given `FileSystemEdits`.
48 pub(crate) fn file_system_edits<L: Into<String>>(label: L, edits: Vec<FileSystemEdit>) -> Self {
49 SourceChange {
50 label: label.into(),
51 source_file_edits: vec![],
52 file_system_edits: edits,
53 cursor_position: None,
54 }
55 }
56
57 /// Creates a new SourceChange with the given label,
58 /// containing only a single `SourceFileEdit`.
59 pub(crate) fn source_file_edit<L: Into<String>>(label: L, edit: SourceFileEdit) -> Self {
60 SourceChange::source_file_edits(label, vec![edit])
61 }
62
63 /// Creates a new SourceChange with the given label
64 /// from the given `FileId` and `TextEdit`
65 pub(crate) fn source_file_edit_from<L: Into<String>>(
66 label: L,
67 file_id: FileId,
68 edit: TextEdit,
69 ) -> Self {
70 SourceChange::source_file_edit(label, SourceFileEdit { file_id, edit })
71 }
72
73 /// Creates a new SourceChange with the given label
74 /// from the given `FileId` and `TextEdit`
75 pub(crate) fn file_system_edit<L: Into<String>>(label: L, edit: FileSystemEdit) -> Self {
76 SourceChange::file_system_edits(label, vec![edit])
77 }
78
79 /// Sets the cursor position to the given `FilePosition`
80 pub(crate) fn with_cursor(mut self, cursor_position: FilePosition) -> Self {
81 self.cursor_position = Some(cursor_position);
82 self
83 }
84
85 /// Sets the cursor position to the given `FilePosition`
86 pub(crate) fn with_cursor_opt(mut self, cursor_position: Option<FilePosition>) -> Self {
87 self.cursor_position = cursor_position;
88 self
89 }
90}
91
92#[derive(Debug)]
93pub struct SourceFileEdit {
94 pub file_id: FileId,
95 pub edit: TextEdit,
96}
97
98#[derive(Debug)]
99pub enum FileSystemEdit {
100 CreateFile { source_root: SourceRootId, path: RelativePathBuf },
101 MoveFile { src: FileId, dst_source_root: SourceRootId, dst_path: RelativePathBuf },
102}
103
104pub(crate) struct SingleFileChange {
105 pub label: String,
106 pub edit: TextEdit,
107 pub cursor_position: Option<TextUnit>,
108}
109
110impl SingleFileChange {
111 pub(crate) fn into_source_change(self, file_id: FileId) -> SourceChange {
112 SourceChange {
113 label: self.label,
114 source_file_edits: vec![SourceFileEdit { file_id, edit: self.edit }],
115 file_system_edits: Vec::new(),
116 cursor_position: self.cursor_position.map(|offset| FilePosition { file_id, offset }),
117 }
118 }
119}
diff --git a/crates/ra_ide_api/src/typing.rs b/crates/ra_ide_api/src/typing.rs
index 2f5782012..d51132f73 100644
--- a/crates/ra_ide_api/src/typing.rs
+++ b/crates/ra_ide_api/src/typing.rs
@@ -1,4 +1,17 @@
1//! FIXME: write short doc here 1//! This module handles auto-magic editing actions applied together with users
2//! edits. For example, if the user typed
3//!
4//! ```text
5//! foo
6//! .bar()
7//! .baz()
8//! | // <- cursor is here
9//! ```
10//!
11//! and types `.` next, we want to indent the dot.
12//!
13//! Language server executes such typing assists synchronously. That is, they
14//! block user's typing and should be pretty fast for this reason!
2 15
3use ra_db::{FilePosition, SourceDatabase}; 16use ra_db::{FilePosition, SourceDatabase};
4use ra_fmt::leading_indent; 17use ra_fmt::leading_indent;
@@ -9,9 +22,9 @@ use ra_syntax::{
9 SyntaxKind::*, 22 SyntaxKind::*,
10 SyntaxToken, TextRange, TextUnit, TokenAtOffset, 23 SyntaxToken, TextRange, TextUnit, TokenAtOffset,
11}; 24};
12use ra_text_edit::{TextEdit, TextEditBuilder}; 25use ra_text_edit::TextEdit;
13 26
14use crate::{db::RootDatabase, SourceChange, SourceFileEdit}; 27use crate::{db::RootDatabase, source_change::SingleFileChange, SourceChange, SourceFileEdit};
15 28
16pub(crate) fn on_enter(db: &RootDatabase, position: FilePosition) -> Option<SourceChange> { 29pub(crate) fn on_enter(db: &RootDatabase, position: FilePosition) -> Option<SourceChange> {
17 let parse = db.parse(position.file_id); 30 let parse = db.parse(position.file_id);
@@ -36,13 +49,12 @@ pub(crate) fn on_enter(db: &RootDatabase, position: FilePosition) -> Option<Sour
36 let indent = node_indent(&file, comment.syntax())?; 49 let indent = node_indent(&file, comment.syntax())?;
37 let inserted = format!("\n{}{} ", indent, prefix); 50 let inserted = format!("\n{}{} ", indent, prefix);
38 let cursor_position = position.offset + TextUnit::of_str(&inserted); 51 let cursor_position = position.offset + TextUnit::of_str(&inserted);
39 let mut edit = TextEditBuilder::default(); 52 let edit = TextEdit::insert(position.offset, inserted);
40 edit.insert(position.offset, inserted);
41 53
42 Some( 54 Some(
43 SourceChange::source_file_edit( 55 SourceChange::source_file_edit(
44 "on enter", 56 "on enter",
45 SourceFileEdit { edit: edit.finish(), file_id: position.file_id }, 57 SourceFileEdit { edit, file_id: position.file_id },
46 ) 58 )
47 .with_cursor(FilePosition { offset: cursor_position, file_id: position.file_id }), 59 .with_cursor(FilePosition { offset: cursor_position, file_id: position.file_id }),
48 ) 60 )
@@ -68,39 +80,67 @@ fn node_indent(file: &SourceFile, token: &SyntaxToken) -> Option<SmolStr> {
68 Some(text[pos..].into()) 80 Some(text[pos..].into())
69} 81}
70 82
71pub fn on_eq_typed(file: &SourceFile, eq_offset: TextUnit) -> Option<TextEdit> { 83pub(crate) const TRIGGER_CHARS: &str = ".=>";
72 assert_eq!(file.syntax().text().char_at(eq_offset), Some('=')); 84
73 let let_stmt: ast::LetStmt = find_node_at_offset(file.syntax(), eq_offset)?; 85pub(crate) fn on_char_typed(
86 db: &RootDatabase,
87 position: FilePosition,
88 char_typed: char,
89) -> Option<SourceChange> {
90 assert!(TRIGGER_CHARS.contains(char_typed));
91 let file = &db.parse(position.file_id).tree();
92 assert_eq!(file.syntax().text().char_at(position.offset), Some(char_typed));
93 let single_file_change = on_char_typed_inner(file, position.offset, char_typed)?;
94 Some(single_file_change.into_source_change(position.file_id))
95}
96
97fn on_char_typed_inner(
98 file: &SourceFile,
99 offset: TextUnit,
100 char_typed: char,
101) -> Option<SingleFileChange> {
102 assert!(TRIGGER_CHARS.contains(char_typed));
103 match char_typed {
104 '.' => on_dot_typed(file, offset),
105 '=' => on_eq_typed(file, offset),
106 '>' => on_arrow_typed(file, offset),
107 _ => unreachable!(),
108 }
109}
110
111/// Returns an edit which should be applied after `=` was typed. Primarily,
112/// this works when adding `let =`.
113// FIXME: use a snippet completion instead of this hack here.
114fn on_eq_typed(file: &SourceFile, offset: TextUnit) -> Option<SingleFileChange> {
115 assert_eq!(file.syntax().text().char_at(offset), Some('='));
116 let let_stmt: ast::LetStmt = find_node_at_offset(file.syntax(), offset)?;
74 if let_stmt.has_semi() { 117 if let_stmt.has_semi() {
75 return None; 118 return None;
76 } 119 }
77 if let Some(expr) = let_stmt.initializer() { 120 if let Some(expr) = let_stmt.initializer() {
78 let expr_range = expr.syntax().text_range(); 121 let expr_range = expr.syntax().text_range();
79 if expr_range.contains(eq_offset) && eq_offset != expr_range.start() { 122 if expr_range.contains(offset) && offset != expr_range.start() {
80 return None; 123 return None;
81 } 124 }
82 if file.syntax().text().slice(eq_offset..expr_range.start()).contains_char('\n') { 125 if file.syntax().text().slice(offset..expr_range.start()).contains_char('\n') {
83 return None; 126 return None;
84 } 127 }
85 } else { 128 } else {
86 return None; 129 return None;
87 } 130 }
88 let offset = let_stmt.syntax().text_range().end(); 131 let offset = let_stmt.syntax().text_range().end();
89 let mut edit = TextEditBuilder::default(); 132 Some(SingleFileChange {
90 edit.insert(offset, ";".to_string()); 133 label: "add semicolon".to_string(),
91 Some(edit.finish()) 134 edit: TextEdit::insert(offset, ";".to_string()),
135 cursor_position: None,
136 })
92} 137}
93 138
94pub(crate) fn on_dot_typed(db: &RootDatabase, position: FilePosition) -> Option<SourceChange> { 139/// Returns an edit which should be applied when a dot ('.') is typed on a blank line, indenting the line appropriately.
95 let parse = db.parse(position.file_id); 140fn on_dot_typed(file: &SourceFile, offset: TextUnit) -> Option<SingleFileChange> {
96 assert_eq!(parse.tree().syntax().text().char_at(position.offset), Some('.')); 141 assert_eq!(file.syntax().text().char_at(offset), Some('.'));
97 142 let whitespace =
98 let whitespace = parse 143 file.syntax().token_at_offset(offset).left_biased().and_then(ast::Whitespace::cast)?;
99 .tree()
100 .syntax()
101 .token_at_offset(position.offset)
102 .left_biased()
103 .and_then(ast::Whitespace::cast)?;
104 144
105 let current_indent = { 145 let current_indent = {
106 let text = whitespace.text(); 146 let text = whitespace.text();
@@ -117,20 +157,36 @@ pub(crate) fn on_dot_typed(db: &RootDatabase, position: FilePosition) -> Option<
117 if current_indent_len == target_indent_len { 157 if current_indent_len == target_indent_len {
118 return None; 158 return None;
119 } 159 }
120 let mut edit = TextEditBuilder::default(); 160
121 edit.replace( 161 Some(SingleFileChange {
122 TextRange::from_to(position.offset - current_indent_len, position.offset), 162 label: "reindent dot".to_string(),
123 target_indent, 163 edit: TextEdit::replace(
124 ); 164 TextRange::from_to(offset - current_indent_len, offset),
125 165 target_indent,
126 let res = SourceChange::source_file_edit_from("reindent dot", position.file_id, edit.finish()) 166 ),
127 .with_cursor(FilePosition { 167 cursor_position: Some(
128 offset: position.offset + target_indent_len - current_indent_len 168 offset + target_indent_len - current_indent_len + TextUnit::of_char('.'),
129 + TextUnit::of_char('.'), 169 ),
130 file_id: position.file_id, 170 })
131 }); 171}
132 172
133 Some(res) 173/// Adds a space after an arrow when `fn foo() { ... }` is turned into `fn foo() -> { ... }`
174fn on_arrow_typed(file: &SourceFile, offset: TextUnit) -> Option<SingleFileChange> {
175 let file_text = file.syntax().text();
176 assert_eq!(file_text.char_at(offset), Some('>'));
177 let after_arrow = offset + TextUnit::of_char('>');
178 if file_text.char_at(after_arrow) != Some('{') {
179 return None;
180 }
181 if find_node_at_offset::<ast::RetType>(file.syntax(), offset).is_none() {
182 return None;
183 }
184
185 Some(SingleFileChange {
186 label: "add space after return type".to_string(),
187 edit: TextEdit::insert(after_arrow, " ".to_string()),
188 cursor_position: Some(after_arrow),
189 })
134} 190}
135 191
136#[cfg(test)] 192#[cfg(test)]
@@ -142,21 +198,87 @@ mod tests {
142 use super::*; 198 use super::*;
143 199
144 #[test] 200 #[test]
145 fn test_on_eq_typed() { 201 fn test_on_enter() {
146 fn type_eq(before: &str, after: &str) { 202 fn apply_on_enter(before: &str) -> Option<String> {
147 let (offset, before) = extract_offset(before); 203 let (offset, before) = extract_offset(before);
148 let mut edit = TextEditBuilder::default(); 204 let (analysis, file_id) = single_file(&before);
149 edit.insert(offset, "=".to_string()); 205 let result = analysis.on_enter(FilePosition { offset, file_id }).unwrap()?;
150 let before = edit.finish().apply(&before); 206
151 let parse = SourceFile::parse(&before); 207 assert_eq!(result.source_file_edits.len(), 1);
152 if let Some(result) = on_eq_typed(&parse.tree(), offset) { 208 let actual = result.source_file_edits[0].edit.apply(&before);
153 let actual = result.apply(&before); 209 let actual = add_cursor(&actual, result.cursor_position.unwrap().offset);
154 assert_eq_text!(after, &actual); 210 Some(actual)
155 } else { 211 }
156 assert_eq_text!(&before, after) 212
157 }; 213 fn do_check(before: &str, after: &str) {
214 let actual = apply_on_enter(before).unwrap();
215 assert_eq_text!(after, &actual);
216 }
217
218 fn do_check_noop(text: &str) {
219 assert!(apply_on_enter(text).is_none())
158 } 220 }
159 221
222 do_check(
223 r"
224/// Some docs<|>
225fn foo() {
226}
227",
228 r"
229/// Some docs
230/// <|>
231fn foo() {
232}
233",
234 );
235 do_check(
236 r"
237impl S {
238 /// Some<|> docs.
239 fn foo() {}
240}
241",
242 r"
243impl S {
244 /// Some
245 /// <|> docs.
246 fn foo() {}
247}
248",
249 );
250 do_check_noop(r"<|>//! docz");
251 }
252
253 fn do_type_char(char_typed: char, before: &str) -> Option<(String, SingleFileChange)> {
254 let (offset, before) = extract_offset(before);
255 let edit = TextEdit::insert(offset, char_typed.to_string());
256 let before = edit.apply(&before);
257 let parse = SourceFile::parse(&before);
258 on_char_typed_inner(&parse.tree(), offset, char_typed)
259 .map(|it| (it.edit.apply(&before), it))
260 }
261
262 fn type_char(char_typed: char, before: &str, after: &str) {
263 let (actual, file_change) = do_type_char(char_typed, before)
264 .unwrap_or_else(|| panic!("typing `{}` did nothing", char_typed));
265
266 if after.contains("<|>") {
267 let (offset, after) = extract_offset(after);
268 assert_eq_text!(&after, &actual);
269 assert_eq!(file_change.cursor_position, Some(offset))
270 } else {
271 assert_eq_text!(after, &actual);
272 }
273 }
274
275 fn type_char_noop(char_typed: char, before: &str) {
276 let file_change = do_type_char(char_typed, before);
277 assert!(file_change.is_none())
278 }
279
280 #[test]
281 fn test_on_eq_typed() {
160 // do_check(r" 282 // do_check(r"
161 // fn foo() { 283 // fn foo() {
162 // let foo =<|> 284 // let foo =<|>
@@ -166,7 +288,8 @@ mod tests {
166 // let foo =; 288 // let foo =;
167 // } 289 // }
168 // "); 290 // ");
169 type_eq( 291 type_char(
292 '=',
170 r" 293 r"
171fn foo() { 294fn foo() {
172 let foo <|> 1 + 1 295 let foo <|> 1 + 1
@@ -191,24 +314,10 @@ fn foo() {
191 // "); 314 // ");
192 } 315 }
193 316
194 fn type_dot(before: &str, after: &str) {
195 let (offset, before) = extract_offset(before);
196 let mut edit = TextEditBuilder::default();
197 edit.insert(offset, ".".to_string());
198 let before = edit.finish().apply(&before);
199 let (analysis, file_id) = single_file(&before);
200 if let Some(result) = analysis.on_dot_typed(FilePosition { offset, file_id }).unwrap() {
201 assert_eq!(result.source_file_edits.len(), 1);
202 let actual = result.source_file_edits[0].edit.apply(&before);
203 assert_eq_text!(after, &actual);
204 } else {
205 assert_eq_text!(&before, after)
206 };
207 }
208
209 #[test] 317 #[test]
210 fn indents_new_chain_call() { 318 fn indents_new_chain_call() {
211 type_dot( 319 type_char(
320 '.',
212 r" 321 r"
213 pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable<Option<Module>> { 322 pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable<Option<Module>> {
214 self.child_impl(db, name) 323 self.child_impl(db, name)
@@ -222,25 +331,21 @@ fn foo() {
222 } 331 }
223 ", 332 ",
224 ); 333 );
225 type_dot( 334 type_char_noop(
335 '.',
226 r" 336 r"
227 pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable<Option<Module>> { 337 pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable<Option<Module>> {
228 self.child_impl(db, name) 338 self.child_impl(db, name)
229 <|> 339 <|>
230 } 340 }
231 ", 341 ",
232 r"
233 pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable<Option<Module>> {
234 self.child_impl(db, name)
235 .
236 }
237 ",
238 ) 342 )
239 } 343 }
240 344
241 #[test] 345 #[test]
242 fn indents_new_chain_call_with_semi() { 346 fn indents_new_chain_call_with_semi() {
243 type_dot( 347 type_char(
348 '.',
244 r" 349 r"
245 pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable<Option<Module>> { 350 pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable<Option<Module>> {
246 self.child_impl(db, name) 351 self.child_impl(db, name)
@@ -254,25 +359,21 @@ fn foo() {
254 } 359 }
255 ", 360 ",
256 ); 361 );
257 type_dot( 362 type_char_noop(
363 '.',
258 r" 364 r"
259 pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable<Option<Module>> { 365 pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable<Option<Module>> {
260 self.child_impl(db, name) 366 self.child_impl(db, name)
261 <|>; 367 <|>;
262 } 368 }
263 ", 369 ",
264 r"
265 pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable<Option<Module>> {
266 self.child_impl(db, name)
267 .;
268 }
269 ",
270 ) 370 )
271 } 371 }
272 372
273 #[test] 373 #[test]
274 fn indents_continued_chain_call() { 374 fn indents_continued_chain_call() {
275 type_dot( 375 type_char(
376 '.',
276 r" 377 r"
277 pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable<Option<Module>> { 378 pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable<Option<Module>> {
278 self.child_impl(db, name) 379 self.child_impl(db, name)
@@ -288,7 +389,8 @@ fn foo() {
288 } 389 }
289 ", 390 ",
290 ); 391 );
291 type_dot( 392 type_char_noop(
393 '.',
292 r" 394 r"
293 pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable<Option<Module>> { 395 pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable<Option<Module>> {
294 self.child_impl(db, name) 396 self.child_impl(db, name)
@@ -296,19 +398,13 @@ fn foo() {
296 <|> 398 <|>
297 } 399 }
298 ", 400 ",
299 r"
300 pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable<Option<Module>> {
301 self.child_impl(db, name)
302 .first()
303 .
304 }
305 ",
306 ); 401 );
307 } 402 }
308 403
309 #[test] 404 #[test]
310 fn indents_middle_of_chain_call() { 405 fn indents_middle_of_chain_call() {
311 type_dot( 406 type_char(
407 '.',
312 r" 408 r"
313 fn source_impl() { 409 fn source_impl() {
314 let var = enum_defvariant_list().unwrap() 410 let var = enum_defvariant_list().unwrap()
@@ -326,7 +422,8 @@ fn foo() {
326 } 422 }
327 ", 423 ",
328 ); 424 );
329 type_dot( 425 type_char_noop(
426 '.',
330 r" 427 r"
331 fn source_impl() { 428 fn source_impl() {
332 let var = enum_defvariant_list().unwrap() 429 let var = enum_defvariant_list().unwrap()
@@ -335,95 +432,31 @@ fn foo() {
335 .unwrap(); 432 .unwrap();
336 } 433 }
337 ", 434 ",
338 r"
339 fn source_impl() {
340 let var = enum_defvariant_list().unwrap()
341 .
342 .nth(92)
343 .unwrap();
344 }
345 ",
346 ); 435 );
347 } 436 }
348 437
349 #[test] 438 #[test]
350 fn dont_indent_freestanding_dot() { 439 fn dont_indent_freestanding_dot() {
351 type_dot( 440 type_char_noop(
441 '.',
352 r" 442 r"
353 pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable<Option<Module>> { 443 pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable<Option<Module>> {
354 <|> 444 <|>
355 } 445 }
356 ", 446 ",
357 r"
358 pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable<Option<Module>> {
359 .
360 }
361 ",
362 ); 447 );
363 type_dot( 448 type_char_noop(
449 '.',
364 r" 450 r"
365 pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable<Option<Module>> { 451 pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable<Option<Module>> {
366 <|> 452 <|>
367 } 453 }
368 ", 454 ",
369 r"
370 pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable<Option<Module>> {
371 .
372 }
373 ",
374 ); 455 );
375 } 456 }
376 457
377 #[test] 458 #[test]
378 fn test_on_enter() { 459 fn adds_space_after_return_type() {
379 fn apply_on_enter(before: &str) -> Option<String> { 460 type_char('>', "fn foo() -<|>{ 92 }", "fn foo() -><|> { 92 }")
380 let (offset, before) = extract_offset(before);
381 let (analysis, file_id) = single_file(&before);
382 let result = analysis.on_enter(FilePosition { offset, file_id }).unwrap()?;
383
384 assert_eq!(result.source_file_edits.len(), 1);
385 let actual = result.source_file_edits[0].edit.apply(&before);
386 let actual = add_cursor(&actual, result.cursor_position.unwrap().offset);
387 Some(actual)
388 }
389
390 fn do_check(before: &str, after: &str) {
391 let actual = apply_on_enter(before).unwrap();
392 assert_eq_text!(after, &actual);
393 }
394
395 fn do_check_noop(text: &str) {
396 assert!(apply_on_enter(text).is_none())
397 }
398
399 do_check(
400 r"
401/// Some docs<|>
402fn foo() {
403}
404",
405 r"
406/// Some docs
407/// <|>
408fn foo() {
409}
410",
411 );
412 do_check(
413 r"
414impl S {
415 /// Some<|> docs.
416 fn foo() {}
417}
418",
419 r"
420impl S {
421 /// Some
422 /// <|> docs.
423 fn foo() {}
424}
425",
426 );
427 do_check_noop(r"<|>//! docz");
428 } 461 }
429} 462}