Skip to content
This repository was archived by the owner on May 28, 2025. It is now read-only.

Commit 2347da8

Browse files
Generate enum variant assist
This also disables "generate function" when what we clearly want is to generate an enum variant. Co-authored-by: Maarten Flippo <[email protected]>
1 parent 1182387 commit 2347da8

File tree

4 files changed

+230
-0
lines changed

4 files changed

+230
-0
lines changed
Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
use hir::HasSource;
2+
use ide_db::assists::{AssistId, AssistKind};
3+
use syntax::{
4+
ast::{self, edit::IndentLevel},
5+
AstNode, TextSize,
6+
};
7+
8+
use crate::assist_context::{AssistContext, Assists};
9+
10+
// Assist: generate_enum_variant
11+
//
12+
// Adds a variant to an enum.
13+
//
14+
// ```
15+
// enum Countries {
16+
// Ghana,
17+
// }
18+
//
19+
// fn main() {
20+
// let country = Countries::Lesotho$0;
21+
// }
22+
// ```
23+
// ->
24+
// ```
25+
// enum Countries {
26+
// Ghana,
27+
// Lesotho,
28+
// }
29+
//
30+
// fn main() {
31+
// let country = Countries::Lesotho;
32+
// }
33+
// ```
34+
pub(crate) fn generate_enum_variant(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
35+
let path_expr: ast::PathExpr = ctx.find_node_at_offset()?;
36+
let path = path_expr.path()?;
37+
38+
if ctx.sema.resolve_path(&path).is_some() {
39+
// No need to generate anything if the path resolves
40+
return None;
41+
}
42+
43+
let name_ref = path.segment()?.name_ref()?;
44+
45+
if let Some(hir::PathResolution::Def(hir::ModuleDef::Adt(hir::Adt::Enum(e)))) =
46+
ctx.sema.resolve_path(&path.qualifier()?)
47+
{
48+
let target = path.syntax().text_range();
49+
return add_variant_to_accumulator(acc, ctx, target, e, &name_ref);
50+
}
51+
52+
None
53+
}
54+
55+
fn add_variant_to_accumulator(
56+
acc: &mut Assists,
57+
ctx: &AssistContext,
58+
target: syntax::TextRange,
59+
adt: hir::Enum,
60+
name_ref: &ast::NameRef,
61+
) -> Option<()> {
62+
let adt_ast = get_enum_ast(ctx, adt)?;
63+
64+
let enum_indent_level = IndentLevel::from_node(&adt_ast.syntax());
65+
66+
let offset = adt_ast.variant_list()?.syntax().text_range().end() - TextSize::of('}');
67+
68+
let prefix = if adt_ast.variant_list()?.variants().next().is_none() {
69+
format!("\n{}", IndentLevel(1))
70+
} else {
71+
format!("{}", IndentLevel(1))
72+
};
73+
let text = format!("{}{},\n{}", prefix, name_ref, enum_indent_level);
74+
75+
acc.add(
76+
AssistId("generate_enum_variant", AssistKind::Generate),
77+
"Generate variant",
78+
target,
79+
|builder| builder.insert(offset, text),
80+
)
81+
}
82+
83+
fn get_enum_ast(ctx: &AssistContext, adt: hir::Enum) -> Option<ast::Enum> {
84+
let range = adt.source(ctx.db())?.syntax().original_file_range(ctx.db());
85+
let file = ctx.sema.parse(range.file_id);
86+
let adt_ast: ast::Enum =
87+
ctx.sema.find_node_at_offset_with_macros(file.syntax(), range.range.start())?;
88+
89+
Some(adt_ast)
90+
}
91+
92+
#[cfg(test)]
93+
mod tests {
94+
use crate::tests::{check_assist, check_assist_not_applicable};
95+
96+
use super::*;
97+
98+
#[test]
99+
fn generate_basic_enum_variant_in_empty_enum() {
100+
check_assist(
101+
generate_enum_variant,
102+
r"
103+
enum Foo {}
104+
fn main() {
105+
Foo::Bar$0
106+
}
107+
",
108+
r"
109+
enum Foo {
110+
Bar,
111+
}
112+
fn main() {
113+
Foo::Bar
114+
}
115+
",
116+
)
117+
}
118+
119+
#[test]
120+
fn generate_basic_enum_variant_in_non_empty_enum() {
121+
check_assist(
122+
generate_enum_variant,
123+
r"
124+
enum Foo {
125+
Bar,
126+
}
127+
fn main() {
128+
Foo::Baz$0
129+
}
130+
",
131+
r"
132+
enum Foo {
133+
Bar,
134+
Baz,
135+
}
136+
fn main() {
137+
Foo::Baz
138+
}
139+
",
140+
)
141+
}
142+
143+
#[test]
144+
fn not_applicable_for_existing_variant() {
145+
check_assist_not_applicable(
146+
generate_enum_variant,
147+
r"
148+
enum Foo {
149+
Bar,
150+
}
151+
fn main() {
152+
Foo::Bar$0
153+
}
154+
",
155+
)
156+
}
157+
158+
#[test]
159+
fn indentation_level_is_correct() {
160+
check_assist(
161+
generate_enum_variant,
162+
r"
163+
mod m {
164+
enum Foo {
165+
Bar,
166+
}
167+
}
168+
fn main() {
169+
m::Foo::Baz$0
170+
}
171+
",
172+
r"
173+
mod m {
174+
enum Foo {
175+
Bar,
176+
Baz,
177+
}
178+
}
179+
fn main() {
180+
m::Foo::Baz
181+
}
182+
",
183+
)
184+
}
185+
}

