aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--crates/assists/src/handlers/generate_enum_match_method.rs14
-rw-r--r--crates/assists/src/handlers/generate_getter.rs156
-rw-r--r--crates/assists/src/handlers/generate_getter_mut.rs159
-rw-r--r--crates/assists/src/handlers/generate_new.rs69
-rw-r--r--crates/assists/src/handlers/generate_setter.rs162
-rw-r--r--crates/assists/src/lib.rs6
-rw-r--r--crates/assists/src/tests.rs7
-rw-r--r--crates/assists/src/tests/generated.rs71
-rw-r--r--crates/assists/src/utils.rs31
-rw-r--r--crates/hir/src/source_analyzer.rs17
-rw-r--r--crates/ide/src/references.rs23
-rw-r--r--docs/dev/architecture.md2
-rw-r--r--editors/code/.eslintrc.js5
-rw-r--r--editors/code/src/client.ts4
-rw-r--r--editors/code/src/commands.ts12
-rw-r--r--editors/code/src/debug.ts12
-rw-r--r--editors/code/src/inlay_hints.ts2
-rw-r--r--editors/code/src/main.ts13
-rw-r--r--editors/code/src/run.ts7
-rw-r--r--editors/code/src/snippets.ts4
20 files changed, 675 insertions, 101 deletions
diff --git a/crates/assists/src/handlers/generate_enum_match_method.rs b/crates/assists/src/handlers/generate_enum_match_method.rs
index 9d6b161c9..c3ff38b66 100644
--- a/crates/assists/src/handlers/generate_enum_match_method.rs
+++ b/crates/assists/src/handlers/generate_enum_match_method.rs
@@ -4,7 +4,7 @@ use syntax::ast::{self, AstNode, NameOwner};
4use test_utils::mark; 4use test_utils::mark;
5 5
6use crate::{ 6use crate::{
7 utils::{find_impl_block, find_struct_impl}, 7 utils::{find_impl_block, find_struct_impl, generate_impl_text},
8 AssistContext, AssistId, AssistKind, Assists, 8 AssistContext, AssistId, AssistKind, Assists,
9}; 9};
10 10
@@ -82,7 +82,7 @@ pub(crate) fn generate_enum_match_method(acc: &mut Assists, ctx: &AssistContext)
82 let start_offset = impl_def 82 let start_offset = impl_def
83 .and_then(|impl_def| find_impl_block(impl_def, &mut buf)) 83 .and_then(|impl_def| find_impl_block(impl_def, &mut buf))
84 .unwrap_or_else(|| { 84 .unwrap_or_else(|| {
85 buf = generate_impl_text(&parent_enum, &buf); 85 buf = generate_impl_text(&ast::Adt::Enum(parent_enum.clone()), &buf);
86 parent_enum.syntax().text_range().end() 86 parent_enum.syntax().text_range().end()
87 }); 87 });
88 88
@@ -91,16 +91,6 @@ pub(crate) fn generate_enum_match_method(acc: &mut Assists, ctx: &AssistContext)
91 ) 91 )
92} 92}
93 93
94// Generates the surrounding `impl Type { <code> }` including type and lifetime
95// parameters
96fn generate_impl_text(strukt: &ast::Enum, code: &str) -> String {
97 let mut buf = String::with_capacity(code.len());
98 buf.push_str("\n\nimpl ");
99 buf.push_str(strukt.name().unwrap().text());
100 format_to!(buf, " {{\n{}\n}}", code);
101 buf
102}
103
104#[cfg(test)] 94#[cfg(test)]
105mod tests { 95mod tests {
106 use test_utils::mark; 96 use test_utils::mark;
diff --git a/crates/assists/src/handlers/generate_getter.rs b/crates/assists/src/handlers/generate_getter.rs
new file mode 100644
index 000000000..b63dfce41
--- /dev/null
+++ b/crates/assists/src/handlers/generate_getter.rs
@@ -0,0 +1,156 @@
1use stdx::{format_to, to_lower_snake_case};
2use syntax::ast::VisibilityOwner;
3use syntax::ast::{self, AstNode, NameOwner};
4
5use crate::{
6 utils::{find_impl_block, find_struct_impl, generate_impl_text},
7 AssistContext, AssistId, AssistKind, Assists,
8};
9
10// Assist: generate_getter
11//
12// Generate a getter method.
13//
14// ```
15// struct Person {
16// nam$0e: String,
17// }
18// ```
19// ->
20// ```
21// struct Person {
22// name: String,
23// }
24//
25// impl Person {
26// /// Get a reference to the person's name.
27// fn name(&self) -> &String {
28// &self.name
29// }
30// }
31// ```
32pub(crate) fn generate_getter(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
33 let strukt = ctx.find_node_at_offset::<ast::Struct>()?;
34 let field = ctx.find_node_at_offset::<ast::RecordField>()?;
35
36 let strukt_name = strukt.name()?;
37 let field_name = field.name()?;
38 let field_ty = field.ty()?;
39
40 // Return early if we've found an existing fn
41 let fn_name = to_lower_snake_case(&field_name.to_string());
42 let impl_def = find_struct_impl(&ctx, &ast::Adt::Struct(strukt.clone()), fn_name.as_str())?;
43
44 let target = field.syntax().text_range();
45 acc.add(
46 AssistId("generate_getter", AssistKind::Generate),
47 "Generate a getter method",
48 target,
49 |builder| {
50 let mut buf = String::with_capacity(512);
51
52 let fn_name_spaced = fn_name.replace('_', " ");
53 let strukt_name_spaced =
54 to_lower_snake_case(&strukt_name.to_string()).replace('_', " ");
55
56 if impl_def.is_some() {
57 buf.push('\n');
58 }
59
60 let vis = strukt.visibility().map_or(String::new(), |v| format!("{} ", v));
61 format_to!(
62 buf,
63 " /// Get a reference to the {}'s {}.
64 {}fn {}(&self) -> &{} {{
65 &self.{}
66 }}",
67 strukt_name_spaced,
68 fn_name_spaced,
69 vis,
70 fn_name,
71 field_ty,
72 fn_name,
73 );
74
75 let start_offset = impl_def
76 .and_then(|impl_def| find_impl_block(impl_def, &mut buf))
77 .unwrap_or_else(|| {
78 buf = generate_impl_text(&ast::Adt::Struct(strukt.clone()), &buf);
79 strukt.syntax().text_range().end()
80 });
81
82 builder.insert(start_offset, buf);
83 },
84 )
85}
86
87#[cfg(test)]
88mod tests {
89 use crate::tests::{check_assist, check_assist_not_applicable};
90
91 use super::*;
92
93 fn check_not_applicable(ra_fixture: &str) {
94 check_assist_not_applicable(generate_getter, ra_fixture)
95 }
96
97 #[test]
98 fn test_generate_getter_from_field() {
99 check_assist(
100 generate_getter,
101 r#"
102struct Context<T: Clone> {
103 dat$0a: T,
104}"#,
105 r#"
106struct Context<T: Clone> {
107 data: T,
108}
109
110impl<T: Clone> Context<T> {
111 /// Get a reference to the context's data.
112 fn data(&self) -> &T {
113 &self.data
114 }
115}"#,
116 );
117 }
118
119 #[test]
120 fn test_generate_getter_already_implemented() {
121 check_not_applicable(
122 r#"
123struct Context<T: Clone> {
124 dat$0a: T,
125}
126
127impl<T: Clone> Context<T> {
128 fn data(&self) -> &T {
129 &self.data
130 }
131}"#,
132 );
133 }
134
135 #[test]
136 fn test_generate_getter_from_field_with_visibility_marker() {
137 check_assist(
138 generate_getter,
139 r#"
140pub(crate) struct Context<T: Clone> {
141 dat$0a: T,
142}"#,
143 r#"
144pub(crate) struct Context<T: Clone> {
145 data: T,
146}
147
148impl<T: Clone> Context<T> {
149 /// Get a reference to the context's data.
150 pub(crate) fn data(&self) -> &T {
151 &self.data
152 }
153}"#,
154 );
155 }
156}
diff --git a/crates/assists/src/handlers/generate_getter_mut.rs b/crates/assists/src/handlers/generate_getter_mut.rs
new file mode 100644
index 000000000..b5085035e
--- /dev/null
+++ b/crates/assists/src/handlers/generate_getter_mut.rs
@@ -0,0 +1,159 @@
1use stdx::{format_to, to_lower_snake_case};
2use syntax::ast::VisibilityOwner;
3use syntax::ast::{self, AstNode, NameOwner};
4
5use crate::{
6 utils::{find_impl_block, find_struct_impl, generate_impl_text},
7 AssistContext, AssistId, AssistKind, Assists,
8};
9
10// Assist: generate_getter_mut
11//
12// Generate a mut getter method.
13//
14// ```
15// struct Person {
16// nam$0e: String,
17// }
18// ```
19// ->
20// ```
21// struct Person {
22// name: String,
23// }
24//
25// impl Person {
26// /// Get a mutable reference to the person's name.
27// fn name_mut(&mut self) -> &mut String {
28// &mut self.name
29// }
30// }
31// ```
32pub(crate) fn generate_getter_mut(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
33 let strukt = ctx.find_node_at_offset::<ast::Struct>()?;
34 let field = ctx.find_node_at_offset::<ast::RecordField>()?;
35
36 let strukt_name = strukt.name()?;
37 let field_name = field.name()?;
38 let field_ty = field.ty()?;
39
40 // Return early if we've found an existing fn
41 let fn_name = to_lower_snake_case(&field_name.to_string());
42 let impl_def = find_struct_impl(
43 &ctx,
44 &ast::Adt::Struct(strukt.clone()),
45 format!("{}_mut", fn_name).as_str(),
46 )?;
47
48 let target = field.syntax().text_range();
49 acc.add(
50 AssistId("generate_getter_mut", AssistKind::Generate),
51 "Generate a mut getter method",
52 target,
53 |builder| {
54 let mut buf = String::with_capacity(512);
55 let fn_name_spaced = fn_name.replace('_', " ");
56 let strukt_name_spaced =
57 to_lower_snake_case(&strukt_name.to_string()).replace('_', " ");
58
59 if impl_def.is_some() {
60 buf.push('\n');
61 }
62
63 let vis = strukt.visibility().map_or(String::new(), |v| format!("{} ", v));
64 format_to!(
65 buf,
66 " /// Get a mutable reference to the {}'s {}.
67 {}fn {}_mut(&mut self) -> &mut {} {{
68 &mut self.{}
69 }}",
70 strukt_name_spaced,
71 fn_name_spaced,
72 vis,
73 fn_name,
74 field_ty,
75 fn_name,
76 );
77
78 let start_offset = impl_def
79 .and_then(|impl_def| find_impl_block(impl_def, &mut buf))
80 .unwrap_or_else(|| {
81 buf = generate_impl_text(&ast::Adt::Struct(strukt.clone()), &buf);
82 strukt.syntax().text_range().end()
83 });
84
85 builder.insert(start_offset, buf);
86 },
87 )
88}
89
90#[cfg(test)]
91mod tests {
92 use crate::tests::{check_assist, check_assist_not_applicable};
93
94 use super::*;
95
96 fn check_not_applicable(ra_fixture: &str) {
97 check_assist_not_applicable(generate_getter_mut, ra_fixture)
98 }
99
100 #[test]
101 fn test_generate_getter_mut_from_field() {
102 check_assist(
103 generate_getter_mut,
104 r#"
105struct Context<T: Clone> {
106 dat$0a: T,
107}"#,
108 r#"
109struct Context<T: Clone> {
110 data: T,
111}
112
113impl<T: Clone> Context<T> {
114 /// Get a mutable reference to the context's data.
115 fn data_mut(&mut self) -> &mut T {
116 &mut self.data
117 }
118}"#,
119 );
120 }
121
122 #[test]
123 fn test_generate_getter_mut_already_implemented() {
124 check_not_applicable(
125 r#"
126struct Context<T: Clone> {
127 dat$0a: T,
128}
129
130impl<T: Clone> Context<T> {
131 fn data_mut(&mut self) -> &mut T {
132 &mut self.data
133 }
134}"#,
135 );
136 }
137
138 #[test]
139 fn test_generate_getter_mut_from_field_with_visibility_marker() {
140 check_assist(
141 generate_getter_mut,
142 r#"
143pub(crate) struct Context<T: Clone> {
144 dat$0a: T,
145}"#,
146 r#"
147pub(crate) struct Context<T: Clone> {
148 data: T,
149}
150
151impl<T: Clone> Context<T> {
152 /// Get a mutable reference to the context's data.
153 pub(crate) fn data_mut(&mut self) -> &mut T {
154 &mut self.data
155 }
156}"#,
157 );
158 }
159}
diff --git a/crates/assists/src/handlers/generate_new.rs b/crates/assists/src/handlers/generate_new.rs
index a9203d33f..c29077225 100644
--- a/crates/assists/src/handlers/generate_new.rs
+++ b/crates/assists/src/handlers/generate_new.rs
@@ -1,12 +1,10 @@
1use ast::Adt;
1use itertools::Itertools; 2use itertools::Itertools;
2use stdx::format_to; 3use stdx::format_to;
3use syntax::{ 4use syntax::ast::{self, AstNode, NameOwner, StructKind, VisibilityOwner};
4 ast::{self, AstNode, GenericParamsOwner, NameOwner, StructKind, VisibilityOwner},
5 SmolStr,
6};
7 5
8use crate::{ 6use crate::{
9 utils::{find_impl_block, find_struct_impl}, 7 utils::{find_impl_block, find_struct_impl, generate_impl_text},
10 AssistContext, AssistId, AssistKind, Assists, 8 AssistContext, AssistId, AssistKind, Assists,
11}; 9};
12 10
@@ -28,7 +26,6 @@ use crate::{
28// impl<T: Clone> Ctx<T> { 26// impl<T: Clone> Ctx<T> {
29// fn $0new(data: T) -> Self { Self { data } } 27// fn $0new(data: T) -> Self { Self { data } }
30// } 28// }
31//
32// ``` 29// ```
33pub(crate) fn generate_new(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { 30pub(crate) fn generate_new(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
34 let strukt = ctx.find_node_at_offset::<ast::Struct>()?; 31 let strukt = ctx.find_node_at_offset::<ast::Struct>()?;
@@ -40,7 +37,7 @@ pub(crate) fn generate_new(acc: &mut Assists, ctx: &AssistContext) -> Option<()>
40 }; 37 };
41 38
42 // Return early if we've found an existing new fn 39 // Return early if we've found an existing new fn
43 let impl_def = find_struct_impl(&ctx, &ast::Adt::Struct(strukt.clone()), "new")?; 40 let impl_def = find_struct_impl(&ctx, &Adt::Struct(strukt.clone()), "new")?;
44 41
45 let target = strukt.syntax().text_range(); 42 let target = strukt.syntax().text_range();
46 acc.add(AssistId("generate_new", AssistKind::Generate), "Generate `new`", target, |builder| { 43 acc.add(AssistId("generate_new", AssistKind::Generate), "Generate `new`", target, |builder| {
@@ -63,7 +60,7 @@ pub(crate) fn generate_new(acc: &mut Assists, ctx: &AssistContext) -> Option<()>
63 let start_offset = impl_def 60 let start_offset = impl_def
64 .and_then(|impl_def| find_impl_block(impl_def, &mut buf)) 61 .and_then(|impl_def| find_impl_block(impl_def, &mut buf))
65 .unwrap_or_else(|| { 62 .unwrap_or_else(|| {
66 buf = generate_impl_text(&strukt, &buf); 63 buf = generate_impl_text(&Adt::Struct(strukt.clone()), &buf);
67 strukt.syntax().text_range().end() 64 strukt.syntax().text_range().end()
68 }); 65 });
69 66
@@ -77,32 +74,6 @@ pub(crate) fn generate_new(acc: &mut Assists, ctx: &AssistContext) -> Option<()>
77 }) 74 })
78} 75}
79 76
80// Generates the surrounding `impl Type { <code> }` including type and lifetime
81// parameters
82fn generate_impl_text(strukt: &ast::Struct, code: &str) -> String {
83 let type_params = strukt.generic_param_list();
84 let mut buf = String::with_capacity(code.len());
85 buf.push_str("\n\nimpl");
86 if let Some(type_params) = &type_params {
87 format_to!(buf, "{}", type_params.syntax());
88 }
89 buf.push(' ');
90 buf.push_str(strukt.name().unwrap().text());
91 if let Some(type_params) = type_params {
92 let lifetime_params = type_params
93 .lifetime_params()
94 .filter_map(|it| it.lifetime())
95 .map(|it| SmolStr::from(it.text()));
96 let type_params =
97 type_params.type_params().filter_map(|it| it.name()).map(|it| SmolStr::from(it.text()));
98 format_to!(buf, "<{}>", lifetime_params.chain(type_params).format(", "))
99 }
100
101 format_to!(buf, " {{\n{}\n}}\n", code);
102
103 buf
104}
105
106#[cfg(test)] 77#[cfg(test)]
107mod tests { 78mod tests {
108 use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target}; 79 use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target};
@@ -120,8 +91,7 @@ mod tests {
120 91
121impl Foo { 92impl Foo {
122 fn $0new() -> Self { Self { } } 93 fn $0new() -> Self { Self { } }
123} 94}",
124",
125 ); 95 );
126 check_assist( 96 check_assist(
127 generate_new, 97 generate_new,
@@ -130,8 +100,7 @@ impl Foo {
130 100
131impl<T: Clone> Foo<T> { 101impl<T: Clone> Foo<T> {
132 fn $0new() -> Self { Self { } } 102 fn $0new() -> Self { Self { } }
133} 103}",
134",
135 ); 104 );
136 check_assist( 105 check_assist(
137 generate_new, 106 generate_new,
@@ -140,8 +109,7 @@ impl<T: Clone> Foo<T> {
140 109
141impl<'a, T: Foo<'a>> Foo<'a, T> { 110impl<'a, T: Foo<'a>> Foo<'a, T> {
142 fn $0new() -> Self { Self { } } 111 fn $0new() -> Self { Self { } }
143} 112}",
144",
145 ); 113 );
146 check_assist( 114 check_assist(
147 generate_new, 115 generate_new,
@@ -150,8 +118,7 @@ impl<'a, T: Foo<'a>> Foo<'a, T> {
150 118
151impl Foo { 119impl Foo {
152 fn $0new(baz: String) -> Self { Self { baz } } 120 fn $0new(baz: String) -> Self { Self { baz } }
153} 121}",
154",
155 ); 122 );
156 check_assist( 123 check_assist(
157 generate_new, 124 generate_new,
@@ -160,8 +127,7 @@ impl Foo {
160 127
161impl Foo { 128impl Foo {
162 fn $0new(baz: String, qux: Vec<i32>) -> Self { Self { baz, qux } } 129 fn $0new(baz: String, qux: Vec<i32>) -> Self { Self { baz, qux } }
163} 130}",
164",
165 ); 131 );
166 132
167 // Check that visibility modifiers don't get brought in for fields 133 // Check that visibility modifiers don't get brought in for fields
@@ -172,8 +138,7 @@ impl Foo {
172 138
173impl Foo { 139impl Foo {
174 fn $0new(baz: String, qux: Vec<i32>) -> Self { Self { baz, qux } } 140 fn $0new(baz: String, qux: Vec<i32>) -> Self { Self { baz, qux } }
175} 141}",
176",
177 ); 142 );
178 143
179 // Check that it reuses existing impls 144 // Check that it reuses existing impls
@@ -240,8 +205,7 @@ impl Foo {
240 205
241impl Foo { 206impl Foo {
242 pub fn $0new() -> Self { Self { } } 207 pub fn $0new() -> Self { Self { } }
243} 208}",
244",
245 ); 209 );
246 check_assist( 210 check_assist(
247 generate_new, 211 generate_new,
@@ -250,8 +214,7 @@ impl Foo {
250 214
251impl Foo { 215impl Foo {
252 pub(crate) fn $0new() -> Self { Self { } } 216 pub(crate) fn $0new() -> Self { Self { } }
253} 217}",
254",
255 ); 218 );
256 } 219 }
257 220
@@ -322,8 +285,7 @@ impl<T> Source<T> {
322 pub fn map<F: FnOnce(T) -> U, U>(self, f: F) -> Source<U> { 285 pub fn map<F: FnOnce(T) -> U, U>(self, f: F) -> Source<U> {
323 Source { file_id: self.file_id, ast: f(self.ast) } 286 Source { file_id: self.file_id, ast: f(self.ast) }
324 } 287 }
325} 288}"##,
326"##,
327 r##" 289 r##"
328pub struct AstId<N: AstNode> { 290pub struct AstId<N: AstNode> {
329 file_id: HirFileId, 291 file_id: HirFileId,
@@ -347,8 +309,7 @@ impl<T> Source<T> {
347 pub fn map<F: FnOnce(T) -> U, U>(self, f: F) -> Source<U> { 309 pub fn map<F: FnOnce(T) -> U, U>(self, f: F) -> Source<U> {
348 Source { file_id: self.file_id, ast: f(self.ast) } 310 Source { file_id: self.file_id, ast: f(self.ast) }
349 } 311 }
350} 312}"##,
351"##,
352 ); 313 );
353 } 314 }
354} 315}
diff --git a/crates/assists/src/handlers/generate_setter.rs b/crates/assists/src/handlers/generate_setter.rs
new file mode 100644
index 000000000..c9043a162
--- /dev/null
+++ b/crates/assists/src/handlers/generate_setter.rs
@@ -0,0 +1,162 @@
1use stdx::{format_to, to_lower_snake_case};
2use syntax::ast::VisibilityOwner;
3use syntax::ast::{self, AstNode, NameOwner};
4
5use crate::{
6 utils::{find_impl_block, find_struct_impl, generate_impl_text},
7 AssistContext, AssistId, AssistKind, Assists,
8};
9
10// Assist: generate_setter
11//
12// Generate a setter method.
13//
14// ```
15// struct Person {
16// nam$0e: String,
17// }
18// ```
19// ->
20// ```
21// struct Person {
22// name: String,
23// }
24//
25// impl Person {
26// /// Set the person's name.
27// fn set_name(&mut self, name: String) {
28// self.name = name;
29// }
30// }
31// ```
32pub(crate) fn generate_setter(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
33 let strukt = ctx.find_node_at_offset::<ast::Struct>()?;
34 let field = ctx.find_node_at_offset::<ast::RecordField>()?;
35
36 let strukt_name = strukt.name()?;
37 let field_name = field.name()?;
38 let field_ty = field.ty()?;
39
40 // Return early if we've found an existing fn
41 let fn_name = to_lower_snake_case(&field_name.to_string());
42 let impl_def = find_struct_impl(
43 &ctx,
44 &ast::Adt::Struct(strukt.clone()),
45 format!("set_{}", fn_name).as_str(),
46 )?;
47
48 let target = field.syntax().text_range();
49 acc.add(
50 AssistId("generate_setter", AssistKind::Generate),
51 "Generate a setter method",
52 target,
53 |builder| {
54 let mut buf = String::with_capacity(512);
55
56 let fn_name_spaced = fn_name.replace('_', " ");
57 let strukt_name_spaced =
58 to_lower_snake_case(&strukt_name.to_string()).replace('_', " ");
59
60 if impl_def.is_some() {
61 buf.push('\n');
62 }
63
64 let vis = strukt.visibility().map_or(String::new(), |v| format!("{} ", v));
65 format_to!(
66 buf,
67 " /// Set the {}'s {}.
68 {}fn set_{}(&mut self, {}: {}) {{
69 self.{} = {};
70 }}",
71 strukt_name_spaced,
72 fn_name_spaced,
73 vis,
74 fn_name,
75 fn_name,
76 field_ty,
77 fn_name,
78 fn_name,
79 );
80
81 let start_offset = impl_def
82 .and_then(|impl_def| find_impl_block(impl_def, &mut buf))
83 .unwrap_or_else(|| {
84 buf = generate_impl_text(&ast::Adt::Struct(strukt.clone()), &buf);
85 strukt.syntax().text_range().end()
86 });
87
88 builder.insert(start_offset, buf);
89 },
90 )
91}
92
93#[cfg(test)]
94mod tests {
95 use crate::tests::{check_assist, check_assist_not_applicable};
96
97 use super::*;
98
99 fn check_not_applicable(ra_fixture: &str) {
100 check_assist_not_applicable(generate_setter, ra_fixture)
101 }
102
103 #[test]
104 fn test_generate_setter_from_field() {
105 check_assist(
106 generate_setter,
107 r#"
108struct Person<T: Clone> {
109 dat$0a: T,
110}"#,
111 r#"
112struct Person<T: Clone> {
113 data: T,
114}
115
116impl<T: Clone> Person<T> {
117 /// Set the person's data.
118 fn set_data(&mut self, data: T) {
119 self.data = data;
120 }
121}"#,
122 );
123 }
124
125 #[test]
126 fn test_generate_setter_already_implemented() {
127 check_not_applicable(
128 r#"
129struct Person<T: Clone> {
130 dat$0a: T,
131}
132
133impl<T: Clone> Person<T> {
134 fn set_data(&mut self, data: T) {
135 self.data = data;
136 }
137}"#,
138 );
139 }
140
141 #[test]
142 fn test_generate_setter_from_field_with_visibility_marker() {
143 check_assist(
144 generate_setter,
145 r#"
146pub(crate) struct Person<T: Clone> {
147 dat$0a: T,
148}"#,
149 r#"
150pub(crate) struct Person<T: Clone> {
151 data: T,
152}
153
154impl<T: Clone> Person<T> {
155 /// Set the person's data.
156 pub(crate) fn set_data(&mut self, data: T) {
157 self.data = data;
158 }
159}"#,
160 );
161 }
162}
diff --git a/crates/assists/src/lib.rs b/crates/assists/src/lib.rs
index 83fbf6986..957efa6b9 100644
--- a/crates/assists/src/lib.rs
+++ b/crates/assists/src/lib.rs
@@ -130,8 +130,11 @@ mod handlers {
130 mod generate_enum_match_method; 130 mod generate_enum_match_method;
131 mod generate_from_impl_for_enum; 131 mod generate_from_impl_for_enum;
132 mod generate_function; 132 mod generate_function;
133 mod generate_getter;
134 mod generate_getter_mut;
133 mod generate_impl; 135 mod generate_impl;
134 mod generate_new; 136 mod generate_new;
137 mod generate_setter;
135 mod infer_function_return_type; 138 mod infer_function_return_type;
136 mod inline_function; 139 mod inline_function;
137 mod inline_local_variable; 140 mod inline_local_variable;
@@ -189,8 +192,11 @@ mod handlers {
189 generate_enum_match_method::generate_enum_match_method, 192 generate_enum_match_method::generate_enum_match_method,
190 generate_from_impl_for_enum::generate_from_impl_for_enum, 193 generate_from_impl_for_enum::generate_from_impl_for_enum,
191 generate_function::generate_function, 194 generate_function::generate_function,
195 generate_getter::generate_getter,
196 generate_getter_mut::generate_getter_mut,
192 generate_impl::generate_impl, 197 generate_impl::generate_impl,
193 generate_new::generate_new, 198 generate_new::generate_new,
199 generate_setter::generate_setter,
194 infer_function_return_type::infer_function_return_type, 200 infer_function_return_type::infer_function_return_type,
195 inline_function::inline_function, 201 inline_function::inline_function,
196 inline_local_variable::inline_local_variable, 202 inline_local_variable::inline_local_variable,
diff --git a/crates/assists/src/tests.rs b/crates/assists/src/tests.rs
index 32bd8698b..5b9992f15 100644
--- a/crates/assists/src/tests.rs
+++ b/crates/assists/src/tests.rs
@@ -49,14 +49,17 @@ pub(crate) fn check_assist_by_label(
49// FIXME: instead of having a separate function here, maybe use 49// FIXME: instead of having a separate function here, maybe use
50// `extract_ranges` and mark the target as `<target> </target>` in the 50// `extract_ranges` and mark the target as `<target> </target>` in the
51// fixture? 51// fixture?
52#[track_caller]
52pub(crate) fn check_assist_target(assist: Handler, ra_fixture: &str, target: &str) { 53pub(crate) fn check_assist_target(assist: Handler, ra_fixture: &str, target: &str) {
53 check(assist, ra_fixture, ExpectedResult::Target(target), None); 54 check(assist, ra_fixture, ExpectedResult::Target(target), None);
54} 55}
55 56
57#[track_caller]
56pub(crate) fn check_assist_not_applicable(assist: Handler, ra_fixture: &str) { 58pub(crate) fn check_assist_not_applicable(assist: Handler, ra_fixture: &str) {
57 check(assist, ra_fixture, ExpectedResult::NotApplicable, None); 59 check(assist, ra_fixture, ExpectedResult::NotApplicable, None);
58} 60}
59 61
62#[track_caller]
60fn check_doc_test(assist_id: &str, before: &str, after: &str) { 63fn check_doc_test(assist_id: &str, before: &str, after: &str) {
61 let after = trim_indent(after); 64 let after = trim_indent(after);
62 let (db, file_id, selection) = RootDatabase::with_range_or_offset(&before); 65 let (db, file_id, selection) = RootDatabase::with_range_or_offset(&before);
@@ -95,6 +98,7 @@ enum ExpectedResult<'a> {
95 Target(&'a str), 98 Target(&'a str),
96} 99}
97 100
101#[track_caller]
98fn check(handler: Handler, before: &str, expected: ExpectedResult, assist_label: Option<&str>) { 102fn check(handler: Handler, before: &str, expected: ExpectedResult, assist_label: Option<&str>) {
99 let (db, file_with_caret_id, range_or_offset) = RootDatabase::with_range_or_offset(before); 103 let (db, file_with_caret_id, range_or_offset) = RootDatabase::with_range_or_offset(before);
100 let text_without_caret = db.file_text(file_with_caret_id).to_string(); 104 let text_without_caret = db.file_text(file_with_caret_id).to_string();
@@ -169,6 +173,9 @@ fn assist_order_field_struct() {
169 let mut assists = assists.iter(); 173 let mut assists = assists.iter();
170 174
171 assert_eq!(assists.next().expect("expected assist").label, "Change visibility to pub(crate)"); 175 assert_eq!(assists.next().expect("expected assist").label, "Change visibility to pub(crate)");
176 assert_eq!(assists.next().expect("expected assist").label, "Generate a getter method");
177 assert_eq!(assists.next().expect("expected assist").label, "Generate a mut getter method");
178 assert_eq!(assists.next().expect("expected assist").label, "Generate a setter method");
172 assert_eq!(assists.next().expect("expected assist").label, "Add `#[derive]`"); 179 assert_eq!(assists.next().expect("expected assist").label, "Add `#[derive]`");
173} 180}
174 181
diff --git a/crates/assists/src/tests/generated.rs b/crates/assists/src/tests/generated.rs
index 0dbb05f2a..6f2b22bc2 100644
--- a/crates/assists/src/tests/generated.rs
+++ b/crates/assists/src/tests/generated.rs
@@ -534,6 +534,54 @@ fn bar(arg: &str, baz: Baz) ${0:-> ()} {
534} 534}
535 535
536#[test] 536#[test]
537fn doctest_generate_getter() {
538 check_doc_test(
539 "generate_getter",
540 r#####"
541struct Person {
542 nam$0e: String,
543}
544"#####,
545 r#####"
546struct Person {
547 name: String,
548}
549
550impl Person {
551 /// Get a reference to the person's name.
552 fn name(&self) -> &String {
553 &self.name
554 }
555}
556"#####,
557 )
558}
559
560#[test]
561fn doctest_generate_getter_mut() {
562 check_doc_test(
563 "generate_getter_mut",
564 r#####"
565struct Person {
566 nam$0e: String,
567}
568"#####,
569 r#####"
570struct Person {
571 name: String,
572}
573
574impl Person {
575 /// Get a mutable reference to the person's name.
576 fn name_mut(&mut self) -> &mut String {
577 &mut self.name
578 }
579}
580"#####,
581 )
582}
583
584#[test]
537fn doctest_generate_impl() { 585fn doctest_generate_impl() {
538 check_doc_test( 586 check_doc_test(
539 "generate_impl", 587 "generate_impl",
@@ -571,7 +619,30 @@ struct Ctx<T: Clone> {
571impl<T: Clone> Ctx<T> { 619impl<T: Clone> Ctx<T> {
572 fn $0new(data: T) -> Self { Self { data } } 620 fn $0new(data: T) -> Self { Self { data } }
573} 621}
622"#####,
623 )
624}
574 625
626#[test]
627fn doctest_generate_setter() {
628 check_doc_test(
629 "generate_setter",
630 r#####"
631struct Person {
632 nam$0e: String,
633}
634"#####,
635 r#####"
636struct Person {
637 name: String,
638}
639
640impl Person {
641 /// Set the person's name.
642 fn set_name(&mut self, name: String) {
643 self.name = name;
644 }
645}
575"#####, 646"#####,
576 ) 647 )
577} 648}
diff --git a/crates/assists/src/utils.rs b/crates/assists/src/utils.rs
index cd80c2958..643dade23 100644
--- a/crates/assists/src/utils.rs
+++ b/crates/assists/src/utils.rs
@@ -5,12 +5,13 @@ use std::ops;
5use hir::{Adt, HasSource}; 5use hir::{Adt, HasSource};
6use ide_db::{helpers::SnippetCap, RootDatabase}; 6use ide_db::{helpers::SnippetCap, RootDatabase};
7use itertools::Itertools; 7use itertools::Itertools;
8use stdx::format_to;
8use syntax::{ 9use syntax::{
9 ast::edit::AstNodeEdit, 10 ast::edit::AstNodeEdit,
10 ast::AttrsOwner, 11 ast::AttrsOwner,
11 ast::NameOwner, 12 ast::NameOwner,
12 ast::{self, edit, make, ArgListOwner}, 13 ast::{self, edit, make, ArgListOwner, GenericParamsOwner},
13 AstNode, Direction, 14 AstNode, Direction, SmolStr,
14 SyntaxKind::*, 15 SyntaxKind::*,
15 SyntaxNode, TextSize, T, 16 SyntaxNode, TextSize, T,
16}; 17};
@@ -354,3 +355,29 @@ pub(crate) fn find_impl_block(impl_def: ast::Impl, buf: &mut String) -> Option<T
354 .end(); 355 .end();
355 Some(start) 356 Some(start)
356} 357}
358
359// Generates the surrounding `impl Type { <code> }` including type and lifetime
360// parameters
361pub(crate) fn generate_impl_text(adt: &ast::Adt, code: &str) -> String {
362 let type_params = adt.generic_param_list();
363 let mut buf = String::with_capacity(code.len());
364 buf.push_str("\n\nimpl");
365 if let Some(type_params) = &type_params {
366 format_to!(buf, "{}", type_params.syntax());
367 }
368 buf.push(' ');
369 buf.push_str(adt.name().unwrap().text());
370 if let Some(type_params) = type_params {
371 let lifetime_params = type_params
372 .lifetime_params()
373 .filter_map(|it| it.lifetime())
374 .map(|it| SmolStr::from(it.text()));
375 let type_params =
376 type_params.type_params().filter_map(|it| it.name()).map(|it| SmolStr::from(it.text()));
377 format_to!(buf, "<{}>", lifetime_params.chain(type_params).format(", "))
378 }
379
380 format_to!(buf, " {{\n{}\n}}", code);
381
382 buf
383}
diff --git a/crates/hir/src/source_analyzer.rs b/crates/hir/src/source_analyzer.rs
index 626c3078a..bed3fa50f 100644
--- a/crates/hir/src/source_analyzer.rs
+++ b/crates/hir/src/source_analyzer.rs
@@ -222,8 +222,9 @@ impl SourceAnalyzer {
222 db: &dyn HirDatabase, 222 db: &dyn HirDatabase,
223 path: &ast::Path, 223 path: &ast::Path,
224 ) -> Option<PathResolution> { 224 ) -> Option<PathResolution> {
225 let parent = || path.syntax().parent();
225 let mut prefer_value_ns = false; 226 let mut prefer_value_ns = false;
226 if let Some(path_expr) = path.syntax().parent().and_then(ast::PathExpr::cast) { 227 if let Some(path_expr) = parent().and_then(ast::PathExpr::cast) {
227 let expr_id = self.expr_id(db, &path_expr.into())?; 228 let expr_id = self.expr_id(db, &path_expr.into())?;
228 let infer = self.infer.as_ref()?; 229 let infer = self.infer.as_ref()?;
229 if let Some(assoc) = infer.assoc_resolutions_for_expr(expr_id) { 230 if let Some(assoc) = infer.assoc_resolutions_for_expr(expr_id) {
@@ -237,7 +238,7 @@ impl SourceAnalyzer {
237 prefer_value_ns = true; 238 prefer_value_ns = true;
238 } 239 }
239 240
240 if let Some(path_pat) = path.syntax().parent().and_then(ast::PathPat::cast) { 241 if let Some(path_pat) = parent().and_then(ast::PathPat::cast) {
241 let pat_id = self.pat_id(&path_pat.into())?; 242 let pat_id = self.pat_id(&path_pat.into())?;
242 if let Some(assoc) = self.infer.as_ref()?.assoc_resolutions_for_pat(pat_id) { 243 if let Some(assoc) = self.infer.as_ref()?.assoc_resolutions_for_pat(pat_id) {
243 return Some(PathResolution::AssocItem(assoc.into())); 244 return Some(PathResolution::AssocItem(assoc.into()));
@@ -249,7 +250,7 @@ impl SourceAnalyzer {
249 } 250 }
250 } 251 }
251 252
252 if let Some(rec_lit) = path.syntax().parent().and_then(ast::RecordExpr::cast) { 253 if let Some(rec_lit) = parent().and_then(ast::RecordExpr::cast) {
253 let expr_id = self.expr_id(db, &rec_lit.into())?; 254 let expr_id = self.expr_id(db, &rec_lit.into())?;
254 if let Some(VariantId::EnumVariantId(variant)) = 255 if let Some(VariantId::EnumVariantId(variant)) =
255 self.infer.as_ref()?.variant_resolution_for_expr(expr_id) 256 self.infer.as_ref()?.variant_resolution_for_expr(expr_id)
@@ -258,8 +259,12 @@ impl SourceAnalyzer {
258 } 259 }
259 } 260 }
260 261
261 if let Some(rec_pat) = path.syntax().parent().and_then(ast::RecordPat::cast) { 262 if let Some(pat) = parent()
262 let pat_id = self.pat_id(&rec_pat.into())?; 263 .and_then(ast::RecordPat::cast)
264 .map(ast::Pat::from)
265 .or_else(|| parent().and_then(ast::TupleStructPat::cast).map(ast::Pat::from))
266 {
267 let pat_id = self.pat_id(&pat)?;
263 if let Some(VariantId::EnumVariantId(variant)) = 268 if let Some(VariantId::EnumVariantId(variant)) =
264 self.infer.as_ref()?.variant_resolution_for_pat(pat_id) 269 self.infer.as_ref()?.variant_resolution_for_pat(pat_id)
265 { 270 {
@@ -272,7 +277,7 @@ impl SourceAnalyzer {
272 277
273 // Case where path is a qualifier of another path, e.g. foo::bar::Baz where we 278 // Case where path is a qualifier of another path, e.g. foo::bar::Baz where we
274 // trying to resolve foo::bar. 279 // trying to resolve foo::bar.
275 if let Some(outer_path) = path.syntax().parent().and_then(ast::Path::cast) { 280 if let Some(outer_path) = parent().and_then(ast::Path::cast) {
276 if let Some(qualifier) = outer_path.qualifier() { 281 if let Some(qualifier) = outer_path.qualifier() {
277 if path == &qualifier { 282 if path == &qualifier {
278 return resolve_hir_path_qualifier(db, &self.resolver, &hir_path); 283 return resolve_hir_path_qualifier(db, &self.resolver, &hir_path);
diff --git a/crates/ide/src/references.rs b/crates/ide/src/references.rs
index 40d9487eb..6999dacee 100644
--- a/crates/ide/src/references.rs
+++ b/crates/ide/src/references.rs
@@ -1114,4 +1114,27 @@ trait Foo {
1114 "#]], 1114 "#]],
1115 ); 1115 );
1116 } 1116 }
1117
1118 #[test]
1119 fn test_self_variant_with_payload() {
1120 check(
1121 r#"
1122enum Foo { Bar() }
1123
1124impl Foo {
1125 fn foo(self) {
1126 match self {
1127 Self::Bar$0() => (),
1128 }
1129 }
1130}
1131
1132"#,
1133 expect![[r#"
1134 Bar Variant FileId(0) 11..16 11..14 Other
1135
1136 FileId(0) 89..92 Other
1137 "#]],
1138 );
1139 }
1117} 1140}
diff --git a/docs/dev/architecture.md b/docs/dev/architecture.md
index 01063824f..081ee5b9d 100644
--- a/docs/dev/architecture.md
+++ b/docs/dev/architecture.md
@@ -372,11 +372,11 @@ Tests which directly call various API functions are a liability, because they ma
372So most of the tests look like this: 372So most of the tests look like this:
373 373
374```rust 374```rust
375#[track_caller]
375fn check(input: &str, expect: expect_test::Expect) { 376fn check(input: &str, expect: expect_test::Expect) {
376 // The single place that actually exercises a particular API 377 // The single place that actually exercises a particular API
377} 378}
378 379
379
380#[test] 380#[test]
381fn foo() { 381fn foo() {
382 check("foo", expect![["bar"]]); 382 check("foo", expect![["bar"]]);
diff --git a/editors/code/.eslintrc.js b/editors/code/.eslintrc.js
index c6bf410f4..b145330a0 100644
--- a/editors/code/.eslintrc.js
+++ b/editors/code/.eslintrc.js
@@ -14,7 +14,7 @@ module.exports = {
14 "rules": { 14 "rules": {
15 "camelcase": ["error"], 15 "camelcase": ["error"],
16 "eqeqeq": ["error", "always", { "null": "ignore" }], 16 "eqeqeq": ["error", "always", { "null": "ignore" }],
17 "no-console": ["error"], 17 "no-console": ["error", { allow: ["warn", "error"] }],
18 "prefer-const": "error", 18 "prefer-const": "error",
19 "@typescript-eslint/member-delimiter-style": [ 19 "@typescript-eslint/member-delimiter-style": [
20 "error", 20 "error",
@@ -33,6 +33,7 @@ module.exports = {
33 "error", 33 "error",
34 "always" 34 "always"
35 ], 35 ],
36 "@typescript-eslint/no-unnecessary-type-assertion": "error" 36 "@typescript-eslint/no-unnecessary-type-assertion": "error",
37 "@typescript-eslint/no-floating-promises": "error"
37 } 38 }
38}; 39};
diff --git a/editors/code/src/client.ts b/editors/code/src/client.ts
index 539e487ec..aec5c70c0 100644
--- a/editors/code/src/client.ts
+++ b/editors/code/src/client.ts
@@ -11,7 +11,7 @@ export interface Env {
11} 11}
12 12
13function renderCommand(cmd: ra.CommandLink) { 13function renderCommand(cmd: ra.CommandLink) {
14 return `[${cmd.title}](command:${cmd.command}?${encodeURIComponent(JSON.stringify(cmd.arguments))} '${cmd.tooltip!}')`; 14 return `[${cmd.title}](command:${cmd.command}?${encodeURIComponent(JSON.stringify(cmd.arguments))} '${cmd.tooltip}')`;
15} 15}
16 16
17function renderHoverActions(actions: ra.CommandLinkGroup[]): vscode.MarkdownString { 17function renderHoverActions(actions: ra.CommandLinkGroup[]): vscode.MarkdownString {
@@ -138,7 +138,7 @@ export function createClient(serverPath: string, cwd: string, extraEnv: Env): lc
138 command: "rust-analyzer.applyActionGroup", 138 command: "rust-analyzer.applyActionGroup",
139 title: "", 139 title: "",
140 arguments: [items.map((item) => { 140 arguments: [items.map((item) => {
141 return { label: item.title, arguments: item.command!!.arguments!![0] }; 141 return { label: item.title, arguments: item.command!.arguments![0] };
142 })], 142 })],
143 }; 143 };
144 144
diff --git a/editors/code/src/commands.ts b/editors/code/src/commands.ts
index e3a23c0d2..283b9a160 100644
--- a/editors/code/src/commands.ts
+++ b/editors/code/src/commands.ts
@@ -125,7 +125,7 @@ export function joinLines(ctx: Ctx): Cmd {
125 ranges: editor.selections.map((it) => client.code2ProtocolConverter.asRange(it)), 125 ranges: editor.selections.map((it) => client.code2ProtocolConverter.asRange(it)),
126 textDocument: ctx.client.code2ProtocolConverter.asTextDocumentIdentifier(editor.document), 126 textDocument: ctx.client.code2ProtocolConverter.asTextDocumentIdentifier(editor.document),
127 }); 127 });
128 editor.edit((builder) => { 128 await editor.edit((builder) => {
129 client.protocol2CodeConverter.asTextEdits(items).forEach((edit: any) => { 129 client.protocol2CodeConverter.asTextEdits(items).forEach((edit: any) => {
130 builder.replace(edit.range, edit.newText); 130 builder.replace(edit.range, edit.newText);
131 }); 131 });
@@ -236,7 +236,7 @@ export function ssr(ctx: Ctx): Cmd {
236 const request = await vscode.window.showInputBox(options); 236 const request = await vscode.window.showInputBox(options);
237 if (!request) return; 237 if (!request) return;
238 238
239 vscode.window.withProgress({ 239 await vscode.window.withProgress({
240 location: vscode.ProgressLocation.Notification, 240 location: vscode.ProgressLocation.Notification,
241 title: "Structured search replace in progress...", 241 title: "Structured search replace in progress...",
242 cancellable: false, 242 cancellable: false,
@@ -457,10 +457,10 @@ export function reloadWorkspace(ctx: Ctx): Cmd {
457} 457}
458 458
459export function showReferences(ctx: Ctx): Cmd { 459export function showReferences(ctx: Ctx): Cmd {
460 return (uri: string, position: lc.Position, locations: lc.Location[]) => { 460 return async (uri: string, position: lc.Position, locations: lc.Location[]) => {
461 const client = ctx.client; 461 const client = ctx.client;
462 if (client) { 462 if (client) {
463 vscode.commands.executeCommand( 463 await vscode.commands.executeCommand(
464 'editor.action.showReferences', 464 'editor.action.showReferences',
465 vscode.Uri.parse(uri), 465 vscode.Uri.parse(uri),
466 client.protocol2CodeConverter.asPosition(position), 466 client.protocol2CodeConverter.asPosition(position),
@@ -474,7 +474,7 @@ export function applyActionGroup(_ctx: Ctx): Cmd {
474 return async (actions: { label: string; arguments: lc.CodeAction }[]) => { 474 return async (actions: { label: string; arguments: lc.CodeAction }[]) => {
475 const selectedAction = await vscode.window.showQuickPick(actions); 475 const selectedAction = await vscode.window.showQuickPick(actions);
476 if (!selectedAction) return; 476 if (!selectedAction) return;
477 vscode.commands.executeCommand( 477 await vscode.commands.executeCommand(
478 'rust-analyzer.resolveCodeAction', 478 'rust-analyzer.resolveCodeAction',
479 selectedAction.arguments, 479 selectedAction.arguments,
480 ); 480 );
@@ -510,7 +510,7 @@ export function openDocs(ctx: Ctx): Cmd {
510 const doclink = await client.sendRequest(ra.openDocs, { position, textDocument }); 510 const doclink = await client.sendRequest(ra.openDocs, { position, textDocument });
511 511
512 if (doclink != null) { 512 if (doclink != null) {
513 vscode.commands.executeCommand("vscode.open", vscode.Uri.parse(doclink)); 513 await vscode.commands.executeCommand("vscode.open", vscode.Uri.parse(doclink));
514 } 514 }
515 }; 515 };
516 516
diff --git a/editors/code/src/debug.ts b/editors/code/src/debug.ts
index 925126a16..3889a2773 100644
--- a/editors/code/src/debug.ts
+++ b/editors/code/src/debug.ts
@@ -77,7 +77,7 @@ async function getDebugConfiguration(ctx: Ctx, runnable: ra.Runnable): Promise<v
77 } 77 }
78 78
79 if (!debugEngine) { 79 if (!debugEngine) {
80 vscode.window.showErrorMessage(`Install [CodeLLDB](https://marketplace.visualstudio.com/items?itemName=vadimcn.vscode-lldb)` 80 await vscode.window.showErrorMessage(`Install [CodeLLDB](https://marketplace.visualstudio.com/items?itemName=vadimcn.vscode-lldb)`
81 + ` or [MS C++ tools](https://marketplace.visualstudio.com/items?itemName=ms-vscode.cpptools) extension for debugging.`); 81 + ` or [MS C++ tools](https://marketplace.visualstudio.com/items?itemName=ms-vscode.cpptools) extension for debugging.`);
82 return; 82 return;
83 } 83 }
@@ -86,12 +86,14 @@ async function getDebugConfiguration(ctx: Ctx, runnable: ra.Runnable): Promise<v
86 if (ctx.config.debug.openDebugPane) { 86 if (ctx.config.debug.openDebugPane) {
87 debugOutput.show(true); 87 debugOutput.show(true);
88 } 88 }
89 89 // folder exists or RA is not active.
90 const isMultiFolderWorkspace = vscode.workspace.workspaceFolders!.length > 1; 90 // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
91 const firstWorkspace = vscode.workspace.workspaceFolders![0]; // folder exists or RA is not active. 91 const workspaceFolders = vscode.workspace.workspaceFolders!;
92 const isMultiFolderWorkspace = workspaceFolders.length > 1;
93 const firstWorkspace = workspaceFolders[0];
92 const workspace = !isMultiFolderWorkspace || !runnable.args.workspaceRoot ? 94 const workspace = !isMultiFolderWorkspace || !runnable.args.workspaceRoot ?
93 firstWorkspace : 95 firstWorkspace :
94 vscode.workspace.workspaceFolders!.find(w => runnable.args.workspaceRoot?.includes(w.uri.fsPath)) || firstWorkspace; 96 workspaceFolders.find(w => runnable.args.workspaceRoot?.includes(w.uri.fsPath)) || firstWorkspace;
95 97
96 const wsFolder = path.normalize(workspace.uri.fsPath); 98 const wsFolder = path.normalize(workspace.uri.fsPath);
97 const workspaceQualifier = isMultiFolderWorkspace ? `:${workspace.name}` : ''; 99 const workspaceQualifier = isMultiFolderWorkspace ? `:${workspace.name}` : '';
diff --git a/editors/code/src/inlay_hints.ts b/editors/code/src/inlay_hints.ts
index 38eb1c15b..61db6b8d0 100644
--- a/editors/code/src/inlay_hints.ts
+++ b/editors/code/src/inlay_hints.ts
@@ -36,7 +36,7 @@ export function activateInlayHints(ctx: Ctx) {
36 maybeUpdater.onConfigChange, maybeUpdater, ctx.subscriptions 36 maybeUpdater.onConfigChange, maybeUpdater, ctx.subscriptions
37 ); 37 );
38 38
39 maybeUpdater.onConfigChange(); 39 maybeUpdater.onConfigChange().catch(console.error);
40} 40}
41 41
42const typeHints = createHintStyle("type"); 42const typeHints = createHintStyle("type");
diff --git a/editors/code/src/main.ts b/editors/code/src/main.ts
index 43cae5c7f..d18d6c8a9 100644
--- a/editors/code/src/main.ts
+++ b/editors/code/src/main.ts
@@ -76,7 +76,7 @@ async function tryActivate(context: vscode.ExtensionContext) {
76 // This a horribly, horribly wrong way to deal with this problem. 76 // This a horribly, horribly wrong way to deal with this problem.
77 ctx = await Ctx.create(config, context, serverPath, workspaceFolder.uri.fsPath); 77 ctx = await Ctx.create(config, context, serverPath, workspaceFolder.uri.fsPath);
78 78
79 setContextValue(RUST_PROJECT_CONTEXT_NAME, true); 79 await setContextValue(RUST_PROJECT_CONTEXT_NAME, true);
80 80
81 // Commands which invokes manually via command palette, shortcut, etc. 81 // Commands which invokes manually via command palette, shortcut, etc.
82 82
@@ -143,7 +143,7 @@ async function tryActivate(context: vscode.ExtensionContext) {
143} 143}
144 144
145export async function deactivate() { 145export async function deactivate() {
146 setContextValue(RUST_PROJECT_CONTEXT_NAME, undefined); 146 await setContextValue(RUST_PROJECT_CONTEXT_NAME, undefined);
147 await ctx?.client.stop(); 147 await ctx?.client.stop();
148 ctx = undefined; 148 ctx = undefined;
149} 149}
@@ -184,10 +184,10 @@ async function bootstrapExtension(config: Config, state: PersistentState): Promi
184 184
185 const release = await downloadWithRetryDialog(state, async () => { 185 const release = await downloadWithRetryDialog(state, async () => {
186 return await fetchRelease("nightly", state.githubToken); 186 return await fetchRelease("nightly", state.githubToken);
187 }).catch((e) => { 187 }).catch(async (e) => {
188 log.error(e); 188 log.error(e);
189 if (state.releaseId === undefined) { // Show error only for the initial download 189 if (state.releaseId === undefined) { // Show error only for the initial download
190 vscode.window.showErrorMessage(`Failed to download rust-analyzer nightly ${e}`); 190 await vscode.window.showErrorMessage(`Failed to download rust-analyzer nightly ${e}`);
191 } 191 }
192 return undefined; 192 return undefined;
193 }); 193 });
@@ -299,7 +299,7 @@ async function getServer(config: Config, state: PersistentState): Promise<string
299 }; 299 };
300 const platform = platforms[`${process.arch} ${process.platform}`]; 300 const platform = platforms[`${process.arch} ${process.platform}`];
301 if (platform === undefined) { 301 if (platform === undefined) {
302 vscode.window.showErrorMessage( 302 await vscode.window.showErrorMessage(
303 "Unfortunately we don't ship binaries for your platform yet. " + 303 "Unfortunately we don't ship binaries for your platform yet. " +
304 "You need to manually clone rust-analyzer repository and " + 304 "You need to manually clone rust-analyzer repository and " +
305 "run `cargo xtask install --server` to build the language server from sources. " + 305 "run `cargo xtask install --server` to build the language server from sources. " +
@@ -434,6 +434,7 @@ function warnAboutExtensionConflicts() {
434 vscode.window.showWarningMessage( 434 vscode.window.showWarningMessage(
435 `You have both the ${fst[0]} (${fst[1]}) and ${sec[0]} (${sec[1]}) ` + 435 `You have both the ${fst[0]} (${fst[1]}) and ${sec[0]} (${sec[1]}) ` +
436 "plugins enabled. These are known to conflict and cause various functions of " + 436 "plugins enabled. These are known to conflict and cause various functions of " +
437 "both plugins to not work correctly. You should disable one of them.", "Got it"); 437 "both plugins to not work correctly. You should disable one of them.", "Got it")
438 .then(() => { }, console.error);
438 }; 439 };
439} 440}
diff --git a/editors/code/src/run.ts b/editors/code/src/run.ts
index f42baed16..138e3f686 100644
--- a/editors/code/src/run.ts
+++ b/editors/code/src/run.ts
@@ -45,7 +45,7 @@ export async function selectRunnable(ctx: Ctx, prevRunnable?: RunnableQuickPick,
45 if (items.length === 0) { 45 if (items.length === 0) {
46 // it is the debug case, run always has at least 'cargo check ...' 46 // it is the debug case, run always has at least 'cargo check ...'
47 // see crates\rust-analyzer\src\main_loop\handlers.rs, handle_runnables 47 // see crates\rust-analyzer\src\main_loop\handlers.rs, handle_runnables
48 vscode.window.showErrorMessage("There's no debug target!"); 48 await vscode.window.showErrorMessage("There's no debug target!");
49 return; 49 return;
50 } 50 }
51 51
@@ -65,8 +65,8 @@ export async function selectRunnable(ctx: Ctx, prevRunnable?: RunnableQuickPick,
65 disposables.push( 65 disposables.push(
66 quickPick.onDidHide(() => close()), 66 quickPick.onDidHide(() => close()),
67 quickPick.onDidAccept(() => close(quickPick.selectedItems[0])), 67 quickPick.onDidAccept(() => close(quickPick.selectedItems[0])),
68 quickPick.onDidTriggerButton((_button) => { 68 quickPick.onDidTriggerButton(async (_button) => {
69 (async () => await makeDebugConfig(ctx, quickPick.activeItems[0].runnable))(); 69 await makeDebugConfig(ctx, quickPick.activeItems[0].runnable);
70 close(); 70 close();
71 }), 71 }),
72 quickPick.onDidChangeActive((active) => { 72 quickPick.onDidChangeActive((active) => {
@@ -139,6 +139,7 @@ export async function createTask(runnable: ra.Runnable, config: Config): Promise
139 overrideCargo: runnable.args.overrideCargo, 139 overrideCargo: runnable.args.overrideCargo,
140 }; 140 };
141 141
142 // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
142 const target = vscode.workspace.workspaceFolders![0]; // safe, see main activate() 143 const target = vscode.workspace.workspaceFolders![0]; // safe, see main activate()
143 const cargoTask = await tasks.buildCargoTask(target, definition, runnable.label, args, config.cargoRunner, true); 144 const cargoTask = await tasks.buildCargoTask(target, definition, runnable.label, args, config.cargoRunner, true);
144 cargoTask.presentationOptions.clear = true; 145 cargoTask.presentationOptions.clear = true;
diff --git a/editors/code/src/snippets.ts b/editors/code/src/snippets.ts
index fee736e7d..dc53ebe2e 100644
--- a/editors/code/src/snippets.ts
+++ b/editors/code/src/snippets.ts
@@ -62,7 +62,9 @@ function parseSnippet(snip: string): [string, [number, number]] | undefined {
62 const m = snip.match(/\$(0|\{0:([^}]*)\})/); 62 const m = snip.match(/\$(0|\{0:([^}]*)\})/);
63 if (!m) return undefined; 63 if (!m) return undefined;
64 const placeholder = m[2] ?? ""; 64 const placeholder = m[2] ?? "";
65 const range: [number, number] = [m.index!!, placeholder.length]; 65 if (m.index == null)
66 return undefined;
67 const range: [number, number] = [m.index, placeholder.length];
66 const insert = snip.replace(m[0], placeholder); 68 const insert = snip.replace(m[0], placeholder);
67 return [insert, range]; 69 return [insert, range];
68} 70}