Skip to content

Commit 3959653

Browse files
bors[bot]gfreezy
andcommitted
Merge #733
733: fill match arms r=matklad a=gfreezy fixed #626 Co-authored-by: gfreezy <[email protected]>
2 parents 5540808 + bfaefed commit 3959653

File tree

7 files changed

+300
-9
lines changed

7 files changed

+300
-9
lines changed

crates/ra_ide_api/src/assits.rs

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
}
Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
---
2+
created: "2019-02-03T15:38:46.094184+00:00"
3+
creator: insta@0.5.2
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+
]
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
---
2+
created: "2019-02-03T15:41:34.640074+00:00"
3+
creator: insta@0.5.2
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+
]

crates/ra_ide_api/src/imp.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ use ra_db::{
1010
SourceDatabase, SourceRoot, SourceRootId,
1111
salsa::{Database, SweepStrategy},
1212
};
13-
use ra_ide_api_light::{self, assists, LocalEdit, Severity};
13+
use ra_ide_api_light::{self, LocalEdit, Severity};
1414
use ra_syntax::{
1515
algo::find_node_at_offset, ast::{self, NameOwner}, AstNode,
1616
SourceFile,
@@ -238,8 +238,9 @@ impl db::RootDatabase {
238238

239239
pub(crate) fn assists(&self, frange: FileRange) -> Vec<SourceChange> {
240240
let file = self.parse(frange.file_id);
241-
assists::assists(&file, frange.range)
241+
ra_ide_api_light::assists::assists(&file, frange.range)
242242
.into_iter()
243+
.chain(crate::assits::assists(self, frange.file_id, &file, frange.range).into_iter())
243244
.map(|local_edit| SourceChange::from_local_edit(frange.file_id, local_edit))
244245
.collect()
245246
}

crates/ra_ide_api/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ mod syntax_highlighting;
2626
mod parent_module;
2727
mod rename;
2828
mod impls;
29+
mod assits;
2930

3031
#[cfg(test)]
3132
mod marks;

crates/ra_ide_api_light/src/assists.rs

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ pub enum Assist {
104104
}
105105

106106
#[derive(Default)]
107-
struct AssistBuilder {
107+
pub struct AssistBuilder {
108108
edit: TextEditBuilder,
109109
cursor_position: Option<TextUnit>,
110110
}
@@ -142,11 +142,7 @@ impl<'a> AssistCtx<'a> {
142142
}
143143
let mut edit = AssistBuilder::default();
144144
f(&mut edit);
145-
Some(Assist::Edit(LocalEdit {
146-
label: label.into(),
147-
edit: edit.edit.finish(),
148-
cursor_position: edit.cursor_position,
149-
}))
145+
Some(edit.build(label))
150146
}
151147

152148
pub(crate) fn leaf_at_offset(&self) -> LeafAtOffset<&'a SyntaxNode> {
@@ -164,7 +160,7 @@ impl AssistBuilder {
164160
fn replace(&mut self, range: TextRange, replace_with: impl Into<String>) {
165161
self.edit.replace(range, replace_with.into())
166162
}
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>) {
168164
let mut replace_with = replace_with.into();
169165
if let Some(indent) = leading_indent(node) {
170166
replace_with = reindent(&replace_with, indent)
@@ -181,6 +177,13 @@ impl AssistBuilder {
181177
fn set_cursor(&mut self, offset: TextUnit) {
182178
self.cursor_position = Some(offset)
183179
}
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+
}
184187
}
185188

186189
fn reindent(text: &str, indent: &str) -> String {

0 commit comments

Comments
 (0)