diff options
-rw-r--r-- | crates/ra_ide_api/src/assits.rs | 89 | ||||
-rw-r--r-- | crates/ra_ide_api/src/assits/fill_match_arm.rs | 157 | ||||
-rw-r--r-- | crates/ra_ide_api/src/assits/snapshots/tests__fill_match_arm1.snap | 20 | ||||
-rw-r--r-- | crates/ra_ide_api/src/assits/snapshots/tests__fill_match_arm2.snap | 20 | ||||
-rw-r--r-- | crates/ra_ide_api/src/imp.rs | 5 | ||||
-rw-r--r-- | crates/ra_ide_api/src/lib.rs | 1 | ||||
-rw-r--r-- | crates/ra_ide_api_light/src/assists.rs | 17 |
7 files changed, 300 insertions, 9 deletions
diff --git a/crates/ra_ide_api/src/assits.rs b/crates/ra_ide_api/src/assits.rs new file mode 100644 index 000000000..2da251df5 --- /dev/null +++ b/crates/ra_ide_api/src/assits.rs | |||
@@ -0,0 +1,89 @@ | |||
1 | mod fill_match_arm; | ||
2 | |||
3 | use ra_syntax::{ | ||
4 | TextRange, SourceFile, AstNode, | ||
5 | algo::find_node_at_offset, | ||
6 | }; | ||
7 | use ra_ide_api_light::{ | ||
8 | LocalEdit, | ||
9 | assists::{ | ||
10 | Assist, | ||
11 | AssistBuilder | ||
12 | } | ||
13 | }; | ||
14 | use crate::{ | ||
15 | db::RootDatabase, | ||
16 | FileId | ||
17 | }; | ||
18 | |||
19 | /// Return all the assists applicable at the given position. | ||
20 | pub(crate) fn assists( | ||
21 | db: &RootDatabase, | ||
22 | file_id: FileId, | ||
23 | file: &SourceFile, | ||
24 | range: TextRange, | ||
25 | ) -> Vec<LocalEdit> { | ||
26 | let ctx = AssistCtx::new(db, file_id, file, range); | ||
27 | [fill_match_arm::fill_match_arm] | ||
28 | .iter() | ||
29 | .filter_map(|&assist| ctx.clone().apply(assist)) | ||
30 | .collect() | ||
31 | } | ||
32 | |||
33 | #[derive(Debug, Clone)] | ||
34 | pub struct AssistCtx<'a> { | ||
35 | file_id: FileId, | ||
36 | source_file: &'a SourceFile, | ||
37 | db: &'a RootDatabase, | ||
38 | range: TextRange, | ||
39 | should_compute_edit: bool, | ||
40 | } | ||
41 | |||
42 | impl<'a> AssistCtx<'a> { | ||
43 | pub(crate) fn new( | ||
44 | db: &'a RootDatabase, | ||
45 | file_id: FileId, | ||
46 | source_file: &'a SourceFile, | ||
47 | range: TextRange, | ||
48 | ) -> AssistCtx<'a> { | ||
49 | AssistCtx { | ||
50 | source_file, | ||
51 | file_id, | ||
52 | db, | ||
53 | range, | ||
54 | should_compute_edit: false, | ||
55 | } | ||
56 | } | ||
57 | |||
58 | pub fn apply(mut self, assist: fn(AssistCtx) -> Option<Assist>) -> Option<LocalEdit> { | ||
59 | self.should_compute_edit = true; | ||
60 | match assist(self) { | ||
61 | None => None, | ||
62 | Some(Assist::Edit(e)) => Some(e), | ||
63 | Some(Assist::Applicable) => unreachable!(), | ||
64 | } | ||
65 | } | ||
66 | |||
67 | #[allow(unused)] | ||
68 | pub fn check(mut self, assist: fn(AssistCtx) -> Option<Assist>) -> bool { | ||
69 | self.should_compute_edit = false; | ||
70 | match assist(self) { | ||
71 | None => false, | ||
72 | Some(Assist::Edit(_)) => unreachable!(), | ||
73 | Some(Assist::Applicable) => true, | ||
74 | } | ||
75 | } | ||
76 | |||
77 | fn build(self, label: impl Into<String>, f: impl FnOnce(&mut AssistBuilder)) -> Option<Assist> { | ||
78 | if !self.should_compute_edit { | ||
79 | return Some(Assist::Applicable); | ||
80 | } | ||
81 | let mut edit = AssistBuilder::default(); | ||
82 | f(&mut edit); | ||
83 | Some(edit.build(label)) | ||
84 | } | ||
85 | |||
86 | pub(crate) fn node_at_offset<N: AstNode>(&self) -> Option<&'a N> { | ||
87 | find_node_at_offset(self.source_file.syntax(), self.range.start()) | ||
88 | } | ||
89 | } | ||
diff --git a/crates/ra_ide_api/src/assits/fill_match_arm.rs b/crates/ra_ide_api/src/assits/fill_match_arm.rs new file mode 100644 index 000000000..d433861a0 --- /dev/null +++ b/crates/ra_ide_api/src/assits/fill_match_arm.rs | |||
@@ -0,0 +1,157 @@ | |||
1 | use std::fmt::Write; | ||
2 | use hir::{ | ||
3 | AdtDef, | ||
4 | source_binder, | ||
5 | Ty, | ||
6 | FieldSource, | ||
7 | }; | ||
8 | use ra_ide_api_light::{ | ||
9 | assists::{ | ||
10 | Assist, | ||
11 | AssistBuilder | ||
12 | } | ||
13 | }; | ||
14 | use ra_syntax::{ | ||
15 | ast::{ | ||
16 | self, | ||
17 | AstNode, | ||
18 | } | ||
19 | }; | ||
20 | |||
21 | use crate::assits::AssistCtx; | ||
22 | |||
23 | pub fn fill_match_arm(ctx: AssistCtx) -> Option<Assist> { | ||
24 | let match_expr = ctx.node_at_offset::<ast::MatchExpr>()?; | ||
25 | |||
26 | // We already have some match arms, so we don't provide any assists. | ||
27 | match match_expr.match_arm_list() { | ||
28 | Some(arm_list) if arm_list.arms().count() > 0 => { | ||
29 | return None; | ||
30 | } | ||
31 | _ => {} | ||
32 | } | ||
33 | |||
34 | let expr = match_expr.expr()?; | ||
35 | let function = source_binder::function_from_child_node(ctx.db, ctx.file_id, expr.syntax())?; | ||
36 | let infer_result = function.infer(ctx.db); | ||
37 | let syntax_mapping = function.body_syntax_mapping(ctx.db); | ||
38 | let node_expr = syntax_mapping.node_expr(expr)?; | ||
39 | let match_expr_ty = infer_result[node_expr].clone(); | ||
40 | match match_expr_ty { | ||
41 | Ty::Adt { def_id, .. } => match def_id { | ||
42 | AdtDef::Enum(e) => { | ||
43 | let mut buf = format!("match {} {{\n", expr.syntax().text().to_string()); | ||
44 | let variants = e.variants(ctx.db); | ||
45 | for variant in variants { | ||
46 | let name = variant.name(ctx.db)?; | ||
47 | write!( | ||
48 | &mut buf, | ||
49 | " {}::{}", | ||
50 | e.name(ctx.db)?.to_string(), | ||
51 | name.to_string() | ||
52 | ) | ||
53 | .expect("write fmt"); | ||
54 | |||
55 | let pat = variant | ||
56 | .fields(ctx.db) | ||
57 | .into_iter() | ||
58 | .map(|field| { | ||
59 | let name = field.name(ctx.db).to_string(); | ||
60 | let (_, source) = field.source(ctx.db); | ||
61 | match source { | ||
62 | FieldSource::Named(_) => name, | ||
63 | FieldSource::Pos(_) => "_".to_string(), | ||
64 | } | ||
65 | }) | ||
66 | .collect::<Vec<_>>(); | ||
67 | |||
68 | match pat.first().map(|s| s.as_str()) { | ||
69 | Some("_") => write!(&mut buf, "({})", pat.join(", ")).expect("write fmt"), | ||
70 | Some(_) => write!(&mut buf, "{{{}}}", pat.join(", ")).expect("write fmt"), | ||
71 | None => (), | ||
72 | }; | ||
73 | |||
74 | buf.push_str(" => (),\n"); | ||
75 | } | ||
76 | buf.push_str("}"); | ||
77 | ctx.build("fill match arms", |edit: &mut AssistBuilder| { | ||
78 | edit.replace_node_and_indent(match_expr.syntax(), buf); | ||
79 | }) | ||
80 | } | ||
81 | _ => None, | ||
82 | }, | ||
83 | _ => None, | ||
84 | } | ||
85 | } | ||
86 | |||
87 | #[cfg(test)] | ||
88 | mod tests { | ||
89 | use insta::assert_debug_snapshot_matches; | ||
90 | |||
91 | use ra_syntax::{TextRange, TextUnit}; | ||
92 | |||
93 | use crate::{ | ||
94 | FileRange, | ||
95 | mock_analysis::{analysis_and_position, single_file_with_position} | ||
96 | }; | ||
97 | use ra_db::SourceDatabase; | ||
98 | |||
99 | fn test_assit(name: &str, code: &str) { | ||
100 | let (analysis, position) = if code.contains("//-") { | ||
101 | analysis_and_position(code) | ||
102 | } else { | ||
103 | single_file_with_position(code) | ||
104 | }; | ||
105 | let frange = FileRange { | ||
106 | file_id: position.file_id, | ||
107 | range: TextRange::offset_len(position.offset, TextUnit::from(1)), | ||
108 | }; | ||
109 | let source_file = analysis | ||
110 | .with_db(|db| db.parse(frange.file_id)) | ||
111 | .expect("source file"); | ||
112 | let ret = analysis | ||
113 | .with_db(|db| crate::assits::assists(db, frange.file_id, &source_file, frange.range)) | ||
114 | .expect("assits"); | ||
115 | |||
116 | assert_debug_snapshot_matches!(name, ret); | ||
117 | } | ||
118 | |||
119 | #[test] | ||
120 | fn test_fill_match_arm() { | ||
121 | test_assit( | ||
122 | "fill_match_arm1", | ||
123 | r#" | ||
124 | enum A { | ||
125 | As, | ||
126 | Bs, | ||
127 | Cs(String), | ||
128 | Ds(String, String), | ||
129 | Es{x: usize, y: usize} | ||
130 | } | ||
131 | |||
132 | fn main() { | ||
133 | let a = A::As; | ||
134 | match a<|> | ||
135 | } | ||
136 | "#, | ||
137 | ); | ||
138 | |||
139 | test_assit( | ||
140 | "fill_match_arm2", | ||
141 | r#" | ||
142 | enum A { | ||
143 | As, | ||
144 | Bs, | ||
145 | Cs(String), | ||
146 | Ds(String, String), | ||
147 | Es{x: usize, y: usize} | ||
148 | } | ||
149 | |||
150 | fn main() { | ||
151 | let a = A::As; | ||
152 | match a<|> {} | ||
153 | } | ||
154 | "#, | ||
155 | ); | ||
156 | } | ||
157 | } | ||
diff --git a/crates/ra_ide_api/src/assits/snapshots/tests__fill_match_arm1.snap b/crates/ra_ide_api/src/assits/snapshots/tests__fill_match_arm1.snap new file mode 100644 index 000000000..980726d92 --- /dev/null +++ b/crates/ra_ide_api/src/assits/snapshots/tests__fill_match_arm1.snap | |||
@@ -0,0 +1,20 @@ | |||
1 | --- | ||
2 | created: "2019-02-03T15:38:46.094184+00:00" | ||
3 | creator: [email protected] | ||
4 | expression: ret | ||
5 | source: crates/ra_ide_api/src/assits/fill_match_arm.rs | ||
6 | --- | ||
7 | [ | ||
8 | LocalEdit { | ||
9 | label: "fill match arms", | ||
10 | edit: TextEdit { | ||
11 | atoms: [ | ||
12 | AtomTextEdit { | ||
13 | delete: [211; 218), | ||
14 | insert: "match a {\n A::As => (),\n A::Bs => (),\n A::Cs(_) => (),\n A::Ds(_, _) => (),\n A::Es{x, y} => (),\n }" | ||
15 | } | ||
16 | ] | ||
17 | }, | ||
18 | cursor_position: None | ||
19 | } | ||
20 | ] | ||
diff --git a/crates/ra_ide_api/src/assits/snapshots/tests__fill_match_arm2.snap b/crates/ra_ide_api/src/assits/snapshots/tests__fill_match_arm2.snap new file mode 100644 index 000000000..cee0efe74 --- /dev/null +++ b/crates/ra_ide_api/src/assits/snapshots/tests__fill_match_arm2.snap | |||
@@ -0,0 +1,20 @@ | |||
1 | --- | ||
2 | created: "2019-02-03T15:41:34.640074+00:00" | ||
3 | creator: [email protected] | ||
4 | expression: ret | ||
5 | source: crates/ra_ide_api/src/assits/fill_match_arm.rs | ||
6 | --- | ||
7 | [ | ||
8 | LocalEdit { | ||
9 | label: "fill match arms", | ||
10 | edit: TextEdit { | ||
11 | atoms: [ | ||
12 | AtomTextEdit { | ||
13 | delete: [211; 221), | ||
14 | insert: "match a {\n A::As => (),\n A::Bs => (),\n A::Cs(_) => (),\n A::Ds(_, _) => (),\n A::Es{x, y} => (),\n }" | ||
15 | } | ||
16 | ] | ||
17 | }, | ||
18 | cursor_position: None | ||
19 | } | ||
20 | ] | ||
diff --git a/crates/ra_ide_api/src/imp.rs b/crates/ra_ide_api/src/imp.rs index 31e0f5d6d..5f672367c 100644 --- a/crates/ra_ide_api/src/imp.rs +++ b/crates/ra_ide_api/src/imp.rs | |||
@@ -10,7 +10,7 @@ use ra_db::{ | |||
10 | SourceDatabase, SourceRoot, SourceRootId, | 10 | SourceDatabase, SourceRoot, SourceRootId, |
11 | salsa::{Database, SweepStrategy}, | 11 | salsa::{Database, SweepStrategy}, |
12 | }; | 12 | }; |
13 | use ra_ide_api_light::{self, assists, LocalEdit, Severity}; | 13 | use ra_ide_api_light::{self, LocalEdit, Severity}; |
14 | use ra_syntax::{ | 14 | use ra_syntax::{ |
15 | algo::find_node_at_offset, ast::{self, NameOwner}, AstNode, | 15 | algo::find_node_at_offset, ast::{self, NameOwner}, AstNode, |
16 | SourceFile, | 16 | SourceFile, |
@@ -238,8 +238,9 @@ impl db::RootDatabase { | |||
238 | 238 | ||
239 | pub(crate) fn assists(&self, frange: FileRange) -> Vec<SourceChange> { | 239 | pub(crate) fn assists(&self, frange: FileRange) -> Vec<SourceChange> { |
240 | let file = self.parse(frange.file_id); | 240 | let file = self.parse(frange.file_id); |
241 | assists::assists(&file, frange.range) | 241 | ra_ide_api_light::assists::assists(&file, frange.range) |
242 | .into_iter() | 242 | .into_iter() |
243 | .chain(crate::assits::assists(self, frange.file_id, &file, frange.range).into_iter()) | ||
243 | .map(|local_edit| SourceChange::from_local_edit(frange.file_id, local_edit)) | 244 | .map(|local_edit| SourceChange::from_local_edit(frange.file_id, local_edit)) |
244 | .collect() | 245 | .collect() |
245 | } | 246 | } |
diff --git a/crates/ra_ide_api/src/lib.rs b/crates/ra_ide_api/src/lib.rs index 5d8acf9df..a087a2fff 100644 --- a/crates/ra_ide_api/src/lib.rs +++ b/crates/ra_ide_api/src/lib.rs | |||
@@ -26,6 +26,7 @@ mod syntax_highlighting; | |||
26 | mod parent_module; | 26 | mod parent_module; |
27 | mod rename; | 27 | mod rename; |
28 | mod impls; | 28 | mod impls; |
29 | mod assits; | ||
29 | 30 | ||
30 | #[cfg(test)] | 31 | #[cfg(test)] |
31 | mod marks; | 32 | mod marks; |
diff --git a/crates/ra_ide_api_light/src/assists.rs b/crates/ra_ide_api_light/src/assists.rs index 8905b0419..e578805f1 100644 --- a/crates/ra_ide_api_light/src/assists.rs +++ b/crates/ra_ide_api_light/src/assists.rs | |||
@@ -104,7 +104,7 @@ pub enum Assist { | |||
104 | } | 104 | } |
105 | 105 | ||
106 | #[derive(Default)] | 106 | #[derive(Default)] |
107 | struct AssistBuilder { | 107 | pub struct AssistBuilder { |
108 | edit: TextEditBuilder, | 108 | edit: TextEditBuilder, |
109 | cursor_position: Option<TextUnit>, | 109 | cursor_position: Option<TextUnit>, |
110 | } | 110 | } |
@@ -142,11 +142,7 @@ impl<'a> AssistCtx<'a> { | |||
142 | } | 142 | } |
143 | let mut edit = AssistBuilder::default(); | 143 | let mut edit = AssistBuilder::default(); |
144 | f(&mut edit); | 144 | f(&mut edit); |
145 | Some(Assist::Edit(LocalEdit { | 145 | Some(edit.build(label)) |
146 | label: label.into(), | ||
147 | edit: edit.edit.finish(), | ||
148 | cursor_position: edit.cursor_position, | ||
149 | })) | ||
150 | } | 146 | } |
151 | 147 | ||
152 | pub(crate) fn leaf_at_offset(&self) -> LeafAtOffset<&'a SyntaxNode> { | 148 | pub(crate) fn leaf_at_offset(&self) -> LeafAtOffset<&'a SyntaxNode> { |
@@ -164,7 +160,7 @@ impl AssistBuilder { | |||
164 | fn replace(&mut self, range: TextRange, replace_with: impl Into<String>) { | 160 | fn replace(&mut self, range: TextRange, replace_with: impl Into<String>) { |
165 | self.edit.replace(range, replace_with.into()) | 161 | self.edit.replace(range, replace_with.into()) |
166 | } | 162 | } |
167 | fn replace_node_and_indent(&mut self, node: &SyntaxNode, replace_with: impl Into<String>) { | 163 | pub fn replace_node_and_indent(&mut self, node: &SyntaxNode, replace_with: impl Into<String>) { |
168 | let mut replace_with = replace_with.into(); | 164 | let mut replace_with = replace_with.into(); |
169 | if let Some(indent) = leading_indent(node) { | 165 | if let Some(indent) = leading_indent(node) { |
170 | replace_with = reindent(&replace_with, indent) | 166 | replace_with = reindent(&replace_with, indent) |
@@ -181,6 +177,13 @@ impl AssistBuilder { | |||
181 | fn set_cursor(&mut self, offset: TextUnit) { | 177 | fn set_cursor(&mut self, offset: TextUnit) { |
182 | self.cursor_position = Some(offset) | 178 | self.cursor_position = Some(offset) |
183 | } | 179 | } |
180 | pub fn build(self, label: impl Into<String>) -> Assist { | ||
181 | Assist::Edit(LocalEdit { | ||
182 | label: label.into(), | ||
183 | cursor_position: self.cursor_position, | ||
184 | edit: self.edit.finish(), | ||
185 | }) | ||
186 | } | ||
184 | } | 187 | } |
185 | 188 | ||
186 | fn reindent(text: &str, indent: &str) -> String { | 189 | fn reindent(text: &str, indent: &str) -> String { |