aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_ide
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ra_ide')
-rw-r--r--crates/ra_ide/src/call_hierarchy.rs29
-rw-r--r--crates/ra_ide/src/completion/completion_item.rs4
-rw-r--r--crates/ra_ide/src/diagnostics.rs103
-rw-r--r--crates/ra_ide/src/display.rs6
-rw-r--r--crates/ra_ide/src/hover.rs17
-rw-r--r--crates/ra_ide/src/join_lines.rs24
-rw-r--r--crates/ra_ide/src/lib.rs31
-rw-r--r--crates/ra_ide/src/mock_analysis.rs123
-rw-r--r--crates/ra_ide/src/references/rename.rs18
-rw-r--r--crates/ra_ide/src/snapshots/highlighting.html2
-rw-r--r--crates/ra_ide/src/syntax_highlighting.rs4
-rw-r--r--crates/ra_ide/src/syntax_highlighting/tags.rs6
-rw-r--r--crates/ra_ide/src/test_utils.rs25
-rw-r--r--crates/ra_ide/src/typing.rs35
-rw-r--r--crates/ra_ide/src/typing/on_enter.rs26
15 files changed, 274 insertions, 179 deletions
diff --git a/crates/ra_ide/src/call_hierarchy.rs b/crates/ra_ide/src/call_hierarchy.rs
index 85d1f0cb1..defd8176f 100644
--- a/crates/ra_ide/src/call_hierarchy.rs
+++ b/crates/ra_ide/src/call_hierarchy.rs
@@ -246,6 +246,35 @@ mod tests {
246 } 246 }
247 247
248 #[test] 248 #[test]
249 fn test_call_hierarchy_in_tests_mod() {
250 check_hierarchy(
251 r#"
252 //- /lib.rs cfg:test
253 fn callee() {}
254 fn caller1() {
255 call<|>ee();
256 }
257
258 #[cfg(test)]
259 mod tests {
260 use super::*;
261
262 #[test]
263 fn test_caller() {
264 callee();
265 }
266 }
267 "#,
268 "callee FN_DEF FileId(1) 0..14 3..9",
269 &[
270 "caller1 FN_DEF FileId(1) 15..45 18..25 : [34..40]",
271 "test_caller FN_DEF FileId(1) 93..147 108..119 : [132..138]",
272 ],
273 &[],
274 );
275 }
276
277 #[test]
249 fn test_call_hierarchy_in_different_files() { 278 fn test_call_hierarchy_in_different_files() {
250 check_hierarchy( 279 check_hierarchy(
251 r#" 280 r#"
diff --git a/crates/ra_ide/src/completion/completion_item.rs b/crates/ra_ide/src/completion/completion_item.rs
index 6021f7279..cfb7c1e38 100644
--- a/crates/ra_ide/src/completion/completion_item.rs
+++ b/crates/ra_ide/src/completion/completion_item.rs
@@ -63,8 +63,8 @@ impl fmt::Debug for CompletionItem {
63 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 63 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
64 let mut s = f.debug_struct("CompletionItem"); 64 let mut s = f.debug_struct("CompletionItem");
65 s.field("label", &self.label()).field("source_range", &self.source_range()); 65 s.field("label", &self.label()).field("source_range", &self.source_range());
66 if self.text_edit().as_indels().len() == 1 { 66 if self.text_edit().len() == 1 {
67 let atom = &self.text_edit().as_indels()[0]; 67 let atom = &self.text_edit().iter().next().unwrap();
68 s.field("delete", &atom.delete); 68 s.field("delete", &atom.delete);
69 s.field("insert", &atom.insert); 69 s.field("insert", &atom.insert);
70 } else { 70 } else {
diff --git a/crates/ra_ide/src/diagnostics.rs b/crates/ra_ide/src/diagnostics.rs
index 54c2bcc09..3d83c0f71 100644
--- a/crates/ra_ide/src/diagnostics.rs
+++ b/crates/ra_ide/src/diagnostics.rs
@@ -21,7 +21,7 @@ use ra_syntax::{
21}; 21};
22use ra_text_edit::{TextEdit, TextEditBuilder}; 22use ra_text_edit::{TextEdit, TextEditBuilder};
23 23
24use crate::{Diagnostic, FileId, FileSystemEdit, SourceChange, SourceFileEdit}; 24use crate::{Diagnostic, FileId, FileSystemEdit, Fix, SourceChange, SourceFileEdit};
25 25
26#[derive(Debug, Copy, Clone)] 26#[derive(Debug, Copy, Clone)]
27pub enum Severity { 27pub enum Severity {
@@ -63,8 +63,8 @@ pub(crate) fn diagnostics(db: &RootDatabase, file_id: FileId) -> Vec<Diagnostic>
63 .parent() 63 .parent()
64 .unwrap_or_else(|| RelativePath::new("")) 64 .unwrap_or_else(|| RelativePath::new(""))
65 .join(&d.candidate); 65 .join(&d.candidate);
66 let create_file = FileSystemEdit::CreateFile { source_root, path }; 66 let fix =
67 let fix = SourceChange::file_system_edit("Create module", create_file); 67 Fix::new("Create module", FileSystemEdit::CreateFile { source_root, path }.into());
68 res.borrow_mut().push(Diagnostic { 68 res.borrow_mut().push(Diagnostic {
69 range: sema.diagnostics_range(d).range, 69 range: sema.diagnostics_range(d).range,
70 message: d.message(), 70 message: d.message(),
@@ -88,14 +88,12 @@ pub(crate) fn diagnostics(db: &RootDatabase, file_id: FileId) -> Vec<Diagnostic>
88 field_list = field_list.append_field(&field); 88 field_list = field_list.append_field(&field);
89 } 89 }
90 90
91 let mut builder = TextEditBuilder::default(); 91 let edit = {
92 algo::diff(&d.ast(db).syntax(), &field_list.syntax()).into_text_edit(&mut builder); 92 let mut builder = TextEditBuilder::default();
93 93 algo::diff(&d.ast(db).syntax(), &field_list.syntax()).into_text_edit(&mut builder);
94 Some(SourceChange::source_file_edit_from( 94 builder.finish()
95 "Fill struct fields", 95 };
96 file_id, 96 Some(Fix::new("Fill struct fields", SourceFileEdit { file_id, edit }.into()))
97 builder.finish(),
98 ))
99 }; 97 };
100 98
101 res.borrow_mut().push(Diagnostic { 99 res.borrow_mut().push(Diagnostic {
@@ -117,7 +115,8 @@ pub(crate) fn diagnostics(db: &RootDatabase, file_id: FileId) -> Vec<Diagnostic>
117 let node = d.ast(db); 115 let node = d.ast(db);
118 let replacement = format!("Ok({})", node.syntax()); 116 let replacement = format!("Ok({})", node.syntax());
119 let edit = TextEdit::replace(node.syntax().text_range(), replacement); 117 let edit = TextEdit::replace(node.syntax().text_range(), replacement);
120 let fix = SourceChange::source_file_edit_from("Wrap with ok", file_id, edit); 118 let source_change = SourceChange::source_file_edit_from(file_id, edit);
119 let fix = Fix::new("Wrap with ok", source_change);
121 res.borrow_mut().push(Diagnostic { 120 res.borrow_mut().push(Diagnostic {
122 range: sema.diagnostics_range(d).range, 121 range: sema.diagnostics_range(d).range,
123 message: d.message(), 122 message: d.message(),
@@ -154,9 +153,9 @@ fn check_unnecessary_braces_in_use_statement(
154 range, 153 range,
155 message: "Unnecessary braces in use statement".to_string(), 154 message: "Unnecessary braces in use statement".to_string(),
156 severity: Severity::WeakWarning, 155 severity: Severity::WeakWarning,
157 fix: Some(SourceChange::source_file_edit( 156 fix: Some(Fix::new(
158 "Remove unnecessary braces", 157 "Remove unnecessary braces",
159 SourceFileEdit { file_id, edit }, 158 SourceFileEdit { file_id, edit }.into(),
160 )), 159 )),
161 }); 160 });
162 } 161 }
@@ -198,9 +197,9 @@ fn check_struct_shorthand_initialization(
198 range: record_field.syntax().text_range(), 197 range: record_field.syntax().text_range(),
199 message: "Shorthand struct initialization".to_string(), 198 message: "Shorthand struct initialization".to_string(),
200 severity: Severity::WeakWarning, 199 severity: Severity::WeakWarning,
201 fix: Some(SourceChange::source_file_edit( 200 fix: Some(Fix::new(
202 "Use struct shorthand initialization", 201 "Use struct shorthand initialization",
203 SourceFileEdit { file_id, edit }, 202 SourceFileEdit { file_id, edit }.into(),
204 )), 203 )),
205 }); 204 });
206 } 205 }
@@ -240,7 +239,7 @@ mod tests {
240 let diagnostic = 239 let diagnostic =
241 diagnostics.pop().unwrap_or_else(|| panic!("no diagnostics for:\n{}\n", before)); 240 diagnostics.pop().unwrap_or_else(|| panic!("no diagnostics for:\n{}\n", before));
242 let mut fix = diagnostic.fix.unwrap(); 241 let mut fix = diagnostic.fix.unwrap();
243 let edit = fix.source_file_edits.pop().unwrap().edit; 242 let edit = fix.source_change.source_file_edits.pop().unwrap().edit;
244 let actual = { 243 let actual = {
245 let mut actual = before.to_string(); 244 let mut actual = before.to_string();
246 edit.apply(&mut actual); 245 edit.apply(&mut actual);
@@ -258,7 +257,7 @@ mod tests {
258 let (analysis, file_position) = analysis_and_position(fixture); 257 let (analysis, file_position) = analysis_and_position(fixture);
259 let diagnostic = analysis.diagnostics(file_position.file_id).unwrap().pop().unwrap(); 258 let diagnostic = analysis.diagnostics(file_position.file_id).unwrap().pop().unwrap();
260 let mut fix = diagnostic.fix.unwrap(); 259 let mut fix = diagnostic.fix.unwrap();
261 let edit = fix.source_file_edits.pop().unwrap().edit; 260 let edit = fix.source_change.source_file_edits.pop().unwrap().edit;
262 let target_file_contents = analysis.file_text(file_position.file_id).unwrap(); 261 let target_file_contents = analysis.file_text(file_position.file_id).unwrap();
263 let actual = { 262 let actual = {
264 let mut actual = target_file_contents.to_string(); 263 let mut actual = target_file_contents.to_string();
@@ -295,7 +294,7 @@ mod tests {
295 let (analysis, file_id) = single_file(before); 294 let (analysis, file_id) = single_file(before);
296 let diagnostic = analysis.diagnostics(file_id).unwrap().pop().unwrap(); 295 let diagnostic = analysis.diagnostics(file_id).unwrap().pop().unwrap();
297 let mut fix = diagnostic.fix.unwrap(); 296 let mut fix = diagnostic.fix.unwrap();
298 let edit = fix.source_file_edits.pop().unwrap().edit; 297 let edit = fix.source_change.source_file_edits.pop().unwrap().edit;
299 let actual = { 298 let actual = {
300 let mut actual = before.to_string(); 299 let mut actual = before.to_string();
301 edit.apply(&mut actual); 300 edit.apply(&mut actual);
@@ -616,23 +615,24 @@ mod tests {
616 Diagnostic { 615 Diagnostic {
617 message: "unresolved module", 616 message: "unresolved module",
618 range: 0..8, 617 range: 0..8,
618 severity: Error,
619 fix: Some( 619 fix: Some(
620 SourceChange { 620 Fix {
621 label: "Create module", 621 label: "Create module",
622 source_file_edits: [], 622 source_change: SourceChange {
623 file_system_edits: [ 623 source_file_edits: [],
624 CreateFile { 624 file_system_edits: [
625 source_root: SourceRootId( 625 CreateFile {
626 0, 626 source_root: SourceRootId(
627 ), 627 0,
628 path: "foo.rs", 628 ),
629 }, 629 path: "foo.rs",
630 ], 630 },
631 cursor_position: None, 631 ],
632 is_snippet: false, 632 is_snippet: false,
633 },
633 }, 634 },
634 ), 635 ),
635 severity: Error,
636 }, 636 },
637 ] 637 ]
638 "###); 638 "###);
@@ -666,30 +666,31 @@ mod tests {
666 Diagnostic { 666 Diagnostic {
667 message: "Missing structure fields:\n- b", 667 message: "Missing structure fields:\n- b",
668 range: 224..233, 668 range: 224..233,
669 severity: Error,
669 fix: Some( 670 fix: Some(
670 SourceChange { 671 Fix {
671 label: "Fill struct fields", 672 label: "Fill struct fields",
672 source_file_edits: [ 673 source_change: SourceChange {
673 SourceFileEdit { 674 source_file_edits: [
674 file_id: FileId( 675 SourceFileEdit {
675 1, 676 file_id: FileId(
676 ), 677 1,
677 edit: TextEdit { 678 ),
678 indels: [ 679 edit: TextEdit {
679 Indel { 680 indels: [
680 insert: "{a:42, b: ()}", 681 Indel {
681 delete: 3..9, 682 insert: "{a:42, b: ()}",
682 }, 683 delete: 3..9,
683 ], 684 },
685 ],
686 },
684 }, 687 },
685 }, 688 ],
686 ], 689 file_system_edits: [],
687 file_system_edits: [], 690 is_snippet: false,
688 cursor_position: None, 691 },
689 is_snippet: false,
690 }, 692 },
691 ), 693 ),
692 severity: Error,
693 }, 694 },
694 ] 695 ]
695 "###); 696 "###);
diff --git a/crates/ra_ide/src/display.rs b/crates/ra_ide/src/display.rs
index 722092de9..8bb312156 100644
--- a/crates/ra_ide/src/display.rs
+++ b/crates/ra_ide/src/display.rs
@@ -79,14 +79,14 @@ pub(crate) fn rust_code_markup_with_doc(
79 doc: Option<&str>, 79 doc: Option<&str>,
80 mod_path: Option<&str>, 80 mod_path: Option<&str>,
81) -> String { 81) -> String {
82 let mut buf = "```rust\n".to_owned(); 82 let mut buf = String::new();
83 83
84 if let Some(mod_path) = mod_path { 84 if let Some(mod_path) = mod_path {
85 if !mod_path.is_empty() { 85 if !mod_path.is_empty() {
86 format_to!(buf, "{}\n", mod_path); 86 format_to!(buf, "{}\n___\n\n", mod_path);
87 } 87 }
88 } 88 }
89 format_to!(buf, "{}\n```", code); 89 format_to!(buf, "```rust\n{}\n```", code);
90 90
91 if let Some(doc) = doc { 91 if let Some(doc) = doc {
92 format_to!(buf, "\n\n{}", doc); 92 format_to!(buf, "\n\n{}", doc);
diff --git a/crates/ra_ide/src/hover.rs b/crates/ra_ide/src/hover.rs
index befa977c7..1f4f6b848 100644
--- a/crates/ra_ide/src/hover.rs
+++ b/crates/ra_ide/src/hover.rs
@@ -405,7 +405,7 @@ mod tests {
405 }; 405 };
406 } 406 }
407 "#, 407 "#,
408 &["Foo\nfield_a: u32"], 408 &["Foo\n___\n\n```rust\nfield_a: u32"],
409 ); 409 );
410 410
411 // Hovering over the field in the definition 411 // Hovering over the field in the definition
@@ -422,7 +422,7 @@ mod tests {
422 }; 422 };
423 } 423 }
424 "#, 424 "#,
425 &["Foo\nfield_a: u32"], 425 &["Foo\n___\n\n```rust\nfield_a: u32"],
426 ); 426 );
427 } 427 }
428 428
@@ -475,7 +475,7 @@ fn main() {
475 ", 475 ",
476 ); 476 );
477 let hover = analysis.hover(position).unwrap().unwrap(); 477 let hover = analysis.hover(position).unwrap().unwrap();
478 assert_eq!(trim_markup_opt(hover.info.first()), Some("Option\nSome")); 478 assert_eq!(trim_markup_opt(hover.info.first()), Some("Option\n___\n\n```rust\nSome"));
479 479
480 let (analysis, position) = single_file_with_position( 480 let (analysis, position) = single_file_with_position(
481 " 481 "
@@ -503,6 +503,9 @@ fn main() {
503 "#, 503 "#,
504 &[" 504 &["
505Option 505Option
506___
507
508```rust
506None 509None
507``` 510```
508 511
@@ -524,6 +527,9 @@ The None variant
524 "#, 527 "#,
525 &[" 528 &["
526Option 529Option
530___
531
532```rust
527Some 533Some
528``` 534```
529 535
@@ -606,7 +612,10 @@ fn func(foo: i32) { if true { <|>foo; }; }
606 ", 612 ",
607 ); 613 );
608 let hover = analysis.hover(position).unwrap().unwrap(); 614 let hover = analysis.hover(position).unwrap().unwrap();
609 assert_eq!(trim_markup_opt(hover.info.first()), Some("wrapper::Thing\nfn new() -> Thing")); 615 assert_eq!(
616 trim_markup_opt(hover.info.first()),
617 Some("wrapper::Thing\n___\n\n```rust\nfn new() -> Thing")
618 );
610 } 619 }
611 620
612 #[test] 621 #[test]
diff --git a/crates/ra_ide/src/join_lines.rs b/crates/ra_ide/src/join_lines.rs
index d3af780c4..af1ade8a1 100644
--- a/crates/ra_ide/src/join_lines.rs
+++ b/crates/ra_ide/src/join_lines.rs
@@ -166,16 +166,28 @@ fn is_trailing_comma(left: SyntaxKind, right: SyntaxKind) -> bool {
166 166
167#[cfg(test)] 167#[cfg(test)]
168mod tests { 168mod tests {
169 use crate::test_utils::{assert_eq_text, check_action, extract_range}; 169 use ra_syntax::SourceFile;
170 use test_utils::{add_cursor, assert_eq_text, extract_offset, extract_range};
170 171
171 use super::*; 172 use super::*;
172 173
173 fn check_join_lines(before: &str, after: &str) { 174 fn check_join_lines(before: &str, after: &str) {
174 check_action(before, after, |file, offset| { 175 let (before_cursor_pos, before) = extract_offset(before);
175 let range = TextRange::empty(offset); 176 let file = SourceFile::parse(&before).ok().unwrap();
176 let res = join_lines(file, range); 177
177 Some(res) 178 let range = TextRange::empty(before_cursor_pos);
178 }) 179 let result = join_lines(&file, range);
180
181 let actual = {
182 let mut actual = before.to_string();
183 result.apply(&mut actual);
184 actual
185 };
186 let actual_cursor_pos = result
187 .apply_to_offset(before_cursor_pos)
188 .expect("cursor position is affected by the edit");
189 let actual = add_cursor(&actual, actual_cursor_pos);
190 assert_eq_text!(after, &actual);
179 } 191 }
180 192
181 #[test] 193 #[test]
diff --git a/crates/ra_ide/src/lib.rs b/crates/ra_ide/src/lib.rs
index 83cb498f7..5ac002d82 100644
--- a/crates/ra_ide/src/lib.rs
+++ b/crates/ra_ide/src/lib.rs
@@ -42,9 +42,6 @@ mod inlay_hints;
42mod expand_macro; 42mod expand_macro;
43mod ssr; 43mod ssr;
44 44
45#[cfg(test)]
46mod test_utils;
47
48use std::sync::Arc; 45use std::sync::Arc;
49 46
50use ra_cfg::CfgOptions; 47use ra_cfg::CfgOptions;
@@ -87,12 +84,12 @@ pub use ra_db::{
87pub use ra_ide_db::{ 84pub use ra_ide_db::{
88 change::{AnalysisChange, LibraryData}, 85 change::{AnalysisChange, LibraryData},
89 line_index::{LineCol, LineIndex}, 86 line_index::{LineCol, LineIndex},
90 line_index_utils::translate_offset_with_edit,
91 search::SearchScope, 87 search::SearchScope,
92 source_change::{FileSystemEdit, SourceChange, SourceFileEdit}, 88 source_change::{FileSystemEdit, SourceChange, SourceFileEdit},
93 symbol_index::Query, 89 symbol_index::Query,
94 RootDatabase, 90 RootDatabase,
95}; 91};
92pub use ra_text_edit::{Indel, TextEdit};
96 93
97pub type Cancelable<T> = Result<T, Canceled>; 94pub type Cancelable<T> = Result<T, Canceled>;
98 95
@@ -100,8 +97,22 @@ pub type Cancelable<T> = Result<T, Canceled>;
100pub struct Diagnostic { 97pub struct Diagnostic {
101 pub message: String, 98 pub message: String,
102 pub range: TextRange, 99 pub range: TextRange,
103 pub fix: Option<SourceChange>,
104 pub severity: Severity, 100 pub severity: Severity,
101 pub fix: Option<Fix>,
102}
103
104#[derive(Debug)]
105pub struct Fix {
106 pub label: String,
107 pub source_change: SourceChange,
108}
109
110impl Fix {
111 pub fn new(label: impl Into<String>, source_change: SourceChange) -> Self {
112 let label = label.into();
113 assert!(label.starts_with(char::is_uppercase) && !label.ends_with('.'));
114 Self { label, source_change }
115 }
105} 116}
106 117
107/// Info associated with a text range. 118/// Info associated with a text range.
@@ -289,14 +300,10 @@ impl Analysis {
289 300
290 /// Returns an edit to remove all newlines in the range, cleaning up minor 301 /// Returns an edit to remove all newlines in the range, cleaning up minor
291 /// stuff like trailing commas. 302 /// stuff like trailing commas.
292 pub fn join_lines(&self, frange: FileRange) -> Cancelable<SourceChange> { 303 pub fn join_lines(&self, frange: FileRange) -> Cancelable<TextEdit> {
293 self.with_db(|db| { 304 self.with_db(|db| {
294 let parse = db.parse(frange.file_id); 305 let parse = db.parse(frange.file_id);
295 let file_edit = SourceFileEdit { 306 join_lines::join_lines(&parse.tree(), frange.range)
296 file_id: frange.file_id,
297 edit: join_lines::join_lines(&parse.tree(), frange.range),
298 };
299 SourceChange::source_file_edit("Join lines", file_edit)
300 }) 307 })
301 } 308 }
302 309
@@ -500,7 +507,7 @@ impl Analysis {
500 ) -> Cancelable<Result<SourceChange, SsrError>> { 507 ) -> Cancelable<Result<SourceChange, SsrError>> {
501 self.with_db(|db| { 508 self.with_db(|db| {
502 let edits = ssr::parse_search_replace(query, parse_only, db)?; 509 let edits = ssr::parse_search_replace(query, parse_only, db)?;
503 Ok(SourceChange::source_file_edits("Structural Search Replace", edits)) 510 Ok(SourceChange::source_file_edits(edits))
504 }) 511 })
505 } 512 }
506 513
diff --git a/crates/ra_ide/src/mock_analysis.rs b/crates/ra_ide/src/mock_analysis.rs
index 2c13f206a..ad78d2d93 100644
--- a/crates/ra_ide/src/mock_analysis.rs
+++ b/crates/ra_ide/src/mock_analysis.rs
@@ -1,21 +1,81 @@
1//! FIXME: write short doc here 1//! FIXME: write short doc here
2 2
3use std::str::FromStr;
3use std::sync::Arc; 4use std::sync::Arc;
4 5
5use ra_cfg::CfgOptions; 6use ra_cfg::CfgOptions;
6use ra_db::{CrateName, Env, RelativePathBuf}; 7use ra_db::{CrateName, Env, RelativePathBuf};
7use test_utils::{extract_offset, extract_range, parse_fixture, CURSOR_MARKER}; 8use test_utils::{extract_offset, extract_range, parse_fixture, FixtureEntry, CURSOR_MARKER};
8 9
9use crate::{ 10use crate::{
10 Analysis, AnalysisChange, AnalysisHost, CrateGraph, Edition::Edition2018, FileId, FilePosition, 11 Analysis, AnalysisChange, AnalysisHost, CrateGraph, Edition, FileId, FilePosition, FileRange,
11 FileRange, SourceRootId, 12 SourceRootId,
12}; 13};
13 14
15#[derive(Debug)]
16enum MockFileData {
17 Plain { path: String, content: String },
18 Fixture(FixtureEntry),
19}
20
21impl MockFileData {
22 fn new(path: String, content: String) -> Self {
23 // `Self::Plain` causes a false warning: 'variant is never constructed: `Plain` '
24 // see https://github.com/rust-lang/rust/issues/69018
25 MockFileData::Plain { path, content }
26 }
27
28 fn path(&self) -> &str {
29 match self {
30 MockFileData::Plain { path, .. } => path.as_str(),
31 MockFileData::Fixture(f) => f.meta.path().as_str(),
32 }
33 }
34
35 fn content(&self) -> &str {
36 match self {
37 MockFileData::Plain { content, .. } => content,
38 MockFileData::Fixture(f) => f.text.as_str(),
39 }
40 }
41
42 fn cfg_options(&self) -> CfgOptions {
43 match self {
44 MockFileData::Fixture(f) => {
45 f.meta.cfg_options().map_or_else(Default::default, |o| o.clone())
46 }
47 _ => CfgOptions::default(),
48 }
49 }
50
51 fn edition(&self) -> Edition {
52 match self {
53 MockFileData::Fixture(f) => {
54 f.meta.edition().map_or(Edition::Edition2018, |v| Edition::from_str(v).unwrap())
55 }
56 _ => Edition::Edition2018,
57 }
58 }
59
60 fn env(&self) -> Env {
61 match self {
62 MockFileData::Fixture(f) => Env::from(f.meta.env()),
63 _ => Env::default(),
64 }
65 }
66}
67
68impl From<FixtureEntry> for MockFileData {
69 fn from(fixture: FixtureEntry) -> Self {
70 Self::Fixture(fixture)
71 }
72}
73
14/// Mock analysis is used in test to bootstrap an AnalysisHost/Analysis 74/// Mock analysis is used in test to bootstrap an AnalysisHost/Analysis
15/// from a set of in-memory files. 75/// from a set of in-memory files.
16#[derive(Debug, Default)] 76#[derive(Debug, Default)]
17pub struct MockAnalysis { 77pub struct MockAnalysis {
18 files: Vec<(String, String)>, 78 files: Vec<MockFileData>,
19} 79}
20 80
21impl MockAnalysis { 81impl MockAnalysis {
@@ -35,7 +95,7 @@ impl MockAnalysis {
35 pub fn with_files(fixture: &str) -> MockAnalysis { 95 pub fn with_files(fixture: &str) -> MockAnalysis {
36 let mut res = MockAnalysis::new(); 96 let mut res = MockAnalysis::new();
37 for entry in parse_fixture(fixture) { 97 for entry in parse_fixture(fixture) {
38 res.add_file(&entry.meta, &entry.text); 98 res.add_file_fixture(entry);
39 } 99 }
40 res 100 res
41 } 101 }
@@ -48,30 +108,44 @@ impl MockAnalysis {
48 for entry in parse_fixture(fixture) { 108 for entry in parse_fixture(fixture) {
49 if entry.text.contains(CURSOR_MARKER) { 109 if entry.text.contains(CURSOR_MARKER) {
50 assert!(position.is_none(), "only one marker (<|>) per fixture is allowed"); 110 assert!(position.is_none(), "only one marker (<|>) per fixture is allowed");
51 position = Some(res.add_file_with_position(&entry.meta, &entry.text)); 111 position = Some(res.add_file_fixture_with_position(entry));
52 } else { 112 } else {
53 res.add_file(&entry.meta, &entry.text); 113 res.add_file_fixture(entry);
54 } 114 }
55 } 115 }
56 let position = position.expect("expected a marker (<|>)"); 116 let position = position.expect("expected a marker (<|>)");
57 (res, position) 117 (res, position)
58 } 118 }
59 119
120 pub fn add_file_fixture(&mut self, fixture: FixtureEntry) -> FileId {
121 let file_id = self.next_id();
122 self.files.push(MockFileData::from(fixture));
123 file_id
124 }
125
126 pub fn add_file_fixture_with_position(&mut self, mut fixture: FixtureEntry) -> FilePosition {
127 let (offset, text) = extract_offset(&fixture.text);
128 fixture.text = text;
129 let file_id = self.next_id();
130 self.files.push(MockFileData::from(fixture));
131 FilePosition { file_id, offset }
132 }
133
60 pub fn add_file(&mut self, path: &str, text: &str) -> FileId { 134 pub fn add_file(&mut self, path: &str, text: &str) -> FileId {
61 let file_id = FileId((self.files.len() + 1) as u32); 135 let file_id = self.next_id();
62 self.files.push((path.to_string(), text.to_string())); 136 self.files.push(MockFileData::new(path.to_string(), text.to_string()));
63 file_id 137 file_id
64 } 138 }
65 pub fn add_file_with_position(&mut self, path: &str, text: &str) -> FilePosition { 139 pub fn add_file_with_position(&mut self, path: &str, text: &str) -> FilePosition {
66 let (offset, text) = extract_offset(text); 140 let (offset, text) = extract_offset(text);
67 let file_id = FileId((self.files.len() + 1) as u32); 141 let file_id = self.next_id();
68 self.files.push((path.to_string(), text)); 142 self.files.push(MockFileData::new(path.to_string(), text));
69 FilePosition { file_id, offset } 143 FilePosition { file_id, offset }
70 } 144 }
71 pub fn add_file_with_range(&mut self, path: &str, text: &str) -> FileRange { 145 pub fn add_file_with_range(&mut self, path: &str, text: &str) -> FileRange {
72 let (range, text) = extract_range(text); 146 let (range, text) = extract_range(text);
73 let file_id = FileId((self.files.len() + 1) as u32); 147 let file_id = self.next_id();
74 self.files.push((path.to_string(), text)); 148 self.files.push(MockFileData::new(path.to_string(), text));
75 FileRange { file_id, range } 149 FileRange { file_id, range }
76 } 150 }
77 pub fn id_of(&self, path: &str) -> FileId { 151 pub fn id_of(&self, path: &str) -> FileId {
@@ -79,7 +153,7 @@ impl MockAnalysis {
79 .files 153 .files
80 .iter() 154 .iter()
81 .enumerate() 155 .enumerate()
82 .find(|(_, (p, _text))| path == p) 156 .find(|(_, data)| path == data.path())
83 .expect("no file in this mock"); 157 .expect("no file in this mock");
84 FileId(idx as u32 + 1) 158 FileId(idx as u32 + 1)
85 } 159 }
@@ -90,18 +164,21 @@ impl MockAnalysis {
90 change.add_root(source_root, true); 164 change.add_root(source_root, true);
91 let mut crate_graph = CrateGraph::default(); 165 let mut crate_graph = CrateGraph::default();
92 let mut root_crate = None; 166 let mut root_crate = None;
93 for (i, (path, contents)) in self.files.into_iter().enumerate() { 167 for (i, data) in self.files.into_iter().enumerate() {
168 let path = data.path();
94 assert!(path.starts_with('/')); 169 assert!(path.starts_with('/'));
95 let path = RelativePathBuf::from_path(&path[1..]).unwrap(); 170 let path = RelativePathBuf::from_path(&path[1..]).unwrap();
171 let cfg_options = data.cfg_options();
96 let file_id = FileId(i as u32 + 1); 172 let file_id = FileId(i as u32 + 1);
97 let cfg_options = CfgOptions::default(); 173 let edition = data.edition();
174 let env = data.env();
98 if path == "/lib.rs" || path == "/main.rs" { 175 if path == "/lib.rs" || path == "/main.rs" {
99 root_crate = Some(crate_graph.add_crate_root( 176 root_crate = Some(crate_graph.add_crate_root(
100 file_id, 177 file_id,
101 Edition2018, 178 edition,
102 None, 179 None,
103 cfg_options, 180 cfg_options,
104 Env::default(), 181 env,
105 Default::default(), 182 Default::default(),
106 Default::default(), 183 Default::default(),
107 )); 184 ));
@@ -109,10 +186,10 @@ impl MockAnalysis {
109 let crate_name = path.parent().unwrap().file_name().unwrap(); 186 let crate_name = path.parent().unwrap().file_name().unwrap();
110 let other_crate = crate_graph.add_crate_root( 187 let other_crate = crate_graph.add_crate_root(
111 file_id, 188 file_id,
112 Edition2018, 189 edition,
113 Some(CrateName::new(crate_name).unwrap()), 190 Some(CrateName::new(crate_name).unwrap()),
114 cfg_options, 191 cfg_options,
115 Env::default(), 192 env,
116 Default::default(), 193 Default::default(),
117 Default::default(), 194 Default::default(),
118 ); 195 );
@@ -122,7 +199,7 @@ impl MockAnalysis {
122 .unwrap(); 199 .unwrap();
123 } 200 }
124 } 201 }
125 change.add_file(source_root, file_id, path, Arc::new(contents)); 202 change.add_file(source_root, file_id, path, Arc::new(data.content().to_owned()));
126 } 203 }
127 change.set_crate_graph(crate_graph); 204 change.set_crate_graph(crate_graph);
128 host.apply_change(change); 205 host.apply_change(change);
@@ -131,6 +208,10 @@ impl MockAnalysis {
131 pub fn analysis(self) -> Analysis { 208 pub fn analysis(self) -> Analysis {
132 self.analysis_host().analysis() 209 self.analysis_host().analysis()
133 } 210 }
211
212 fn next_id(&self) -> FileId {
213 FileId((self.files.len() + 1) as u32)
214 }
134} 215}
135 216
136/// Creates analysis from a multi-file fixture, returns positions marked with <|>. 217/// Creates analysis from a multi-file fixture, returns positions marked with <|>.
diff --git a/crates/ra_ide/src/references/rename.rs b/crates/ra_ide/src/references/rename.rs
index 62ec8d85d..28c6349b1 100644
--- a/crates/ra_ide/src/references/rename.rs
+++ b/crates/ra_ide/src/references/rename.rs
@@ -128,7 +128,7 @@ fn rename_mod(
128 source_file_edits.extend(ref_edits); 128 source_file_edits.extend(ref_edits);
129 } 129 }
130 130
131 Some(SourceChange::from_edits("Rename", source_file_edits, file_system_edits)) 131 Some(SourceChange::from_edits(source_file_edits, file_system_edits))
132} 132}
133 133
134fn rename_to_self(db: &RootDatabase, position: FilePosition) -> Option<RangeInfo<SourceChange>> { 134fn rename_to_self(db: &RootDatabase, position: FilePosition) -> Option<RangeInfo<SourceChange>> {
@@ -171,7 +171,7 @@ fn rename_to_self(db: &RootDatabase, position: FilePosition) -> Option<RangeInfo
171 ), 171 ),
172 }); 172 });
173 173
174 Some(RangeInfo::new(range, SourceChange::source_file_edits("Rename", edits))) 174 Some(RangeInfo::new(range, SourceChange::source_file_edits(edits)))
175} 175}
176 176
177fn text_edit_from_self_param( 177fn text_edit_from_self_param(
@@ -234,7 +234,7 @@ fn rename_self_to_param(
234 let range = ast::SelfParam::cast(self_token.parent()) 234 let range = ast::SelfParam::cast(self_token.parent())
235 .map_or(self_token.text_range(), |p| p.syntax().text_range()); 235 .map_or(self_token.text_range(), |p| p.syntax().text_range());
236 236
237 Some(RangeInfo::new(range, SourceChange::source_file_edits("Rename", edits))) 237 Some(RangeInfo::new(range, SourceChange::source_file_edits(edits)))
238} 238}
239 239
240fn rename_reference( 240fn rename_reference(
@@ -253,7 +253,7 @@ fn rename_reference(
253 return None; 253 return None;
254 } 254 }
255 255
256 Some(RangeInfo::new(range, SourceChange::source_file_edits("Rename", edit))) 256 Some(RangeInfo::new(range, SourceChange::source_file_edits(edit)))
257} 257}
258 258
259#[cfg(test)] 259#[cfg(test)]
@@ -642,7 +642,6 @@ mod tests {
642 RangeInfo { 642 RangeInfo {
643 range: 4..7, 643 range: 4..7,
644 info: SourceChange { 644 info: SourceChange {
645 label: "Rename",
646 source_file_edits: [ 645 source_file_edits: [
647 SourceFileEdit { 646 SourceFileEdit {
648 file_id: FileId( 647 file_id: FileId(
@@ -669,7 +668,6 @@ mod tests {
669 dst_path: "bar/foo2.rs", 668 dst_path: "bar/foo2.rs",
670 }, 669 },
671 ], 670 ],
672 cursor_position: None,
673 is_snippet: false, 671 is_snippet: false,
674 }, 672 },
675 }, 673 },
@@ -695,7 +693,6 @@ mod tests {
695 RangeInfo { 693 RangeInfo {
696 range: 4..7, 694 range: 4..7,
697 info: SourceChange { 695 info: SourceChange {
698 label: "Rename",
699 source_file_edits: [ 696 source_file_edits: [
700 SourceFileEdit { 697 SourceFileEdit {
701 file_id: FileId( 698 file_id: FileId(
@@ -722,7 +719,6 @@ mod tests {
722 dst_path: "foo2/mod.rs", 719 dst_path: "foo2/mod.rs",
723 }, 720 },
724 ], 721 ],
725 cursor_position: None,
726 is_snippet: false, 722 is_snippet: false,
727 }, 723 },
728 }, 724 },
@@ -779,7 +775,6 @@ mod tests {
779 RangeInfo { 775 RangeInfo {
780 range: 8..11, 776 range: 8..11,
781 info: SourceChange { 777 info: SourceChange {
782 label: "Rename",
783 source_file_edits: [ 778 source_file_edits: [
784 SourceFileEdit { 779 SourceFileEdit {
785 file_id: FileId( 780 file_id: FileId(
@@ -819,7 +814,6 @@ mod tests {
819 dst_path: "bar/foo2.rs", 814 dst_path: "bar/foo2.rs",
820 }, 815 },
821 ], 816 ],
822 cursor_position: None,
823 is_snippet: false, 817 is_snippet: false,
824 }, 818 },
825 }, 819 },
@@ -986,8 +980,8 @@ mod tests {
986 if let Some(change) = source_change { 980 if let Some(change) = source_change {
987 for edit in change.info.source_file_edits { 981 for edit in change.info.source_file_edits {
988 file_id = Some(edit.file_id); 982 file_id = Some(edit.file_id);
989 for indel in edit.edit.as_indels() { 983 for indel in edit.edit.into_iter() {
990 text_edit_builder.replace(indel.delete, indel.insert.clone()); 984 text_edit_builder.replace(indel.delete, indel.insert);
991 } 985 }
992 } 986 }
993 } 987 }
diff --git a/crates/ra_ide/src/snapshots/highlighting.html b/crates/ra_ide/src/snapshots/highlighting.html
index 635fe5cf9..2ceadf2fc 100644
--- a/crates/ra_ide/src/snapshots/highlighting.html
+++ b/crates/ra_ide/src/snapshots/highlighting.html
@@ -27,7 +27,7 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
27.keyword.unsafe { color: #BC8383; font-weight: bold; } 27.keyword.unsafe { color: #BC8383; font-weight: bold; }
28.control { font-style: italic; } 28.control { font-style: italic; }
29</style> 29</style>
30<pre><code><span class="attribute">#[derive(Clone, Debug)]</span> 30<pre><code><span class="attribute">#[</span><span class="function attribute">derive</span><span class="attribute">(Clone, Debug)]</span>
31<span class="keyword">struct</span> <span class="struct declaration">Foo</span> { 31<span class="keyword">struct</span> <span class="struct declaration">Foo</span> {
32 <span class="keyword">pub</span> <span class="field declaration">x</span>: <span class="builtin_type">i32</span>, 32 <span class="keyword">pub</span> <span class="field declaration">x</span>: <span class="builtin_type">i32</span>,
33 <span class="keyword">pub</span> <span class="field declaration">y</span>: <span class="builtin_type">i32</span>, 33 <span class="keyword">pub</span> <span class="field declaration">y</span>: <span class="builtin_type">i32</span>,
diff --git a/crates/ra_ide/src/syntax_highlighting.rs b/crates/ra_ide/src/syntax_highlighting.rs
index be57eeb0a..b55cf748d 100644
--- a/crates/ra_ide/src/syntax_highlighting.rs
+++ b/crates/ra_ide/src/syntax_highlighting.rs
@@ -361,7 +361,9 @@ fn highlight_element(
361 } 361 }
362 362
363 // Highlight references like the definitions they resolve to 363 // Highlight references like the definitions they resolve to
364 NAME_REF if element.ancestors().any(|it| it.kind() == ATTR) => return None, 364 NAME_REF if element.ancestors().any(|it| it.kind() == ATTR) => {
365 Highlight::from(HighlightTag::Function) | HighlightModifier::Attribute
366 }
365 NAME_REF => { 367 NAME_REF => {
366 let name_ref = element.into_node().and_then(ast::NameRef::cast).unwrap(); 368 let name_ref = element.into_node().and_then(ast::NameRef::cast).unwrap();
367 match classify_name_ref(sema, &name_ref) { 369 match classify_name_ref(sema, &name_ref) {
diff --git a/crates/ra_ide/src/syntax_highlighting/tags.rs b/crates/ra_ide/src/syntax_highlighting/tags.rs
index be1a0f12b..33e6619ec 100644
--- a/crates/ra_ide/src/syntax_highlighting/tags.rs
+++ b/crates/ra_ide/src/syntax_highlighting/tags.rs
@@ -45,8 +45,10 @@ pub enum HighlightTag {
45#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] 45#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
46#[repr(u8)] 46#[repr(u8)]
47pub enum HighlightModifier { 47pub enum HighlightModifier {
48 /// Used to differentiate individual elements within attributes.
49 Attribute = 0,
48 /// Used with keywords like `if` and `break`. 50 /// Used with keywords like `if` and `break`.
49 ControlFlow = 0, 51 ControlFlow,
50 /// `foo` in `fn foo(x: i32)` is a definition, `foo` in `foo(90 + 2)` is 52 /// `foo` in `fn foo(x: i32)` is a definition, `foo` in `foo(90 + 2)` is
51 /// not. 53 /// not.
52 Definition, 54 Definition,
@@ -95,6 +97,7 @@ impl fmt::Display for HighlightTag {
95 97
96impl HighlightModifier { 98impl HighlightModifier {
97 const ALL: &'static [HighlightModifier] = &[ 99 const ALL: &'static [HighlightModifier] = &[
100 HighlightModifier::Attribute,
98 HighlightModifier::ControlFlow, 101 HighlightModifier::ControlFlow,
99 HighlightModifier::Definition, 102 HighlightModifier::Definition,
100 HighlightModifier::Mutable, 103 HighlightModifier::Mutable,
@@ -103,6 +106,7 @@ impl HighlightModifier {
103 106
104 fn as_str(self) -> &'static str { 107 fn as_str(self) -> &'static str {
105 match self { 108 match self {
109 HighlightModifier::Attribute => "attribute",
106 HighlightModifier::ControlFlow => "control", 110 HighlightModifier::ControlFlow => "control",
107 HighlightModifier::Definition => "declaration", 111 HighlightModifier::Definition => "declaration",
108 HighlightModifier::Mutable => "mutable", 112 HighlightModifier::Mutable => "mutable",
diff --git a/crates/ra_ide/src/test_utils.rs b/crates/ra_ide/src/test_utils.rs
deleted file mode 100644
index 48c8fd1f4..000000000
--- a/crates/ra_ide/src/test_utils.rs
+++ /dev/null
@@ -1,25 +0,0 @@
1//! FIXME: write short doc here
2
3use ra_syntax::{SourceFile, TextSize};
4use ra_text_edit::TextEdit;
5
6pub use test_utils::*;
7
8pub fn check_action<F: Fn(&SourceFile, TextSize) -> Option<TextEdit>>(
9 before: &str,
10 after: &str,
11 f: F,
12) {
13 let (before_cursor_pos, before) = extract_offset(before);
14 let file = SourceFile::parse(&before).ok().unwrap();
15 let result = f(&file, before_cursor_pos).expect("code action is not applicable");
16 let actual = {
17 let mut actual = before.to_string();
18 result.apply(&mut actual);
19 actual
20 };
21 let actual_cursor_pos =
22 result.apply_to_offset(before_cursor_pos).expect("cursor position is affected by the edit");
23 let actual = add_cursor(&actual, actual_cursor_pos);
24 assert_eq_text!(after, &actual);
25}
diff --git a/crates/ra_ide/src/typing.rs b/crates/ra_ide/src/typing.rs
index cd48cad93..39bb3b357 100644
--- a/crates/ra_ide/src/typing.rs
+++ b/crates/ra_ide/src/typing.rs
@@ -17,7 +17,7 @@ mod on_enter;
17 17
18use ra_db::{FilePosition, SourceDatabase}; 18use ra_db::{FilePosition, SourceDatabase};
19use ra_fmt::leading_indent; 19use ra_fmt::leading_indent;
20use ra_ide_db::{source_change::SingleFileChange, RootDatabase}; 20use ra_ide_db::RootDatabase;
21use ra_syntax::{ 21use ra_syntax::{
22 algo::find_node_at_offset, 22 algo::find_node_at_offset,
23 ast::{self, AstToken}, 23 ast::{self, AstToken},
@@ -40,15 +40,11 @@ pub(crate) fn on_char_typed(
40 assert!(TRIGGER_CHARS.contains(char_typed)); 40 assert!(TRIGGER_CHARS.contains(char_typed));
41 let file = &db.parse(position.file_id).tree(); 41 let file = &db.parse(position.file_id).tree();
42 assert_eq!(file.syntax().text().char_at(position.offset), Some(char_typed)); 42 assert_eq!(file.syntax().text().char_at(position.offset), Some(char_typed));
43 let single_file_change = on_char_typed_inner(file, position.offset, char_typed)?; 43 let text_edit = on_char_typed_inner(file, position.offset, char_typed)?;
44 Some(single_file_change.into_source_change(position.file_id)) 44 Some(SourceChange::source_file_edit_from(position.file_id, text_edit))
45} 45}
46 46
47fn on_char_typed_inner( 47fn on_char_typed_inner(file: &SourceFile, offset: TextSize, char_typed: char) -> Option<TextEdit> {
48 file: &SourceFile,
49 offset: TextSize,
50 char_typed: char,
51) -> Option<SingleFileChange> {
52 assert!(TRIGGER_CHARS.contains(char_typed)); 48 assert!(TRIGGER_CHARS.contains(char_typed));
53 match char_typed { 49 match char_typed {
54 '.' => on_dot_typed(file, offset), 50 '.' => on_dot_typed(file, offset),
@@ -61,7 +57,7 @@ fn on_char_typed_inner(
61/// Returns an edit which should be applied after `=` was typed. Primarily, 57/// Returns an edit which should be applied after `=` was typed. Primarily,
62/// this works when adding `let =`. 58/// this works when adding `let =`.
63// FIXME: use a snippet completion instead of this hack here. 59// FIXME: use a snippet completion instead of this hack here.
64fn on_eq_typed(file: &SourceFile, offset: TextSize) -> Option<SingleFileChange> { 60fn on_eq_typed(file: &SourceFile, offset: TextSize) -> Option<TextEdit> {
65 assert_eq!(file.syntax().text().char_at(offset), Some('=')); 61 assert_eq!(file.syntax().text().char_at(offset), Some('='));
66 let let_stmt: ast::LetStmt = find_node_at_offset(file.syntax(), offset)?; 62 let let_stmt: ast::LetStmt = find_node_at_offset(file.syntax(), offset)?;
67 if let_stmt.semicolon_token().is_some() { 63 if let_stmt.semicolon_token().is_some() {
@@ -79,14 +75,11 @@ fn on_eq_typed(file: &SourceFile, offset: TextSize) -> Option<SingleFileChange>
79 return None; 75 return None;
80 } 76 }
81 let offset = let_stmt.syntax().text_range().end(); 77 let offset = let_stmt.syntax().text_range().end();
82 Some(SingleFileChange { 78 Some(TextEdit::insert(offset, ";".to_string()))
83 label: "add semicolon".to_string(),
84 edit: TextEdit::insert(offset, ";".to_string()),
85 })
86} 79}
87 80
88/// Returns an edit which should be applied when a dot ('.') is typed on a blank line, indenting the line appropriately. 81/// Returns an edit which should be applied when a dot ('.') is typed on a blank line, indenting the line appropriately.
89fn on_dot_typed(file: &SourceFile, offset: TextSize) -> Option<SingleFileChange> { 82fn on_dot_typed(file: &SourceFile, offset: TextSize) -> Option<TextEdit> {
90 assert_eq!(file.syntax().text().char_at(offset), Some('.')); 83 assert_eq!(file.syntax().text().char_at(offset), Some('.'));
91 let whitespace = 84 let whitespace =
92 file.syntax().token_at_offset(offset).left_biased().and_then(ast::Whitespace::cast)?; 85 file.syntax().token_at_offset(offset).left_biased().and_then(ast::Whitespace::cast)?;
@@ -107,14 +100,11 @@ fn on_dot_typed(file: &SourceFile, offset: TextSize) -> Option<SingleFileChange>
107 return None; 100 return None;
108 } 101 }
109 102
110 Some(SingleFileChange { 103 Some(TextEdit::replace(TextRange::new(offset - current_indent_len, offset), target_indent))
111 label: "reindent dot".to_string(),
112 edit: TextEdit::replace(TextRange::new(offset - current_indent_len, offset), target_indent),
113 })
114} 104}
115 105
116/// Adds a space after an arrow when `fn foo() { ... }` is turned into `fn foo() -> { ... }` 106/// Adds a space after an arrow when `fn foo() { ... }` is turned into `fn foo() -> { ... }`
117fn on_arrow_typed(file: &SourceFile, offset: TextSize) -> Option<SingleFileChange> { 107fn on_arrow_typed(file: &SourceFile, offset: TextSize) -> Option<TextEdit> {
118 let file_text = file.syntax().text(); 108 let file_text = file.syntax().text();
119 assert_eq!(file_text.char_at(offset), Some('>')); 109 assert_eq!(file_text.char_at(offset), Some('>'));
120 let after_arrow = offset + TextSize::of('>'); 110 let after_arrow = offset + TextSize::of('>');
@@ -125,10 +115,7 @@ fn on_arrow_typed(file: &SourceFile, offset: TextSize) -> Option<SingleFileChang
125 return None; 115 return None;
126 } 116 }
127 117
128 Some(SingleFileChange { 118 Some(TextEdit::insert(after_arrow, " ".to_string()))
129 label: "add space after return type".to_string(),
130 edit: TextEdit::insert(after_arrow, " ".to_string()),
131 })
132} 119}
133 120
134#[cfg(test)] 121#[cfg(test)]
@@ -144,7 +131,7 @@ mod tests {
144 edit.apply(&mut before); 131 edit.apply(&mut before);
145 let parse = SourceFile::parse(&before); 132 let parse = SourceFile::parse(&before);
146 on_char_typed_inner(&parse.tree(), offset, char_typed).map(|it| { 133 on_char_typed_inner(&parse.tree(), offset, char_typed).map(|it| {
147 it.edit.apply(&mut before); 134 it.apply(&mut before);
148 before.to_string() 135 before.to_string()
149 }) 136 })
150 } 137 }
diff --git a/crates/ra_ide/src/typing/on_enter.rs b/crates/ra_ide/src/typing/on_enter.rs
index 78a40cc94..e7d64b4f6 100644
--- a/crates/ra_ide/src/typing/on_enter.rs
+++ b/crates/ra_ide/src/typing/on_enter.rs
@@ -38,17 +38,12 @@ pub(crate) fn on_enter(db: &RootDatabase, position: FilePosition) -> Option<Sour
38 } 38 }
39 39
40 let indent = node_indent(&file, comment.syntax())?; 40 let indent = node_indent(&file, comment.syntax())?;
41 let inserted = format!("\n{}{} ", indent, prefix); 41 let inserted = format!("\n{}{} $0", indent, prefix);
42 let cursor_position = position.offset + TextSize::of(&inserted);
43 let edit = TextEdit::insert(position.offset, inserted); 42 let edit = TextEdit::insert(position.offset, inserted);
44 43
45 Some( 44 let mut res = SourceChange::from(SourceFileEdit { edit, file_id: position.file_id });
46 SourceChange::source_file_edit( 45 res.is_snippet = true;
47 "On enter", 46 Some(res)
48 SourceFileEdit { edit, file_id: position.file_id },
49 )
50 .with_cursor(FilePosition { offset: cursor_position, file_id: position.file_id }),
51 )
52} 47}
53 48
54fn followed_by_comment(comment: &ast::Comment) -> bool { 49fn followed_by_comment(comment: &ast::Comment) -> bool {
@@ -84,7 +79,7 @@ fn node_indent(file: &SourceFile, token: &SyntaxToken) -> Option<SmolStr> {
84 79
85#[cfg(test)] 80#[cfg(test)]
86mod tests { 81mod tests {
87 use test_utils::{add_cursor, assert_eq_text, extract_offset}; 82 use test_utils::{assert_eq_text, extract_offset};
88 83
89 use crate::mock_analysis::single_file; 84 use crate::mock_analysis::single_file;
90 85
@@ -98,7 +93,6 @@ mod tests {
98 assert_eq!(result.source_file_edits.len(), 1); 93 assert_eq!(result.source_file_edits.len(), 1);
99 let mut actual = before.to_string(); 94 let mut actual = before.to_string();
100 result.source_file_edits[0].edit.apply(&mut actual); 95 result.source_file_edits[0].edit.apply(&mut actual);
101 let actual = add_cursor(&actual, result.cursor_position.unwrap().offset);
102 Some(actual) 96 Some(actual)
103 } 97 }
104 98
@@ -121,7 +115,7 @@ fn foo() {
121", 115",
122 r" 116 r"
123/// Some docs 117/// Some docs
124/// <|> 118/// $0
125fn foo() { 119fn foo() {
126} 120}
127", 121",
@@ -137,7 +131,7 @@ impl S {
137 r" 131 r"
138impl S { 132impl S {
139 /// Some 133 /// Some
140 /// <|> docs. 134 /// $0 docs.
141 fn foo() {} 135 fn foo() {}
142} 136}
143", 137",
@@ -151,7 +145,7 @@ fn foo() {
151", 145",
152 r" 146 r"
153/// 147///
154/// <|> Some docs 148/// $0 Some docs
155fn foo() { 149fn foo() {
156} 150}
157", 151",
@@ -175,7 +169,7 @@ fn main() {
175 r" 169 r"
176fn main() { 170fn main() {
177 // Fix 171 // Fix
178 // <|> me 172 // $0 me
179 let x = 1 + 1; 173 let x = 1 + 1;
180} 174}
181", 175",
@@ -195,7 +189,7 @@ fn main() {
195 r" 189 r"
196fn main() { 190fn main() {
197 // Fix 191 // Fix
198 // <|> 192 // $0
199 // me 193 // me
200 let x = 1 + 1; 194 let x = 1 + 1;
201} 195}