aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--crates/ra_ide_api/src/assits.rs89
-rw-r--r--crates/ra_ide_api/src/assits/fill_match_arm.rs157
-rw-r--r--crates/ra_ide_api/src/assits/snapshots/tests__fill_match_arm1.snap20
-rw-r--r--crates/ra_ide_api/src/assits/snapshots/tests__fill_match_arm2.snap20
-rw-r--r--crates/ra_ide_api/src/imp.rs5
-rw-r--r--crates/ra_ide_api/src/lib.rs1
-rw-r--r--crates/ra_ide_api_light/src/assists.rs17
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 @@
1mod fill_match_arm;
2
3use ra_syntax::{
4 TextRange, SourceFile, AstNode,
5 algo::find_node_at_offset,
6};
7use ra_ide_api_light::{
8 LocalEdit,
9 assists::{
10 Assist,
11 AssistBuilder
12 }
13};
14use crate::{
15 db::RootDatabase,
16 FileId
17};
18
19/// Return all the assists applicable at the given position.
20pub(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)]
34pub 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
42impl<'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 @@
1use std::fmt::Write;
2use hir::{
3 AdtDef,
4 source_binder,
5 Ty,
6 FieldSource,
7};
8use ra_ide_api_light::{
9 assists::{
10 Assist,
11 AssistBuilder
12 }
13};
14use ra_syntax::{
15 ast::{
16 self,
17 AstNode,
18 }
19};
20
21use crate::assits::AssistCtx;
22
23pub 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)]
88mod 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---
2created: "2019-02-03T15:38:46.094184+00:00"
3creator: [email protected]
4expression: ret
5source: 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---
2created: "2019-02-03T15:41:34.640074+00:00"
3creator: [email protected]
4expression: ret
5source: 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};
13use ra_ide_api_light::{self, assists, LocalEdit, Severity}; 13use ra_ide_api_light::{self, LocalEdit, Severity};
14use ra_syntax::{ 14use 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;
26mod parent_module; 26mod parent_module;
27mod rename; 27mod rename;
28mod impls; 28mod impls;
29mod assits;
29 30
30#[cfg(test)] 31#[cfg(test)]
31mod marks; 32mod 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)]
107struct AssistBuilder { 107pub 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
186fn reindent(text: &str, indent: &str) -> String { 189fn reindent(text: &str, indent: &str) -> String {