crates/ide-assists/src/handlers/generate_function.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,10 @@ fn gen_fn(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
7171
get_fn_target(ctx, &target_module, call.clone())?
7272
}
7373
Some(hir::PathResolution::Def(hir::ModuleDef::Adt(adt))) => {
74+
if let hir::Adt::Enum(_) = adt {
75+
return None;
76+
}
77+
7478
let current_module = ctx.sema.scope(call.syntax())?.module();
7579
let module = adt.module(ctx.sema.db);
7680
target_module = if current_module == module { None } else { Some(module) };
@@ -1738,4 +1742,17 @@ fn foo(value: usize) ${0:-> _} {
17381742
",
17391743
)
17401744
}
1745+
1746+
#[test]
1747+
fn not_applicable_for_enum_variant() {
1748+
check_assist_not_applicable(
1749+
generate_function,
1750+
r"
1751+
enum Foo {}
1752+
fn main() {
1753+
Foo::Bar$0(true)
1754+
}
1755+
",
1756+
);
1757+
}
17411758
}

crates/ide-assists/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,7 @@ mod handlers {
139139
mod generate_documentation_template;
140140
mod generate_enum_is_method;
141141
mod generate_enum_projection_method;
142+
mod generate_enum_variant;
142143
mod generate_from_impl_for_enum;
143144
mod generate_function;
144145
mod generate_getter;
@@ -227,6 +228,7 @@ mod handlers {
227228
generate_enum_is_method::generate_enum_is_method,
228229
generate_enum_projection_method::generate_enum_as_method,
229230
generate_enum_projection_method::generate_enum_try_into_method,
231+
generate_enum_variant::generate_enum_variant,
230232
generate_from_impl_for_enum::generate_from_impl_for_enum,
231233
generate_function::generate_function,
232234
generate_impl::generate_impl,

crates/ide-assists/src/tests/generated.rs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1006,6 +1006,32 @@ impl Value {
10061006
)
10071007
}
10081008

1009+
#[test]
1010+
fn doctest_generate_enum_variant() {
1011+
check_doc_test(
1012+
"generate_enum_variant",
1013+
r#####"
1014+
enum Countries {
1015+
Ghana,
1016+
}
1017+
1018+
fn main() {
1019+
let country = Countries::Lesotho$0;
1020+
}
1021+
"#####,
1022+
r#####"
1023+
enum Countries {
1024+
Ghana,
1025+
Lesotho,
1026+
}
1027+
1028+
fn main() {
1029+
let country = Countries::Lesotho;
1030+
}
1031+
"#####,
1032+
)
1033+
}
1034+
10091035
#[test]
10101036
fn doctest_generate_from_impl_for_enum() {
10111037
check_doc_test(

0 commit comments

Comments
 (0)