diff options
Diffstat (limited to 'crates/ra_ide_api/src')
-rw-r--r-- | crates/ra_ide_api/src/assists.rs | 11 | ||||
-rw-r--r-- | crates/ra_ide_api/src/diagnostics.rs | 112 | ||||
-rw-r--r-- | crates/ra_ide_api/src/lib.rs | 101 | ||||
-rw-r--r-- | crates/ra_ide_api/src/references.rs | 14 | ||||
-rw-r--r-- | crates/ra_ide_api/src/snapshots/tests__file_structure.snap | 182 | ||||
-rw-r--r-- | crates/ra_ide_api/src/structure.rs | 190 | ||||
-rw-r--r-- | crates/ra_ide_api/src/typing.rs | 26 |
7 files changed, 538 insertions, 98 deletions
diff --git a/crates/ra_ide_api/src/assists.rs b/crates/ra_ide_api/src/assists.rs index 3c0475a51..355c0a42a 100644 --- a/crates/ra_ide_api/src/assists.rs +++ b/crates/ra_ide_api/src/assists.rs | |||
@@ -17,14 +17,9 @@ pub(crate) fn assists(db: &RootDatabase, frange: FileRange) -> Vec<Assist> { | |||
17 | let file_id = frange.file_id; | 17 | let file_id = frange.file_id; |
18 | let file_edit = SourceFileEdit { file_id, edit: action.edit }; | 18 | let file_edit = SourceFileEdit { file_id, edit: action.edit }; |
19 | let id = label.id; | 19 | let id = label.id; |
20 | let change = SourceChange { | 20 | let change = SourceChange::source_file_edit(label.label, file_edit).with_cursor_opt( |
21 | label: label.label, | 21 | action.cursor_position.map(|offset| FilePosition { offset, file_id }), |
22 | source_file_edits: vec![file_edit], | 22 | ); |
23 | file_system_edits: vec![], | ||
24 | cursor_position: action | ||
25 | .cursor_position | ||
26 | .map(|offset| FilePosition { offset, file_id }), | ||
27 | }; | ||
28 | Assist { id, change } | 23 | Assist { id, change } |
29 | }) | 24 | }) |
30 | .collect() | 25 | .collect() |
diff --git a/crates/ra_ide_api/src/diagnostics.rs b/crates/ra_ide_api/src/diagnostics.rs index b9dc424c6..5a78e94d8 100644 --- a/crates/ra_ide_api/src/diagnostics.rs +++ b/crates/ra_ide_api/src/diagnostics.rs | |||
@@ -1,10 +1,11 @@ | |||
1 | use std::cell::RefCell; | ||
2 | |||
1 | use itertools::Itertools; | 3 | use itertools::Itertools; |
2 | use hir::{Problem, source_binder}; | 4 | use hir::{source_binder, diagnostics::{Diagnostic as _, DiagnosticSink}}; |
3 | use ra_db::SourceDatabase; | 5 | use ra_db::SourceDatabase; |
4 | use ra_syntax::{ | 6 | use ra_syntax::{ |
5 | Location, SourceFile, SyntaxKind, TextRange, SyntaxNode, | 7 | Location, SourceFile, SyntaxKind, TextRange, SyntaxNode, |
6 | ast::{self, AstNode}, | 8 | ast::{self, AstNode}, |
7 | |||
8 | }; | 9 | }; |
9 | use ra_text_edit::{TextEdit, TextEditBuilder}; | 10 | use ra_text_edit::{TextEdit, TextEditBuilder}; |
10 | 11 | ||
@@ -26,11 +27,31 @@ pub(crate) fn diagnostics(db: &RootDatabase, file_id: FileId) -> Vec<Diagnostic> | |||
26 | check_unnecessary_braces_in_use_statement(&mut res, file_id, node); | 27 | check_unnecessary_braces_in_use_statement(&mut res, file_id, node); |
27 | check_struct_shorthand_initialization(&mut res, file_id, node); | 28 | check_struct_shorthand_initialization(&mut res, file_id, node); |
28 | } | 29 | } |
29 | 30 | let res = RefCell::new(res); | |
31 | let mut sink = DiagnosticSink::new(|d| { | ||
32 | res.borrow_mut().push(Diagnostic { | ||
33 | message: d.message(), | ||
34 | range: d.highlight_range(), | ||
35 | severity: Severity::Error, | ||
36 | fix: None, | ||
37 | }) | ||
38 | }) | ||
39 | .on::<hir::diagnostics::UnresolvedModule, _>(|d| { | ||
40 | let source_root = db.file_source_root(d.file().original_file(db)); | ||
41 | let create_file = FileSystemEdit::CreateFile { source_root, path: d.candidate.clone() }; | ||
42 | let fix = SourceChange::file_system_edit("create module", create_file); | ||
43 | res.borrow_mut().push(Diagnostic { | ||
44 | range: d.highlight_range(), | ||
45 | message: d.message(), | ||
46 | severity: Severity::Error, | ||
47 | fix: Some(fix), | ||
48 | }) | ||
49 | }); | ||
30 | if let Some(m) = source_binder::module_from_file_id(db, file_id) { | 50 | if let Some(m) = source_binder::module_from_file_id(db, file_id) { |
31 | check_module(&mut res, db, file_id, m); | 51 | m.diagnostics(db, &mut sink); |
32 | }; | 52 | }; |
33 | res | 53 | drop(sink); |
54 | res.into_inner() | ||
34 | } | 55 | } |
35 | 56 | ||
36 | fn syntax_errors(acc: &mut Vec<Diagnostic>, source_file: &SourceFile) { | 57 | fn syntax_errors(acc: &mut Vec<Diagnostic>, source_file: &SourceFile) { |
@@ -71,12 +92,10 @@ fn check_unnecessary_braces_in_use_statement( | |||
71 | range, | 92 | range, |
72 | message: format!("Unnecessary braces in use statement"), | 93 | message: format!("Unnecessary braces in use statement"), |
73 | severity: Severity::WeakWarning, | 94 | severity: Severity::WeakWarning, |
74 | fix: Some(SourceChange { | 95 | fix: Some(SourceChange::source_file_edit( |
75 | label: "Remove unnecessary braces".to_string(), | 96 | "Remove unnecessary braces", |
76 | source_file_edits: vec![SourceFileEdit { file_id, edit }], | 97 | SourceFileEdit { file_id, edit }, |
77 | file_system_edits: Vec::new(), | 98 | )), |
78 | cursor_position: None, | ||
79 | }), | ||
80 | }); | 99 | }); |
81 | } | 100 | } |
82 | 101 | ||
@@ -119,12 +138,10 @@ fn check_struct_shorthand_initialization( | |||
119 | range: named_field.syntax().range(), | 138 | range: named_field.syntax().range(), |
120 | message: format!("Shorthand struct initialization"), | 139 | message: format!("Shorthand struct initialization"), |
121 | severity: Severity::WeakWarning, | 140 | severity: Severity::WeakWarning, |
122 | fix: Some(SourceChange { | 141 | fix: Some(SourceChange::source_file_edit( |
123 | label: "use struct shorthand initialization".to_string(), | 142 | "use struct shorthand initialization", |
124 | source_file_edits: vec![SourceFileEdit { file_id, edit }], | 143 | SourceFileEdit { file_id, edit }, |
125 | file_system_edits: Vec::new(), | 144 | )), |
126 | cursor_position: None, | ||
127 | }), | ||
128 | }); | 145 | }); |
129 | } | 146 | } |
130 | } | 147 | } |
@@ -132,39 +149,12 @@ fn check_struct_shorthand_initialization( | |||
132 | Some(()) | 149 | Some(()) |
133 | } | 150 | } |
134 | 151 | ||
135 | fn check_module( | ||
136 | acc: &mut Vec<Diagnostic>, | ||
137 | db: &RootDatabase, | ||
138 | file_id: FileId, | ||
139 | module: hir::Module, | ||
140 | ) { | ||
141 | let source_root = db.file_source_root(file_id); | ||
142 | for (name_node, problem) in module.problems(db) { | ||
143 | let diag = match problem { | ||
144 | Problem::UnresolvedModule { candidate } => { | ||
145 | let create_file = | ||
146 | FileSystemEdit::CreateFile { source_root, path: candidate.clone() }; | ||
147 | let fix = SourceChange { | ||
148 | label: "create module".to_string(), | ||
149 | source_file_edits: Vec::new(), | ||
150 | file_system_edits: vec![create_file], | ||
151 | cursor_position: None, | ||
152 | }; | ||
153 | Diagnostic { | ||
154 | range: name_node.range(), | ||
155 | message: "unresolved module".to_string(), | ||
156 | severity: Severity::Error, | ||
157 | fix: Some(fix), | ||
158 | } | ||
159 | } | ||
160 | }; | ||
161 | acc.push(diag) | ||
162 | } | ||
163 | } | ||
164 | |||
165 | #[cfg(test)] | 152 | #[cfg(test)] |
166 | mod tests { | 153 | mod tests { |
167 | use test_utils::assert_eq_text; | 154 | use test_utils::assert_eq_text; |
155 | use insta::assert_debug_snapshot_matches; | ||
156 | |||
157 | use crate::mock_analysis::single_file; | ||
168 | 158 | ||
169 | use super::*; | 159 | use super::*; |
170 | 160 | ||
@@ -194,6 +184,34 @@ mod tests { | |||
194 | } | 184 | } |
195 | 185 | ||
196 | #[test] | 186 | #[test] |
187 | fn test_unresolved_module_diagnostic() { | ||
188 | let (analysis, file_id) = single_file("mod foo;"); | ||
189 | let diagnostics = analysis.diagnostics(file_id).unwrap(); | ||
190 | assert_debug_snapshot_matches!(diagnostics, @r####"[ | ||
191 | Diagnostic { | ||
192 | message: "unresolved module", | ||
193 | range: [0; 8), | ||
194 | fix: Some( | ||
195 | SourceChange { | ||
196 | label: "create module", | ||
197 | source_file_edits: [], | ||
198 | file_system_edits: [ | ||
199 | CreateFile { | ||
200 | source_root: SourceRootId( | ||
201 | 0 | ||
202 | ), | ||
203 | path: "foo.rs" | ||
204 | } | ||
205 | ], | ||
206 | cursor_position: None | ||
207 | } | ||
208 | ), | ||
209 | severity: Error | ||
210 | } | ||
211 | ]"####); | ||
212 | } | ||
213 | |||
214 | #[test] | ||
197 | fn test_check_unnecessary_braces_in_use_statement() { | 215 | fn test_check_unnecessary_braces_in_use_statement() { |
198 | check_not_applicable( | 216 | check_not_applicable( |
199 | " | 217 | " |
diff --git a/crates/ra_ide_api/src/lib.rs b/crates/ra_ide_api/src/lib.rs index 99f18b6b8..9063f78a9 100644 --- a/crates/ra_ide_api/src/lib.rs +++ b/crates/ra_ide_api/src/lib.rs | |||
@@ -6,9 +6,6 @@ | |||
6 | //! database, and the `ra_hir` crate, where majority of the analysis happens. | 6 | //! database, and the `ra_hir` crate, where majority of the analysis happens. |
7 | //! However, IDE specific bits of the analysis (most notably completion) happen | 7 | //! However, IDE specific bits of the analysis (most notably completion) happen |
8 | //! in this crate. | 8 | //! in this crate. |
9 | //! | ||
10 | //! The sibling `ra_ide_api_light` handles those bits of IDE functionality | ||
11 | //! which are restricted to a single file and need only syntax. | ||
12 | 9 | ||
13 | // For proving that RootDatabase is RefUnwindSafe. | 10 | // For proving that RootDatabase is RefUnwindSafe. |
14 | #![recursion_limit = "128"] | 11 | #![recursion_limit = "128"] |
@@ -33,10 +30,11 @@ mod impls; | |||
33 | mod assists; | 30 | mod assists; |
34 | mod diagnostics; | 31 | mod diagnostics; |
35 | mod syntax_tree; | 32 | mod syntax_tree; |
36 | mod line_index; | ||
37 | mod folding_ranges; | 33 | mod folding_ranges; |
34 | mod line_index; | ||
38 | mod line_index_utils; | 35 | mod line_index_utils; |
39 | mod join_lines; | 36 | mod join_lines; |
37 | mod structure; | ||
40 | mod typing; | 38 | mod typing; |
41 | mod matching_brace; | 39 | mod matching_brace; |
42 | 40 | ||
@@ -72,9 +70,10 @@ pub use crate::{ | |||
72 | line_index_utils::translate_offset_with_edit, | 70 | line_index_utils::translate_offset_with_edit, |
73 | folding_ranges::{Fold, FoldKind}, | 71 | folding_ranges::{Fold, FoldKind}, |
74 | syntax_highlighting::HighlightedRange, | 72 | syntax_highlighting::HighlightedRange, |
73 | structure::{StructureNode, file_structure}, | ||
75 | diagnostics::Severity, | 74 | diagnostics::Severity, |
76 | }; | 75 | }; |
77 | pub use ra_ide_api_light::StructureNode; | 76 | |
78 | pub use ra_db::{ | 77 | pub use ra_db::{ |
79 | Canceled, CrateGraph, CrateId, FileId, FilePosition, FileRange, SourceRootId, | 78 | Canceled, CrateGraph, CrateId, FileId, FilePosition, FileRange, SourceRootId, |
80 | Edition | 79 | Edition |
@@ -97,6 +96,79 @@ pub struct SourceChange { | |||
97 | pub cursor_position: Option<FilePosition>, | 96 | pub cursor_position: Option<FilePosition>, |
98 | } | 97 | } |
99 | 98 | ||
99 | impl SourceChange { | ||
100 | /// Creates a new SourceChange with the given label | ||
101 | /// from the edits. | ||
102 | pub(crate) fn from_edits<L: Into<String>>( | ||
103 | label: L, | ||
104 | source_file_edits: Vec<SourceFileEdit>, | ||
105 | file_system_edits: Vec<FileSystemEdit>, | ||
106 | ) -> Self { | ||
107 | SourceChange { | ||
108 | label: label.into(), | ||
109 | source_file_edits, | ||
110 | file_system_edits, | ||
111 | cursor_position: None, | ||
112 | } | ||
113 | } | ||
114 | |||
115 | /// Creates a new SourceChange with the given label, | ||
116 | /// containing only the given `SourceFileEdits`. | ||
117 | pub(crate) fn source_file_edits<L: Into<String>>(label: L, edits: Vec<SourceFileEdit>) -> Self { | ||
118 | SourceChange { | ||
119 | label: label.into(), | ||
120 | source_file_edits: edits, | ||
121 | file_system_edits: vec![], | ||
122 | cursor_position: None, | ||
123 | } | ||
124 | } | ||
125 | |||
126 | /// Creates a new SourceChange with the given label, | ||
127 | /// containing only the given `FileSystemEdits`. | ||
128 | pub(crate) fn file_system_edits<L: Into<String>>(label: L, edits: Vec<FileSystemEdit>) -> Self { | ||
129 | SourceChange { | ||
130 | label: label.into(), | ||
131 | source_file_edits: vec![], | ||
132 | file_system_edits: edits, | ||
133 | cursor_position: None, | ||
134 | } | ||
135 | } | ||
136 | |||
137 | /// Creates a new SourceChange with the given label, | ||
138 | /// containing only a single `SourceFileEdit`. | ||
139 | pub(crate) fn source_file_edit<L: Into<String>>(label: L, edit: SourceFileEdit) -> Self { | ||
140 | SourceChange::source_file_edits(label, vec![edit]) | ||
141 | } | ||
142 | |||
143 | /// Creates a new SourceChange with the given label | ||
144 | /// from the given `FileId` and `TextEdit` | ||
145 | pub(crate) fn source_file_edit_from<L: Into<String>>( | ||
146 | label: L, | ||
147 | file_id: FileId, | ||
148 | edit: TextEdit, | ||
149 | ) -> Self { | ||
150 | SourceChange::source_file_edit(label, SourceFileEdit { file_id, edit }) | ||
151 | } | ||
152 | |||
153 | /// Creates a new SourceChange with the given label | ||
154 | /// from the given `FileId` and `TextEdit` | ||
155 | pub(crate) fn file_system_edit<L: Into<String>>(label: L, edit: FileSystemEdit) -> Self { | ||
156 | SourceChange::file_system_edits(label, vec![edit]) | ||
157 | } | ||
158 | |||
159 | /// Sets the cursor position to the given `FilePosition` | ||
160 | pub(crate) fn with_cursor(mut self, cursor_position: FilePosition) -> Self { | ||
161 | self.cursor_position = Some(cursor_position); | ||
162 | self | ||
163 | } | ||
164 | |||
165 | /// Sets the cursor position to the given `FilePosition` | ||
166 | pub(crate) fn with_cursor_opt(mut self, cursor_position: Option<FilePosition>) -> Self { | ||
167 | self.cursor_position = cursor_position; | ||
168 | self | ||
169 | } | ||
170 | } | ||
171 | |||
100 | #[derive(Debug)] | 172 | #[derive(Debug)] |
101 | pub struct SourceFileEdit { | 173 | pub struct SourceFileEdit { |
102 | pub file_id: FileId, | 174 | pub file_id: FileId, |
@@ -285,12 +357,7 @@ impl Analysis { | |||
285 | file_id: frange.file_id, | 357 | file_id: frange.file_id, |
286 | edit: join_lines::join_lines(&file, frange.range), | 358 | edit: join_lines::join_lines(&file, frange.range), |
287 | }; | 359 | }; |
288 | SourceChange { | 360 | SourceChange::source_file_edit("join lines", file_edit) |
289 | label: "join lines".to_string(), | ||
290 | source_file_edits: vec![file_edit], | ||
291 | file_system_edits: vec![], | ||
292 | cursor_position: None, | ||
293 | } | ||
294 | } | 361 | } |
295 | 362 | ||
296 | /// Returns an edit which should be applied when opening a new line, fixing | 363 | /// Returns an edit which should be applied when opening a new line, fixing |
@@ -305,12 +372,10 @@ impl Analysis { | |||
305 | pub fn on_eq_typed(&self, position: FilePosition) -> Option<SourceChange> { | 372 | pub fn on_eq_typed(&self, position: FilePosition) -> Option<SourceChange> { |
306 | let file = self.db.parse(position.file_id); | 373 | let file = self.db.parse(position.file_id); |
307 | let edit = typing::on_eq_typed(&file, position.offset)?; | 374 | let edit = typing::on_eq_typed(&file, position.offset)?; |
308 | Some(SourceChange { | 375 | Some(SourceChange::source_file_edit( |
309 | label: "add semicolon".to_string(), | 376 | "add semicolon", |
310 | source_file_edits: vec![SourceFileEdit { edit, file_id: position.file_id }], | 377 | SourceFileEdit { edit, file_id: position.file_id }, |
311 | file_system_edits: vec![], | 378 | )) |
312 | cursor_position: None, | ||
313 | }) | ||
314 | } | 379 | } |
315 | 380 | ||
316 | /// Returns an edit which should be applied when a dot ('.') is typed on a blank line, indenting the line appropriately. | 381 | /// Returns an edit which should be applied when a dot ('.') is typed on a blank line, indenting the line appropriately. |
@@ -322,7 +387,7 @@ impl Analysis { | |||
322 | /// file outline. | 387 | /// file outline. |
323 | pub fn file_structure(&self, file_id: FileId) -> Vec<StructureNode> { | 388 | pub fn file_structure(&self, file_id: FileId) -> Vec<StructureNode> { |
324 | let file = self.db.parse(file_id); | 389 | let file = self.db.parse(file_id); |
325 | ra_ide_api_light::file_structure(&file) | 390 | structure::file_structure(&file) |
326 | } | 391 | } |
327 | 392 | ||
328 | /// Returns the set of folding ranges. | 393 | /// Returns the set of folding ranges. |
diff --git a/crates/ra_ide_api/src/references.rs b/crates/ra_ide_api/src/references.rs index b7784e577..22741445a 100644 --- a/crates/ra_ide_api/src/references.rs +++ b/crates/ra_ide_api/src/references.rs | |||
@@ -187,12 +187,7 @@ fn rename_mod( | |||
187 | }; | 187 | }; |
188 | source_file_edits.push(edit); | 188 | source_file_edits.push(edit); |
189 | 189 | ||
190 | Some(SourceChange { | 190 | Some(SourceChange::from_edits("rename", source_file_edits, file_system_edits)) |
191 | label: "rename".to_string(), | ||
192 | source_file_edits, | ||
193 | file_system_edits, | ||
194 | cursor_position: None, | ||
195 | }) | ||
196 | } | 191 | } |
197 | 192 | ||
198 | fn rename_reference( | 193 | fn rename_reference( |
@@ -211,12 +206,7 @@ fn rename_reference( | |||
211 | return None; | 206 | return None; |
212 | } | 207 | } |
213 | 208 | ||
214 | Some(SourceChange { | 209 | Some(SourceChange::source_file_edits("rename", edit)) |
215 | label: "rename".to_string(), | ||
216 | source_file_edits: edit, | ||
217 | file_system_edits: Vec::new(), | ||
218 | cursor_position: None, | ||
219 | }) | ||
220 | } | 210 | } |
221 | 211 | ||
222 | #[cfg(test)] | 212 | #[cfg(test)] |
diff --git a/crates/ra_ide_api/src/snapshots/tests__file_structure.snap b/crates/ra_ide_api/src/snapshots/tests__file_structure.snap new file mode 100644 index 000000000..2efa8e22c --- /dev/null +++ b/crates/ra_ide_api/src/snapshots/tests__file_structure.snap | |||
@@ -0,0 +1,182 @@ | |||
1 | --- | ||
2 | created: "2019-02-05T22:03:50.763530100Z" | ||
3 | creator: [email protected] | ||
4 | source: crates/ra_ide_api/src/structure.rs | ||
5 | expression: structure | ||
6 | --- | ||
7 | [ | ||
8 | StructureNode { | ||
9 | parent: None, | ||
10 | label: "Foo", | ||
11 | navigation_range: [8; 11), | ||
12 | node_range: [1; 26), | ||
13 | kind: STRUCT_DEF, | ||
14 | detail: None, | ||
15 | deprecated: false | ||
16 | }, | ||
17 | StructureNode { | ||
18 | parent: Some( | ||
19 | 0 | ||
20 | ), | ||
21 | label: "x", | ||
22 | navigation_range: [18; 19), | ||
23 | node_range: [18; 24), | ||
24 | kind: NAMED_FIELD_DEF, | ||
25 | detail: Some( | ||
26 | "i32" | ||
27 | ), | ||
28 | deprecated: false | ||
29 | }, | ||
30 | StructureNode { | ||
31 | parent: None, | ||
32 | label: "m", | ||
33 | navigation_range: [32; 33), | ||
34 | node_range: [28; 158), | ||
35 | kind: MODULE, | ||
36 | detail: None, | ||
37 | deprecated: false | ||
38 | }, | ||
39 | StructureNode { | ||
40 | parent: Some( | ||
41 | 2 | ||
42 | ), | ||
43 | label: "bar1", | ||
44 | navigation_range: [43; 47), | ||
45 | node_range: [40; 52), | ||
46 | kind: FN_DEF, | ||
47 | detail: Some( | ||
48 | "fn()" | ||
49 | ), | ||
50 | deprecated: false | ||
51 | }, | ||
52 | StructureNode { | ||
53 | parent: Some( | ||
54 | 2 | ||
55 | ), | ||
56 | label: "bar2", | ||
57 | navigation_range: [60; 64), | ||
58 | node_range: [57; 81), | ||
59 | kind: FN_DEF, | ||
60 | detail: Some( | ||
61 | "fn<T>(t: T) -> T" | ||
62 | ), | ||
63 | deprecated: false | ||
64 | }, | ||
65 | StructureNode { | ||
66 | parent: Some( | ||
67 | 2 | ||
68 | ), | ||
69 | label: "bar3", | ||
70 | navigation_range: [89; 93), | ||
71 | node_range: [86; 156), | ||
72 | kind: FN_DEF, | ||
73 | detail: Some( | ||
74 | "fn<A, B>(a: A, b: B) -> Vec< u32 >" | ||
75 | ), | ||
76 | deprecated: false | ||
77 | }, | ||
78 | StructureNode { | ||
79 | parent: None, | ||
80 | label: "E", | ||
81 | navigation_range: [165; 166), | ||
82 | node_range: [160; 180), | ||
83 | kind: ENUM_DEF, | ||
84 | detail: None, | ||
85 | deprecated: false | ||
86 | }, | ||
87 | StructureNode { | ||
88 | parent: Some( | ||
89 | 6 | ||
90 | ), | ||
91 | label: "X", | ||
92 | navigation_range: [169; 170), | ||
93 | node_range: [169; 170), | ||
94 | kind: ENUM_VARIANT, | ||
95 | detail: None, | ||
96 | deprecated: false | ||
97 | }, | ||
98 | StructureNode { | ||
99 | parent: Some( | ||
100 | 6 | ||
101 | ), | ||
102 | label: "Y", | ||
103 | navigation_range: [172; 173), | ||
104 | node_range: [172; 178), | ||
105 | kind: ENUM_VARIANT, | ||
106 | detail: None, | ||
107 | deprecated: false | ||
108 | }, | ||
109 | StructureNode { | ||
110 | parent: None, | ||
111 | label: "T", | ||
112 | navigation_range: [186; 187), | ||
113 | node_range: [181; 193), | ||
114 | kind: TYPE_ALIAS_DEF, | ||
115 | detail: Some( | ||
116 | "()" | ||
117 | ), | ||
118 | deprecated: false | ||
119 | }, | ||
120 | StructureNode { | ||
121 | parent: None, | ||
122 | label: "S", | ||
123 | navigation_range: [201; 202), | ||
124 | node_range: [194; 213), | ||
125 | kind: STATIC_DEF, | ||
126 | detail: Some( | ||
127 | "i32" | ||
128 | ), | ||
129 | deprecated: false | ||
130 | }, | ||
131 | StructureNode { | ||
132 | parent: None, | ||
133 | label: "C", | ||
134 | navigation_range: [220; 221), | ||
135 | node_range: [214; 232), | ||
136 | kind: CONST_DEF, | ||
137 | detail: Some( | ||
138 | "i32" | ||
139 | ), | ||
140 | deprecated: false | ||
141 | }, | ||
142 | StructureNode { | ||
143 | parent: None, | ||
144 | label: "impl E", | ||
145 | navigation_range: [239; 240), | ||
146 | node_range: [234; 243), | ||
147 | kind: IMPL_BLOCK, | ||
148 | detail: None, | ||
149 | deprecated: false | ||
150 | }, | ||
151 | StructureNode { | ||
152 | parent: None, | ||
153 | label: "impl fmt::Debug for E", | ||
154 | navigation_range: [265; 266), | ||
155 | node_range: [245; 269), | ||
156 | kind: IMPL_BLOCK, | ||
157 | detail: None, | ||
158 | deprecated: false | ||
159 | }, | ||
160 | StructureNode { | ||
161 | parent: None, | ||
162 | label: "obsolete", | ||
163 | navigation_range: [288; 296), | ||
164 | node_range: [271; 301), | ||
165 | kind: FN_DEF, | ||
166 | detail: Some( | ||
167 | "fn()" | ||
168 | ), | ||
169 | deprecated: true | ||
170 | }, | ||
171 | StructureNode { | ||
172 | parent: None, | ||
173 | label: "very_obsolete", | ||
174 | navigation_range: [341; 354), | ||
175 | node_range: [303; 359), | ||
176 | kind: FN_DEF, | ||
177 | detail: Some( | ||
178 | "fn()" | ||
179 | ), | ||
180 | deprecated: true | ||
181 | } | ||
182 | ] | ||
diff --git a/crates/ra_ide_api/src/structure.rs b/crates/ra_ide_api/src/structure.rs new file mode 100644 index 000000000..ec2c9bbc6 --- /dev/null +++ b/crates/ra_ide_api/src/structure.rs | |||
@@ -0,0 +1,190 @@ | |||
1 | use crate::TextRange; | ||
2 | |||
3 | use ra_syntax::{ | ||
4 | algo::visit::{visitor, Visitor}, | ||
5 | ast::{self, AttrsOwner, NameOwner, TypeParamsOwner, TypeAscriptionOwner}, | ||
6 | AstNode, SourceFile, SyntaxKind, SyntaxNode, WalkEvent, | ||
7 | }; | ||
8 | |||
9 | #[derive(Debug, Clone)] | ||
10 | pub struct StructureNode { | ||
11 | pub parent: Option<usize>, | ||
12 | pub label: String, | ||
13 | pub navigation_range: TextRange, | ||
14 | pub node_range: TextRange, | ||
15 | pub kind: SyntaxKind, | ||
16 | pub detail: Option<String>, | ||
17 | pub deprecated: bool, | ||
18 | } | ||
19 | |||
20 | pub fn file_structure(file: &SourceFile) -> Vec<StructureNode> { | ||
21 | let mut res = Vec::new(); | ||
22 | let mut stack = Vec::new(); | ||
23 | |||
24 | for event in file.syntax().preorder() { | ||
25 | match event { | ||
26 | WalkEvent::Enter(node) => { | ||
27 | if let Some(mut symbol) = structure_node(node) { | ||
28 | symbol.parent = stack.last().map(|&n| n); | ||
29 | stack.push(res.len()); | ||
30 | res.push(symbol); | ||
31 | } | ||
32 | } | ||
33 | WalkEvent::Leave(node) => { | ||
34 | if structure_node(node).is_some() { | ||
35 | stack.pop().unwrap(); | ||
36 | } | ||
37 | } | ||
38 | } | ||
39 | } | ||
40 | res | ||
41 | } | ||
42 | |||
43 | fn structure_node(node: &SyntaxNode) -> Option<StructureNode> { | ||
44 | fn decl<N: NameOwner + AttrsOwner>(node: &N) -> Option<StructureNode> { | ||
45 | decl_with_detail(node, None) | ||
46 | } | ||
47 | |||
48 | fn decl_with_ascription<N: NameOwner + AttrsOwner + TypeAscriptionOwner>( | ||
49 | node: &N, | ||
50 | ) -> Option<StructureNode> { | ||
51 | decl_with_type_ref(node, node.ascribed_type()) | ||
52 | } | ||
53 | |||
54 | fn decl_with_type_ref<N: NameOwner + AttrsOwner>( | ||
55 | node: &N, | ||
56 | type_ref: Option<&ast::TypeRef>, | ||
57 | ) -> Option<StructureNode> { | ||
58 | let detail = type_ref.map(|type_ref| { | ||
59 | let mut detail = String::new(); | ||
60 | collapse_ws(type_ref.syntax(), &mut detail); | ||
61 | detail | ||
62 | }); | ||
63 | decl_with_detail(node, detail) | ||
64 | } | ||
65 | |||
66 | fn decl_with_detail<N: NameOwner + AttrsOwner>( | ||
67 | node: &N, | ||
68 | detail: Option<String>, | ||
69 | ) -> Option<StructureNode> { | ||
70 | let name = node.name()?; | ||
71 | |||
72 | Some(StructureNode { | ||
73 | parent: None, | ||
74 | label: name.text().to_string(), | ||
75 | navigation_range: name.syntax().range(), | ||
76 | node_range: node.syntax().range(), | ||
77 | kind: node.syntax().kind(), | ||
78 | detail, | ||
79 | deprecated: node.attrs().filter_map(|x| x.as_named()).any(|x| x == "deprecated"), | ||
80 | }) | ||
81 | } | ||
82 | |||
83 | fn collapse_ws(node: &SyntaxNode, output: &mut String) { | ||
84 | let mut can_insert_ws = false; | ||
85 | for line in node.text().chunks().flat_map(|chunk| chunk.lines()) { | ||
86 | let line = line.trim(); | ||
87 | if line.is_empty() { | ||
88 | if can_insert_ws { | ||
89 | output.push_str(" "); | ||
90 | can_insert_ws = false; | ||
91 | } | ||
92 | } else { | ||
93 | output.push_str(line); | ||
94 | can_insert_ws = true; | ||
95 | } | ||
96 | } | ||
97 | } | ||
98 | |||
99 | visitor() | ||
100 | .visit(|fn_def: &ast::FnDef| { | ||
101 | let mut detail = String::from("fn"); | ||
102 | if let Some(type_param_list) = fn_def.type_param_list() { | ||
103 | collapse_ws(type_param_list.syntax(), &mut detail); | ||
104 | } | ||
105 | if let Some(param_list) = fn_def.param_list() { | ||
106 | collapse_ws(param_list.syntax(), &mut detail); | ||
107 | } | ||
108 | if let Some(ret_type) = fn_def.ret_type() { | ||
109 | detail.push_str(" "); | ||
110 | collapse_ws(ret_type.syntax(), &mut detail); | ||
111 | } | ||
112 | |||
113 | decl_with_detail(fn_def, Some(detail)) | ||
114 | }) | ||
115 | .visit(decl::<ast::StructDef>) | ||
116 | .visit(decl::<ast::EnumDef>) | ||
117 | .visit(decl::<ast::EnumVariant>) | ||
118 | .visit(decl::<ast::TraitDef>) | ||
119 | .visit(decl::<ast::Module>) | ||
120 | .visit(|td: &ast::TypeAliasDef| decl_with_type_ref(td, td.type_ref())) | ||
121 | .visit(decl_with_ascription::<ast::NamedFieldDef>) | ||
122 | .visit(decl_with_ascription::<ast::ConstDef>) | ||
123 | .visit(decl_with_ascription::<ast::StaticDef>) | ||
124 | .visit(|im: &ast::ImplBlock| { | ||
125 | let target_type = im.target_type()?; | ||
126 | let target_trait = im.target_trait(); | ||
127 | let label = match target_trait { | ||
128 | None => format!("impl {}", target_type.syntax().text()), | ||
129 | Some(t) => { | ||
130 | format!("impl {} for {}", t.syntax().text(), target_type.syntax().text(),) | ||
131 | } | ||
132 | }; | ||
133 | |||
134 | let node = StructureNode { | ||
135 | parent: None, | ||
136 | label, | ||
137 | navigation_range: target_type.syntax().range(), | ||
138 | node_range: im.syntax().range(), | ||
139 | kind: im.syntax().kind(), | ||
140 | detail: None, | ||
141 | deprecated: false, | ||
142 | }; | ||
143 | Some(node) | ||
144 | }) | ||
145 | .accept(node)? | ||
146 | } | ||
147 | |||
148 | #[cfg(test)] | ||
149 | mod tests { | ||
150 | use super::*; | ||
151 | use insta::assert_debug_snapshot_matches; | ||
152 | |||
153 | #[test] | ||
154 | fn test_file_structure() { | ||
155 | let file = SourceFile::parse( | ||
156 | r#" | ||
157 | struct Foo { | ||
158 | x: i32 | ||
159 | } | ||
160 | |||
161 | mod m { | ||
162 | fn bar1() {} | ||
163 | fn bar2<T>(t: T) -> T {} | ||
164 | fn bar3<A, | ||
165 | B>(a: A, | ||
166 | b: B) -> Vec< | ||
167 | u32 | ||
168 | > {} | ||
169 | } | ||
170 | |||
171 | enum E { X, Y(i32) } | ||
172 | type T = (); | ||
173 | static S: i32 = 92; | ||
174 | const C: i32 = 92; | ||
175 | |||
176 | impl E {} | ||
177 | |||
178 | impl fmt::Debug for E {} | ||
179 | |||
180 | #[deprecated] | ||
181 | fn obsolete() {} | ||
182 | |||
183 | #[deprecated(note = "for awhile")] | ||
184 | fn very_obsolete() {} | ||
185 | "#, | ||
186 | ); | ||
187 | let structure = file_structure(&file); | ||
188 | assert_debug_snapshot_matches!("file_structure", structure); | ||
189 | } | ||
190 | } | ||
diff --git a/crates/ra_ide_api/src/typing.rs b/crates/ra_ide_api/src/typing.rs index 94b228466..501d44dbb 100644 --- a/crates/ra_ide_api/src/typing.rs +++ b/crates/ra_ide_api/src/typing.rs | |||
@@ -31,12 +31,14 @@ pub(crate) fn on_enter(db: &RootDatabase, position: FilePosition) -> Option<Sour | |||
31 | let cursor_position = position.offset + TextUnit::of_str(&inserted); | 31 | let cursor_position = position.offset + TextUnit::of_str(&inserted); |
32 | let mut edit = TextEditBuilder::default(); | 32 | let mut edit = TextEditBuilder::default(); |
33 | edit.insert(position.offset, inserted); | 33 | edit.insert(position.offset, inserted); |
34 | Some(SourceChange { | 34 | |
35 | label: "on enter".to_string(), | 35 | Some( |
36 | source_file_edits: vec![SourceFileEdit { edit: edit.finish(), file_id: position.file_id }], | 36 | SourceChange::source_file_edit( |
37 | file_system_edits: vec![], | 37 | "on enter", |
38 | cursor_position: Some(FilePosition { offset: cursor_position, file_id: position.file_id }), | 38 | SourceFileEdit { edit: edit.finish(), file_id: position.file_id }, |
39 | }) | 39 | ) |
40 | .with_cursor(FilePosition { offset: cursor_position, file_id: position.file_id }), | ||
41 | ) | ||
40 | } | 42 | } |
41 | 43 | ||
42 | fn node_indent<'a>(file: &'a SourceFile, node: &SyntaxNode) -> Option<&'a str> { | 44 | fn node_indent<'a>(file: &'a SourceFile, node: &SyntaxNode) -> Option<&'a str> { |
@@ -110,16 +112,14 @@ pub(crate) fn on_dot_typed(db: &RootDatabase, position: FilePosition) -> Option< | |||
110 | TextRange::from_to(position.offset - current_indent_len, position.offset), | 112 | TextRange::from_to(position.offset - current_indent_len, position.offset), |
111 | target_indent.into(), | 113 | target_indent.into(), |
112 | ); | 114 | ); |
113 | let res = SourceChange { | 115 | |
114 | label: "reindent dot".to_string(), | 116 | let res = SourceChange::source_file_edit_from("reindent dot", position.file_id, edit.finish()) |
115 | source_file_edits: vec![SourceFileEdit { edit: edit.finish(), file_id: position.file_id }], | 117 | .with_cursor(FilePosition { |
116 | file_system_edits: vec![], | ||
117 | cursor_position: Some(FilePosition { | ||
118 | offset: position.offset + target_indent_len - current_indent_len | 118 | offset: position.offset + target_indent_len - current_indent_len |
119 | + TextUnit::of_char('.'), | 119 | + TextUnit::of_char('.'), |
120 | file_id: position.file_id, | 120 | file_id: position.file_id, |
121 | }), | 121 | }); |
122 | }; | 122 | |
123 | Some(res) | 123 | Some(res) |
124 | } | 124 | } |
125 | 125 | ||