diff options
53 files changed, 1620 insertions, 835 deletions
diff --git a/.gitignore b/.gitignore index f835edef0..dab51647d 100644 --- a/.gitignore +++ b/.gitignore | |||
@@ -7,4 +7,4 @@ crates/*/target | |||
7 | *.log | 7 | *.log |
8 | *.iml | 8 | *.iml |
9 | .vscode/settings.json | 9 | .vscode/settings.json |
10 | cargo-timing*.html | 10 | *.html |
diff --git a/Cargo.lock b/Cargo.lock index 07f18c760..af27bfc85 100644 --- a/Cargo.lock +++ b/Cargo.lock | |||
@@ -243,9 +243,9 @@ dependencies = [ | |||
243 | 243 | ||
244 | [[package]] | 244 | [[package]] |
245 | name = "crossbeam-queue" | 245 | name = "crossbeam-queue" |
246 | version = "0.2.1" | 246 | version = "0.2.2" |
247 | source = "registry+https://github.com/rust-lang/crates.io-index" | 247 | source = "registry+https://github.com/rust-lang/crates.io-index" |
248 | checksum = "c695eeca1e7173472a32221542ae469b3e9aac3a4fc81f7696bcad82029493db" | 248 | checksum = "ab6bffe714b6bb07e42f201352c34f51fefd355ace793f9e638ebd52d23f98d2" |
249 | dependencies = [ | 249 | dependencies = [ |
250 | "cfg-if", | 250 | "cfg-if", |
251 | "crossbeam-utils", | 251 | "crossbeam-utils", |
@@ -640,9 +640,9 @@ dependencies = [ | |||
640 | 640 | ||
641 | [[package]] | 641 | [[package]] |
642 | name = "lsp-types" | 642 | name = "lsp-types" |
643 | version = "0.74.1" | 643 | version = "0.74.2" |
644 | source = "registry+https://github.com/rust-lang/crates.io-index" | 644 | source = "registry+https://github.com/rust-lang/crates.io-index" |
645 | checksum = "57c0e6a2b8837d27b29deb3f3e6dc1c6d2f57947677f9be1024e482ec5b59525" | 645 | checksum = "b360754e89e0e13c114245131382ba921d4ff1efabb918e549422938aaa8d392" |
646 | dependencies = [ | 646 | dependencies = [ |
647 | "base64", | 647 | "base64", |
648 | "bitflags", | 648 | "bitflags", |
@@ -809,9 +809,9 @@ dependencies = [ | |||
809 | 809 | ||
810 | [[package]] | 810 | [[package]] |
811 | name = "paste" | 811 | name = "paste" |
812 | version = "0.1.14" | 812 | version = "0.1.15" |
813 | source = "registry+https://github.com/rust-lang/crates.io-index" | 813 | source = "registry+https://github.com/rust-lang/crates.io-index" |
814 | checksum = "3431e8f72b90f8a7af91dec890d9814000cb371258e0ec7370d93e085361f531" | 814 | checksum = "d53181dcd37421c08d3b69f887784956674d09c3f9a47a04fece2b130a5b346b" |
815 | dependencies = [ | 815 | dependencies = [ |
816 | "paste-impl", | 816 | "paste-impl", |
817 | "proc-macro-hack", | 817 | "proc-macro-hack", |
@@ -819,9 +819,9 @@ dependencies = [ | |||
819 | 819 | ||
820 | [[package]] | 820 | [[package]] |
821 | name = "paste-impl" | 821 | name = "paste-impl" |
822 | version = "0.1.14" | 822 | version = "0.1.15" |
823 | source = "registry+https://github.com/rust-lang/crates.io-index" | 823 | source = "registry+https://github.com/rust-lang/crates.io-index" |
824 | checksum = "25af5fc872ba284d8d84608bf8a0fa9b5376c96c23f503b007dfd9e34dde5606" | 824 | checksum = "05ca490fa1c034a71412b4d1edcb904ec5a0981a4426c9eb2128c0fda7a68d17" |
825 | dependencies = [ | 825 | dependencies = [ |
826 | "proc-macro-hack", | 826 | "proc-macro-hack", |
827 | "proc-macro2", | 827 | "proc-macro2", |
@@ -1292,9 +1292,9 @@ checksum = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84" | |||
1292 | 1292 | ||
1293 | [[package]] | 1293 | [[package]] |
1294 | name = "regex" | 1294 | name = "regex" |
1295 | version = "1.3.7" | 1295 | version = "1.3.9" |
1296 | source = "registry+https://github.com/rust-lang/crates.io-index" | 1296 | source = "registry+https://github.com/rust-lang/crates.io-index" |
1297 | checksum = "a6020f034922e3194c711b82a627453881bc4682166cabb07134a10c26ba7692" | 1297 | checksum = "9c3780fcf44b193bc4d09f36d2a3c87b251da4a046c87795a0d35f4f927ad8e6" |
1298 | dependencies = [ | 1298 | dependencies = [ |
1299 | "aho-corasick", | 1299 | "aho-corasick", |
1300 | "memchr", | 1300 | "memchr", |
@@ -1304,9 +1304,9 @@ dependencies = [ | |||
1304 | 1304 | ||
1305 | [[package]] | 1305 | [[package]] |
1306 | name = "regex-syntax" | 1306 | name = "regex-syntax" |
1307 | version = "0.6.17" | 1307 | version = "0.6.18" |
1308 | source = "registry+https://github.com/rust-lang/crates.io-index" | 1308 | source = "registry+https://github.com/rust-lang/crates.io-index" |
1309 | checksum = "7fe5bd57d1d7414c6b5ed48563a2c855d995ff777729dcd91c369ec7fea395ae" | 1309 | checksum = "26412eb97c6b088a6997e05f69403a802a92d520de2f8e63c2b65f9e0f47c4e8" |
1310 | 1310 | ||
1311 | [[package]] | 1311 | [[package]] |
1312 | name = "relative-path" | 1312 | name = "relative-path" |
@@ -1492,18 +1492,18 @@ checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" | |||
1492 | 1492 | ||
1493 | [[package]] | 1493 | [[package]] |
1494 | name = "serde" | 1494 | name = "serde" |
1495 | version = "1.0.110" | 1495 | version = "1.0.111" |
1496 | source = "registry+https://github.com/rust-lang/crates.io-index" | 1496 | source = "registry+https://github.com/rust-lang/crates.io-index" |
1497 | checksum = "99e7b308464d16b56eba9964e4972a3eee817760ab60d88c3f86e1fecb08204c" | 1497 | checksum = "c9124df5b40cbd380080b2cc6ab894c040a3070d995f5c9dc77e18c34a8ae37d" |
1498 | dependencies = [ | 1498 | dependencies = [ |
1499 | "serde_derive", | 1499 | "serde_derive", |
1500 | ] | 1500 | ] |
1501 | 1501 | ||
1502 | [[package]] | 1502 | [[package]] |
1503 | name = "serde_derive" | 1503 | name = "serde_derive" |
1504 | version = "1.0.110" | 1504 | version = "1.0.111" |
1505 | source = "registry+https://github.com/rust-lang/crates.io-index" | 1505 | source = "registry+https://github.com/rust-lang/crates.io-index" |
1506 | checksum = "818fbf6bfa9a42d3bfcaca148547aa00c7b915bec71d1757aa2d44ca68771984" | 1506 | checksum = "3f2c3ac8e6ca1e9c80b8be1023940162bf81ae3cffbb1809474152f2ce1eb250" |
1507 | dependencies = [ | 1507 | dependencies = [ |
1508 | "proc-macro2", | 1508 | "proc-macro2", |
1509 | "quote", | 1509 | "quote", |
@@ -1577,9 +1577,9 @@ checksum = "ab16ced94dbd8a46c82fd81e3ed9a8727dac2977ea869d217bcc4ea1f122e81f" | |||
1577 | 1577 | ||
1578 | [[package]] | 1578 | [[package]] |
1579 | name = "syn" | 1579 | name = "syn" |
1580 | version = "1.0.25" | 1580 | version = "1.0.29" |
1581 | source = "registry+https://github.com/rust-lang/crates.io-index" | 1581 | source = "registry+https://github.com/rust-lang/crates.io-index" |
1582 | checksum = "f14a640819f79b72a710c0be059dce779f9339ae046c8bef12c361d56702146f" | 1582 | checksum = "bb37da98a55b1d08529362d9cbb863be17556873df2585904ab9d2bc951291d0" |
1583 | dependencies = [ | 1583 | dependencies = [ |
1584 | "proc-macro2", | 1584 | "proc-macro2", |
1585 | "quote", | 1585 | "quote", |
diff --git a/crates/ra_assists/src/handlers/add_from_impl_for_enum.rs b/crates/ra_assists/src/handlers/add_from_impl_for_enum.rs index 6a675e812..776bddf91 100644 --- a/crates/ra_assists/src/handlers/add_from_impl_for_enum.rs +++ b/crates/ra_assists/src/handlers/add_from_impl_for_enum.rs | |||
@@ -4,9 +4,9 @@ use test_utils::mark; | |||
4 | 4 | ||
5 | use crate::{utils::FamousDefs, AssistContext, AssistId, Assists}; | 5 | use crate::{utils::FamousDefs, AssistContext, AssistId, Assists}; |
6 | 6 | ||
7 | // Assist add_from_impl_for_enum | 7 | // Assist: add_from_impl_for_enum |
8 | // | 8 | // |
9 | // Adds a From impl for an enum variant with one tuple field | 9 | // Adds a From impl for an enum variant with one tuple field. |
10 | // | 10 | // |
11 | // ``` | 11 | // ``` |
12 | // enum A { <|>One(u32) } | 12 | // enum A { <|>One(u32) } |
diff --git a/crates/ra_assists/src/handlers/change_lifetime_anon_to_named.rs b/crates/ra_assists/src/handlers/introduce_named_lifetime.rs index 999aec421..beb5b7366 100644 --- a/crates/ra_assists/src/handlers/change_lifetime_anon_to_named.rs +++ b/crates/ra_assists/src/handlers/introduce_named_lifetime.rs | |||
@@ -1,12 +1,15 @@ | |||
1 | use crate::{assist_context::AssistBuilder, AssistContext, AssistId, Assists}; | 1 | use ra_syntax::{ |
2 | use ast::{NameOwner, ParamList, TypeAscriptionOwner, TypeParamList, TypeRef}; | 2 | ast::{self, NameOwner, TypeAscriptionOwner, TypeParamsOwner}, |
3 | use ra_syntax::{ast, ast::TypeParamsOwner, AstNode, SyntaxKind, TextRange, TextSize}; | 3 | AstNode, SyntaxKind, TextRange, TextSize, |
4 | }; | ||
4 | use rustc_hash::FxHashSet; | 5 | use rustc_hash::FxHashSet; |
5 | 6 | ||
6 | static ASSIST_NAME: &str = "change_lifetime_anon_to_named"; | 7 | use crate::{assist_context::AssistBuilder, AssistContext, AssistId, Assists}; |
7 | static ASSIST_LABEL: &str = "Give anonymous lifetime a name"; | 8 | |
9 | static ASSIST_NAME: &str = "introduce_named_lifetime"; | ||
10 | static ASSIST_LABEL: &str = "Introduce named lifetime"; | ||
8 | 11 | ||
9 | // Assist: change_lifetime_anon_to_named | 12 | // Assist: introduce_named_lifetime |
10 | // | 13 | // |
11 | // Change an anonymous lifetime to a named lifetime. | 14 | // Change an anonymous lifetime to a named lifetime. |
12 | // | 15 | // |
@@ -31,7 +34,7 @@ static ASSIST_LABEL: &str = "Give anonymous lifetime a name"; | |||
31 | // ``` | 34 | // ``` |
32 | // FIXME: How can we handle renaming any one of multiple anonymous lifetimes? | 35 | // FIXME: How can we handle renaming any one of multiple anonymous lifetimes? |
33 | // FIXME: should also add support for the case fun(f: &Foo) -> &<|>Foo | 36 | // FIXME: should also add support for the case fun(f: &Foo) -> &<|>Foo |
34 | pub(crate) fn change_lifetime_anon_to_named(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { | 37 | pub(crate) fn introduce_named_lifetime(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { |
35 | let lifetime_token = ctx | 38 | let lifetime_token = ctx |
36 | .find_token_at_offset(SyntaxKind::LIFETIME) | 39 | .find_token_at_offset(SyntaxKind::LIFETIME) |
37 | .filter(|lifetime| lifetime.text() == "'_")?; | 40 | .filter(|lifetime| lifetime.text() == "'_")?; |
@@ -52,7 +55,7 @@ fn generate_fn_def_assist( | |||
52 | fn_def: &ast::FnDef, | 55 | fn_def: &ast::FnDef, |
53 | lifetime_loc: TextRange, | 56 | lifetime_loc: TextRange, |
54 | ) -> Option<()> { | 57 | ) -> Option<()> { |
55 | let param_list: ParamList = fn_def.param_list()?; | 58 | let param_list: ast::ParamList = fn_def.param_list()?; |
56 | let new_lifetime_param = generate_unique_lifetime_param_name(&fn_def.type_param_list())?; | 59 | let new_lifetime_param = generate_unique_lifetime_param_name(&fn_def.type_param_list())?; |
57 | let end_of_fn_ident = fn_def.name()?.ident_token()?.text_range().end(); | 60 | let end_of_fn_ident = fn_def.name()?.ident_token()?.text_range().end(); |
58 | let self_param = | 61 | let self_param = |
@@ -67,7 +70,7 @@ fn generate_fn_def_assist( | |||
67 | let fn_params_without_lifetime: Vec<_> = param_list | 70 | let fn_params_without_lifetime: Vec<_> = param_list |
68 | .params() | 71 | .params() |
69 | .filter_map(|param| match param.ascribed_type() { | 72 | .filter_map(|param| match param.ascribed_type() { |
70 | Some(TypeRef::ReferenceType(ascribed_type)) | 73 | Some(ast::TypeRef::ReferenceType(ascribed_type)) |
71 | if ascribed_type.lifetime_token() == None => | 74 | if ascribed_type.lifetime_token() == None => |
72 | { | 75 | { |
73 | Some(ascribed_type.amp_token()?.text_range().end()) | 76 | Some(ascribed_type.amp_token()?.text_range().end()) |
@@ -106,7 +109,7 @@ fn generate_impl_def_assist( | |||
106 | /// Given a type parameter list, generate a unique lifetime parameter name | 109 | /// Given a type parameter list, generate a unique lifetime parameter name |
107 | /// which is not in the list | 110 | /// which is not in the list |
108 | fn generate_unique_lifetime_param_name( | 111 | fn generate_unique_lifetime_param_name( |
109 | existing_type_param_list: &Option<TypeParamList>, | 112 | existing_type_param_list: &Option<ast::TypeParamList>, |
110 | ) -> Option<char> { | 113 | ) -> Option<char> { |
111 | match existing_type_param_list { | 114 | match existing_type_param_list { |
112 | Some(type_params) => { | 115 | Some(type_params) => { |
@@ -151,7 +154,7 @@ mod tests { | |||
151 | #[test] | 154 | #[test] |
152 | fn test_example_case() { | 155 | fn test_example_case() { |
153 | check_assist( | 156 | check_assist( |
154 | change_lifetime_anon_to_named, | 157 | introduce_named_lifetime, |
155 | r#"impl Cursor<'_<|>> { | 158 | r#"impl Cursor<'_<|>> { |
156 | fn node(self) -> &SyntaxNode { | 159 | fn node(self) -> &SyntaxNode { |
157 | match self { | 160 | match self { |
@@ -172,7 +175,7 @@ mod tests { | |||
172 | #[test] | 175 | #[test] |
173 | fn test_example_case_simplified() { | 176 | fn test_example_case_simplified() { |
174 | check_assist( | 177 | check_assist( |
175 | change_lifetime_anon_to_named, | 178 | introduce_named_lifetime, |
176 | r#"impl Cursor<'_<|>> {"#, | 179 | r#"impl Cursor<'_<|>> {"#, |
177 | r#"impl<'a> Cursor<'a> {"#, | 180 | r#"impl<'a> Cursor<'a> {"#, |
178 | ); | 181 | ); |
@@ -181,7 +184,7 @@ mod tests { | |||
181 | #[test] | 184 | #[test] |
182 | fn test_example_case_cursor_after_tick() { | 185 | fn test_example_case_cursor_after_tick() { |
183 | check_assist( | 186 | check_assist( |
184 | change_lifetime_anon_to_named, | 187 | introduce_named_lifetime, |
185 | r#"impl Cursor<'<|>_> {"#, | 188 | r#"impl Cursor<'<|>_> {"#, |
186 | r#"impl<'a> Cursor<'a> {"#, | 189 | r#"impl<'a> Cursor<'a> {"#, |
187 | ); | 190 | ); |
@@ -190,7 +193,7 @@ mod tests { | |||
190 | #[test] | 193 | #[test] |
191 | fn test_example_case_cursor_before_tick() { | 194 | fn test_example_case_cursor_before_tick() { |
192 | check_assist( | 195 | check_assist( |
193 | change_lifetime_anon_to_named, | 196 | introduce_named_lifetime, |
194 | r#"impl Cursor<<|>'_> {"#, | 197 | r#"impl Cursor<<|>'_> {"#, |
195 | r#"impl<'a> Cursor<'a> {"#, | 198 | r#"impl<'a> Cursor<'a> {"#, |
196 | ); | 199 | ); |
@@ -198,23 +201,20 @@ mod tests { | |||
198 | 201 | ||
199 | #[test] | 202 | #[test] |
200 | fn test_not_applicable_cursor_position() { | 203 | fn test_not_applicable_cursor_position() { |
201 | check_assist_not_applicable(change_lifetime_anon_to_named, r#"impl Cursor<'_><|> {"#); | 204 | check_assist_not_applicable(introduce_named_lifetime, r#"impl Cursor<'_><|> {"#); |
202 | check_assist_not_applicable(change_lifetime_anon_to_named, r#"impl Cursor<|><'_> {"#); | 205 | check_assist_not_applicable(introduce_named_lifetime, r#"impl Cursor<|><'_> {"#); |
203 | } | 206 | } |
204 | 207 | ||
205 | #[test] | 208 | #[test] |
206 | fn test_not_applicable_lifetime_already_name() { | 209 | fn test_not_applicable_lifetime_already_name() { |
207 | check_assist_not_applicable(change_lifetime_anon_to_named, r#"impl Cursor<'a<|>> {"#); | 210 | check_assist_not_applicable(introduce_named_lifetime, r#"impl Cursor<'a<|>> {"#); |
208 | check_assist_not_applicable( | 211 | check_assist_not_applicable(introduce_named_lifetime, r#"fn my_fun<'a>() -> X<'a<|>>"#); |
209 | change_lifetime_anon_to_named, | ||
210 | r#"fn my_fun<'a>() -> X<'a<|>>"#, | ||
211 | ); | ||
212 | } | 212 | } |
213 | 213 | ||
214 | #[test] | 214 | #[test] |
215 | fn test_with_type_parameter() { | 215 | fn test_with_type_parameter() { |
216 | check_assist( | 216 | check_assist( |
217 | change_lifetime_anon_to_named, | 217 | introduce_named_lifetime, |
218 | r#"impl<T> Cursor<T, '_<|>>"#, | 218 | r#"impl<T> Cursor<T, '_<|>>"#, |
219 | r#"impl<T, 'a> Cursor<T, 'a>"#, | 219 | r#"impl<T, 'a> Cursor<T, 'a>"#, |
220 | ); | 220 | ); |
@@ -223,7 +223,7 @@ mod tests { | |||
223 | #[test] | 223 | #[test] |
224 | fn test_with_existing_lifetime_name_conflict() { | 224 | fn test_with_existing_lifetime_name_conflict() { |
225 | check_assist( | 225 | check_assist( |
226 | change_lifetime_anon_to_named, | 226 | introduce_named_lifetime, |
227 | r#"impl<'a, 'b> Cursor<'a, 'b, '_<|>>"#, | 227 | r#"impl<'a, 'b> Cursor<'a, 'b, '_<|>>"#, |
228 | r#"impl<'a, 'b, 'c> Cursor<'a, 'b, 'c>"#, | 228 | r#"impl<'a, 'b, 'c> Cursor<'a, 'b, 'c>"#, |
229 | ); | 229 | ); |
@@ -232,7 +232,7 @@ mod tests { | |||
232 | #[test] | 232 | #[test] |
233 | fn test_function_return_value_anon_lifetime_param() { | 233 | fn test_function_return_value_anon_lifetime_param() { |
234 | check_assist( | 234 | check_assist( |
235 | change_lifetime_anon_to_named, | 235 | introduce_named_lifetime, |
236 | r#"fn my_fun() -> X<'_<|>>"#, | 236 | r#"fn my_fun() -> X<'_<|>>"#, |
237 | r#"fn my_fun<'a>() -> X<'a>"#, | 237 | r#"fn my_fun<'a>() -> X<'a>"#, |
238 | ); | 238 | ); |
@@ -241,7 +241,7 @@ mod tests { | |||
241 | #[test] | 241 | #[test] |
242 | fn test_function_return_value_anon_reference_lifetime() { | 242 | fn test_function_return_value_anon_reference_lifetime() { |
243 | check_assist( | 243 | check_assist( |
244 | change_lifetime_anon_to_named, | 244 | introduce_named_lifetime, |
245 | r#"fn my_fun() -> &'_<|> X"#, | 245 | r#"fn my_fun() -> &'_<|> X"#, |
246 | r#"fn my_fun<'a>() -> &'a X"#, | 246 | r#"fn my_fun<'a>() -> &'a X"#, |
247 | ); | 247 | ); |
@@ -250,7 +250,7 @@ mod tests { | |||
250 | #[test] | 250 | #[test] |
251 | fn test_function_param_anon_lifetime() { | 251 | fn test_function_param_anon_lifetime() { |
252 | check_assist( | 252 | check_assist( |
253 | change_lifetime_anon_to_named, | 253 | introduce_named_lifetime, |
254 | r#"fn my_fun(x: X<'_<|>>)"#, | 254 | r#"fn my_fun(x: X<'_<|>>)"#, |
255 | r#"fn my_fun<'a>(x: X<'a>)"#, | 255 | r#"fn my_fun<'a>(x: X<'a>)"#, |
256 | ); | 256 | ); |
@@ -259,7 +259,7 @@ mod tests { | |||
259 | #[test] | 259 | #[test] |
260 | fn test_function_add_lifetime_to_params() { | 260 | fn test_function_add_lifetime_to_params() { |
261 | check_assist( | 261 | check_assist( |
262 | change_lifetime_anon_to_named, | 262 | introduce_named_lifetime, |
263 | r#"fn my_fun(f: &Foo) -> X<'_<|>>"#, | 263 | r#"fn my_fun(f: &Foo) -> X<'_<|>>"#, |
264 | r#"fn my_fun<'a>(f: &'a Foo) -> X<'a>"#, | 264 | r#"fn my_fun<'a>(f: &'a Foo) -> X<'a>"#, |
265 | ); | 265 | ); |
@@ -268,7 +268,7 @@ mod tests { | |||
268 | #[test] | 268 | #[test] |
269 | fn test_function_add_lifetime_to_params_in_presence_of_other_lifetime() { | 269 | fn test_function_add_lifetime_to_params_in_presence_of_other_lifetime() { |
270 | check_assist( | 270 | check_assist( |
271 | change_lifetime_anon_to_named, | 271 | introduce_named_lifetime, |
272 | r#"fn my_fun<'other>(f: &Foo, b: &'other Bar) -> X<'_<|>>"#, | 272 | r#"fn my_fun<'other>(f: &Foo, b: &'other Bar) -> X<'_<|>>"#, |
273 | r#"fn my_fun<'other, 'a>(f: &'a Foo, b: &'other Bar) -> X<'a>"#, | 273 | r#"fn my_fun<'other, 'a>(f: &'a Foo, b: &'other Bar) -> X<'a>"#, |
274 | ); | 274 | ); |
@@ -278,7 +278,7 @@ mod tests { | |||
278 | fn test_function_not_applicable_without_self_and_multiple_unnamed_param_lifetimes() { | 278 | fn test_function_not_applicable_without_self_and_multiple_unnamed_param_lifetimes() { |
279 | // this is not permitted under lifetime elision rules | 279 | // this is not permitted under lifetime elision rules |
280 | check_assist_not_applicable( | 280 | check_assist_not_applicable( |
281 | change_lifetime_anon_to_named, | 281 | introduce_named_lifetime, |
282 | r#"fn my_fun(f: &Foo, b: &Bar) -> X<'_<|>>"#, | 282 | r#"fn my_fun(f: &Foo, b: &Bar) -> X<'_<|>>"#, |
283 | ); | 283 | ); |
284 | } | 284 | } |
@@ -286,7 +286,7 @@ mod tests { | |||
286 | #[test] | 286 | #[test] |
287 | fn test_function_add_lifetime_to_self_ref_param() { | 287 | fn test_function_add_lifetime_to_self_ref_param() { |
288 | check_assist( | 288 | check_assist( |
289 | change_lifetime_anon_to_named, | 289 | introduce_named_lifetime, |
290 | r#"fn my_fun<'other>(&self, f: &Foo, b: &'other Bar) -> X<'_<|>>"#, | 290 | r#"fn my_fun<'other>(&self, f: &Foo, b: &'other Bar) -> X<'_<|>>"#, |
291 | r#"fn my_fun<'other, 'a>(&'a self, f: &Foo, b: &'other Bar) -> X<'a>"#, | 291 | r#"fn my_fun<'other, 'a>(&'a self, f: &Foo, b: &'other Bar) -> X<'a>"#, |
292 | ); | 292 | ); |
@@ -295,7 +295,7 @@ mod tests { | |||
295 | #[test] | 295 | #[test] |
296 | fn test_function_add_lifetime_to_param_with_non_ref_self() { | 296 | fn test_function_add_lifetime_to_param_with_non_ref_self() { |
297 | check_assist( | 297 | check_assist( |
298 | change_lifetime_anon_to_named, | 298 | introduce_named_lifetime, |
299 | r#"fn my_fun<'other>(self, f: &Foo, b: &'other Bar) -> X<'_<|>>"#, | 299 | r#"fn my_fun<'other>(self, f: &Foo, b: &'other Bar) -> X<'_<|>>"#, |
300 | r#"fn my_fun<'other, 'a>(self, f: &'a Foo, b: &'other Bar) -> X<'a>"#, | 300 | r#"fn my_fun<'other, 'a>(self, f: &'a Foo, b: &'other Bar) -> X<'a>"#, |
301 | ); | 301 | ); |
diff --git a/crates/ra_assists/src/lib.rs b/crates/ra_assists/src/lib.rs index 3f8f7ffbf..fb5d59a87 100644 --- a/crates/ra_assists/src/lib.rs +++ b/crates/ra_assists/src/lib.rs | |||
@@ -112,7 +112,6 @@ mod handlers { | |||
112 | mod add_turbo_fish; | 112 | mod add_turbo_fish; |
113 | mod apply_demorgan; | 113 | mod apply_demorgan; |
114 | mod auto_import; | 114 | mod auto_import; |
115 | mod change_lifetime_anon_to_named; | ||
116 | mod change_return_type_to_result; | 115 | mod change_return_type_to_result; |
117 | mod change_visibility; | 116 | mod change_visibility; |
118 | mod early_return; | 117 | mod early_return; |
@@ -122,6 +121,7 @@ mod handlers { | |||
122 | mod flip_comma; | 121 | mod flip_comma; |
123 | mod flip_trait_bound; | 122 | mod flip_trait_bound; |
124 | mod inline_local_variable; | 123 | mod inline_local_variable; |
124 | mod introduce_named_lifetime; | ||
125 | mod introduce_variable; | 125 | mod introduce_variable; |
126 | mod invert_if; | 126 | mod invert_if; |
127 | mod merge_imports; | 127 | mod merge_imports; |
@@ -152,7 +152,6 @@ mod handlers { | |||
152 | add_turbo_fish::add_turbo_fish, | 152 | add_turbo_fish::add_turbo_fish, |
153 | apply_demorgan::apply_demorgan, | 153 | apply_demorgan::apply_demorgan, |
154 | auto_import::auto_import, | 154 | auto_import::auto_import, |
155 | change_lifetime_anon_to_named::change_lifetime_anon_to_named, | ||
156 | change_return_type_to_result::change_return_type_to_result, | 155 | change_return_type_to_result::change_return_type_to_result, |
157 | change_visibility::change_visibility, | 156 | change_visibility::change_visibility, |
158 | early_return::convert_to_guarded_return, | 157 | early_return::convert_to_guarded_return, |
@@ -162,6 +161,7 @@ mod handlers { | |||
162 | flip_comma::flip_comma, | 161 | flip_comma::flip_comma, |
163 | flip_trait_bound::flip_trait_bound, | 162 | flip_trait_bound::flip_trait_bound, |
164 | inline_local_variable::inline_local_variable, | 163 | inline_local_variable::inline_local_variable, |
164 | introduce_named_lifetime::introduce_named_lifetime, | ||
165 | introduce_variable::introduce_variable, | 165 | introduce_variable::introduce_variable, |
166 | invert_if::invert_if, | 166 | invert_if::invert_if, |
167 | merge_imports::merge_imports, | 167 | merge_imports::merge_imports, |
diff --git a/crates/ra_assists/src/tests/generated.rs b/crates/ra_assists/src/tests/generated.rs index abffbf97c..d17504529 100644 --- a/crates/ra_assists/src/tests/generated.rs +++ b/crates/ra_assists/src/tests/generated.rs | |||
@@ -59,6 +59,25 @@ fn main() { | |||
59 | } | 59 | } |
60 | 60 | ||
61 | #[test] | 61 | #[test] |
62 | fn doctest_add_from_impl_for_enum() { | ||
63 | check_doc_test( | ||
64 | "add_from_impl_for_enum", | ||
65 | r#####" | ||
66 | enum A { <|>One(u32) } | ||
67 | "#####, | ||
68 | r#####" | ||
69 | enum A { One(u32) } | ||
70 | |||
71 | impl From<u32> for A { | ||
72 | fn from(v: u32) -> Self { | ||
73 | A::One(v) | ||
74 | } | ||
75 | } | ||
76 | "#####, | ||
77 | ) | ||
78 | } | ||
79 | |||
80 | #[test] | ||
62 | fn doctest_add_function() { | 81 | fn doctest_add_function() { |
63 | check_doc_test( | 82 | check_doc_test( |
64 | "add_function", | 83 | "add_function", |
@@ -269,31 +288,6 @@ pub mod std { pub mod collections { pub struct HashMap { } } } | |||
269 | } | 288 | } |
270 | 289 | ||
271 | #[test] | 290 | #[test] |
272 | fn doctest_change_lifetime_anon_to_named() { | ||
273 | check_doc_test( | ||
274 | "change_lifetime_anon_to_named", | ||
275 | r#####" | ||
276 | impl Cursor<'_<|>> { | ||
277 | fn node(self) -> &SyntaxNode { | ||
278 | match self { | ||
279 | Cursor::Replace(node) | Cursor::Before(node) => node, | ||
280 | } | ||
281 | } | ||
282 | } | ||
283 | "#####, | ||
284 | r#####" | ||
285 | impl<'a> Cursor<'a> { | ||
286 | fn node(self) -> &SyntaxNode { | ||
287 | match self { | ||
288 | Cursor::Replace(node) | Cursor::Before(node) => node, | ||
289 | } | ||
290 | } | ||
291 | } | ||
292 | "#####, | ||
293 | ) | ||
294 | } | ||
295 | |||
296 | #[test] | ||
297 | fn doctest_change_return_type_to_result() { | 291 | fn doctest_change_return_type_to_result() { |
298 | check_doc_test( | 292 | check_doc_test( |
299 | "change_return_type_to_result", | 293 | "change_return_type_to_result", |
@@ -458,6 +452,31 @@ fn main() { | |||
458 | } | 452 | } |
459 | 453 | ||
460 | #[test] | 454 | #[test] |
455 | fn doctest_introduce_named_lifetime() { | ||
456 | check_doc_test( | ||
457 | "introduce_named_lifetime", | ||
458 | r#####" | ||
459 | impl Cursor<'_<|>> { | ||
460 | fn node(self) -> &SyntaxNode { | ||
461 | match self { | ||
462 | Cursor::Replace(node) | Cursor::Before(node) => node, | ||
463 | } | ||
464 | } | ||
465 | } | ||
466 | "#####, | ||
467 | r#####" | ||
468 | impl<'a> Cursor<'a> { | ||
469 | fn node(self) -> &SyntaxNode { | ||
470 | match self { | ||
471 | Cursor::Replace(node) | Cursor::Before(node) => node, | ||
472 | } | ||
473 | } | ||
474 | } | ||
475 | "#####, | ||
476 | ) | ||
477 | } | ||
478 | |||
479 | #[test] | ||
461 | fn doctest_introduce_variable() { | 480 | fn doctest_introduce_variable() { |
462 | check_doc_test( | 481 | check_doc_test( |
463 | "introduce_variable", | 482 | "introduce_variable", |
diff --git a/crates/ra_hir_def/src/body/lower.rs b/crates/ra_hir_def/src/body/lower.rs index 905c0cf5d..f159f80af 100644 --- a/crates/ra_hir_def/src/body/lower.rs +++ b/crates/ra_hir_def/src/body/lower.rs | |||
@@ -134,7 +134,7 @@ impl ExprCollector<'_> { | |||
134 | self.make_expr(expr, Err(SyntheticSyntax)) | 134 | self.make_expr(expr, Err(SyntheticSyntax)) |
135 | } | 135 | } |
136 | fn empty_block(&mut self) -> ExprId { | 136 | fn empty_block(&mut self) -> ExprId { |
137 | self.alloc_expr_desugared(Expr::Block { statements: Vec::new(), tail: None }) | 137 | self.alloc_expr_desugared(Expr::Block { statements: Vec::new(), tail: None, label: None }) |
138 | } | 138 | } |
139 | fn missing_expr(&mut self) -> ExprId { | 139 | fn missing_expr(&mut self) -> ExprId { |
140 | self.alloc_expr_desugared(Expr::Missing) | 140 | self.alloc_expr_desugared(Expr::Missing) |
@@ -215,7 +215,16 @@ impl ExprCollector<'_> { | |||
215 | ast::Expr::BlockExpr(e) => self.collect_block(e), | 215 | ast::Expr::BlockExpr(e) => self.collect_block(e), |
216 | ast::Expr::LoopExpr(e) => { | 216 | ast::Expr::LoopExpr(e) => { |
217 | let body = self.collect_block_opt(e.loop_body()); | 217 | let body = self.collect_block_opt(e.loop_body()); |
218 | self.alloc_expr(Expr::Loop { body }, syntax_ptr) | 218 | self.alloc_expr( |
219 | Expr::Loop { | ||
220 | body, | ||
221 | label: e | ||
222 | .label() | ||
223 | .and_then(|l| l.lifetime_token()) | ||
224 | .map(|l| Name::new_lifetime(&l)), | ||
225 | }, | ||
226 | syntax_ptr, | ||
227 | ) | ||
219 | } | 228 | } |
220 | ast::Expr::WhileExpr(e) => { | 229 | ast::Expr::WhileExpr(e) => { |
221 | let body = self.collect_block_opt(e.loop_body()); | 230 | let body = self.collect_block_opt(e.loop_body()); |
@@ -230,25 +239,56 @@ impl ExprCollector<'_> { | |||
230 | let pat = self.collect_pat(pat); | 239 | let pat = self.collect_pat(pat); |
231 | let match_expr = self.collect_expr_opt(condition.expr()); | 240 | let match_expr = self.collect_expr_opt(condition.expr()); |
232 | let placeholder_pat = self.missing_pat(); | 241 | let placeholder_pat = self.missing_pat(); |
233 | let break_ = self.alloc_expr_desugared(Expr::Break { expr: None }); | 242 | let break_ = |
243 | self.alloc_expr_desugared(Expr::Break { expr: None, label: None }); | ||
234 | let arms = vec![ | 244 | let arms = vec![ |
235 | MatchArm { pat, expr: body, guard: None }, | 245 | MatchArm { pat, expr: body, guard: None }, |
236 | MatchArm { pat: placeholder_pat, expr: break_, guard: None }, | 246 | MatchArm { pat: placeholder_pat, expr: break_, guard: None }, |
237 | ]; | 247 | ]; |
238 | let match_expr = | 248 | let match_expr = |
239 | self.alloc_expr_desugared(Expr::Match { expr: match_expr, arms }); | 249 | self.alloc_expr_desugared(Expr::Match { expr: match_expr, arms }); |
240 | return self.alloc_expr(Expr::Loop { body: match_expr }, syntax_ptr); | 250 | return self.alloc_expr( |
251 | Expr::Loop { | ||
252 | body: match_expr, | ||
253 | label: e | ||
254 | .label() | ||
255 | .and_then(|l| l.lifetime_token()) | ||
256 | .map(|l| Name::new_lifetime(&l)), | ||
257 | }, | ||
258 | syntax_ptr, | ||
259 | ); | ||
241 | } | 260 | } |
242 | }, | 261 | }, |
243 | }; | 262 | }; |
244 | 263 | ||
245 | self.alloc_expr(Expr::While { condition, body }, syntax_ptr) | 264 | self.alloc_expr( |
265 | Expr::While { | ||
266 | condition, | ||
267 | body, | ||
268 | label: e | ||
269 | .label() | ||
270 | .and_then(|l| l.lifetime_token()) | ||
271 | .map(|l| Name::new_lifetime(&l)), | ||
272 | }, | ||
273 | syntax_ptr, | ||
274 | ) | ||
246 | } | 275 | } |
247 | ast::Expr::ForExpr(e) => { | 276 | ast::Expr::ForExpr(e) => { |
248 | let iterable = self.collect_expr_opt(e.iterable()); | 277 | let iterable = self.collect_expr_opt(e.iterable()); |
249 | let pat = self.collect_pat_opt(e.pat()); | 278 | let pat = self.collect_pat_opt(e.pat()); |
250 | let body = self.collect_block_opt(e.loop_body()); | 279 | let body = self.collect_block_opt(e.loop_body()); |
251 | self.alloc_expr(Expr::For { iterable, pat, body }, syntax_ptr) | 280 | self.alloc_expr( |
281 | Expr::For { | ||
282 | iterable, | ||
283 | pat, | ||
284 | body, | ||
285 | label: e | ||
286 | .label() | ||
287 | .and_then(|l| l.lifetime_token()) | ||
288 | .map(|l| Name::new_lifetime(&l)), | ||
289 | }, | ||
290 | syntax_ptr, | ||
291 | ) | ||
252 | } | 292 | } |
253 | ast::Expr::CallExpr(e) => { | 293 | ast::Expr::CallExpr(e) => { |
254 | let callee = self.collect_expr_opt(e.expr()); | 294 | let callee = self.collect_expr_opt(e.expr()); |
@@ -301,13 +341,16 @@ impl ExprCollector<'_> { | |||
301 | .unwrap_or(Expr::Missing); | 341 | .unwrap_or(Expr::Missing); |
302 | self.alloc_expr(path, syntax_ptr) | 342 | self.alloc_expr(path, syntax_ptr) |
303 | } | 343 | } |
304 | ast::Expr::ContinueExpr(_e) => { | 344 | ast::Expr::ContinueExpr(e) => self.alloc_expr( |
305 | // FIXME: labels | 345 | Expr::Continue { label: e.lifetime_token().map(|l| Name::new_lifetime(&l)) }, |
306 | self.alloc_expr(Expr::Continue, syntax_ptr) | 346 | syntax_ptr, |
307 | } | 347 | ), |
308 | ast::Expr::BreakExpr(e) => { | 348 | ast::Expr::BreakExpr(e) => { |
309 | let expr = e.expr().map(|e| self.collect_expr(e)); | 349 | let expr = e.expr().map(|e| self.collect_expr(e)); |
310 | self.alloc_expr(Expr::Break { expr }, syntax_ptr) | 350 | self.alloc_expr( |
351 | Expr::Break { expr, label: e.lifetime_token().map(|l| Name::new_lifetime(&l)) }, | ||
352 | syntax_ptr, | ||
353 | ) | ||
311 | } | 354 | } |
312 | ast::Expr::ParenExpr(e) => { | 355 | ast::Expr::ParenExpr(e) => { |
313 | let inner = self.collect_expr_opt(e.expr()); | 356 | let inner = self.collect_expr_opt(e.expr()); |
@@ -529,7 +572,8 @@ impl ExprCollector<'_> { | |||
529 | }) | 572 | }) |
530 | .collect(); | 573 | .collect(); |
531 | let tail = block.expr().map(|e| self.collect_expr(e)); | 574 | let tail = block.expr().map(|e| self.collect_expr(e)); |
532 | self.alloc_expr(Expr::Block { statements, tail }, syntax_node_ptr) | 575 | let label = block.label().and_then(|l| l.lifetime_token()).map(|t| Name::new_lifetime(&t)); |
576 | self.alloc_expr(Expr::Block { statements, tail, label }, syntax_node_ptr) | ||
533 | } | 577 | } |
534 | 578 | ||
535 | fn collect_block_items(&mut self, block: &ast::BlockExpr) { | 579 | fn collect_block_items(&mut self, block: &ast::BlockExpr) { |
diff --git a/crates/ra_hir_def/src/body/scope.rs b/crates/ra_hir_def/src/body/scope.rs index 09e92b74e..e48ff38f9 100644 --- a/crates/ra_hir_def/src/body/scope.rs +++ b/crates/ra_hir_def/src/body/scope.rs | |||
@@ -138,10 +138,10 @@ fn compute_block_scopes( | |||
138 | fn compute_expr_scopes(expr: ExprId, body: &Body, scopes: &mut ExprScopes, scope: ScopeId) { | 138 | fn compute_expr_scopes(expr: ExprId, body: &Body, scopes: &mut ExprScopes, scope: ScopeId) { |
139 | scopes.set_scope(expr, scope); | 139 | scopes.set_scope(expr, scope); |
140 | match &body[expr] { | 140 | match &body[expr] { |
141 | Expr::Block { statements, tail } => { | 141 | Expr::Block { statements, tail, .. } => { |
142 | compute_block_scopes(&statements, *tail, body, scopes, scope); | 142 | compute_block_scopes(&statements, *tail, body, scopes, scope); |
143 | } | 143 | } |
144 | Expr::For { iterable, pat, body: body_expr } => { | 144 | Expr::For { iterable, pat, body: body_expr, .. } => { |
145 | compute_expr_scopes(*iterable, body, scopes, scope); | 145 | compute_expr_scopes(*iterable, body, scopes, scope); |
146 | let scope = scopes.new_scope(scope); | 146 | let scope = scopes.new_scope(scope); |
147 | scopes.add_bindings(body, scope, *pat); | 147 | scopes.add_bindings(body, scope, *pat); |
diff --git a/crates/ra_hir_def/src/expr.rs b/crates/ra_hir_def/src/expr.rs index f25c6f958..ca49b26d1 100644 --- a/crates/ra_hir_def/src/expr.rs +++ b/crates/ra_hir_def/src/expr.rs | |||
@@ -52,18 +52,22 @@ pub enum Expr { | |||
52 | Block { | 52 | Block { |
53 | statements: Vec<Statement>, | 53 | statements: Vec<Statement>, |
54 | tail: Option<ExprId>, | 54 | tail: Option<ExprId>, |
55 | label: Option<Name>, | ||
55 | }, | 56 | }, |
56 | Loop { | 57 | Loop { |
57 | body: ExprId, | 58 | body: ExprId, |
59 | label: Option<Name>, | ||
58 | }, | 60 | }, |
59 | While { | 61 | While { |
60 | condition: ExprId, | 62 | condition: ExprId, |
61 | body: ExprId, | 63 | body: ExprId, |
64 | label: Option<Name>, | ||
62 | }, | 65 | }, |
63 | For { | 66 | For { |
64 | iterable: ExprId, | 67 | iterable: ExprId, |
65 | pat: PatId, | 68 | pat: PatId, |
66 | body: ExprId, | 69 | body: ExprId, |
70 | label: Option<Name>, | ||
67 | }, | 71 | }, |
68 | Call { | 72 | Call { |
69 | callee: ExprId, | 73 | callee: ExprId, |
@@ -79,9 +83,12 @@ pub enum Expr { | |||
79 | expr: ExprId, | 83 | expr: ExprId, |
80 | arms: Vec<MatchArm>, | 84 | arms: Vec<MatchArm>, |
81 | }, | 85 | }, |
82 | Continue, | 86 | Continue { |
87 | label: Option<Name>, | ||
88 | }, | ||
83 | Break { | 89 | Break { |
84 | expr: Option<ExprId>, | 90 | expr: Option<ExprId>, |
91 | label: Option<Name>, | ||
85 | }, | 92 | }, |
86 | Return { | 93 | Return { |
87 | expr: Option<ExprId>, | 94 | expr: Option<ExprId>, |
@@ -225,7 +232,7 @@ impl Expr { | |||
225 | f(*else_branch); | 232 | f(*else_branch); |
226 | } | 233 | } |
227 | } | 234 | } |
228 | Expr::Block { statements, tail } => { | 235 | Expr::Block { statements, tail, .. } => { |
229 | for stmt in statements { | 236 | for stmt in statements { |
230 | match stmt { | 237 | match stmt { |
231 | Statement::Let { initializer, .. } => { | 238 | Statement::Let { initializer, .. } => { |
@@ -241,8 +248,8 @@ impl Expr { | |||
241 | } | 248 | } |
242 | } | 249 | } |
243 | Expr::TryBlock { body } => f(*body), | 250 | Expr::TryBlock { body } => f(*body), |
244 | Expr::Loop { body } => f(*body), | 251 | Expr::Loop { body, .. } => f(*body), |
245 | Expr::While { condition, body } => { | 252 | Expr::While { condition, body, .. } => { |
246 | f(*condition); | 253 | f(*condition); |
247 | f(*body); | 254 | f(*body); |
248 | } | 255 | } |
@@ -268,8 +275,8 @@ impl Expr { | |||
268 | f(arm.expr); | 275 | f(arm.expr); |
269 | } | 276 | } |
270 | } | 277 | } |
271 | Expr::Continue => {} | 278 | Expr::Continue { .. } => {} |
272 | Expr::Break { expr } | Expr::Return { expr } => { | 279 | Expr::Break { expr, .. } | Expr::Return { expr } => { |
273 | if let Some(expr) = expr { | 280 | if let Some(expr) = expr { |
274 | f(*expr); | 281 | f(*expr); |
275 | } | 282 | } |
diff --git a/crates/ra_hir_expand/src/name.rs b/crates/ra_hir_expand/src/name.rs index fecce224e..ea495cb11 100644 --- a/crates/ra_hir_expand/src/name.rs +++ b/crates/ra_hir_expand/src/name.rs | |||
@@ -37,6 +37,11 @@ impl Name { | |||
37 | Name(Repr::TupleField(idx)) | 37 | Name(Repr::TupleField(idx)) |
38 | } | 38 | } |
39 | 39 | ||
40 | pub fn new_lifetime(lt: &ra_syntax::SyntaxToken) -> Name { | ||
41 | assert!(lt.kind() == ra_syntax::SyntaxKind::LIFETIME); | ||
42 | Name(Repr::Text(lt.text().clone())) | ||
43 | } | ||
44 | |||
40 | /// Shortcut to create inline plain text name | 45 | /// Shortcut to create inline plain text name |
41 | const fn new_inline_ascii(text: &[u8]) -> Name { | 46 | const fn new_inline_ascii(text: &[u8]) -> Name { |
42 | Name::new_text(SmolStr::new_inline_from_ascii(text.len(), text)) | 47 | Name::new_text(SmolStr::new_inline_from_ascii(text.len(), text)) |
diff --git a/crates/ra_hir_ty/src/infer.rs b/crates/ra_hir_ty/src/infer.rs index 957d6e0b5..dc77e88e5 100644 --- a/crates/ra_hir_ty/src/infer.rs +++ b/crates/ra_hir_ty/src/infer.rs | |||
@@ -219,6 +219,17 @@ struct InferenceContext<'a> { | |||
219 | struct BreakableContext { | 219 | struct BreakableContext { |
220 | pub may_break: bool, | 220 | pub may_break: bool, |
221 | pub break_ty: Ty, | 221 | pub break_ty: Ty, |
222 | pub label: Option<name::Name>, | ||
223 | } | ||
224 | |||
225 | fn find_breakable<'c>( | ||
226 | ctxs: &'c mut [BreakableContext], | ||
227 | label: Option<&name::Name>, | ||
228 | ) -> Option<&'c mut BreakableContext> { | ||
229 | match label { | ||
230 | Some(_) => ctxs.iter_mut().rev().find(|ctx| ctx.label.as_ref() == label), | ||
231 | None => ctxs.last_mut(), | ||
232 | } | ||
222 | } | 233 | } |
223 | 234 | ||
224 | impl<'a> InferenceContext<'a> { | 235 | impl<'a> InferenceContext<'a> { |
diff --git a/crates/ra_hir_ty/src/infer/expr.rs b/crates/ra_hir_ty/src/infer/expr.rs index 78084cb57..4a98e2deb 100644 --- a/crates/ra_hir_ty/src/infer/expr.rs +++ b/crates/ra_hir_ty/src/infer/expr.rs | |||
@@ -22,8 +22,8 @@ use crate::{ | |||
22 | }; | 22 | }; |
23 | 23 | ||
24 | use super::{ | 24 | use super::{ |
25 | BindingMode, BreakableContext, Diverges, Expectation, InferenceContext, InferenceDiagnostic, | 25 | find_breakable, BindingMode, BreakableContext, Diverges, Expectation, InferenceContext, |
26 | TypeMismatch, | 26 | InferenceDiagnostic, TypeMismatch, |
27 | }; | 27 | }; |
28 | 28 | ||
29 | impl<'a> InferenceContext<'a> { | 29 | impl<'a> InferenceContext<'a> { |
@@ -86,16 +86,20 @@ impl<'a> InferenceContext<'a> { | |||
86 | 86 | ||
87 | self.coerce_merge_branch(&then_ty, &else_ty) | 87 | self.coerce_merge_branch(&then_ty, &else_ty) |
88 | } | 88 | } |
89 | Expr::Block { statements, tail } => self.infer_block(statements, *tail, expected), | 89 | Expr::Block { statements, tail, .. } => { |
90 | // FIXME: Breakable block inference | ||
91 | self.infer_block(statements, *tail, expected) | ||
92 | } | ||
90 | Expr::TryBlock { body } => { | 93 | Expr::TryBlock { body } => { |
91 | let _inner = self.infer_expr(*body, expected); | 94 | let _inner = self.infer_expr(*body, expected); |
92 | // FIXME should be std::result::Result<{inner}, _> | 95 | // FIXME should be std::result::Result<{inner}, _> |
93 | Ty::Unknown | 96 | Ty::Unknown |
94 | } | 97 | } |
95 | Expr::Loop { body } => { | 98 | Expr::Loop { body, label } => { |
96 | self.breakables.push(BreakableContext { | 99 | self.breakables.push(BreakableContext { |
97 | may_break: false, | 100 | may_break: false, |
98 | break_ty: self.table.new_type_var(), | 101 | break_ty: self.table.new_type_var(), |
102 | label: label.clone(), | ||
99 | }); | 103 | }); |
100 | self.infer_expr(*body, &Expectation::has_type(Ty::unit())); | 104 | self.infer_expr(*body, &Expectation::has_type(Ty::unit())); |
101 | 105 | ||
@@ -110,8 +114,12 @@ impl<'a> InferenceContext<'a> { | |||
110 | Ty::simple(TypeCtor::Never) | 114 | Ty::simple(TypeCtor::Never) |
111 | } | 115 | } |
112 | } | 116 | } |
113 | Expr::While { condition, body } => { | 117 | Expr::While { condition, body, label } => { |
114 | self.breakables.push(BreakableContext { may_break: false, break_ty: Ty::Unknown }); | 118 | self.breakables.push(BreakableContext { |
119 | may_break: false, | ||
120 | break_ty: Ty::Unknown, | ||
121 | label: label.clone(), | ||
122 | }); | ||
115 | // while let is desugared to a match loop, so this is always simple while | 123 | // while let is desugared to a match loop, so this is always simple while |
116 | self.infer_expr(*condition, &Expectation::has_type(Ty::simple(TypeCtor::Bool))); | 124 | self.infer_expr(*condition, &Expectation::has_type(Ty::simple(TypeCtor::Bool))); |
117 | self.infer_expr(*body, &Expectation::has_type(Ty::unit())); | 125 | self.infer_expr(*body, &Expectation::has_type(Ty::unit())); |
@@ -120,10 +128,14 @@ impl<'a> InferenceContext<'a> { | |||
120 | self.diverges = Diverges::Maybe; | 128 | self.diverges = Diverges::Maybe; |
121 | Ty::unit() | 129 | Ty::unit() |
122 | } | 130 | } |
123 | Expr::For { iterable, body, pat } => { | 131 | Expr::For { iterable, body, pat, label } => { |
124 | let iterable_ty = self.infer_expr(*iterable, &Expectation::none()); | 132 | let iterable_ty = self.infer_expr(*iterable, &Expectation::none()); |
125 | 133 | ||
126 | self.breakables.push(BreakableContext { may_break: false, break_ty: Ty::Unknown }); | 134 | self.breakables.push(BreakableContext { |
135 | may_break: false, | ||
136 | break_ty: Ty::Unknown, | ||
137 | label: label.clone(), | ||
138 | }); | ||
127 | let pat_ty = | 139 | let pat_ty = |
128 | self.resolve_associated_type(iterable_ty, self.resolve_into_iter_item()); | 140 | self.resolve_associated_type(iterable_ty, self.resolve_into_iter_item()); |
129 | 141 | ||
@@ -236,23 +248,24 @@ impl<'a> InferenceContext<'a> { | |||
236 | let resolver = resolver_for_expr(self.db.upcast(), self.owner, tgt_expr); | 248 | let resolver = resolver_for_expr(self.db.upcast(), self.owner, tgt_expr); |
237 | self.infer_path(&resolver, p, tgt_expr.into()).unwrap_or(Ty::Unknown) | 249 | self.infer_path(&resolver, p, tgt_expr.into()).unwrap_or(Ty::Unknown) |
238 | } | 250 | } |
239 | Expr::Continue => Ty::simple(TypeCtor::Never), | 251 | Expr::Continue { .. } => Ty::simple(TypeCtor::Never), |
240 | Expr::Break { expr } => { | 252 | Expr::Break { expr, label } => { |
241 | let val_ty = if let Some(expr) = expr { | 253 | let val_ty = if let Some(expr) = expr { |
242 | self.infer_expr(*expr, &Expectation::none()) | 254 | self.infer_expr(*expr, &Expectation::none()) |
243 | } else { | 255 | } else { |
244 | Ty::unit() | 256 | Ty::unit() |
245 | }; | 257 | }; |
246 | 258 | ||
247 | let last_ty = if let Some(ctxt) = self.breakables.last() { | 259 | let last_ty = |
248 | ctxt.break_ty.clone() | 260 | if let Some(ctxt) = find_breakable(&mut self.breakables, label.as_ref()) { |
249 | } else { | 261 | ctxt.break_ty.clone() |
250 | Ty::Unknown | 262 | } else { |
251 | }; | 263 | Ty::Unknown |
264 | }; | ||
252 | 265 | ||
253 | let merged_type = self.coerce_merge_branch(&last_ty, &val_ty); | 266 | let merged_type = self.coerce_merge_branch(&last_ty, &val_ty); |
254 | 267 | ||
255 | if let Some(ctxt) = self.breakables.last_mut() { | 268 | if let Some(ctxt) = find_breakable(&mut self.breakables, label.as_ref()) { |
256 | ctxt.break_ty = merged_type; | 269 | ctxt.break_ty = merged_type; |
257 | ctxt.may_break = true; | 270 | ctxt.may_break = true; |
258 | } else { | 271 | } else { |
diff --git a/crates/ra_hir_ty/src/tests/simple.rs b/crates/ra_hir_ty/src/tests/simple.rs index 839491b9e..88309157b 100644 --- a/crates/ra_hir_ty/src/tests/simple.rs +++ b/crates/ra_hir_ty/src/tests/simple.rs | |||
@@ -1943,3 +1943,57 @@ fn test() { | |||
1943 | "### | 1943 | "### |
1944 | ); | 1944 | ); |
1945 | } | 1945 | } |
1946 | |||
1947 | #[test] | ||
1948 | fn infer_labelled_break_with_val() { | ||
1949 | assert_snapshot!( | ||
1950 | infer(r#" | ||
1951 | fn foo() { | ||
1952 | let _x = || 'outer: loop { | ||
1953 | let inner = 'inner: loop { | ||
1954 | let i = Default::default(); | ||
1955 | if (break 'outer i) { | ||
1956 | loop { break 'inner 5i8; }; | ||
1957 | } else if true { | ||
1958 | break 'inner 6; | ||
1959 | } | ||
1960 | break 7; | ||
1961 | }; | ||
1962 | break inner < 8; | ||
1963 | }; | ||
1964 | } | ||
1965 | "#), | ||
1966 | @r###" | ||
1967 | 10..336 '{ ... }; }': () | ||
1968 | 20..22 '_x': || -> bool | ||
1969 | 25..333 '|| 'ou... }': || -> bool | ||
1970 | 28..333 ''outer... }': bool | ||
1971 | 41..333 '{ ... }': () | ||
1972 | 55..60 'inner': i8 | ||
1973 | 63..301 ''inner... }': i8 | ||
1974 | 76..301 '{ ... }': () | ||
1975 | 94..95 'i': bool | ||
1976 | 98..114 'Defaul...efault': {unknown} | ||
1977 | 98..116 'Defaul...ault()': bool | ||
1978 | 130..270 'if (br... }': () | ||
1979 | 134..148 'break 'outer i': ! | ||
1980 | 147..148 'i': bool | ||
1981 | 150..209 '{ ... }': () | ||
1982 | 168..194 'loop {...5i8; }': ! | ||
1983 | 173..194 '{ brea...5i8; }': () | ||
1984 | 175..191 'break ...er 5i8': ! | ||
1985 | 188..191 '5i8': i8 | ||
1986 | 215..270 'if tru... }': () | ||
1987 | 218..222 'true': bool | ||
1988 | 223..270 '{ ... }': () | ||
1989 | 241..255 'break 'inner 6': ! | ||
1990 | 254..255 '6': i8 | ||
1991 | 283..290 'break 7': ! | ||
1992 | 289..290 '7': i8 | ||
1993 | 311..326 'break inner < 8': ! | ||
1994 | 317..322 'inner': i8 | ||
1995 | 317..326 'inner < 8': bool | ||
1996 | 325..326 '8': i8 | ||
1997 | "### | ||
1998 | ); | ||
1999 | } | ||
diff --git a/crates/ra_ide/src/completion.rs b/crates/ra_ide/src/completion.rs index 191300704..d890b69d2 100644 --- a/crates/ra_ide/src/completion.rs +++ b/crates/ra_ide/src/completion.rs | |||
@@ -1,5 +1,3 @@ | |||
1 | //! FIXME: write short doc here | ||
2 | |||
3 | mod completion_config; | 1 | mod completion_config; |
4 | mod completion_item; | 2 | mod completion_item; |
5 | mod completion_context; | 3 | mod completion_context; |
@@ -35,6 +33,51 @@ pub use crate::completion::{ | |||
35 | completion_item::{CompletionItem, CompletionItemKind, CompletionScore, InsertTextFormat}, | 33 | completion_item::{CompletionItem, CompletionItemKind, CompletionScore, InsertTextFormat}, |
36 | }; | 34 | }; |
37 | 35 | ||
36 | //FIXME: split the following feature into fine-grained features. | ||
37 | |||
38 | // Feature: Magic Completions | ||
39 | // | ||
40 | // In addition to usual reference completion, rust-analyzer provides some ✨magic✨ | ||
41 | // completions as well: | ||
42 | // | ||
43 | // Keywords like `if`, `else` `while`, `loop` are completed with braces, and cursor | ||
44 | // is placed at the appropriate position. Even though `if` is easy to type, you | ||
45 | // still want to complete it, to get ` { }` for free! `return` is inserted with a | ||
46 | // space or `;` depending on the return type of the function. | ||
47 | // | ||
48 | // When completing a function call, `()` are automatically inserted. If a function | ||
49 | // takes arguments, the cursor is positioned inside the parenthesis. | ||
50 | // | ||
51 | // There are postfix completions, which can be triggered by typing something like | ||
52 | // `foo().if`. The word after `.` determines postfix completion. Possible variants are: | ||
53 | // | ||
54 | // - `expr.if` -> `if expr {}` or `if let ... {}` for `Option` or `Result` | ||
55 | // - `expr.match` -> `match expr {}` | ||
56 | // - `expr.while` -> `while expr {}` or `while let ... {}` for `Option` or `Result` | ||
57 | // - `expr.ref` -> `&expr` | ||
58 | // - `expr.refm` -> `&mut expr` | ||
59 | // - `expr.not` -> `!expr` | ||
60 | // - `expr.dbg` -> `dbg!(expr)` | ||
61 | // | ||
62 | // There also snippet completions: | ||
63 | // | ||
64 | // .Expressions | ||
65 | // - `pd` -> `println!("{:?}")` | ||
66 | // - `ppd` -> `println!("{:#?}")` | ||
67 | // | ||
68 | // .Items | ||
69 | // - `tfn` -> `#[test] fn f(){}` | ||
70 | // - `tmod` -> | ||
71 | // ```rust | ||
72 | // #[cfg(test)] | ||
73 | // mod tests { | ||
74 | // use super::*; | ||
75 | // | ||
76 | // #[test] | ||
77 | // fn test_fn() {} | ||
78 | // } | ||
79 | // ``` | ||
80 | |||
38 | /// Main entry point for completion. We run completion as a two-phase process. | 81 | /// Main entry point for completion. We run completion as a two-phase process. |
39 | /// | 82 | /// |
40 | /// First, we look at the position and collect a so-called `CompletionContext. | 83 | /// First, we look at the position and collect a so-called `CompletionContext. |
diff --git a/crates/ra_ide/src/completion/complete_postfix.rs b/crates/ra_ide/src/completion/complete_postfix.rs index 02e660ca8..59b58bf98 100644 --- a/crates/ra_ide/src/completion/complete_postfix.rs +++ b/crates/ra_ide/src/completion/complete_postfix.rs | |||
@@ -1,12 +1,11 @@ | |||
1 | //! FIXME: write short doc here | 1 | //! FIXME: write short doc here |
2 | 2 | use ra_assists::utils::TryEnum; | |
3 | use ra_syntax::{ | 3 | use ra_syntax::{ |
4 | ast::{self, AstNode}, | 4 | ast::{self, AstNode}, |
5 | TextRange, TextSize, | 5 | TextRange, TextSize, |
6 | }; | 6 | }; |
7 | use ra_text_edit::TextEdit; | 7 | use ra_text_edit::TextEdit; |
8 | 8 | ||
9 | use super::completion_config::SnippetCap; | ||
10 | use crate::{ | 9 | use crate::{ |
11 | completion::{ | 10 | completion::{ |
12 | completion_context::CompletionContext, | 11 | completion_context::CompletionContext, |
@@ -14,7 +13,8 @@ use crate::{ | |||
14 | }, | 13 | }, |
15 | CompletionItem, | 14 | CompletionItem, |
16 | }; | 15 | }; |
17 | use ra_assists::utils::TryEnum; | 16 | |
17 | use super::completion_config::SnippetCap; | ||
18 | 18 | ||
19 | pub(super) fn complete_postfix(acc: &mut Completions, ctx: &CompletionContext) { | 19 | pub(super) fn complete_postfix(acc: &mut Completions, ctx: &CompletionContext) { |
20 | if !ctx.config.enable_postfix_completions { | 20 | if !ctx.config.enable_postfix_completions { |
diff --git a/crates/ra_ide/src/display/structure.rs b/crates/ra_ide/src/display/structure.rs index 967eee5d2..aad5a8e4d 100644 --- a/crates/ra_ide/src/display/structure.rs +++ b/crates/ra_ide/src/display/structure.rs | |||
@@ -1,10 +1,6 @@ | |||
1 | //! FIXME: write short doc here | ||
2 | |||
3 | use crate::TextRange; | ||
4 | |||
5 | use ra_syntax::{ | 1 | use ra_syntax::{ |
6 | ast::{self, AttrsOwner, NameOwner, TypeAscriptionOwner, TypeParamsOwner}, | 2 | ast::{self, AttrsOwner, NameOwner, TypeAscriptionOwner, TypeParamsOwner}, |
7 | match_ast, AstNode, SourceFile, SyntaxKind, SyntaxNode, WalkEvent, | 3 | match_ast, AstNode, SourceFile, SyntaxKind, SyntaxNode, TextRange, WalkEvent, |
8 | }; | 4 | }; |
9 | 5 | ||
10 | #[derive(Debug, Clone)] | 6 | #[derive(Debug, Clone)] |
@@ -18,6 +14,19 @@ pub struct StructureNode { | |||
18 | pub deprecated: bool, | 14 | pub deprecated: bool, |
19 | } | 15 | } |
20 | 16 | ||
17 | // Feature: File Structure | ||
18 | // | ||
19 | // Provides a tree of the symbols defined in the file. Can be used to | ||
20 | // | ||
21 | // * fuzzy search symbol in a file (super useful) | ||
22 | // * draw breadcrumbs to describe the context around the cursor | ||
23 | // * draw outline of the file | ||
24 | // | ||
25 | // |=== | ||
26 | // | Editor | Shortcut | ||
27 | // | ||
28 | // | VS Code | kbd:[Ctrl+Shift+O] | ||
29 | // |=== | ||
21 | pub fn file_structure(file: &SourceFile) -> Vec<StructureNode> { | 30 | pub fn file_structure(file: &SourceFile) -> Vec<StructureNode> { |
22 | let mut res = Vec::new(); | 31 | let mut res = Vec::new(); |
23 | let mut stack = Vec::new(); | 32 | let mut stack = Vec::new(); |
diff --git a/crates/ra_ide/src/expand_macro.rs b/crates/ra_ide/src/expand_macro.rs index f536ba3e7..54a47aac0 100644 --- a/crates/ra_ide/src/expand_macro.rs +++ b/crates/ra_ide/src/expand_macro.rs | |||
@@ -1,5 +1,3 @@ | |||
1 | //! This modules implements "expand macro" functionality in the IDE | ||
2 | |||
3 | use hir::Semantics; | 1 | use hir::Semantics; |
4 | use ra_ide_db::RootDatabase; | 2 | use ra_ide_db::RootDatabase; |
5 | use ra_syntax::{ | 3 | use ra_syntax::{ |
@@ -14,6 +12,15 @@ pub struct ExpandedMacro { | |||
14 | pub expansion: String, | 12 | pub expansion: String, |
15 | } | 13 | } |
16 | 14 | ||
15 | // Feature: Expand Macro Recursively | ||
16 | // | ||
17 | // Shows the full macro expansion of the macro at current cursor. | ||
18 | // | ||
19 | // |=== | ||
20 | // | Editor | Action Name | ||
21 | // | ||
22 | // | VS Code | **Rust Analyzer: Expand macro recursively** | ||
23 | // |=== | ||
17 | pub(crate) fn expand_macro(db: &RootDatabase, position: FilePosition) -> Option<ExpandedMacro> { | 24 | pub(crate) fn expand_macro(db: &RootDatabase, position: FilePosition) -> Option<ExpandedMacro> { |
18 | let sema = Semantics::new(db); | 25 | let sema = Semantics::new(db); |
19 | let file = sema.parse(position.file_id); | 26 | let file = sema.parse(position.file_id); |
diff --git a/crates/ra_ide/src/extend_selection.rs b/crates/ra_ide/src/extend_selection.rs index 554594a43..a4bc93cdb 100644 --- a/crates/ra_ide/src/extend_selection.rs +++ b/crates/ra_ide/src/extend_selection.rs | |||
@@ -1,5 +1,3 @@ | |||
1 | //! FIXME: write short doc here | ||
2 | |||
3 | use std::iter::successors; | 1 | use std::iter::successors; |
4 | 2 | ||
5 | use hir::Semantics; | 3 | use hir::Semantics; |
@@ -14,6 +12,16 @@ use ra_syntax::{ | |||
14 | 12 | ||
15 | use crate::FileRange; | 13 | use crate::FileRange; |
16 | 14 | ||
15 | // Feature: Extend Selection | ||
16 | // | ||
17 | // Extends the current selection to the encompassing syntactic construct | ||
18 | // (expression, statement, item, module, etc). It works with multiple cursors. | ||
19 | // | ||
20 | // |=== | ||
21 | // | Editor | Shortcut | ||
22 | // | ||
23 | // | VS Code | kbd:[Ctrl+Shift+→] | ||
24 | // |=== | ||
17 | pub(crate) fn extend_selection(db: &RootDatabase, frange: FileRange) -> TextRange { | 25 | pub(crate) fn extend_selection(db: &RootDatabase, frange: FileRange) -> TextRange { |
18 | let sema = Semantics::new(db); | 26 | let sema = Semantics::new(db); |
19 | let src = sema.parse(frange.file_id); | 27 | let src = sema.parse(frange.file_id); |
diff --git a/crates/ra_ide/src/goto_definition.rs b/crates/ra_ide/src/goto_definition.rs index 90e85d419..a6c86e99c 100644 --- a/crates/ra_ide/src/goto_definition.rs +++ b/crates/ra_ide/src/goto_definition.rs | |||
@@ -1,5 +1,3 @@ | |||
1 | //! FIXME: write short doc here | ||
2 | |||
3 | use hir::Semantics; | 1 | use hir::Semantics; |
4 | use ra_ide_db::{ | 2 | use ra_ide_db::{ |
5 | defs::{classify_name, classify_name_ref}, | 3 | defs::{classify_name, classify_name_ref}, |
@@ -17,6 +15,15 @@ use crate::{ | |||
17 | FilePosition, NavigationTarget, RangeInfo, | 15 | FilePosition, NavigationTarget, RangeInfo, |
18 | }; | 16 | }; |
19 | 17 | ||
18 | // Feature: Go to Definition | ||
19 | // | ||
20 | // Navigates to the definition of an identifier. | ||
21 | // | ||
22 | // |=== | ||
23 | // | Editor | Shortcut | ||
24 | // | ||
25 | // | VS Code | kbd:[F12] | ||
26 | // |=== | ||
20 | pub(crate) fn goto_definition( | 27 | pub(crate) fn goto_definition( |
21 | db: &RootDatabase, | 28 | db: &RootDatabase, |
22 | position: FilePosition, | 29 | position: FilePosition, |
diff --git a/crates/ra_ide/src/impls.rs b/crates/ra_ide/src/goto_implementation.rs index ea2225f70..0cec0657e 100644 --- a/crates/ra_ide/src/impls.rs +++ b/crates/ra_ide/src/goto_implementation.rs | |||
@@ -1,11 +1,18 @@ | |||
1 | //! FIXME: write short doc here | ||
2 | |||
3 | use hir::{Crate, ImplDef, Semantics}; | 1 | use hir::{Crate, ImplDef, Semantics}; |
4 | use ra_ide_db::RootDatabase; | 2 | use ra_ide_db::RootDatabase; |
5 | use ra_syntax::{algo::find_node_at_offset, ast, AstNode}; | 3 | use ra_syntax::{algo::find_node_at_offset, ast, AstNode}; |
6 | 4 | ||
7 | use crate::{display::ToNav, FilePosition, NavigationTarget, RangeInfo}; | 5 | use crate::{display::ToNav, FilePosition, NavigationTarget, RangeInfo}; |
8 | 6 | ||
7 | // Feature: Go to Implementation | ||
8 | // | ||
9 | // Navigates to the impl block of structs, enums or traits. Also implemented as a code lens. | ||
10 | // | ||
11 | // |=== | ||
12 | // | Editor | Shortcut | ||
13 | // | ||
14 | // | VS Code | kbd:[Ctrl+F12] | ||
15 | // |=== | ||
9 | pub(crate) fn goto_implementation( | 16 | pub(crate) fn goto_implementation( |
10 | db: &RootDatabase, | 17 | db: &RootDatabase, |
11 | position: FilePosition, | 18 | position: FilePosition, |
diff --git a/crates/ra_ide/src/goto_type_definition.rs b/crates/ra_ide/src/goto_type_definition.rs index a84637489..91a3097fb 100644 --- a/crates/ra_ide/src/goto_type_definition.rs +++ b/crates/ra_ide/src/goto_type_definition.rs | |||
@@ -1,10 +1,17 @@ | |||
1 | //! FIXME: write short doc here | ||
2 | |||
3 | use ra_ide_db::RootDatabase; | 1 | use ra_ide_db::RootDatabase; |
4 | use ra_syntax::{ast, match_ast, AstNode, SyntaxKind::*, SyntaxToken, TokenAtOffset}; | 2 | use ra_syntax::{ast, match_ast, AstNode, SyntaxKind::*, SyntaxToken, TokenAtOffset}; |
5 | 3 | ||
6 | use crate::{display::ToNav, FilePosition, NavigationTarget, RangeInfo}; | 4 | use crate::{display::ToNav, FilePosition, NavigationTarget, RangeInfo}; |
7 | 5 | ||
6 | // Feature: Go to Type Definition | ||
7 | // | ||
8 | // Navigates to the type of an identifier. | ||
9 | // | ||
10 | // |=== | ||
11 | // | Editor | Action Name | ||
12 | // | ||
13 | // | VS Code | **Go to Type Definition* | ||
14 | // |=== | ||
8 | pub(crate) fn goto_type_definition( | 15 | pub(crate) fn goto_type_definition( |
9 | db: &RootDatabase, | 16 | db: &RootDatabase, |
10 | position: FilePosition, | 17 | position: FilePosition, |
diff --git a/crates/ra_ide/src/hover.rs b/crates/ra_ide/src/hover.rs index 3e721dcca..d96cb5596 100644 --- a/crates/ra_ide/src/hover.rs +++ b/crates/ra_ide/src/hover.rs | |||
@@ -1,10 +1,10 @@ | |||
1 | //! Logic for computing info that is displayed when the user hovers over any | 1 | use std::iter::once; |
2 | //! source code items (e.g. function call, struct field, variable symbol...) | ||
3 | 2 | ||
4 | use hir::{ | 3 | use hir::{ |
5 | Adt, AsAssocItem, AssocItemContainer, FieldSource, HasSource, HirDisplay, ModuleDef, | 4 | Adt, AsAssocItem, AssocItemContainer, FieldSource, HasSource, HirDisplay, ModuleDef, |
6 | ModuleSource, Semantics, | 5 | ModuleSource, Semantics, |
7 | }; | 6 | }; |
7 | use itertools::Itertools; | ||
8 | use ra_db::SourceDatabase; | 8 | use ra_db::SourceDatabase; |
9 | use ra_ide_db::{ | 9 | use ra_ide_db::{ |
10 | defs::{classify_name, classify_name_ref, Definition}, | 10 | defs::{classify_name, classify_name_ref, Definition}, |
@@ -21,8 +21,6 @@ use crate::{ | |||
21 | display::{macro_label, rust_code_markup, rust_code_markup_with_doc, ShortLabel}, | 21 | display::{macro_label, rust_code_markup, rust_code_markup_with_doc, ShortLabel}, |
22 | FilePosition, RangeInfo, | 22 | FilePosition, RangeInfo, |
23 | }; | 23 | }; |
24 | use itertools::Itertools; | ||
25 | use std::iter::once; | ||
26 | 24 | ||
27 | /// Contains the results when hovering over an item | 25 | /// Contains the results when hovering over an item |
28 | #[derive(Debug, Default)] | 26 | #[derive(Debug, Default)] |
@@ -62,6 +60,63 @@ impl HoverResult { | |||
62 | } | 60 | } |
63 | } | 61 | } |
64 | 62 | ||
63 | // Feature: Hover | ||
64 | // | ||
65 | // Shows additional information, like type of an expression or documentation for definition when "focusing" code. | ||
66 | // Focusing is usually hovering with a mouse, but can also be triggered with a shortcut. | ||
67 | pub(crate) fn hover(db: &RootDatabase, position: FilePosition) -> Option<RangeInfo<HoverResult>> { | ||
68 | let sema = Semantics::new(db); | ||
69 | let file = sema.parse(position.file_id).syntax().clone(); | ||
70 | let token = pick_best(file.token_at_offset(position.offset))?; | ||
71 | let token = sema.descend_into_macros(token); | ||
72 | |||
73 | let mut res = HoverResult::new(); | ||
74 | |||
75 | if let Some((node, name_kind)) = match_ast! { | ||
76 | match (token.parent()) { | ||
77 | ast::NameRef(name_ref) => { | ||
78 | classify_name_ref(&sema, &name_ref).map(|d| (name_ref.syntax().clone(), d.definition())) | ||
79 | }, | ||
80 | ast::Name(name) => { | ||
81 | classify_name(&sema, &name).map(|d| (name.syntax().clone(), d.definition())) | ||
82 | }, | ||
83 | _ => None, | ||
84 | } | ||
85 | } { | ||
86 | let range = sema.original_range(&node).range; | ||
87 | res.extend(hover_text_from_name_kind(db, name_kind)); | ||
88 | |||
89 | if !res.is_empty() { | ||
90 | return Some(RangeInfo::new(range, res)); | ||
91 | } | ||
92 | } | ||
93 | |||
94 | let node = token | ||
95 | .ancestors() | ||
96 | .find(|n| ast::Expr::cast(n.clone()).is_some() || ast::Pat::cast(n.clone()).is_some())?; | ||
97 | |||
98 | let ty = match_ast! { | ||
99 | match node { | ||
100 | ast::MacroCall(_it) => { | ||
101 | // If this node is a MACRO_CALL, it means that `descend_into_macros` failed to resolve. | ||
102 | // (e.g expanding a builtin macro). So we give up here. | ||
103 | return None; | ||
104 | }, | ||
105 | ast::Expr(it) => { | ||
106 | sema.type_of_expr(&it) | ||
107 | }, | ||
108 | ast::Pat(it) => { | ||
109 | sema.type_of_pat(&it) | ||
110 | }, | ||
111 | _ => None, | ||
112 | } | ||
113 | }?; | ||
114 | |||
115 | res.extend(Some(rust_code_markup(&ty.display(db)))); | ||
116 | let range = sema.original_range(&node).range; | ||
117 | Some(RangeInfo::new(range, res)) | ||
118 | } | ||
119 | |||
65 | fn hover_text( | 120 | fn hover_text( |
66 | docs: Option<String>, | 121 | docs: Option<String>, |
67 | desc: Option<String>, | 122 | desc: Option<String>, |
@@ -160,59 +215,6 @@ fn hover_text_from_name_kind(db: &RootDatabase, def: Definition) -> Option<Strin | |||
160 | } | 215 | } |
161 | } | 216 | } |
162 | 217 | ||
163 | pub(crate) fn hover(db: &RootDatabase, position: FilePosition) -> Option<RangeInfo<HoverResult>> { | ||
164 | let sema = Semantics::new(db); | ||
165 | let file = sema.parse(position.file_id).syntax().clone(); | ||
166 | let token = pick_best(file.token_at_offset(position.offset))?; | ||
167 | let token = sema.descend_into_macros(token); | ||
168 | |||
169 | let mut res = HoverResult::new(); | ||
170 | |||
171 | if let Some((node, name_kind)) = match_ast! { | ||
172 | match (token.parent()) { | ||
173 | ast::NameRef(name_ref) => { | ||
174 | classify_name_ref(&sema, &name_ref).map(|d| (name_ref.syntax().clone(), d.definition())) | ||
175 | }, | ||
176 | ast::Name(name) => { | ||
177 | classify_name(&sema, &name).map(|d| (name.syntax().clone(), d.definition())) | ||
178 | }, | ||
179 | _ => None, | ||
180 | } | ||
181 | } { | ||
182 | let range = sema.original_range(&node).range; | ||
183 | res.extend(hover_text_from_name_kind(db, name_kind)); | ||
184 | |||
185 | if !res.is_empty() { | ||
186 | return Some(RangeInfo::new(range, res)); | ||
187 | } | ||
188 | } | ||
189 | |||
190 | let node = token | ||
191 | .ancestors() | ||
192 | .find(|n| ast::Expr::cast(n.clone()).is_some() || ast::Pat::cast(n.clone()).is_some())?; | ||
193 | |||
194 | let ty = match_ast! { | ||
195 | match node { | ||
196 | ast::MacroCall(_it) => { | ||
197 | // If this node is a MACRO_CALL, it means that `descend_into_macros` failed to resolve. | ||
198 | // (e.g expanding a builtin macro). So we give up here. | ||
199 | return None; | ||
200 | }, | ||
201 | ast::Expr(it) => { | ||
202 | sema.type_of_expr(&it) | ||
203 | }, | ||
204 | ast::Pat(it) => { | ||
205 | sema.type_of_pat(&it) | ||
206 | }, | ||
207 | _ => None, | ||
208 | } | ||
209 | }?; | ||
210 | |||
211 | res.extend(Some(rust_code_markup(&ty.display(db)))); | ||
212 | let range = sema.original_range(&node).range; | ||
213 | Some(RangeInfo::new(range, res)) | ||
214 | } | ||
215 | |||
216 | fn pick_best(tokens: TokenAtOffset<SyntaxToken>) -> Option<SyntaxToken> { | 218 | fn pick_best(tokens: TokenAtOffset<SyntaxToken>) -> Option<SyntaxToken> { |
217 | return tokens.max_by_key(priority); | 219 | return tokens.max_by_key(priority); |
218 | fn priority(n: &SyntaxToken) -> usize { | 220 | fn priority(n: &SyntaxToken) -> usize { |
diff --git a/crates/ra_ide/src/inlay_hints.rs b/crates/ra_ide/src/inlay_hints.rs index b391f903a..75bd3c96b 100644 --- a/crates/ra_ide/src/inlay_hints.rs +++ b/crates/ra_ide/src/inlay_hints.rs | |||
@@ -1,5 +1,3 @@ | |||
1 | //! This module defines multiple types of inlay hints and their visibility | ||
2 | |||
3 | use hir::{Adt, HirDisplay, Semantics, Type}; | 1 | use hir::{Adt, HirDisplay, Semantics, Type}; |
4 | use ra_ide_db::RootDatabase; | 2 | use ra_ide_db::RootDatabase; |
5 | use ra_prof::profile; | 3 | use ra_prof::profile; |
@@ -39,6 +37,26 @@ pub struct InlayHint { | |||
39 | pub label: SmolStr, | 37 | pub label: SmolStr, |
40 | } | 38 | } |
41 | 39 | ||
40 | // Feature: Inlay Hints | ||
41 | // | ||
42 | // rust-analyzer shows additional information inline with the source code. | ||
43 | // Editors usually render this using read-only virtual text snippets interspersed with code. | ||
44 | // | ||
45 | // rust-analyzer shows hits for | ||
46 | // | ||
47 | // * types of local variables | ||
48 | // * names of function arguments | ||
49 | // * types of chained expressions | ||
50 | // | ||
51 | // **Note:** VS Code does not have native support for inlay hints https://github.com/microsoft/vscode/issues/16221[yet] and the hints are implemented using decorations. | ||
52 | // This approach has limitations, the caret movement and bracket highlighting near the edges of the hint may be weird: | ||
53 | // https://github.com/rust-analyzer/rust-analyzer/issues/1623[1], https://github.com/rust-analyzer/rust-analyzer/issues/3453[2]. | ||
54 | // | ||
55 | // |=== | ||
56 | // | Editor | Action Name | ||
57 | // | ||
58 | // | VS Code | **Rust Analyzer: Toggle inlay hints* | ||
59 | // |=== | ||
42 | pub(crate) fn inlay_hints( | 60 | pub(crate) fn inlay_hints( |
43 | db: &RootDatabase, | 61 | db: &RootDatabase, |
44 | file_id: FileId, | 62 | file_id: FileId, |
diff --git a/crates/ra_ide/src/join_lines.rs b/crates/ra_ide/src/join_lines.rs index af1ade8a1..5036c1fb0 100644 --- a/crates/ra_ide/src/join_lines.rs +++ b/crates/ra_ide/src/join_lines.rs | |||
@@ -1,5 +1,3 @@ | |||
1 | //! FIXME: write short doc here | ||
2 | |||
3 | use itertools::Itertools; | 1 | use itertools::Itertools; |
4 | use ra_fmt::{compute_ws, extract_trivial_expression}; | 2 | use ra_fmt::{compute_ws, extract_trivial_expression}; |
5 | use ra_syntax::{ | 3 | use ra_syntax::{ |
@@ -11,6 +9,15 @@ use ra_syntax::{ | |||
11 | }; | 9 | }; |
12 | use ra_text_edit::{TextEdit, TextEditBuilder}; | 10 | use ra_text_edit::{TextEdit, TextEditBuilder}; |
13 | 11 | ||
12 | // Feature: Join Lines | ||
13 | // | ||
14 | // Join selected lines into one, smartly fixing up whitespace, trailing commas, and braces. | ||
15 | // | ||
16 | // |=== | ||
17 | // | Editor | Action Name | ||
18 | // | ||
19 | // | VS Code | **Rust Analyzer: Join lines** | ||
20 | // |=== | ||
14 | pub fn join_lines(file: &SourceFile, range: TextRange) -> TextEdit { | 21 | pub fn join_lines(file: &SourceFile, range: TextRange) -> TextEdit { |
15 | let range = if range.is_empty() { | 22 | let range = if range.is_empty() { |
16 | let syntax = file.syntax(); | 23 | let syntax = file.syntax(); |
diff --git a/crates/ra_ide/src/lib.rs b/crates/ra_ide/src/lib.rs index d983cd910..12d5716e8 100644 --- a/crates/ra_ide/src/lib.rs +++ b/crates/ra_ide/src/lib.rs | |||
@@ -23,6 +23,7 @@ mod completion; | |||
23 | mod runnables; | 23 | mod runnables; |
24 | mod goto_definition; | 24 | mod goto_definition; |
25 | mod goto_type_definition; | 25 | mod goto_type_definition; |
26 | mod goto_implementation; | ||
26 | mod extend_selection; | 27 | mod extend_selection; |
27 | mod hover; | 28 | mod hover; |
28 | mod call_hierarchy; | 29 | mod call_hierarchy; |
@@ -30,7 +31,6 @@ mod call_info; | |||
30 | mod syntax_highlighting; | 31 | mod syntax_highlighting; |
31 | mod parent_module; | 32 | mod parent_module; |
32 | mod references; | 33 | mod references; |
33 | mod impls; | ||
34 | mod diagnostics; | 34 | mod diagnostics; |
35 | mod syntax_tree; | 35 | mod syntax_tree; |
36 | mod folding_ranges; | 36 | mod folding_ranges; |
@@ -373,7 +373,7 @@ impl Analysis { | |||
373 | &self, | 373 | &self, |
374 | position: FilePosition, | 374 | position: FilePosition, |
375 | ) -> Cancelable<Option<RangeInfo<Vec<NavigationTarget>>>> { | 375 | ) -> Cancelable<Option<RangeInfo<Vec<NavigationTarget>>>> { |
376 | self.with_db(|db| impls::goto_implementation(db, position)) | 376 | self.with_db(|db| goto_implementation::goto_implementation(db, position)) |
377 | } | 377 | } |
378 | 378 | ||
379 | /// Returns the type definitions for the symbol at `position`. | 379 | /// Returns the type definitions for the symbol at `position`. |
diff --git a/crates/ra_ide/src/matching_brace.rs b/crates/ra_ide/src/matching_brace.rs index b85348706..407a9636d 100644 --- a/crates/ra_ide/src/matching_brace.rs +++ b/crates/ra_ide/src/matching_brace.rs | |||
@@ -1,7 +1,16 @@ | |||
1 | //! FIXME: write short doc here | ||
2 | |||
3 | use ra_syntax::{ast::AstNode, SourceFile, SyntaxKind, TextSize, T}; | 1 | use ra_syntax::{ast::AstNode, SourceFile, SyntaxKind, TextSize, T}; |
4 | 2 | ||
3 | // Feature: Matching Brace | ||
4 | // | ||
5 | // If the cursor is on any brace (`<>(){}[]`) which is a part of a brace-pair, | ||
6 | // moves cursor to the matching brace. It uses the actual parser to determine | ||
7 | // braces, so it won't confuse generics with comparisons. | ||
8 | // | ||
9 | // |=== | ||
10 | // | Editor | Action Name | ||
11 | // | ||
12 | // | VS Code | **Rust Analyzer: Find matching brace** | ||
13 | // |=== | ||
5 | pub fn matching_brace(file: &SourceFile, offset: TextSize) -> Option<TextSize> { | 14 | pub fn matching_brace(file: &SourceFile, offset: TextSize) -> Option<TextSize> { |
6 | const BRACES: &[SyntaxKind] = | 15 | const BRACES: &[SyntaxKind] = |
7 | &[T!['{'], T!['}'], T!['['], T![']'], T!['('], T![')'], T![<], T![>]]; | 16 | &[T!['{'], T!['}'], T!['['], T![']'], T!['('], T![')'], T![<], T![>]]; |
diff --git a/crates/ra_ide/src/parent_module.rs b/crates/ra_ide/src/parent_module.rs index a083fb1eb..fa1535da5 100644 --- a/crates/ra_ide/src/parent_module.rs +++ b/crates/ra_ide/src/parent_module.rs | |||
@@ -1,5 +1,3 @@ | |||
1 | //! FIXME: write short doc here | ||
2 | |||
3 | use hir::Semantics; | 1 | use hir::Semantics; |
4 | use ra_db::{CrateId, FileId, FilePosition}; | 2 | use ra_db::{CrateId, FileId, FilePosition}; |
5 | use ra_ide_db::RootDatabase; | 3 | use ra_ide_db::RootDatabase; |
@@ -11,6 +9,16 @@ use test_utils::mark; | |||
11 | 9 | ||
12 | use crate::NavigationTarget; | 10 | use crate::NavigationTarget; |
13 | 11 | ||
12 | // Feature: Parent Module | ||
13 | // | ||
14 | // Navigates to the parent module of the current module. | ||
15 | // | ||
16 | // |=== | ||
17 | // | Editor | Action Name | ||
18 | // | ||
19 | // | VS Code | **Rust Analyzer: Locate parent module** | ||
20 | // |=== | ||
21 | |||
14 | /// This returns `Vec` because a module may be included from several places. We | 22 | /// This returns `Vec` because a module may be included from several places. We |
15 | /// don't handle this case yet though, so the Vec has length at most one. | 23 | /// don't handle this case yet though, so the Vec has length at most one. |
16 | pub(crate) fn parent_module(db: &RootDatabase, position: FilePosition) -> Vec<NavigationTarget> { | 24 | pub(crate) fn parent_module(db: &RootDatabase, position: FilePosition) -> Vec<NavigationTarget> { |
diff --git a/crates/ra_ide/src/references.rs b/crates/ra_ide/src/references.rs index 96444bf6a..bb40d2043 100644 --- a/crates/ra_ide/src/references.rs +++ b/crates/ra_ide/src/references.rs | |||
@@ -615,6 +615,33 @@ mod tests { | |||
615 | ); | 615 | ); |
616 | } | 616 | } |
617 | 617 | ||
618 | #[test] | ||
619 | fn test_find_all_refs_nested_module() { | ||
620 | let code = r#" | ||
621 | //- /lib.rs | ||
622 | mod foo { | ||
623 | mod bar; | ||
624 | } | ||
625 | |||
626 | fn f<|>() {} | ||
627 | |||
628 | //- /foo/bar.rs | ||
629 | use crate::f; | ||
630 | |||
631 | fn g() { | ||
632 | f(); | ||
633 | } | ||
634 | "#; | ||
635 | |||
636 | let (analysis, pos) = analysis_and_position(code); | ||
637 | let refs = analysis.find_all_refs(pos, None).unwrap().unwrap(); | ||
638 | check_result( | ||
639 | refs, | ||
640 | "f FN_DEF FileId(1) 25..34 28..29 Other", | ||
641 | &["FileId(2) 11..12 Other", "FileId(2) 27..28 StructLiteral"], | ||
642 | ); | ||
643 | } | ||
644 | |||
618 | fn get_all_refs(text: &str) -> ReferenceSearchResult { | 645 | fn get_all_refs(text: &str) -> ReferenceSearchResult { |
619 | let (analysis, position) = single_file_with_position(text); | 646 | let (analysis, position) = single_file_with_position(text); |
620 | analysis.find_all_refs(position, None).unwrap().unwrap() | 647 | analysis.find_all_refs(position, None).unwrap().unwrap() |
diff --git a/crates/ra_ide/src/runnables.rs b/crates/ra_ide/src/runnables.rs index 6e7e47199..286d45eee 100644 --- a/crates/ra_ide/src/runnables.rs +++ b/crates/ra_ide/src/runnables.rs | |||
@@ -1,5 +1,3 @@ | |||
1 | //! FIXME: write short doc here | ||
2 | |||
3 | use hir::{AsAssocItem, Attrs, HirFileId, InFile, Semantics}; | 1 | use hir::{AsAssocItem, Attrs, HirFileId, InFile, Semantics}; |
4 | use itertools::Itertools; | 2 | use itertools::Itertools; |
5 | use ra_ide_db::RootDatabase; | 3 | use ra_ide_db::RootDatabase; |
@@ -44,6 +42,17 @@ pub enum RunnableKind { | |||
44 | Bin, | 42 | Bin, |
45 | } | 43 | } |
46 | 44 | ||
45 | // Feature: Run | ||
46 | // | ||
47 | // Shows a popup suggesting to run a test/benchmark/binary **at the current cursor | ||
48 | // location**. Super useful for repeatedly running just a single test. Do bind this | ||
49 | // to a shortcut! | ||
50 | // | ||
51 | // |=== | ||
52 | // | Editor | Action Name | ||
53 | // | ||
54 | // | VS Code | **Rust Analyzer: Run** | ||
55 | // |=== | ||
47 | pub(crate) fn runnables(db: &RootDatabase, file_id: FileId) -> Vec<Runnable> { | 56 | pub(crate) fn runnables(db: &RootDatabase, file_id: FileId) -> Vec<Runnable> { |
48 | let sema = Semantics::new(db); | 57 | let sema = Semantics::new(db); |
49 | let source_file = sema.parse(file_id); | 58 | let source_file = sema.parse(file_id); |
diff --git a/crates/ra_ide/src/ssr.rs b/crates/ra_ide/src/ssr.rs index 130d3b4c3..93e9aee1d 100644 --- a/crates/ra_ide/src/ssr.rs +++ b/crates/ra_ide/src/ssr.rs | |||
@@ -1,5 +1,3 @@ | |||
1 | //! structural search replace | ||
2 | |||
3 | use std::{collections::HashMap, iter::once, str::FromStr}; | 1 | use std::{collections::HashMap, iter::once, str::FromStr}; |
4 | 2 | ||
5 | use ra_db::{SourceDatabase, SourceDatabaseExt}; | 3 | use ra_db::{SourceDatabase, SourceDatabaseExt}; |
@@ -25,6 +23,28 @@ impl std::fmt::Display for SsrError { | |||
25 | 23 | ||
26 | impl std::error::Error for SsrError {} | 24 | impl std::error::Error for SsrError {} |
27 | 25 | ||
26 | // Feature: Structural Seach and Replace | ||
27 | // | ||
28 | // Search and replace with named wildcards that will match any expression. | ||
29 | // The syntax for a structural search replace command is `<search_pattern> ==>> <replace_pattern>`. | ||
30 | // A `$<name>:expr` placeholder in the search pattern will match any expression and `$<name>` will reference it in the replacement. | ||
31 | // Available via the command `rust-analyzer.ssr`. | ||
32 | // | ||
33 | // ```rust | ||
34 | // // Using structural search replace command [foo($a:expr, $b:expr) ==>> ($a).foo($b)] | ||
35 | // | ||
36 | // // BEFORE | ||
37 | // String::from(foo(y + 5, z)) | ||
38 | // | ||
39 | // // AFTER | ||
40 | // String::from((y + 5).foo(z)) | ||
41 | // ``` | ||
42 | // | ||
43 | // |=== | ||
44 | // | Editor | Action Name | ||
45 | // | ||
46 | // | VS Code | **Rust Analyzer: Structural Search Replace** | ||
47 | // |=== | ||
28 | pub fn parse_search_replace( | 48 | pub fn parse_search_replace( |
29 | query: &str, | 49 | query: &str, |
30 | parse_only: bool, | 50 | parse_only: bool, |
diff --git a/crates/ra_ide/src/status.rs b/crates/ra_ide/src/status.rs index 30eb5c995..5b7992920 100644 --- a/crates/ra_ide/src/status.rs +++ b/crates/ra_ide/src/status.rs | |||
@@ -1,5 +1,3 @@ | |||
1 | //! FIXME: write short doc here | ||
2 | |||
3 | use std::{fmt, iter::FromIterator, sync::Arc}; | 1 | use std::{fmt, iter::FromIterator, sync::Arc}; |
4 | 2 | ||
5 | use hir::MacroFile; | 3 | use hir::MacroFile; |
@@ -26,6 +24,15 @@ fn macro_syntax_tree_stats(db: &RootDatabase) -> SyntaxTreeStats { | |||
26 | db.query(hir::db::ParseMacroQuery).entries::<SyntaxTreeStats>() | 24 | db.query(hir::db::ParseMacroQuery).entries::<SyntaxTreeStats>() |
27 | } | 25 | } |
28 | 26 | ||
27 | // Feature: Status | ||
28 | // | ||
29 | // Shows internal statistic about memory usage of rust-analyzer. | ||
30 | // | ||
31 | // |=== | ||
32 | // | Editor | Action Name | ||
33 | // | ||
34 | // | VS Code | **Rust Analyzer: Status** | ||
35 | // |=== | ||
29 | pub(crate) fn status(db: &RootDatabase) -> String { | 36 | pub(crate) fn status(db: &RootDatabase) -> String { |
30 | let files_stats = db.query(FileTextQuery).entries::<FilesStats>(); | 37 | let files_stats = db.query(FileTextQuery).entries::<FilesStats>(); |
31 | let syntax_tree_stats = syntax_tree_stats(db); | 38 | let syntax_tree_stats = syntax_tree_stats(db); |
diff --git a/crates/ra_ide/src/syntax_highlighting.rs b/crates/ra_ide/src/syntax_highlighting.rs index cd6464b40..0b53ebe69 100644 --- a/crates/ra_ide/src/syntax_highlighting.rs +++ b/crates/ra_ide/src/syntax_highlighting.rs | |||
@@ -1,5 +1,3 @@ | |||
1 | //! Implements syntax highlighting. | ||
2 | |||
3 | mod tags; | 1 | mod tags; |
4 | mod html; | 2 | mod html; |
5 | #[cfg(test)] | 3 | #[cfg(test)] |
@@ -32,81 +30,15 @@ pub struct HighlightedRange { | |||
32 | pub binding_hash: Option<u64>, | 30 | pub binding_hash: Option<u64>, |
33 | } | 31 | } |
34 | 32 | ||
35 | #[derive(Debug)] | 33 | // Feature: Semantic Syntax Highlighting |
36 | struct HighlightedRangeStack { | 34 | // |
37 | stack: Vec<Vec<HighlightedRange>>, | 35 | // rust-analyzer highlights the code semantically. |
38 | } | 36 | // For example, `bar` in `foo::Bar` might be colored differently depending on whether `Bar` is an enum or a trait. |
39 | 37 | // rust-analyzer does not specify colors directly, instead it assigns tag (like `struct`) and a set of modifiers (like `declaration`) to each token. | |
40 | /// We use a stack to implement the flattening logic for the highlighted | 38 | // It's up to the client to map those to specific colors. |
41 | /// syntax ranges. | 39 | // |
42 | impl HighlightedRangeStack { | 40 | // The general rule is that a reference to an entity gets colored the same way as the entity itself. |
43 | fn new() -> Self { | 41 | // We also give special modifier for `mut` and `&mut` local variables. |
44 | Self { stack: vec![Vec::new()] } | ||
45 | } | ||
46 | |||
47 | fn push(&mut self) { | ||
48 | self.stack.push(Vec::new()); | ||
49 | } | ||
50 | |||
51 | /// Flattens the highlighted ranges. | ||
52 | /// | ||
53 | /// For example `#[cfg(feature = "foo")]` contains the nested ranges: | ||
54 | /// 1) parent-range: Attribute [0, 23) | ||
55 | /// 2) child-range: String [16, 21) | ||
56 | /// | ||
57 | /// The following code implements the flattening, for our example this results to: | ||
58 | /// `[Attribute [0, 16), String [16, 21), Attribute [21, 23)]` | ||
59 | fn pop(&mut self) { | ||
60 | let children = self.stack.pop().unwrap(); | ||
61 | let prev = self.stack.last_mut().unwrap(); | ||
62 | let needs_flattening = !children.is_empty() | ||
63 | && !prev.is_empty() | ||
64 | && prev.last().unwrap().range.contains_range(children.first().unwrap().range); | ||
65 | if !needs_flattening { | ||
66 | prev.extend(children); | ||
67 | } else { | ||
68 | let mut parent = prev.pop().unwrap(); | ||
69 | for ele in children { | ||
70 | assert!(parent.range.contains_range(ele.range)); | ||
71 | let mut cloned = parent.clone(); | ||
72 | parent.range = TextRange::new(parent.range.start(), ele.range.start()); | ||
73 | cloned.range = TextRange::new(ele.range.end(), cloned.range.end()); | ||
74 | if !parent.range.is_empty() { | ||
75 | prev.push(parent); | ||
76 | } | ||
77 | prev.push(ele); | ||
78 | parent = cloned; | ||
79 | } | ||
80 | if !parent.range.is_empty() { | ||
81 | prev.push(parent); | ||
82 | } | ||
83 | } | ||
84 | } | ||
85 | |||
86 | fn add(&mut self, range: HighlightedRange) { | ||
87 | self.stack | ||
88 | .last_mut() | ||
89 | .expect("during DFS traversal, the stack must not be empty") | ||
90 | .push(range) | ||
91 | } | ||
92 | |||
93 | fn flattened(mut self) -> Vec<HighlightedRange> { | ||
94 | assert_eq!( | ||
95 | self.stack.len(), | ||
96 | 1, | ||
97 | "after DFS traversal, the stack should only contain a single element" | ||
98 | ); | ||
99 | let mut res = self.stack.pop().unwrap(); | ||
100 | res.sort_by_key(|range| range.range.start()); | ||
101 | // Check that ranges are sorted and disjoint | ||
102 | assert!(res | ||
103 | .iter() | ||
104 | .zip(res.iter().skip(1)) | ||
105 | .all(|(left, right)| left.range.end() <= right.range.start())); | ||
106 | res | ||
107 | } | ||
108 | } | ||
109 | |||
110 | pub(crate) fn highlight( | 42 | pub(crate) fn highlight( |
111 | db: &RootDatabase, | 43 | db: &RootDatabase, |
112 | file_id: FileId, | 44 | file_id: FileId, |
@@ -291,6 +223,81 @@ pub(crate) fn highlight( | |||
291 | stack.flattened() | 223 | stack.flattened() |
292 | } | 224 | } |
293 | 225 | ||
226 | #[derive(Debug)] | ||
227 | struct HighlightedRangeStack { | ||
228 | stack: Vec<Vec<HighlightedRange>>, | ||
229 | } | ||
230 | |||
231 | /// We use a stack to implement the flattening logic for the highlighted | ||
232 | /// syntax ranges. | ||
233 | impl HighlightedRangeStack { | ||
234 | fn new() -> Self { | ||
235 | Self { stack: vec![Vec::new()] } | ||
236 | } | ||
237 | |||
238 | fn push(&mut self) { | ||
239 | self.stack.push(Vec::new()); | ||
240 | } | ||
241 | |||
242 | /// Flattens the highlighted ranges. | ||
243 | /// | ||
244 | /// For example `#[cfg(feature = "foo")]` contains the nested ranges: | ||
245 | /// 1) parent-range: Attribute [0, 23) | ||
246 | /// 2) child-range: String [16, 21) | ||
247 | /// | ||
248 | /// The following code implements the flattening, for our example this results to: | ||
249 | /// `[Attribute [0, 16), String [16, 21), Attribute [21, 23)]` | ||
250 | fn pop(&mut self) { | ||
251 | let children = self.stack.pop().unwrap(); | ||
252 | let prev = self.stack.last_mut().unwrap(); | ||
253 | let needs_flattening = !children.is_empty() | ||
254 | && !prev.is_empty() | ||
255 | && prev.last().unwrap().range.contains_range(children.first().unwrap().range); | ||
256 | if !needs_flattening { | ||
257 | prev.extend(children); | ||
258 | } else { | ||
259 | let mut parent = prev.pop().unwrap(); | ||
260 | for ele in children { | ||
261 | assert!(parent.range.contains_range(ele.range)); | ||
262 | let mut cloned = parent.clone(); | ||
263 | parent.range = TextRange::new(parent.range.start(), ele.range.start()); | ||
264 | cloned.range = TextRange::new(ele.range.end(), cloned.range.end()); | ||
265 | if !parent.range.is_empty() { | ||
266 | prev.push(parent); | ||
267 | } | ||
268 | prev.push(ele); | ||
269 | parent = cloned; | ||
270 | } | ||
271 | if !parent.range.is_empty() { | ||
272 | prev.push(parent); | ||
273 | } | ||
274 | } | ||
275 | } | ||
276 | |||
277 | fn add(&mut self, range: HighlightedRange) { | ||
278 | self.stack | ||
279 | .last_mut() | ||
280 | .expect("during DFS traversal, the stack must not be empty") | ||
281 | .push(range) | ||
282 | } | ||
283 | |||
284 | fn flattened(mut self) -> Vec<HighlightedRange> { | ||
285 | assert_eq!( | ||
286 | self.stack.len(), | ||
287 | 1, | ||
288 | "after DFS traversal, the stack should only contain a single element" | ||
289 | ); | ||
290 | let mut res = self.stack.pop().unwrap(); | ||
291 | res.sort_by_key(|range| range.range.start()); | ||
292 | // Check that ranges are sorted and disjoint | ||
293 | assert!(res | ||
294 | .iter() | ||
295 | .zip(res.iter().skip(1)) | ||
296 | .all(|(left, right)| left.range.end() <= right.range.start())); | ||
297 | res | ||
298 | } | ||
299 | } | ||
300 | |||
294 | fn highlight_format_specifier(kind: FormatSpecifier) -> Option<HighlightTag> { | 301 | fn highlight_format_specifier(kind: FormatSpecifier) -> Option<HighlightTag> { |
295 | Some(match kind { | 302 | Some(match kind { |
296 | FormatSpecifier::Open | 303 | FormatSpecifier::Open |
diff --git a/crates/ra_ide/src/syntax_tree.rs b/crates/ra_ide/src/syntax_tree.rs index 86c70ff83..a341684fd 100644 --- a/crates/ra_ide/src/syntax_tree.rs +++ b/crates/ra_ide/src/syntax_tree.rs | |||
@@ -1,6 +1,4 @@ | |||
1 | //! FIXME: write short doc here | 1 | use ra_db::{FileId, SourceDatabase}; |
2 | |||
3 | use ra_db::SourceDatabase; | ||
4 | use ra_ide_db::RootDatabase; | 2 | use ra_ide_db::RootDatabase; |
5 | use ra_syntax::{ | 3 | use ra_syntax::{ |
6 | algo, AstNode, NodeOrToken, SourceFile, | 4 | algo, AstNode, NodeOrToken, SourceFile, |
@@ -8,8 +6,16 @@ use ra_syntax::{ | |||
8 | SyntaxToken, TextRange, TextSize, | 6 | SyntaxToken, TextRange, TextSize, |
9 | }; | 7 | }; |
10 | 8 | ||
11 | pub use ra_db::FileId; | 9 | // Feature: Show Syntax Tree |
12 | 10 | // | |
11 | // Shows the parse tree of the current file. It exists mostly for debugging | ||
12 | // rust-analyzer itself. | ||
13 | // | ||
14 | // |=== | ||
15 | // | Editor | Action Name | ||
16 | // | ||
17 | // | VS Code | **Rust Analyzer: Show Syntax Tree** | ||
18 | // |=== | ||
13 | pub(crate) fn syntax_tree( | 19 | pub(crate) fn syntax_tree( |
14 | db: &RootDatabase, | 20 | db: &RootDatabase, |
15 | file_id: FileId, | 21 | file_id: FileId, |
diff --git a/crates/ra_ide/src/typing.rs b/crates/ra_ide/src/typing.rs index 39bb3b357..67e2c33a0 100644 --- a/crates/ra_ide/src/typing.rs +++ b/crates/ra_ide/src/typing.rs | |||
@@ -32,6 +32,13 @@ pub(crate) use on_enter::on_enter; | |||
32 | 32 | ||
33 | pub(crate) const TRIGGER_CHARS: &str = ".=>"; | 33 | pub(crate) const TRIGGER_CHARS: &str = ".=>"; |
34 | 34 | ||
35 | // Feature: On Typing Assists | ||
36 | // | ||
37 | // Some features trigger on typing certain characters: | ||
38 | // | ||
39 | // - typing `let =` tries to smartly add `;` if `=` is followed by an existing expression | ||
40 | // - Enter inside comments automatically inserts `///` | ||
41 | // - typing `.` in a chain method call auto-indents | ||
35 | pub(crate) fn on_char_typed( | 42 | pub(crate) fn on_char_typed( |
36 | db: &RootDatabase, | 43 | db: &RootDatabase, |
37 | position: FilePosition, | 44 | position: FilePosition, |
diff --git a/crates/ra_ide_db/src/search.rs b/crates/ra_ide_db/src/search.rs index 589f44771..335a1ad03 100644 --- a/crates/ra_ide_db/src/search.rs +++ b/crates/ra_ide_db/src/search.rs | |||
@@ -124,29 +124,33 @@ impl Definition { | |||
124 | 124 | ||
125 | let vis = self.visibility(db); | 125 | let vis = self.visibility(db); |
126 | 126 | ||
127 | // FIXME: | ||
128 | // The following logic are wrong that it does not search | ||
129 | // for submodules within other files recursively. | ||
130 | |||
131 | if let Some(Visibility::Module(module)) = vis.and_then(|it| it.into()) { | 127 | if let Some(Visibility::Module(module)) = vis.and_then(|it| it.into()) { |
132 | let module: Module = module.into(); | 128 | let module: Module = module.into(); |
133 | let mut res = FxHashMap::default(); | 129 | let mut res = FxHashMap::default(); |
134 | let src = module.definition_source(db); | ||
135 | let file_id = src.file_id.original_file(db); | ||
136 | 130 | ||
137 | match src.value { | 131 | let mut to_visit = vec![module]; |
138 | ModuleSource::Module(m) => { | 132 | let mut is_first = true; |
139 | let range = Some(m.syntax().text_range()); | 133 | while let Some(module) = to_visit.pop() { |
140 | res.insert(file_id, range); | 134 | let src = module.definition_source(db); |
141 | } | 135 | let file_id = src.file_id.original_file(db); |
142 | ModuleSource::SourceFile(_) => { | 136 | match src.value { |
143 | res.insert(file_id, None); | 137 | ModuleSource::Module(m) => { |
144 | res.extend(module.children(db).map(|m| { | 138 | if is_first { |
145 | let src = m.definition_source(db); | 139 | let range = Some(m.syntax().text_range()); |
146 | (src.file_id.original_file(db), None) | 140 | res.insert(file_id, range); |
147 | })); | 141 | } else { |
148 | } | 142 | // We have already added the enclosing file to the search scope, |
143 | // so do nothing. | ||
144 | } | ||
145 | } | ||
146 | ModuleSource::SourceFile(_) => { | ||
147 | res.insert(file_id, None); | ||
148 | } | ||
149 | }; | ||
150 | is_first = false; | ||
151 | to_visit.extend(module.children(db)); | ||
149 | } | 152 | } |
153 | |||
150 | return SearchScope::new(res); | 154 | return SearchScope::new(res); |
151 | } | 155 | } |
152 | 156 | ||
diff --git a/crates/ra_ide_db/src/symbol_index.rs b/crates/ra_ide_db/src/symbol_index.rs index 95be11134..acc31fe3b 100644 --- a/crates/ra_ide_db/src/symbol_index.rs +++ b/crates/ra_ide_db/src/symbol_index.rs | |||
@@ -110,6 +110,27 @@ fn file_symbols(db: &impl SymbolsDatabase, file_id: FileId) -> Arc<SymbolIndex> | |||
110 | Arc::new(SymbolIndex::new(symbols)) | 110 | Arc::new(SymbolIndex::new(symbols)) |
111 | } | 111 | } |
112 | 112 | ||
113 | // Feature: Workspace Symbol | ||
114 | // | ||
115 | // Uses fuzzy-search to find types, modules and functions by name across your | ||
116 | // project and dependencies. This is **the** most useful feature, which improves code | ||
117 | // navigation tremendously. It mostly works on top of the built-in LSP | ||
118 | // functionality, however `#` and `*` symbols can be used to narrow down the | ||
119 | // search. Specifically, | ||
120 | // | ||
121 | // - `Foo` searches for `Foo` type in the current workspace | ||
122 | // - `foo#` searches for `foo` function in the current workspace | ||
123 | // - `Foo*` searches for `Foo` type among dependencies, including `stdlib` | ||
124 | // - `foo#*` searches for `foo` function among dependencies | ||
125 | // | ||
126 | // That is, `#` switches from "types" to all symbols, `*` switches from the current | ||
127 | // workspace to dependencies. | ||
128 | // | ||
129 | // |=== | ||
130 | // | Editor | Shortcut | ||
131 | // | ||
132 | // | VS Code | kbd:[Ctrl+T] | ||
133 | // |=== | ||
113 | pub fn world_symbols(db: &RootDatabase, query: Query) -> Vec<FileSymbol> { | 134 | pub fn world_symbols(db: &RootDatabase, query: Query) -> Vec<FileSymbol> { |
114 | /// Need to wrap Snapshot to provide `Clone` impl for `map_with` | 135 | /// Need to wrap Snapshot to provide `Clone` impl for `map_with` |
115 | struct Snap(salsa::Snapshot<RootDatabase>); | 136 | struct Snap(salsa::Snapshot<RootDatabase>); |
diff --git a/crates/ra_syntax/src/ast.rs b/crates/ra_syntax/src/ast.rs index 1876afe95..eddc807d5 100644 --- a/crates/ra_syntax/src/ast.rs +++ b/crates/ra_syntax/src/ast.rs | |||
@@ -75,7 +75,7 @@ impl<N> AstChildren<N> { | |||
75 | impl<N: AstNode> Iterator for AstChildren<N> { | 75 | impl<N: AstNode> Iterator for AstChildren<N> { |
76 | type Item = N; | 76 | type Item = N; |
77 | fn next(&mut self) -> Option<N> { | 77 | fn next(&mut self) -> Option<N> { |
78 | self.inner.by_ref().find_map(N::cast) | 78 | self.inner.find_map(N::cast) |
79 | } | 79 | } |
80 | } | 80 | } |
81 | 81 | ||
diff --git a/crates/ra_syntax/src/ast/generated/nodes.rs b/crates/ra_syntax/src/ast/generated/nodes.rs index 255402fbc..cb430ca01 100644 --- a/crates/ra_syntax/src/ast/generated/nodes.rs +++ b/crates/ra_syntax/src/ast/generated/nodes.rs | |||
@@ -1081,6 +1081,7 @@ pub struct BlockExpr { | |||
1081 | impl ast::AttrsOwner for BlockExpr {} | 1081 | impl ast::AttrsOwner for BlockExpr {} |
1082 | impl ast::ModuleItemOwner for BlockExpr {} | 1082 | impl ast::ModuleItemOwner for BlockExpr {} |
1083 | impl BlockExpr { | 1083 | impl BlockExpr { |
1084 | pub fn label(&self) -> Option<Label> { support::child(&self.syntax) } | ||
1084 | pub fn l_curly_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T!['{']) } | 1085 | pub fn l_curly_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T!['{']) } |
1085 | pub fn statements(&self) -> AstChildren<Stmt> { support::children(&self.syntax) } | 1086 | pub fn statements(&self) -> AstChildren<Stmt> { support::children(&self.syntax) } |
1086 | pub fn expr(&self) -> Option<Expr> { support::child(&self.syntax) } | 1087 | pub fn expr(&self) -> Option<Expr> { support::child(&self.syntax) } |
diff --git a/crates/ra_syntax/src/syntax_node.rs b/crates/ra_syntax/src/syntax_node.rs index e566af7e8..9650b8781 100644 --- a/crates/ra_syntax/src/syntax_node.rs +++ b/crates/ra_syntax/src/syntax_node.rs | |||
@@ -48,11 +48,11 @@ impl SyntaxTreeBuilder { | |||
48 | 48 | ||
49 | pub fn finish(self) -> Parse<SyntaxNode> { | 49 | pub fn finish(self) -> Parse<SyntaxNode> { |
50 | let (green, errors) = self.finish_raw(); | 50 | let (green, errors) = self.finish_raw(); |
51 | let node = SyntaxNode::new_root(green); | ||
52 | if cfg!(debug_assertions) { | 51 | if cfg!(debug_assertions) { |
52 | let node = SyntaxNode::new_root(green.clone()); | ||
53 | crate::validation::validate_block_structure(&node); | 53 | crate::validation::validate_block_structure(&node); |
54 | } | 54 | } |
55 | Parse::new(node.green().clone(), errors) | 55 | Parse::new(green, errors) |
56 | } | 56 | } |
57 | 57 | ||
58 | pub fn token(&mut self, kind: SyntaxKind, text: SmolStr) { | 58 | pub fn token(&mut self, kind: SyntaxKind, text: SmolStr) { |
diff --git a/docs/dev/lsp-extensions.md b/docs/dev/lsp-extensions.md index d06da355d..b7237ee90 100644 --- a/docs/dev/lsp-extensions.md +++ b/docs/dev/lsp-extensions.md | |||
@@ -7,6 +7,16 @@ All capabilities are enabled via `experimental` field of `ClientCapabilities` or | |||
7 | Requests which we hope to upstream live under `experimental/` namespace. | 7 | Requests which we hope to upstream live under `experimental/` namespace. |
8 | Requests, which are likely to always remain specific to `rust-analyzer` are under `rust-analyzer/` namespace. | 8 | Requests, which are likely to always remain specific to `rust-analyzer` are under `rust-analyzer/` namespace. |
9 | 9 | ||
10 | If you want to be notified about the changes to this document, subscribe to [#4604](https://github.com/rust-analyzer/rust-analyzer/issues/4604). | ||
11 | |||
12 | ## `initializationOptions` | ||
13 | |||
14 | As `initializationOptions`, `rust-analyzer` expects `"rust-analyzer"` section of the configuration. | ||
15 | That is, `rust-analyzer` usually sends `"workspace/configuration"` request with `{ "items": ["rust-analyzer"] }` payload. | ||
16 | `initializationOptions` should contain the same data that would be in the first item of the result. | ||
17 | It's OK to not send anything, then all the settings would take their default values. | ||
18 | However, some settings can not be changed after startup at the moment. | ||
19 | |||
10 | ## Snippet `TextEdit` | 20 | ## Snippet `TextEdit` |
11 | 21 | ||
12 | **Issue:** https://github.com/microsoft/language-server-protocol/issues/724 | 22 | **Issue:** https://github.com/microsoft/language-server-protocol/issues/724 |
diff --git a/docs/dev/lsp-features.md b/docs/dev/lsp-features.md deleted file mode 100644 index 00b0867d7..000000000 --- a/docs/dev/lsp-features.md +++ /dev/null | |||
@@ -1,72 +0,0 @@ | |||
1 | # Supported LSP features | ||
2 | |||
3 | This list documents LSP features, supported by rust-analyzer. | ||
4 | |||
5 | ## General | ||
6 | - [x] [initialize](https://microsoft.github.io/language-server-protocol/specification#initialize) | ||
7 | - [x] [initialized](https://microsoft.github.io/language-server-protocol/specification#initialized) | ||
8 | - [x] [shutdown](https://microsoft.github.io/language-server-protocol/specification#shutdown) | ||
9 | - [ ] [exit](https://microsoft.github.io/language-server-protocol/specification#exit) | ||
10 | - [x] [$/cancelRequest](https://microsoft.github.io/language-server-protocol/specification#cancelRequest) | ||
11 | |||
12 | ## Workspace | ||
13 | - [ ] [workspace/workspaceFolders](https://microsoft.github.io/language-server-protocol/specification#workspace_workspaceFolders) | ||
14 | - [ ] [workspace/didChangeWorkspaceFolders](https://microsoft.github.io/language-server-protocol/specification#workspace_didChangeWorkspaceFolders) | ||
15 | - [x] [workspace/didChangeConfiguration](https://microsoft.github.io/language-server-protocol/specification#workspace_didChangeConfiguration) | ||
16 | - [ ] [workspace/configuration](https://microsoft.github.io/language-server-protocol/specification#workspace_configuration) | ||
17 | - [x] [workspace/didChangeWatchedFiles](https://microsoft.github.io/language-server-protocol/specification#workspace_didChangeWatchedFiles) | ||
18 | - [x] [workspace/symbol](https://microsoft.github.io/language-server-protocol/specification#workspace_symbol) | ||
19 | - [ ] [workspace/applyEdit](https://microsoft.github.io/language-server-protocol/specification#workspace_applyEdit) | ||
20 | |||
21 | ## Text Synchronization | ||
22 | - [x] [textDocument/didOpen](https://microsoft.github.io/language-server-protocol/specification#textDocument_didOpen) | ||
23 | - [x] [textDocument/didChange](https://microsoft.github.io/language-server-protocol/specification#textDocument_didChange) | ||
24 | - [ ] [textDocument/willSave](https://microsoft.github.io/language-server-protocol/specification#textDocument_willSave) | ||
25 | - [ ] [textDocument/willSaveWaitUntil](https://microsoft.github.io/language-server-protocol/specification#textDocument_willSaveWaitUntil) | ||
26 | - [x] [textDocument/didSave](https://microsoft.github.io/language-server-protocol/specification#textDocument_didSave) | ||
27 | - [x] [textDocument/didClose](https://microsoft.github.io/language-server-protocol/specification#textDocument_didClose) | ||
28 | |||
29 | ## Diagnostics | ||
30 | - [x] [textDocument/publishDiagnostics](https://microsoft.github.io/language-server-protocol/specification#textDocument_publishDiagnostics) | ||
31 | |||
32 | ## Lanuguage Features | ||
33 | - [x] [textDocument/completion](https://microsoft.github.io/language-server-protocol/specification#textDocument_completion) | ||
34 | - open close: false | ||
35 | - change: Full | ||
36 | - will save: false | ||
37 | - will save wait until: false | ||
38 | - save: false | ||
39 | - [x] [completionItem/resolve](https://microsoft.github.io/language-server-protocol/specification#completionItem_resolve) | ||
40 | - resolve provider: none | ||
41 | - trigger characters: `:`, `.` | ||
42 | - [x] [textDocument/hover](https://microsoft.github.io/language-server-protocol/specification#textDocument_hover) | ||
43 | - [x] [textDocument/signatureHelp](https://microsoft.github.io/language-server-protocol/specification#textDocument_signatureHelp) | ||
44 | - trigger characters: `(`, `,` | ||
45 | - [ ] [textDocument/declaration](https://microsoft.github.io/language-server-protocol/specification#textDocument_declaration) | ||
46 | - [x] [textDocument/definition](https://microsoft.github.io/language-server-protocol/specification#textDocument_definition) | ||
47 | - [x] [textDocument/typeDefinition](https://microsoft.github.io/language-server-protocol/specification#textDocument_typeDefinition) | ||
48 | - [x] [textDocument/implementation](https://microsoft.github.io/language-server-protocol/specification#textDocument_implementation) | ||
49 | - [x] [textDocument/references](https://microsoft.github.io/language-server-protocol/specification#textDocument_references) | ||
50 | - [x] [textDocument/documentHighlight](https://microsoft.github.io/language-server-protocol/specification#textDocument_documentHighlight) | ||
51 | - [x] [textDocument/documentSymbol](https://microsoft.github.io/language-server-protocol/specification#textDocument_documentSymbol) | ||
52 | - [x] [textDocument/codeAction](https://microsoft.github.io/language-server-protocol/specification#textDocument_codeAction) | ||
53 | - [x] [textDocument/selectionRange](https://github.com/Microsoft/language-server-protocol/issues/613) | ||
54 | - rust-analyzer.syntaxTree | ||
55 | - rust-analyzer.matchingBrace | ||
56 | - rust-analyzer.parentModule | ||
57 | - rust-analyzer.joinLines | ||
58 | - rust-analyzer.run | ||
59 | - rust-analyzer.analyzerStatus | ||
60 | - [x] [textDocument/codeLens](https://microsoft.github.io/language-server-protocol/specification#textDocument_codeLens) | ||
61 | - [x] [codeLens/resolve](https://microsoft.github.io/language-server-protocol/specification#codeLens_resolve) | ||
62 | - [ ] [documentLink/resolve](https://microsoft.github.io/language-server-protocol/specification#documentLink_resolve) | ||
63 | - [ ] [textDocument/documentColor](https://microsoft.github.io/language-server-protocol/specification#textDocument_documentColor) | ||
64 | - [ ] [textDocument/colorPresentation](https://microsoft.github.io/language-server-protocol/specification#textDocument_colorPresentation) | ||
65 | - [x] [textDocument/formatting](https://microsoft.github.io/language-server-protocol/specification#textDocument_formatting) | ||
66 | - [ ] [textDocument/rangeFormatting](https://microsoft.github.io/language-server-protocol/specification#textDocument_rangeFormatting) | ||
67 | - [x] [textDocument/onTypeFormatting](https://microsoft.github.io/language-server-protocol/specification#textDocument_onTypeFormatting) | ||
68 | - first trigger character: `=` | ||
69 | - more trigger character `.` | ||
70 | - [x] [textDocument/rename](https://microsoft.github.io/language-server-protocol/specification#textDocument_rename) | ||
71 | - [x] [textDocument/prepareRename](https://microsoft.github.io/language-server-protocol/specification#textDocument_prepareRename) | ||
72 | - [x] [textDocument/foldingRange](https://microsoft.github.io/language-server-protocol/specification#textDocument_foldingRange) | ||
diff --git a/docs/user/features.md b/docs/user/features.md deleted file mode 100644 index 12ecdec13..000000000 --- a/docs/user/features.md +++ /dev/null | |||
@@ -1,218 +0,0 @@ | |||
1 | This document is an index of features that the rust-analyzer language server | ||
2 | provides. Shortcuts are for the default VS Code layout. If there's no shortcut, | ||
3 | you can use <kbd>Ctrl+Shift+P</kbd> to search for the corresponding action. | ||
4 | |||
5 | ### Workspace Symbol <kbd>ctrl+t</kbd> | ||
6 | |||
7 | Uses fuzzy-search to find types, modules and functions by name across your | ||
8 | project and dependencies. This is **the** most useful feature, which improves code | ||
9 | navigation tremendously. It mostly works on top of the built-in LSP | ||
10 | functionality, however `#` and `*` symbols can be used to narrow down the | ||
11 | search. Specifically, | ||
12 | |||
13 | - `Foo` searches for `Foo` type in the current workspace | ||
14 | - `foo#` searches for `foo` function in the current workspace | ||
15 | - `Foo*` searches for `Foo` type among dependencies, including `stdlib` | ||
16 | - `foo#*` searches for `foo` function among dependencies | ||
17 | |||
18 | That is, `#` switches from "types" to all symbols, `*` switches from the current | ||
19 | workspace to dependencies. | ||
20 | |||
21 | ### Document Symbol <kbd>ctrl+shift+o</kbd> | ||
22 | |||
23 | Provides a tree of the symbols defined in the file. Can be used to | ||
24 | |||
25 | * fuzzy search symbol in a file (super useful) | ||
26 | * draw breadcrumbs to describe the context around the cursor | ||
27 | * draw outline of the file | ||
28 | |||
29 | ### On Typing Assists | ||
30 | |||
31 | Some features trigger on typing certain characters: | ||
32 | |||
33 | - typing `let =` tries to smartly add `;` if `=` is followed by an existing expression | ||
34 | - Enter inside comments automatically inserts `///` | ||
35 | - typing `.` in a chain method call auto-indents | ||
36 | |||
37 | ### Extend Selection | ||
38 | |||
39 | Extends the current selection to the encompassing syntactic construct | ||
40 | (expression, statement, item, module, etc). It works with multiple cursors. This | ||
41 | is a relatively new feature of LSP: | ||
42 | https://github.com/Microsoft/language-server-protocol/issues/613, check your | ||
43 | editor's LSP library to see if this feature is supported. | ||
44 | |||
45 | ### Go to Definition | ||
46 | |||
47 | Navigates to the definition of an identifier. | ||
48 | |||
49 | ### Go to Implementation | ||
50 | |||
51 | Navigates to the impl block of structs, enums or traits. Also implemented as a code lens. | ||
52 | |||
53 | ### Go to Type Defintion | ||
54 | |||
55 | Navigates to the type of an identifier. | ||
56 | |||
57 | ### Commands <kbd>ctrl+shift+p</kbd> | ||
58 | |||
59 | #### Run | ||
60 | |||
61 | Shows a popup suggesting to run a test/benchmark/binary **at the current cursor | ||
62 | location**. Super useful for repeatedly running just a single test. Do bind this | ||
63 | to a shortcut! | ||
64 | |||
65 | #### Parent Module | ||
66 | |||
67 | Navigates to the parent module of the current module. | ||
68 | |||
69 | #### Matching Brace | ||
70 | |||
71 | If the cursor is on any brace (`<>(){}[]`) which is a part of a brace-pair, | ||
72 | moves cursor to the matching brace. It uses the actual parser to determine | ||
73 | braces, so it won't confuse generics with comparisons. | ||
74 | |||
75 | #### Join Lines | ||
76 | |||
77 | Join selected lines into one, smartly fixing up whitespace and trailing commas. | ||
78 | |||
79 | #### Show Syntax Tree | ||
80 | |||
81 | Shows the parse tree of the current file. It exists mostly for debugging | ||
82 | rust-analyzer itself. | ||
83 | |||
84 | #### Expand Macro Recursively | ||
85 | |||
86 | Shows the full macro expansion of the macro at current cursor. | ||
87 | |||
88 | #### Status | ||
89 | |||
90 | Shows internal statistic about memory usage of rust-analyzer. | ||
91 | |||
92 | #### Show RA Version | ||
93 | |||
94 | Show current rust-analyzer version. | ||
95 | |||
96 | #### Toggle inlay hints | ||
97 | |||
98 | Toggle inlay hints view for the current workspace. | ||
99 | It is recommended to assign a shortcut for this command to quickly turn off | ||
100 | inlay hints when they prevent you from reading/writing the code. | ||
101 | |||
102 | #### Run Garbage Collection | ||
103 | |||
104 | Manually triggers GC. | ||
105 | |||
106 | #### Start Cargo Watch | ||
107 | |||
108 | Start `cargo watch` for live error highlighting. Will prompt to install if it's not already installed. | ||
109 | |||
110 | #### Stop Cargo Watch | ||
111 | |||
112 | Stop `cargo watch`. | ||
113 | |||
114 | #### Structural Seach and Replace | ||
115 | |||
116 | Search and replace with named wildcards that will match any expression. | ||
117 | The syntax for a structural search replace command is `<search_pattern> ==>> <replace_pattern>`. A `$<name>:expr` placeholder in the search pattern will match any expression and `$<name>` will reference it in the replacement. Available via the command `rust-analyzer.ssr`. | ||
118 | |||
119 | ```rust | ||
120 | // Using structural search replace command [foo($a:expr, $b:expr) ==>> ($a).foo($b)] | ||
121 | |||
122 | // BEFORE | ||
123 | String::from(foo(y + 5, z)) | ||
124 | |||
125 | // AFTER | ||
126 | String::from((y + 5).foo(z)) | ||
127 | ``` | ||
128 | |||
129 | ### Assists (Code Actions) | ||
130 | |||
131 | Assists, or code actions, are small local refactorings, available in a particular context. | ||
132 | They are usually triggered by a shortcut or by clicking a light bulb icon in the editor. | ||
133 | |||
134 | See [assists.md](./assists.md) for the list of available assists. | ||
135 | |||
136 | ### Magic Completions | ||
137 | |||
138 | In addition to usual reference completion, rust-analyzer provides some ✨magic✨ | ||
139 | completions as well: | ||
140 | |||
141 | Keywords like `if`, `else` `while`, `loop` are completed with braces, and cursor | ||
142 | is placed at the appropriate position. Even though `if` is easy to type, you | ||
143 | still want to complete it, to get ` { }` for free! `return` is inserted with a | ||
144 | space or `;` depending on the return type of the function. | ||
145 | |||
146 | When completing a function call, `()` are automatically inserted. If a function | ||
147 | takes arguments, the cursor is positioned inside the parenthesis. | ||
148 | |||
149 | There are postfix completions, which can be triggered by typing something like | ||
150 | `foo().if`. The word after `.` determines postfix completion. Possible variants are: | ||
151 | |||
152 | - `expr.if` -> `if expr {}` or `if let ... {}` for `Option` or `Result` | ||
153 | - `expr.match` -> `match expr {}` | ||
154 | - `expr.while` -> `while expr {}` or `while let ... {}` for `Option` or `Result` | ||
155 | - `expr.ref` -> `&expr` | ||
156 | - `expr.refm` -> `&mut expr` | ||
157 | - `expr.not` -> `!expr` | ||
158 | - `expr.dbg` -> `dbg!(expr)` | ||
159 | |||
160 | There also snippet completions: | ||
161 | |||
162 | #### Inside Expressions | ||
163 | |||
164 | - `pd` -> `println!("{:?}")` | ||
165 | - `ppd` -> `println!("{:#?}")` | ||
166 | |||
167 | #### Inside Modules | ||
168 | |||
169 | - `tfn` -> `#[test] fn f(){}` | ||
170 | - `tmod` -> | ||
171 | ```rust | ||
172 | #[cfg(test)] | ||
173 | mod tests { | ||
174 | use super::*; | ||
175 | |||
176 | #[test] | ||
177 | fn test_fn() {} | ||
178 | } | ||
179 | ``` | ||
180 | |||
181 | ### Code Highlighting | ||
182 | |||
183 | Experimental feature to let rust-analyzer highlight Rust code instead of using the | ||
184 | default highlighter. | ||
185 | |||
186 | #### Rainbow Highlighting | ||
187 | |||
188 | Experimental feature that, given code highlighting using rust-analyzer is | ||
189 | active, will pick unique colors for identifiers. | ||
190 | |||
191 | ### Code hints | ||
192 | |||
193 | Rust-analyzer has two types of hints to show the information about the code: | ||
194 | |||
195 | * hover hints, appearing on hover on any element. | ||
196 | |||
197 | These contain extended information on the hovered language item. | ||
198 | |||
199 | * inlay hints, shown near the element hinted directly in the editor. | ||
200 | |||
201 | Two types of inlay hints are displayed currently: | ||
202 | |||
203 | * type hints, displaying the minimal information on the type of the expression (if the information is available) | ||
204 | * method chaining hints, type information for multi-line method chains | ||
205 | * parameter name hints, displaying the names of the parameters in the corresponding methods | ||
206 | |||
207 | #### VS Code | ||
208 | |||
209 | In VS Code, the following settings can be used to configure the inlay hints: | ||
210 | |||
211 | * `rust-analyzer.inlayHints.typeHints` - enable hints for inferred types. | ||
212 | * `rust-analyzer.inlayHints.chainingHints` - enable hints for inferred types on method chains. | ||
213 | * `rust-analyzer.inlayHints.parameterHints` - enable hints for function parameters. | ||
214 | * `rust-analyzer.inlayHints.maxLength` — shortens the hints if their length exceeds the value specified. If no value is specified (`null`), no shortening is applied. | ||
215 | |||
216 | **Note:** VS Code does not have native support for inlay hints [yet](https://github.com/microsoft/vscode/issues/16221) and the hints are implemented using decorations. | ||
217 | This approach has limitations, the caret movement and bracket highlighting near the edges of the hint may be weird: | ||
218 | [1](https://github.com/rust-analyzer/rust-analyzer/issues/1623), [2](https://github.com/rust-analyzer/rust-analyzer/issues/3453). | ||
diff --git a/docs/user/assists.md b/docs/user/generated_assists.adoc index a1058ecde..4d2fb31d4 100644 --- a/docs/user/assists.md +++ b/docs/user/generated_assists.adoc | |||
@@ -1,18 +1,17 @@ | |||
1 | # Assists | 1 | [discrete] |
2 | 2 | === `add_custom_impl` | |
3 | Cursor position or selection is signified by `┃` character. | 3 | **Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_assists/src/handlers/add_custom_impl.rs#L14[add_custom_impl.rs] |
4 | |||
5 | |||
6 | ## `add_custom_impl` | ||
7 | 4 | ||
8 | Adds impl block for derived trait. | 5 | Adds impl block for derived trait. |
9 | 6 | ||
7 | .Before | ||
10 | ```rust | 8 | ```rust |
11 | // BEFORE | ||
12 | #[derive(Deb┃ug, Display)] | 9 | #[derive(Deb┃ug, Display)] |
13 | struct S; | 10 | struct S; |
11 | ``` | ||
14 | 12 | ||
15 | // AFTER | 13 | .After |
14 | ```rust | ||
16 | #[derive(Display)] | 15 | #[derive(Display)] |
17 | struct S; | 16 | struct S; |
18 | 17 | ||
@@ -21,18 +20,23 @@ impl Debug for S { | |||
21 | } | 20 | } |
22 | ``` | 21 | ``` |
23 | 22 | ||
24 | ## `add_derive` | 23 | |
24 | [discrete] | ||
25 | === `add_derive` | ||
26 | **Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_assists/src/handlers/add_derive.rs#L9[add_derive.rs] | ||
25 | 27 | ||
26 | Adds a new `#[derive()]` clause to a struct or enum. | 28 | Adds a new `#[derive()]` clause to a struct or enum. |
27 | 29 | ||
30 | .Before | ||
28 | ```rust | 31 | ```rust |
29 | // BEFORE | ||
30 | struct Point { | 32 | struct Point { |
31 | x: u32, | 33 | x: u32, |
32 | y: u32,┃ | 34 | y: u32,┃ |
33 | } | 35 | } |
36 | ``` | ||
34 | 37 | ||
35 | // AFTER | 38 | .After |
39 | ```rust | ||
36 | #[derive($0)] | 40 | #[derive($0)] |
37 | struct Point { | 41 | struct Point { |
38 | x: u32, | 42 | x: u32, |
@@ -40,36 +44,69 @@ struct Point { | |||
40 | } | 44 | } |
41 | ``` | 45 | ``` |
42 | 46 | ||
43 | ## `add_explicit_type` | 47 | |
48 | [discrete] | ||
49 | === `add_explicit_type` | ||
50 | **Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_assists/src/handlers/add_explicit_type.rs#L9[add_explicit_type.rs] | ||
44 | 51 | ||
45 | Specify type for a let binding. | 52 | Specify type for a let binding. |
46 | 53 | ||
54 | .Before | ||
47 | ```rust | 55 | ```rust |
48 | // BEFORE | ||
49 | fn main() { | 56 | fn main() { |
50 | let x┃ = 92; | 57 | let x┃ = 92; |
51 | } | 58 | } |
59 | ``` | ||
52 | 60 | ||
53 | // AFTER | 61 | .After |
62 | ```rust | ||
54 | fn main() { | 63 | fn main() { |
55 | let x: i32 = 92; | 64 | let x: i32 = 92; |
56 | } | 65 | } |
57 | ``` | 66 | ``` |
58 | 67 | ||
59 | ## `add_function` | 68 | |
69 | [discrete] | ||
70 | === `add_from_impl_for_enum` | ||
71 | **Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_assists/src/handlers/add_from_impl_for_enum.rs#L7[add_from_impl_for_enum.rs] | ||
72 | |||
73 | Adds a From impl for an enum variant with one tuple field. | ||
74 | |||
75 | .Before | ||
76 | ```rust | ||
77 | enum A { ┃One(u32) } | ||
78 | ``` | ||
79 | |||
80 | .After | ||
81 | ```rust | ||
82 | enum A { One(u32) } | ||
83 | |||
84 | impl From<u32> for A { | ||
85 | fn from(v: u32) -> Self { | ||
86 | A::One(v) | ||
87 | } | ||
88 | } | ||
89 | ``` | ||
90 | |||
91 | |||
92 | [discrete] | ||
93 | === `add_function` | ||
94 | **Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_assists/src/handlers/add_function.rs#L19[add_function.rs] | ||
60 | 95 | ||
61 | Adds a stub function with a signature matching the function under the cursor. | 96 | Adds a stub function with a signature matching the function under the cursor. |
62 | 97 | ||
98 | .Before | ||
63 | ```rust | 99 | ```rust |
64 | // BEFORE | ||
65 | struct Baz; | 100 | struct Baz; |
66 | fn baz() -> Baz { Baz } | 101 | fn baz() -> Baz { Baz } |
67 | fn foo() { | 102 | fn foo() { |
68 | bar┃("", baz()); | 103 | bar┃("", baz()); |
69 | } | 104 | } |
70 | 105 | ||
106 | ``` | ||
71 | 107 | ||
72 | // AFTER | 108 | .After |
109 | ```rust | ||
73 | struct Baz; | 110 | struct Baz; |
74 | fn baz() -> Baz { Baz } | 111 | fn baz() -> Baz { Baz } |
75 | fn foo() { | 112 | fn foo() { |
@@ -82,33 +119,43 @@ fn bar(arg: &str, baz: Baz) { | |||
82 | 119 | ||
83 | ``` | 120 | ``` |
84 | 121 | ||
85 | ## `add_hash` | 122 | |
123 | [discrete] | ||
124 | === `add_hash` | ||
125 | **Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_assists/src/handlers/raw_string.rs#L65[raw_string.rs] | ||
86 | 126 | ||
87 | Adds a hash to a raw string literal. | 127 | Adds a hash to a raw string literal. |
88 | 128 | ||
129 | .Before | ||
89 | ```rust | 130 | ```rust |
90 | // BEFORE | ||
91 | fn main() { | 131 | fn main() { |
92 | r#"Hello,┃ World!"#; | 132 | r#"Hello,┃ World!"#; |
93 | } | 133 | } |
134 | ``` | ||
94 | 135 | ||
95 | // AFTER | 136 | .After |
137 | ```rust | ||
96 | fn main() { | 138 | fn main() { |
97 | r##"Hello, World!"##; | 139 | r##"Hello, World!"##; |
98 | } | 140 | } |
99 | ``` | 141 | ``` |
100 | 142 | ||
101 | ## `add_impl` | 143 | |
144 | [discrete] | ||
145 | === `add_impl` | ||
146 | **Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_assists/src/handlers/add_impl.rs#L6[add_impl.rs] | ||
102 | 147 | ||
103 | Adds a new inherent impl for a type. | 148 | Adds a new inherent impl for a type. |
104 | 149 | ||
150 | .Before | ||
105 | ```rust | 151 | ```rust |
106 | // BEFORE | ||
107 | struct Ctx<T: Clone> { | 152 | struct Ctx<T: Clone> { |
108 | data: T,┃ | 153 | data: T,┃ |
109 | } | 154 | } |
155 | ``` | ||
110 | 156 | ||
111 | // AFTER | 157 | .After |
158 | ```rust | ||
112 | struct Ctx<T: Clone> { | 159 | struct Ctx<T: Clone> { |
113 | data: T, | 160 | data: T, |
114 | } | 161 | } |
@@ -118,12 +165,15 @@ impl<T: Clone> Ctx<T> { | |||
118 | } | 165 | } |
119 | ``` | 166 | ``` |
120 | 167 | ||
121 | ## `add_impl_default_members` | 168 | |
169 | [discrete] | ||
170 | === `add_impl_default_members` | ||
171 | **Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_assists/src/handlers/add_missing_impl_members.rs#L64[add_missing_impl_members.rs] | ||
122 | 172 | ||
123 | Adds scaffold for overriding default impl members. | 173 | Adds scaffold for overriding default impl members. |
124 | 174 | ||
175 | .Before | ||
125 | ```rust | 176 | ```rust |
126 | // BEFORE | ||
127 | trait Trait { | 177 | trait Trait { |
128 | Type X; | 178 | Type X; |
129 | fn foo(&self); | 179 | fn foo(&self); |
@@ -135,8 +185,10 @@ impl Trait for () { | |||
135 | fn foo(&self) {}┃ | 185 | fn foo(&self) {}┃ |
136 | 186 | ||
137 | } | 187 | } |
188 | ``` | ||
138 | 189 | ||
139 | // AFTER | 190 | .After |
191 | ```rust | ||
140 | trait Trait { | 192 | trait Trait { |
141 | Type X; | 193 | Type X; |
142 | fn foo(&self); | 194 | fn foo(&self); |
@@ -151,12 +203,15 @@ impl Trait for () { | |||
151 | } | 203 | } |
152 | ``` | 204 | ``` |
153 | 205 | ||
154 | ## `add_impl_missing_members` | 206 | |
207 | [discrete] | ||
208 | === `add_impl_missing_members` | ||
209 | **Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_assists/src/handlers/add_missing_impl_members.rs#L24[add_missing_impl_members.rs] | ||
155 | 210 | ||
156 | Adds scaffold for required impl members. | 211 | Adds scaffold for required impl members. |
157 | 212 | ||
213 | .Before | ||
158 | ```rust | 214 | ```rust |
159 | // BEFORE | ||
160 | trait Trait<T> { | 215 | trait Trait<T> { |
161 | Type X; | 216 | Type X; |
162 | fn foo(&self) -> T; | 217 | fn foo(&self) -> T; |
@@ -166,8 +221,10 @@ trait Trait<T> { | |||
166 | impl Trait<u32> for () {┃ | 221 | impl Trait<u32> for () {┃ |
167 | 222 | ||
168 | } | 223 | } |
224 | ``` | ||
169 | 225 | ||
170 | // AFTER | 226 | .After |
227 | ```rust | ||
171 | trait Trait<T> { | 228 | trait Trait<T> { |
172 | Type X; | 229 | Type X; |
173 | fn foo(&self) -> T; | 230 | fn foo(&self) -> T; |
@@ -182,17 +239,22 @@ impl Trait<u32> for () { | |||
182 | } | 239 | } |
183 | ``` | 240 | ``` |
184 | 241 | ||
185 | ## `add_new` | 242 | |
243 | [discrete] | ||
244 | === `add_new` | ||
245 | **Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_assists/src/handlers/add_new.rs#L12[add_new.rs] | ||
186 | 246 | ||
187 | Adds a new inherent impl for a type. | 247 | Adds a new inherent impl for a type. |
188 | 248 | ||
249 | .Before | ||
189 | ```rust | 250 | ```rust |
190 | // BEFORE | ||
191 | struct Ctx<T: Clone> { | 251 | struct Ctx<T: Clone> { |
192 | data: T,┃ | 252 | data: T,┃ |
193 | } | 253 | } |
254 | ``` | ||
194 | 255 | ||
195 | // AFTER | 256 | .After |
257 | ```rust | ||
196 | struct Ctx<T: Clone> { | 258 | struct Ctx<T: Clone> { |
197 | data: T, | 259 | data: T, |
198 | } | 260 | } |
@@ -203,25 +265,33 @@ impl<T: Clone> Ctx<T> { | |||
203 | 265 | ||
204 | ``` | 266 | ``` |
205 | 267 | ||
206 | ## `add_turbo_fish` | 268 | |
269 | [discrete] | ||
270 | === `add_turbo_fish` | ||
271 | **Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_assists/src/handlers/add_turbo_fish.rs#L10[add_turbo_fish.rs] | ||
207 | 272 | ||
208 | Adds `::<_>` to a call of a generic method or function. | 273 | Adds `::<_>` to a call of a generic method or function. |
209 | 274 | ||
275 | .Before | ||
210 | ```rust | 276 | ```rust |
211 | // BEFORE | ||
212 | fn make<T>() -> T { todo!() } | 277 | fn make<T>() -> T { todo!() } |
213 | fn main() { | 278 | fn main() { |
214 | let x = make┃(); | 279 | let x = make┃(); |
215 | } | 280 | } |
281 | ``` | ||
216 | 282 | ||
217 | // AFTER | 283 | .After |
284 | ```rust | ||
218 | fn make<T>() -> T { todo!() } | 285 | fn make<T>() -> T { todo!() } |
219 | fn main() { | 286 | fn main() { |
220 | let x = make::<${0:_}>(); | 287 | let x = make::<${0:_}>(); |
221 | } | 288 | } |
222 | ``` | 289 | ``` |
223 | 290 | ||
224 | ## `apply_demorgan` | 291 | |
292 | [discrete] | ||
293 | === `apply_demorgan` | ||
294 | **Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_assists/src/handlers/apply_demorgan.rs#L5[apply_demorgan.rs] | ||
225 | 295 | ||
226 | Apply [De Morgan's law](https://en.wikipedia.org/wiki/De_Morgan%27s_laws). | 296 | Apply [De Morgan's law](https://en.wikipedia.org/wiki/De_Morgan%27s_laws). |
227 | This transforms expressions of the form `!l || !r` into `!(l && r)`. | 297 | This transforms expressions of the form `!l || !r` into `!(l && r)`. |
@@ -229,29 +299,36 @@ This also works with `&&`. This assist can only be applied with the cursor | |||
229 | on either `||` or `&&`, with both operands being a negation of some kind. | 299 | on either `||` or `&&`, with both operands being a negation of some kind. |
230 | This means something of the form `!x` or `x != y`. | 300 | This means something of the form `!x` or `x != y`. |
231 | 301 | ||
302 | .Before | ||
232 | ```rust | 303 | ```rust |
233 | // BEFORE | ||
234 | fn main() { | 304 | fn main() { |
235 | if x != 4 ||┃ !y {} | 305 | if x != 4 ||┃ !y {} |
236 | } | 306 | } |
307 | ``` | ||
237 | 308 | ||
238 | // AFTER | 309 | .After |
310 | ```rust | ||
239 | fn main() { | 311 | fn main() { |
240 | if !(x == 4 && y) {} | 312 | if !(x == 4 && y) {} |
241 | } | 313 | } |
242 | ``` | 314 | ``` |
243 | 315 | ||
244 | ## `auto_import` | 316 | |
317 | [discrete] | ||
318 | === `auto_import` | ||
319 | **Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_assists/src/handlers/auto_import.rs#L18[auto_import.rs] | ||
245 | 320 | ||
246 | If the name is unresolved, provides all possible imports for it. | 321 | If the name is unresolved, provides all possible imports for it. |
247 | 322 | ||
323 | .Before | ||
248 | ```rust | 324 | ```rust |
249 | // BEFORE | ||
250 | fn main() { | 325 | fn main() { |
251 | let map = HashMap┃::new(); | 326 | let map = HashMap┃::new(); |
252 | } | 327 | } |
328 | ``` | ||
253 | 329 | ||
254 | // AFTER | 330 | .After |
331 | ```rust | ||
255 | use std::collections::HashMap; | 332 | use std::collections::HashMap; |
256 | 333 | ||
257 | fn main() { | 334 | fn main() { |
@@ -259,68 +336,59 @@ fn main() { | |||
259 | } | 336 | } |
260 | ``` | 337 | ``` |
261 | 338 | ||
262 | ## `change_lifetime_anon_to_named` | ||
263 | |||
264 | Change an anonymous lifetime to a named lifetime. | ||
265 | |||
266 | ```rust | ||
267 | // BEFORE | ||
268 | impl Cursor<'_┃> { | ||
269 | fn node(self) -> &SyntaxNode { | ||
270 | match self { | ||
271 | Cursor::Replace(node) | Cursor::Before(node) => node, | ||
272 | } | ||
273 | } | ||
274 | } | ||
275 | |||
276 | // AFTER | ||
277 | impl<'a> Cursor<'a> { | ||
278 | fn node(self) -> &SyntaxNode { | ||
279 | match self { | ||
280 | Cursor::Replace(node) | Cursor::Before(node) => node, | ||
281 | } | ||
282 | } | ||
283 | } | ||
284 | ``` | ||
285 | 339 | ||
286 | ## `change_return_type_to_result` | 340 | [discrete] |
341 | === `change_return_type_to_result` | ||
342 | **Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_assists/src/handlers/change_return_type_to_result.rs#L8[change_return_type_to_result.rs] | ||
287 | 343 | ||
288 | Change the function's return type to Result. | 344 | Change the function's return type to Result. |
289 | 345 | ||
346 | .Before | ||
290 | ```rust | 347 | ```rust |
291 | // BEFORE | ||
292 | fn foo() -> i32┃ { 42i32 } | 348 | fn foo() -> i32┃ { 42i32 } |
349 | ``` | ||
293 | 350 | ||
294 | // AFTER | 351 | .After |
352 | ```rust | ||
295 | fn foo() -> Result<i32, ${0:_}> { Ok(42i32) } | 353 | fn foo() -> Result<i32, ${0:_}> { Ok(42i32) } |
296 | ``` | 354 | ``` |
297 | 355 | ||
298 | ## `change_visibility` | 356 | |
357 | [discrete] | ||
358 | === `change_visibility` | ||
359 | **Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_assists/src/handlers/change_visibility.rs#L14[change_visibility.rs] | ||
299 | 360 | ||
300 | Adds or changes existing visibility specifier. | 361 | Adds or changes existing visibility specifier. |
301 | 362 | ||
363 | .Before | ||
302 | ```rust | 364 | ```rust |
303 | // BEFORE | ||
304 | ┃fn frobnicate() {} | 365 | ┃fn frobnicate() {} |
366 | ``` | ||
305 | 367 | ||
306 | // AFTER | 368 | .After |
369 | ```rust | ||
307 | pub(crate) fn frobnicate() {} | 370 | pub(crate) fn frobnicate() {} |
308 | ``` | 371 | ``` |
309 | 372 | ||
310 | ## `convert_to_guarded_return` | 373 | |
374 | [discrete] | ||
375 | === `convert_to_guarded_return` | ||
376 | **Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_assists/src/handlers/early_return.rs#L21[early_return.rs] | ||
311 | 377 | ||
312 | Replace a large conditional with a guarded return. | 378 | Replace a large conditional with a guarded return. |
313 | 379 | ||
380 | .Before | ||
314 | ```rust | 381 | ```rust |
315 | // BEFORE | ||
316 | fn main() { | 382 | fn main() { |
317 | ┃if cond { | 383 | ┃if cond { |
318 | foo(); | 384 | foo(); |
319 | bar(); | 385 | bar(); |
320 | } | 386 | } |
321 | } | 387 | } |
388 | ``` | ||
322 | 389 | ||
323 | // AFTER | 390 | .After |
391 | ```rust | ||
324 | fn main() { | 392 | fn main() { |
325 | if !cond { | 393 | if !cond { |
326 | return; | 394 | return; |
@@ -330,12 +398,15 @@ fn main() { | |||
330 | } | 398 | } |
331 | ``` | 399 | ``` |
332 | 400 | ||
333 | ## `fill_match_arms` | 401 | |
402 | [discrete] | ||
403 | === `fill_match_arms` | ||
404 | **Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_assists/src/handlers/fill_match_arms.rs#L14[fill_match_arms.rs] | ||
334 | 405 | ||
335 | Adds missing clauses to a `match` expression. | 406 | Adds missing clauses to a `match` expression. |
336 | 407 | ||
408 | .Before | ||
337 | ```rust | 409 | ```rust |
338 | // BEFORE | ||
339 | enum Action { Move { distance: u32 }, Stop } | 410 | enum Action { Move { distance: u32 }, Stop } |
340 | 411 | ||
341 | fn handle(action: Action) { | 412 | fn handle(action: Action) { |
@@ -343,8 +414,10 @@ fn handle(action: Action) { | |||
343 | ┃ | 414 | ┃ |
344 | } | 415 | } |
345 | } | 416 | } |
417 | ``` | ||
346 | 418 | ||
347 | // AFTER | 419 | .After |
420 | ```rust | ||
348 | enum Action { Move { distance: u32 }, Stop } | 421 | enum Action { Move { distance: u32 }, Stop } |
349 | 422 | ||
350 | fn handle(action: Action) { | 423 | fn handle(action: Action) { |
@@ -355,20 +428,25 @@ fn handle(action: Action) { | |||
355 | } | 428 | } |
356 | ``` | 429 | ``` |
357 | 430 | ||
358 | ## `fix_visibility` | 431 | |
432 | [discrete] | ||
433 | === `fix_visibility` | ||
434 | **Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_assists/src/handlers/fix_visibility.rs#L13[fix_visibility.rs] | ||
359 | 435 | ||
360 | Makes inaccessible item public. | 436 | Makes inaccessible item public. |
361 | 437 | ||
438 | .Before | ||
362 | ```rust | 439 | ```rust |
363 | // BEFORE | ||
364 | mod m { | 440 | mod m { |
365 | fn frobnicate() {} | 441 | fn frobnicate() {} |
366 | } | 442 | } |
367 | fn main() { | 443 | fn main() { |
368 | m::frobnicate┃() {} | 444 | m::frobnicate┃() {} |
369 | } | 445 | } |
446 | ``` | ||
370 | 447 | ||
371 | // AFTER | 448 | .After |
449 | ```rust | ||
372 | mod m { | 450 | mod m { |
373 | $0pub(crate) fn frobnicate() {} | 451 | $0pub(crate) fn frobnicate() {} |
374 | } | 452 | } |
@@ -377,154 +455,231 @@ fn main() { | |||
377 | } | 455 | } |
378 | ``` | 456 | ``` |
379 | 457 | ||
380 | ## `flip_binexpr` | 458 | |
459 | [discrete] | ||
460 | === `flip_binexpr` | ||
461 | **Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_assists/src/handlers/flip_binexpr.rs#L5[flip_binexpr.rs] | ||
381 | 462 | ||
382 | Flips operands of a binary expression. | 463 | Flips operands of a binary expression. |
383 | 464 | ||
465 | .Before | ||
384 | ```rust | 466 | ```rust |
385 | // BEFORE | ||
386 | fn main() { | 467 | fn main() { |
387 | let _ = 90 +┃ 2; | 468 | let _ = 90 +┃ 2; |
388 | } | 469 | } |
470 | ``` | ||
389 | 471 | ||
390 | // AFTER | 472 | .After |
473 | ```rust | ||
391 | fn main() { | 474 | fn main() { |
392 | let _ = 2 + 90; | 475 | let _ = 2 + 90; |
393 | } | 476 | } |
394 | ``` | 477 | ``` |
395 | 478 | ||
396 | ## `flip_comma` | 479 | |
480 | [discrete] | ||
481 | === `flip_comma` | ||
482 | **Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_assists/src/handlers/flip_comma.rs#L5[flip_comma.rs] | ||
397 | 483 | ||
398 | Flips two comma-separated items. | 484 | Flips two comma-separated items. |
399 | 485 | ||
486 | .Before | ||
400 | ```rust | 487 | ```rust |
401 | // BEFORE | ||
402 | fn main() { | 488 | fn main() { |
403 | ((1, 2),┃ (3, 4)); | 489 | ((1, 2),┃ (3, 4)); |
404 | } | 490 | } |
491 | ``` | ||
405 | 492 | ||
406 | // AFTER | 493 | .After |
494 | ```rust | ||
407 | fn main() { | 495 | fn main() { |
408 | ((3, 4), (1, 2)); | 496 | ((3, 4), (1, 2)); |
409 | } | 497 | } |
410 | ``` | 498 | ``` |
411 | 499 | ||
412 | ## `flip_trait_bound` | 500 | |
501 | [discrete] | ||
502 | === `flip_trait_bound` | ||
503 | **Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_assists/src/handlers/flip_trait_bound.rs#L9[flip_trait_bound.rs] | ||
413 | 504 | ||
414 | Flips two trait bounds. | 505 | Flips two trait bounds. |
415 | 506 | ||
507 | .Before | ||
416 | ```rust | 508 | ```rust |
417 | // BEFORE | ||
418 | fn foo<T: Clone +┃ Copy>() { } | 509 | fn foo<T: Clone +┃ Copy>() { } |
510 | ``` | ||
419 | 511 | ||
420 | // AFTER | 512 | .After |
513 | ```rust | ||
421 | fn foo<T: Copy + Clone>() { } | 514 | fn foo<T: Copy + Clone>() { } |
422 | ``` | 515 | ``` |
423 | 516 | ||
424 | ## `inline_local_variable` | 517 | |
518 | [discrete] | ||
519 | === `inline_local_variable` | ||
520 | **Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_assists/src/handlers/inline_local_variable.rs#L13[inline_local_variable.rs] | ||
425 | 521 | ||
426 | Inlines local variable. | 522 | Inlines local variable. |
427 | 523 | ||
524 | .Before | ||
428 | ```rust | 525 | ```rust |
429 | // BEFORE | ||
430 | fn main() { | 526 | fn main() { |
431 | let x┃ = 1 + 2; | 527 | let x┃ = 1 + 2; |
432 | x * 4; | 528 | x * 4; |
433 | } | 529 | } |
530 | ``` | ||
434 | 531 | ||
435 | // AFTER | 532 | .After |
533 | ```rust | ||
436 | fn main() { | 534 | fn main() { |
437 | (1 + 2) * 4; | 535 | (1 + 2) * 4; |
438 | } | 536 | } |
439 | ``` | 537 | ``` |
440 | 538 | ||
441 | ## `introduce_variable` | 539 | |
540 | [discrete] | ||
541 | === `introduce_named_lifetime` | ||
542 | **Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_assists/src/handlers/introduce_named_lifetime.rs#L12[introduce_named_lifetime.rs] | ||
543 | |||
544 | Change an anonymous lifetime to a named lifetime. | ||
545 | |||
546 | .Before | ||
547 | ```rust | ||
548 | impl Cursor<'_┃> { | ||
549 | fn node(self) -> &SyntaxNode { | ||
550 | match self { | ||
551 | Cursor::Replace(node) | Cursor::Before(node) => node, | ||
552 | } | ||
553 | } | ||
554 | } | ||
555 | ``` | ||
556 | |||
557 | .After | ||
558 | ```rust | ||
559 | impl<'a> Cursor<'a> { | ||
560 | fn node(self) -> &SyntaxNode { | ||
561 | match self { | ||
562 | Cursor::Replace(node) | Cursor::Before(node) => node, | ||
563 | } | ||
564 | } | ||
565 | } | ||
566 | ``` | ||
567 | |||
568 | |||
569 | [discrete] | ||
570 | === `introduce_variable` | ||
571 | **Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_assists/src/handlers/introduce_variable.rs#L14[introduce_variable.rs] | ||
442 | 572 | ||
443 | Extracts subexpression into a variable. | 573 | Extracts subexpression into a variable. |
444 | 574 | ||
575 | .Before | ||
445 | ```rust | 576 | ```rust |
446 | // BEFORE | ||
447 | fn main() { | 577 | fn main() { |
448 | ┃(1 + 2)┃ * 4; | 578 | ┃(1 + 2)┃ * 4; |
449 | } | 579 | } |
580 | ``` | ||
450 | 581 | ||
451 | // AFTER | 582 | .After |
583 | ```rust | ||
452 | fn main() { | 584 | fn main() { |
453 | let $0var_name = (1 + 2); | 585 | let $0var_name = (1 + 2); |
454 | var_name * 4; | 586 | var_name * 4; |
455 | } | 587 | } |
456 | ``` | 588 | ``` |
457 | 589 | ||
458 | ## `invert_if` | 590 | |
591 | [discrete] | ||
592 | === `invert_if` | ||
593 | **Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_assists/src/handlers/invert_if.rs#L12[invert_if.rs] | ||
459 | 594 | ||
460 | Apply invert_if | 595 | Apply invert_if |
461 | This transforms if expressions of the form `if !x {A} else {B}` into `if x {B} else {A}` | 596 | This transforms if expressions of the form `if !x {A} else {B}` into `if x {B} else {A}` |
462 | This also works with `!=`. This assist can only be applied with the cursor | 597 | This also works with `!=`. This assist can only be applied with the cursor |
463 | on `if`. | 598 | on `if`. |
464 | 599 | ||
600 | .Before | ||
465 | ```rust | 601 | ```rust |
466 | // BEFORE | ||
467 | fn main() { | 602 | fn main() { |
468 | if┃ !y { A } else { B } | 603 | if┃ !y { A } else { B } |
469 | } | 604 | } |
605 | ``` | ||
470 | 606 | ||
471 | // AFTER | 607 | .After |
608 | ```rust | ||
472 | fn main() { | 609 | fn main() { |
473 | if y { B } else { A } | 610 | if y { B } else { A } |
474 | } | 611 | } |
475 | ``` | 612 | ``` |
476 | 613 | ||
477 | ## `make_raw_string` | 614 | |
615 | [discrete] | ||
616 | === `make_raw_string` | ||
617 | **Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_assists/src/handlers/raw_string.rs#L10[raw_string.rs] | ||
478 | 618 | ||
479 | Adds `r#` to a plain string literal. | 619 | Adds `r#` to a plain string literal. |
480 | 620 | ||
621 | .Before | ||
481 | ```rust | 622 | ```rust |
482 | // BEFORE | ||
483 | fn main() { | 623 | fn main() { |
484 | "Hello,┃ World!"; | 624 | "Hello,┃ World!"; |
485 | } | 625 | } |
626 | ``` | ||
486 | 627 | ||
487 | // AFTER | 628 | .After |
629 | ```rust | ||
488 | fn main() { | 630 | fn main() { |
489 | r#"Hello, World!"#; | 631 | r#"Hello, World!"#; |
490 | } | 632 | } |
491 | ``` | 633 | ``` |
492 | 634 | ||
493 | ## `make_usual_string` | 635 | |
636 | [discrete] | ||
637 | === `make_usual_string` | ||
638 | **Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_assists/src/handlers/raw_string.rs#L39[raw_string.rs] | ||
494 | 639 | ||
495 | Turns a raw string into a plain string. | 640 | Turns a raw string into a plain string. |
496 | 641 | ||
642 | .Before | ||
497 | ```rust | 643 | ```rust |
498 | // BEFORE | ||
499 | fn main() { | 644 | fn main() { |
500 | r#"Hello,┃ "World!""#; | 645 | r#"Hello,┃ "World!""#; |
501 | } | 646 | } |
647 | ``` | ||
502 | 648 | ||
503 | // AFTER | 649 | .After |
650 | ```rust | ||
504 | fn main() { | 651 | fn main() { |
505 | "Hello, \"World!\""; | 652 | "Hello, \"World!\""; |
506 | } | 653 | } |
507 | ``` | 654 | ``` |
508 | 655 | ||
509 | ## `merge_imports` | 656 | |
657 | [discrete] | ||
658 | === `merge_imports` | ||
659 | **Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_assists/src/handlers/merge_imports.rs#L14[merge_imports.rs] | ||
510 | 660 | ||
511 | Merges two imports with a common prefix. | 661 | Merges two imports with a common prefix. |
512 | 662 | ||
663 | .Before | ||
513 | ```rust | 664 | ```rust |
514 | // BEFORE | ||
515 | use std::┃fmt::Formatter; | 665 | use std::┃fmt::Formatter; |
516 | use std::io; | 666 | use std::io; |
667 | ``` | ||
517 | 668 | ||
518 | // AFTER | 669 | .After |
670 | ```rust | ||
519 | use std::{fmt::Formatter, io}; | 671 | use std::{fmt::Formatter, io}; |
520 | ``` | 672 | ``` |
521 | 673 | ||
522 | ## `merge_match_arms` | 674 | |
675 | [discrete] | ||
676 | === `merge_match_arms` | ||
677 | **Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_assists/src/handlers/merge_match_arms.rs#L11[merge_match_arms.rs] | ||
523 | 678 | ||
524 | Merges identical match arms. | 679 | Merges identical match arms. |
525 | 680 | ||
681 | .Before | ||
526 | ```rust | 682 | ```rust |
527 | // BEFORE | ||
528 | enum Action { Move { distance: u32 }, Stop } | 683 | enum Action { Move { distance: u32 }, Stop } |
529 | 684 | ||
530 | fn handle(action: Action) { | 685 | fn handle(action: Action) { |
@@ -533,8 +688,10 @@ fn handle(action: Action) { | |||
533 | Action::Stop => foo(), | 688 | Action::Stop => foo(), |
534 | } | 689 | } |
535 | } | 690 | } |
691 | ``` | ||
536 | 692 | ||
537 | // AFTER | 693 | .After |
694 | ```rust | ||
538 | enum Action { Move { distance: u32 }, Stop } | 695 | enum Action { Move { distance: u32 }, Stop } |
539 | 696 | ||
540 | fn handle(action: Action) { | 697 | fn handle(action: Action) { |
@@ -544,12 +701,15 @@ fn handle(action: Action) { | |||
544 | } | 701 | } |
545 | ``` | 702 | ``` |
546 | 703 | ||
547 | ## `move_arm_cond_to_match_guard` | 704 | |
705 | [discrete] | ||
706 | === `move_arm_cond_to_match_guard` | ||
707 | **Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_assists/src/handlers/move_guard.rs#L56[move_guard.rs] | ||
548 | 708 | ||
549 | Moves if expression from match arm body into a guard. | 709 | Moves if expression from match arm body into a guard. |
550 | 710 | ||
711 | .Before | ||
551 | ```rust | 712 | ```rust |
552 | // BEFORE | ||
553 | enum Action { Move { distance: u32 }, Stop } | 713 | enum Action { Move { distance: u32 }, Stop } |
554 | 714 | ||
555 | fn handle(action: Action) { | 715 | fn handle(action: Action) { |
@@ -558,8 +718,10 @@ fn handle(action: Action) { | |||
558 | _ => (), | 718 | _ => (), |
559 | } | 719 | } |
560 | } | 720 | } |
721 | ``` | ||
561 | 722 | ||
562 | // AFTER | 723 | .After |
724 | ```rust | ||
563 | enum Action { Move { distance: u32 }, Stop } | 725 | enum Action { Move { distance: u32 }, Stop } |
564 | 726 | ||
565 | fn handle(action: Action) { | 727 | fn handle(action: Action) { |
@@ -570,28 +732,36 @@ fn handle(action: Action) { | |||
570 | } | 732 | } |
571 | ``` | 733 | ``` |
572 | 734 | ||
573 | ## `move_bounds_to_where_clause` | 735 | |
736 | [discrete] | ||
737 | === `move_bounds_to_where_clause` | ||
738 | **Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_assists/src/handlers/move_bounds.rs#L10[move_bounds.rs] | ||
574 | 739 | ||
575 | Moves inline type bounds to a where clause. | 740 | Moves inline type bounds to a where clause. |
576 | 741 | ||
742 | .Before | ||
577 | ```rust | 743 | ```rust |
578 | // BEFORE | ||
579 | fn apply<T, U, ┃F: FnOnce(T) -> U>(f: F, x: T) -> U { | 744 | fn apply<T, U, ┃F: FnOnce(T) -> U>(f: F, x: T) -> U { |
580 | f(x) | 745 | f(x) |
581 | } | 746 | } |
747 | ``` | ||
582 | 748 | ||
583 | // AFTER | 749 | .After |
750 | ```rust | ||
584 | fn apply<T, U, F>(f: F, x: T) -> U where F: FnOnce(T) -> U { | 751 | fn apply<T, U, F>(f: F, x: T) -> U where F: FnOnce(T) -> U { |
585 | f(x) | 752 | f(x) |
586 | } | 753 | } |
587 | ``` | 754 | ``` |
588 | 755 | ||
589 | ## `move_guard_to_arm_body` | 756 | |
757 | [discrete] | ||
758 | === `move_guard_to_arm_body` | ||
759 | **Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_assists/src/handlers/move_guard.rs#L8[move_guard.rs] | ||
590 | 760 | ||
591 | Moves match guard into match arm body. | 761 | Moves match guard into match arm body. |
592 | 762 | ||
763 | .Before | ||
593 | ```rust | 764 | ```rust |
594 | // BEFORE | ||
595 | enum Action { Move { distance: u32 }, Stop } | 765 | enum Action { Move { distance: u32 }, Stop } |
596 | 766 | ||
597 | fn handle(action: Action) { | 767 | fn handle(action: Action) { |
@@ -600,8 +770,10 @@ fn handle(action: Action) { | |||
600 | _ => (), | 770 | _ => (), |
601 | } | 771 | } |
602 | } | 772 | } |
773 | ``` | ||
603 | 774 | ||
604 | // AFTER | 775 | .After |
776 | ```rust | ||
605 | enum Action { Move { distance: u32 }, Stop } | 777 | enum Action { Move { distance: u32 }, Stop } |
606 | 778 | ||
607 | fn handle(action: Action) { | 779 | fn handle(action: Action) { |
@@ -612,75 +784,98 @@ fn handle(action: Action) { | |||
612 | } | 784 | } |
613 | ``` | 785 | ``` |
614 | 786 | ||
615 | ## `remove_dbg` | 787 | |
788 | [discrete] | ||
789 | === `remove_dbg` | ||
790 | **Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_assists/src/handlers/remove_dbg.rs#L8[remove_dbg.rs] | ||
616 | 791 | ||
617 | Removes `dbg!()` macro call. | 792 | Removes `dbg!()` macro call. |
618 | 793 | ||
794 | .Before | ||
619 | ```rust | 795 | ```rust |
620 | // BEFORE | ||
621 | fn main() { | 796 | fn main() { |
622 | ┃dbg!(92); | 797 | ┃dbg!(92); |
623 | } | 798 | } |
799 | ``` | ||
624 | 800 | ||
625 | // AFTER | 801 | .After |
802 | ```rust | ||
626 | fn main() { | 803 | fn main() { |
627 | 92; | 804 | 92; |
628 | } | 805 | } |
629 | ``` | 806 | ``` |
630 | 807 | ||
631 | ## `remove_hash` | 808 | |
809 | [discrete] | ||
810 | === `remove_hash` | ||
811 | **Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_assists/src/handlers/raw_string.rs#L89[raw_string.rs] | ||
632 | 812 | ||
633 | Removes a hash from a raw string literal. | 813 | Removes a hash from a raw string literal. |
634 | 814 | ||
815 | .Before | ||
635 | ```rust | 816 | ```rust |
636 | // BEFORE | ||
637 | fn main() { | 817 | fn main() { |
638 | r#"Hello,┃ World!"#; | 818 | r#"Hello,┃ World!"#; |
639 | } | 819 | } |
820 | ``` | ||
640 | 821 | ||
641 | // AFTER | 822 | .After |
823 | ```rust | ||
642 | fn main() { | 824 | fn main() { |
643 | r"Hello, World!"; | 825 | r"Hello, World!"; |
644 | } | 826 | } |
645 | ``` | 827 | ``` |
646 | 828 | ||
647 | ## `remove_mut` | 829 | |
830 | [discrete] | ||
831 | === `remove_mut` | ||
832 | **Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_assists/src/handlers/remove_mut.rs#L5[remove_mut.rs] | ||
648 | 833 | ||
649 | Removes the `mut` keyword. | 834 | Removes the `mut` keyword. |
650 | 835 | ||
836 | .Before | ||
651 | ```rust | 837 | ```rust |
652 | // BEFORE | ||
653 | impl Walrus { | 838 | impl Walrus { |
654 | fn feed(&mut┃ self, amount: u32) {} | 839 | fn feed(&mut┃ self, amount: u32) {} |
655 | } | 840 | } |
841 | ``` | ||
656 | 842 | ||
657 | // AFTER | 843 | .After |
844 | ```rust | ||
658 | impl Walrus { | 845 | impl Walrus { |
659 | fn feed(&self, amount: u32) {} | 846 | fn feed(&self, amount: u32) {} |
660 | } | 847 | } |
661 | ``` | 848 | ``` |
662 | 849 | ||
663 | ## `reorder_fields` | 850 | |
851 | [discrete] | ||
852 | === `reorder_fields` | ||
853 | **Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_assists/src/handlers/reorder_fields.rs#L10[reorder_fields.rs] | ||
664 | 854 | ||
665 | Reorder the fields of record literals and record patterns in the same order as in | 855 | Reorder the fields of record literals and record patterns in the same order as in |
666 | the definition. | 856 | the definition. |
667 | 857 | ||
858 | .Before | ||
668 | ```rust | 859 | ```rust |
669 | // BEFORE | ||
670 | struct Foo {foo: i32, bar: i32}; | 860 | struct Foo {foo: i32, bar: i32}; |
671 | const test: Foo = ┃Foo {bar: 0, foo: 1} | 861 | const test: Foo = ┃Foo {bar: 0, foo: 1} |
862 | ``` | ||
672 | 863 | ||
673 | // AFTER | 864 | .After |
865 | ```rust | ||
674 | struct Foo {foo: i32, bar: i32}; | 866 | struct Foo {foo: i32, bar: i32}; |
675 | const test: Foo = Foo {foo: 1, bar: 0} | 867 | const test: Foo = Foo {foo: 1, bar: 0} |
676 | ``` | 868 | ``` |
677 | 869 | ||
678 | ## `replace_if_let_with_match` | 870 | |
871 | [discrete] | ||
872 | === `replace_if_let_with_match` | ||
873 | **Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_assists/src/handlers/replace_if_let_with_match.rs#L13[replace_if_let_with_match.rs] | ||
679 | 874 | ||
680 | Replaces `if let` with an else branch with a `match` expression. | 875 | Replaces `if let` with an else branch with a `match` expression. |
681 | 876 | ||
877 | .Before | ||
682 | ```rust | 878 | ```rust |
683 | // BEFORE | ||
684 | enum Action { Move { distance: u32 }, Stop } | 879 | enum Action { Move { distance: u32 }, Stop } |
685 | 880 | ||
686 | fn handle(action: Action) { | 881 | fn handle(action: Action) { |
@@ -690,8 +885,10 @@ fn handle(action: Action) { | |||
690 | bar() | 885 | bar() |
691 | } | 886 | } |
692 | } | 887 | } |
888 | ``` | ||
693 | 889 | ||
694 | // AFTER | 890 | .After |
891 | ```rust | ||
695 | enum Action { Move { distance: u32 }, Stop } | 892 | enum Action { Move { distance: u32 }, Stop } |
696 | 893 | ||
697 | fn handle(action: Action) { | 894 | fn handle(action: Action) { |
@@ -702,20 +899,25 @@ fn handle(action: Action) { | |||
702 | } | 899 | } |
703 | ``` | 900 | ``` |
704 | 901 | ||
705 | ## `replace_let_with_if_let` | 902 | |
903 | [discrete] | ||
904 | === `replace_let_with_if_let` | ||
905 | **Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_assists/src/handlers/replace_let_with_if_let.rs#L14[replace_let_with_if_let.rs] | ||
706 | 906 | ||
707 | Replaces `let` with an `if-let`. | 907 | Replaces `let` with an `if-let`. |
708 | 908 | ||
909 | .Before | ||
709 | ```rust | 910 | ```rust |
710 | // BEFORE | ||
711 | 911 | ||
712 | fn main(action: Action) { | 912 | fn main(action: Action) { |
713 | ┃let x = compute(); | 913 | ┃let x = compute(); |
714 | } | 914 | } |
715 | 915 | ||
716 | fn compute() -> Option<i32> { None } | 916 | fn compute() -> Option<i32> { None } |
917 | ``` | ||
717 | 918 | ||
718 | // AFTER | 919 | .After |
920 | ```rust | ||
719 | 921 | ||
720 | fn main(action: Action) { | 922 | fn main(action: Action) { |
721 | if let Some(x) = compute() { | 923 | if let Some(x) = compute() { |
@@ -725,33 +927,43 @@ fn main(action: Action) { | |||
725 | fn compute() -> Option<i32> { None } | 927 | fn compute() -> Option<i32> { None } |
726 | ``` | 928 | ``` |
727 | 929 | ||
728 | ## `replace_qualified_name_with_use` | 930 | |
931 | [discrete] | ||
932 | === `replace_qualified_name_with_use` | ||
933 | **Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_assists/src/handlers/replace_qualified_name_with_use.rs#L6[replace_qualified_name_with_use.rs] | ||
729 | 934 | ||
730 | Adds a use statement for a given fully-qualified name. | 935 | Adds a use statement for a given fully-qualified name. |
731 | 936 | ||
937 | .Before | ||
732 | ```rust | 938 | ```rust |
733 | // BEFORE | ||
734 | fn process(map: std::collections::┃HashMap<String, String>) {} | 939 | fn process(map: std::collections::┃HashMap<String, String>) {} |
940 | ``` | ||
735 | 941 | ||
736 | // AFTER | 942 | .After |
943 | ```rust | ||
737 | use std::collections::HashMap; | 944 | use std::collections::HashMap; |
738 | 945 | ||
739 | fn process(map: HashMap<String, String>) {} | 946 | fn process(map: HashMap<String, String>) {} |
740 | ``` | 947 | ``` |
741 | 948 | ||
742 | ## `replace_unwrap_with_match` | 949 | |
950 | [discrete] | ||
951 | === `replace_unwrap_with_match` | ||
952 | **Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_assists/src/handlers/replace_unwrap_with_match.rs#L17[replace_unwrap_with_match.rs] | ||
743 | 953 | ||
744 | Replaces `unwrap` a `match` expression. Works for Result and Option. | 954 | Replaces `unwrap` a `match` expression. Works for Result and Option. |
745 | 955 | ||
956 | .Before | ||
746 | ```rust | 957 | ```rust |
747 | // BEFORE | ||
748 | enum Result<T, E> { Ok(T), Err(E) } | 958 | enum Result<T, E> { Ok(T), Err(E) } |
749 | fn main() { | 959 | fn main() { |
750 | let x: Result<i32, i32> = Result::Ok(92); | 960 | let x: Result<i32, i32> = Result::Ok(92); |
751 | let y = x.┃unwrap(); | 961 | let y = x.┃unwrap(); |
752 | } | 962 | } |
963 | ``` | ||
753 | 964 | ||
754 | // AFTER | 965 | .After |
966 | ```rust | ||
755 | enum Result<T, E> { Ok(T), Err(E) } | 967 | enum Result<T, E> { Ok(T), Err(E) } |
756 | fn main() { | 968 | fn main() { |
757 | let x: Result<i32, i32> = Result::Ok(92); | 969 | let x: Result<i32, i32> = Result::Ok(92); |
@@ -762,31 +974,41 @@ fn main() { | |||
762 | } | 974 | } |
763 | ``` | 975 | ``` |
764 | 976 | ||
765 | ## `split_import` | 977 | |
978 | [discrete] | ||
979 | === `split_import` | ||
980 | **Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_assists/src/handlers/split_import.rs#L7[split_import.rs] | ||
766 | 981 | ||
767 | Wraps the tail of import into braces. | 982 | Wraps the tail of import into braces. |
768 | 983 | ||
984 | .Before | ||
769 | ```rust | 985 | ```rust |
770 | // BEFORE | ||
771 | use std::┃collections::HashMap; | 986 | use std::┃collections::HashMap; |
987 | ``` | ||
772 | 988 | ||
773 | // AFTER | 989 | .After |
990 | ```rust | ||
774 | use std::{collections::HashMap}; | 991 | use std::{collections::HashMap}; |
775 | ``` | 992 | ``` |
776 | 993 | ||
777 | ## `unwrap_block` | 994 | |
995 | [discrete] | ||
996 | === `unwrap_block` | ||
997 | **Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_assists/src/handlers/unwrap_block.rs#L9[unwrap_block.rs] | ||
778 | 998 | ||
779 | This assist removes if...else, for, while and loop control statements to just keep the body. | 999 | This assist removes if...else, for, while and loop control statements to just keep the body. |
780 | 1000 | ||
1001 | .Before | ||
781 | ```rust | 1002 | ```rust |
782 | // BEFORE | ||
783 | fn foo() { | 1003 | fn foo() { |
784 | if true {┃ | 1004 | if true {┃ |
785 | println!("foo"); | 1005 | println!("foo"); |
786 | } | 1006 | } |
787 | } | 1007 | } |
1008 | ``` | ||
788 | 1009 | ||
789 | // AFTER | 1010 | .After |
1011 | ```rust | ||
790 | fn foo() { | 1012 | fn foo() { |
791 | println!("foo"); | 1013 | println!("foo"); |
792 | } | 1014 | } |
diff --git a/docs/user/generated_features.adoc b/docs/user/generated_features.adoc new file mode 100644 index 000000000..12812fa0b --- /dev/null +++ b/docs/user/generated_features.adoc | |||
@@ -0,0 +1,298 @@ | |||
1 | === Expand Macro Recursively | ||
2 | **Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_ide/src/expand_macro.rs#L15[expand_macro.rs] | ||
3 | |||
4 | Shows the full macro expansion of the macro at current cursor. | ||
5 | |||
6 | |=== | ||
7 | | Editor | Action Name | ||
8 | |||
9 | | VS Code | **Rust Analyzer: Expand macro recursively** | ||
10 | |=== | ||
11 | |||
12 | |||
13 | === Extend Selection | ||
14 | **Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_ide/src/extend_selection.rs#L15[extend_selection.rs] | ||
15 | |||
16 | Extends the current selection to the encompassing syntactic construct | ||
17 | (expression, statement, item, module, etc). It works with multiple cursors. | ||
18 | |||
19 | |=== | ||
20 | | Editor | Shortcut | ||
21 | |||
22 | | VS Code | kbd:[Ctrl+Shift+→] | ||
23 | |=== | ||
24 | |||
25 | |||
26 | === File Structure | ||
27 | **Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_ide/src/display/structure.rs#L17[structure.rs] | ||
28 | |||
29 | Provides a tree of the symbols defined in the file. Can be used to | ||
30 | |||
31 | * fuzzy search symbol in a file (super useful) | ||
32 | * draw breadcrumbs to describe the context around the cursor | ||
33 | * draw outline of the file | ||
34 | |||
35 | |=== | ||
36 | | Editor | Shortcut | ||
37 | |||
38 | | VS Code | kbd:[Ctrl+Shift+O] | ||
39 | |=== | ||
40 | |||
41 | |||
42 | === Go to Definition | ||
43 | **Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_ide/src/goto_definition.rs#L18[goto_definition.rs] | ||
44 | |||
45 | Navigates to the definition of an identifier. | ||
46 | |||
47 | |=== | ||
48 | | Editor | Shortcut | ||
49 | |||
50 | | VS Code | kbd:[F12] | ||
51 | |=== | ||
52 | |||
53 | |||
54 | === Go to Implementation | ||
55 | **Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_ide/src/goto_implementation.rs#L7[goto_implementation.rs] | ||
56 | |||
57 | Navigates to the impl block of structs, enums or traits. Also implemented as a code lens. | ||
58 | |||
59 | |=== | ||
60 | | Editor | Shortcut | ||
61 | |||
62 | | VS Code | kbd:[Ctrl+F12] | ||
63 | |=== | ||
64 | |||
65 | |||
66 | === Go to Type Definition | ||
67 | **Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_ide/src/goto_type_definition.rs#L6[goto_type_definition.rs] | ||
68 | |||
69 | Navigates to the type of an identifier. | ||
70 | |||
71 | |=== | ||
72 | | Editor | Action Name | ||
73 | |||
74 | | VS Code | **Go to Type Definition* | ||
75 | |=== | ||
76 | |||
77 | |||
78 | === Hover | ||
79 | **Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_ide/src/hover.rs#L63[hover.rs] | ||
80 | |||
81 | Shows additional information, like type of an expression or documentation for definition when "focusing" code. | ||
82 | Focusing is usually hovering with a mouse, but can also be triggered with a shortcut. | ||
83 | |||
84 | |||
85 | === Inlay Hints | ||
86 | **Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_ide/src/inlay_hints.rs#L40[inlay_hints.rs] | ||
87 | |||
88 | rust-analyzer shows additional information inline with the source code. | ||
89 | Editors usually render this using read-only virtual text snippets interspersed with code. | ||
90 | |||
91 | rust-analyzer shows hits for | ||
92 | |||
93 | * types of local variables | ||
94 | * names of function arguments | ||
95 | * types of chained expressions | ||
96 | |||
97 | **Note:** VS Code does not have native support for inlay hints https://github.com/microsoft/vscode/issues/16221[yet] and the hints are implemented using decorations. | ||
98 | This approach has limitations, the caret movement and bracket highlighting near the edges of the hint may be weird: | ||
99 | https://github.com/rust-analyzer/rust-analyzer/issues/1623[1], https://github.com/rust-analyzer/rust-analyzer/issues/3453[2]. | ||
100 | |||
101 | |=== | ||
102 | | Editor | Action Name | ||
103 | |||
104 | | VS Code | **Rust Analyzer: Toggle inlay hints* | ||
105 | |=== | ||
106 | |||
107 | |||
108 | === Join Lines | ||
109 | **Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_ide/src/join_lines.rs#L12[join_lines.rs] | ||
110 | |||
111 | Join selected lines into one, smartly fixing up whitespace, trailing commas, and braces. | ||
112 | |||
113 | |=== | ||
114 | | Editor | Action Name | ||
115 | |||
116 | | VS Code | **Rust Analyzer: Join lines** | ||
117 | |=== | ||
118 | |||
119 | |||
120 | === Magic Completions | ||
121 | **Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_ide/src/completion.rs#L38[completion.rs] | ||
122 | |||
123 | In addition to usual reference completion, rust-analyzer provides some ✨magic✨ | ||
124 | completions as well: | ||
125 | |||
126 | Keywords like `if`, `else` `while`, `loop` are completed with braces, and cursor | ||
127 | is placed at the appropriate position. Even though `if` is easy to type, you | ||
128 | still want to complete it, to get ` { }` for free! `return` is inserted with a | ||
129 | space or `;` depending on the return type of the function. | ||
130 | |||
131 | When completing a function call, `()` are automatically inserted. If a function | ||
132 | takes arguments, the cursor is positioned inside the parenthesis. | ||
133 | |||
134 | There are postfix completions, which can be triggered by typing something like | ||
135 | `foo().if`. The word after `.` determines postfix completion. Possible variants are: | ||
136 | |||
137 | - `expr.if` -> `if expr {}` or `if let ... {}` for `Option` or `Result` | ||
138 | - `expr.match` -> `match expr {}` | ||
139 | - `expr.while` -> `while expr {}` or `while let ... {}` for `Option` or `Result` | ||
140 | - `expr.ref` -> `&expr` | ||
141 | - `expr.refm` -> `&mut expr` | ||
142 | - `expr.not` -> `!expr` | ||
143 | - `expr.dbg` -> `dbg!(expr)` | ||
144 | |||
145 | There also snippet completions: | ||
146 | |||
147 | .Expressions | ||
148 | - `pd` -> `println!("{:?}")` | ||
149 | - `ppd` -> `println!("{:#?}")` | ||
150 | |||
151 | .Items | ||
152 | - `tfn` -> `#[test] fn f(){}` | ||
153 | - `tmod` -> | ||
154 | ```rust | ||
155 | #[cfg(test)] | ||
156 | mod tests { | ||
157 | use super::*; | ||
158 | |||
159 | #[test] | ||
160 | fn test_fn() {} | ||
161 | } | ||
162 | ``` | ||
163 | |||
164 | |||
165 | === Matching Brace | ||
166 | **Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_ide/src/matching_brace.rs#L3[matching_brace.rs] | ||
167 | |||
168 | If the cursor is on any brace (`<>(){}[]`) which is a part of a brace-pair, | ||
169 | moves cursor to the matching brace. It uses the actual parser to determine | ||
170 | braces, so it won't confuse generics with comparisons. | ||
171 | |||
172 | |=== | ||
173 | | Editor | Action Name | ||
174 | |||
175 | | VS Code | **Rust Analyzer: Find matching brace** | ||
176 | |=== | ||
177 | |||
178 | |||
179 | === On Typing Assists | ||
180 | **Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_ide/src/typing.rs#L35[typing.rs] | ||
181 | |||
182 | Some features trigger on typing certain characters: | ||
183 | |||
184 | - typing `let =` tries to smartly add `;` if `=` is followed by an existing expression | ||
185 | - Enter inside comments automatically inserts `///` | ||
186 | - typing `.` in a chain method call auto-indents | ||
187 | |||
188 | |||
189 | === Parent Module | ||
190 | **Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_ide/src/parent_module.rs#L12[parent_module.rs] | ||
191 | |||
192 | Navigates to the parent module of the current module. | ||
193 | |||
194 | |=== | ||
195 | | Editor | Action Name | ||
196 | |||
197 | | VS Code | **Rust Analyzer: Locate parent module** | ||
198 | |=== | ||
199 | |||
200 | |||
201 | === Run | ||
202 | **Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_ide/src/runnables.rs#L45[runnables.rs] | ||
203 | |||
204 | Shows a popup suggesting to run a test/benchmark/binary **at the current cursor | ||
205 | location**. Super useful for repeatedly running just a single test. Do bind this | ||
206 | to a shortcut! | ||
207 | |||
208 | |=== | ||
209 | | Editor | Action Name | ||
210 | |||
211 | | VS Code | **Rust Analyzer: Run** | ||
212 | |=== | ||
213 | |||
214 | |||
215 | === Semantic Syntax Highlighting | ||
216 | **Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_ide/src/syntax_highlighting.rs#L33[syntax_highlighting.rs] | ||
217 | |||
218 | rust-analyzer highlights the code semantically. | ||
219 | For example, `bar` in `foo::Bar` might be colored differently depending on whether `Bar` is an enum or a trait. | ||
220 | rust-analyzer does not specify colors directly, instead it assigns tag (like `struct`) and a set of modifiers (like `declaration`) to each token. | ||
221 | It's up to the client to map those to specific colors. | ||
222 | |||
223 | The general rule is that a reference to an entity gets colored the same way as the entity itself. | ||
224 | We also give special modifier for `mut` and `&mut` local variables. | ||
225 | |||
226 | |||
227 | === Show Syntax Tree | ||
228 | **Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_ide/src/syntax_tree.rs#L9[syntax_tree.rs] | ||
229 | |||
230 | Shows the parse tree of the current file. It exists mostly for debugging | ||
231 | rust-analyzer itself. | ||
232 | |||
233 | |=== | ||
234 | | Editor | Action Name | ||
235 | |||
236 | | VS Code | **Rust Analyzer: Show Syntax Tree** | ||
237 | |=== | ||
238 | |||
239 | |||
240 | === Status | ||
241 | **Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_ide/src/status.rs#L27[status.rs] | ||
242 | |||
243 | Shows internal statistic about memory usage of rust-analyzer. | ||
244 | |||
245 | |=== | ||
246 | | Editor | Action Name | ||
247 | |||
248 | | VS Code | **Rust Analyzer: Status** | ||
249 | |=== | ||
250 | |||
251 | |||
252 | === Structural Seach and Replace | ||
253 | **Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_ide/src/ssr.rs#L26[ssr.rs] | ||
254 | |||
255 | Search and replace with named wildcards that will match any expression. | ||
256 | The syntax for a structural search replace command is `<search_pattern> ==>> <replace_pattern>`. | ||
257 | A `$<name>:expr` placeholder in the search pattern will match any expression and `$<name>` will reference it in the replacement. | ||
258 | Available via the command `rust-analyzer.ssr`. | ||
259 | |||
260 | ```rust | ||
261 | // Using structural search replace command [foo($a:expr, $b:expr) ==>> ($a).foo($b)] | ||
262 | |||
263 | // BEFORE | ||
264 | String::from(foo(y + 5, z)) | ||
265 | |||
266 | // AFTER | ||
267 | String::from((y + 5).foo(z)) | ||
268 | ``` | ||
269 | |||
270 | |=== | ||
271 | | Editor | Action Name | ||
272 | |||
273 | | VS Code | **Rust Analyzer: Structural Search Replace** | ||
274 | |=== | ||
275 | |||
276 | |||
277 | === Workspace Symbol | ||
278 | **Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_ide_db/src/symbol_index.rs#L113[symbol_index.rs] | ||
279 | |||
280 | Uses fuzzy-search to find types, modules and functions by name across your | ||
281 | project and dependencies. This is **the** most useful feature, which improves code | ||
282 | navigation tremendously. It mostly works on top of the built-in LSP | ||
283 | functionality, however `#` and `*` symbols can be used to narrow down the | ||
284 | search. Specifically, | ||
285 | |||
286 | - `Foo` searches for `Foo` type in the current workspace | ||
287 | - `foo#` searches for `foo` function in the current workspace | ||
288 | - `Foo*` searches for `Foo` type among dependencies, including `stdlib` | ||
289 | - `foo#*` searches for `foo` function among dependencies | ||
290 | |||
291 | That is, `#` switches from "types" to all symbols, `*` switches from the current | ||
292 | workspace to dependencies. | ||
293 | |||
294 | |=== | ||
295 | | Editor | Shortcut | ||
296 | |||
297 | | VS Code | kbd:[Ctrl+T] | ||
298 | |=== | ||
diff --git a/docs/user/readme.adoc b/docs/user/manual.adoc index 64bd0feb1..202783fd9 100644 --- a/docs/user/readme.adoc +++ b/docs/user/manual.adoc | |||
@@ -2,12 +2,9 @@ | |||
2 | :toc: preamble | 2 | :toc: preamble |
3 | :sectanchors: | 3 | :sectanchors: |
4 | :page-layout: post | 4 | :page-layout: post |
5 | // https://gist.github.com/dcode/0cfbf2699a1fe9b46ff04c41721dda74#admonitions | 5 | :icons: font |
6 | :tip-caption: :bulb: | 6 | :source-highlighter: rouge |
7 | :note-caption: :information_source: | 7 | :experimental: |
8 | :important-caption: :heavy_exclamation_mark: | ||
9 | :caution-caption: :fire: | ||
10 | :warning-caption: :warning: | ||
11 | 8 | ||
12 | // Master copy of this document lives in the https://github.com/rust-analyzer/rust-analyzer repository | 9 | // Master copy of this document lives in the https://github.com/rust-analyzer/rust-analyzer repository |
13 | 10 | ||
@@ -17,7 +14,7 @@ https://microsoft.github.io/language-server-protocol/[Language Server Protocol] | |||
17 | The LSP allows various code editors, like VS Code, Emacs or Vim, to implement semantic features like completion or goto definition by talking to an external language server process. | 14 | The LSP allows various code editors, like VS Code, Emacs or Vim, to implement semantic features like completion or goto definition by talking to an external language server process. |
18 | 15 | ||
19 | To improve this document, send a pull request against | 16 | To improve this document, send a pull request against |
20 | https://github.com/rust-analyzer/rust-analyzer/blob/master/docs/user/readme.adoc[this file]. | 17 | https://github.com/rust-analyzer/rust-analyzer/blob/master/docs/user/manual.adoc[this file]. |
21 | 18 | ||
22 | If you have questions about using rust-analyzer, please ask them in the https://users.rust-lang.org/c/ide/14["`IDEs and Editors`"] topic of Rust users forum. | 19 | If you have questions about using rust-analyzer, please ask them in the https://users.rust-lang.org/c/ide/14["`IDEs and Editors`"] topic of Rust users forum. |
23 | 20 | ||
@@ -65,16 +62,6 @@ The server binary is stored in: | |||
65 | 62 | ||
66 | Note that we only support two most recent versions of VS Code. | 63 | Note that we only support two most recent versions of VS Code. |
67 | 64 | ||
68 | ==== Special `when` clause context for keybindings. | ||
69 | You may use `inRustProject` context to configure keybindings for rust projects only. For example: | ||
70 | [source,json] | ||
71 | ---- | ||
72 | { "key": "ctrl+shift+f5", "command": "workbench.action.debug.restart", "when": "inDebugMode && !inRustProject"}, | ||
73 | { "key": "ctrl+shift+f5", "command": "rust-analyzer.debug", "when": "inRustProject"}, | ||
74 | { "key": "ctrl+i", "command": "rust-analyzer.toggleInlayHints", "when": "inRustProject" } | ||
75 | ---- | ||
76 | More about `when` clause contexts https://code.visualstudio.com/docs/getstarted/keybindings#_when-clause-contexts[here]. | ||
77 | |||
78 | ==== Updates | 65 | ==== Updates |
79 | 66 | ||
80 | The extension will be updated automatically as new versions become available. It will ask your permission to download the matching language server version binary if needed. | 67 | The extension will be updated automatically as new versions become available. It will ask your permission to download the matching language server version binary if needed. |
@@ -122,10 +109,23 @@ Here are some useful self-diagnostic commands: | |||
122 | * To log all LSP requests, add `"rust-analyzer.trace.server": "verbose"` to the settings and look for `Server Trace` in the panel. | 109 | * To log all LSP requests, add `"rust-analyzer.trace.server": "verbose"` to the settings and look for `Server Trace` in the panel. |
123 | * To enable client-side logging, add `"rust-analyzer.trace.extension": true` to the settings and open the `Console` tab of VS Code developer tools. | 110 | * To enable client-side logging, add `"rust-analyzer.trace.extension": true` to the settings and open the `Console` tab of VS Code developer tools. |
124 | 111 | ||
112 | ==== Special `when` clause context for keybindings. | ||
113 | You may use `inRustProject` context to configure keybindings for rust projects only. For example: | ||
114 | [source,json] | ||
115 | ---- | ||
116 | { | ||
117 | "key": "ctrl+i", | ||
118 | "command": "rust-analyzer.toggleInlayHints", | ||
119 | "when": "inRustProject" | ||
120 | } | ||
121 | ---- | ||
122 | More about `when` clause contexts https://code.visualstudio.com/docs/getstarted/keybindings#_when-clause-contexts[here]. | ||
123 | |||
125 | === rust-analyzer Language Server Binary | 124 | === rust-analyzer Language Server Binary |
126 | 125 | ||
127 | Other editors generally require the `rust-analyzer` binary to be in `$PATH`. | 126 | Other editors generally require the `rust-analyzer` binary to be in `$PATH`. |
128 | You can download the pre-built binary from the https://github.com/rust-analyzer/rust-analyzer/releases[releases] page. Typically, you then need to rename the binary for your platform, e.g. `rust-analyzer-mac` if you're on Mac OS, to `rust-analyzer` and make it executable in addition to moving it into a directory in your `$PATH`. | 127 | You can download the pre-built binary from the https://github.com/rust-analyzer/rust-analyzer/releases[releases] page. |
128 | Typically, you then need to rename the binary for your platform, e.g. `rust-analyzer-mac` if you're on Mac OS, to `rust-analyzer` and make it executable in addition to moving it into a directory in your `$PATH`. | ||
129 | 129 | ||
130 | On Linux to install the `rust-analyzer` binary into `~/.local/bin`, this commands could be used | 130 | On Linux to install the `rust-analyzer` binary into `~/.local/bin`, this commands could be used |
131 | 131 | ||
@@ -145,7 +145,8 @@ $ git clone https://github.com/rust-analyzer/rust-analyzer.git && cd rust-analyz | |||
145 | $ cargo xtask install --server | 145 | $ cargo xtask install --server |
146 | ---- | 146 | ---- |
147 | 147 | ||
148 | If your editor can't find the binary even though the binary is on your `$PATH`, the likely explanation is that it doesn't see the same `$PATH` as the shell, see https://github.com/rust-analyzer/rust-analyzer/issues/1811[this issue]. On Unix, running the editor from a shell or changing the `.desktop` file to set the environment should help. | 148 | If your editor can't find the binary even though the binary is on your `$PATH`, the likely explanation is that it doesn't see the same `$PATH` as the shell, see https://github.com/rust-analyzer/rust-analyzer/issues/1811[this issue]. |
149 | On Unix, running the editor from a shell or changing the `.desktop` file to set the environment should help. | ||
149 | 150 | ||
150 | ==== Arch Linux | 151 | ==== Arch Linux |
151 | 152 | ||
@@ -268,6 +269,14 @@ Gnome Builder currently has support for RLS, and there's no way to configure the | |||
268 | 1. Rename, symlink or copy the `rust-analyzer` binary to `rls` and place it somewhere Builder can find (in `PATH`, or under `~/.cargo/bin`). | 269 | 1. Rename, symlink or copy the `rust-analyzer` binary to `rls` and place it somewhere Builder can find (in `PATH`, or under `~/.cargo/bin`). |
269 | 2. Enable the Rust Builder plugin. | 270 | 2. Enable the Rust Builder plugin. |
270 | 271 | ||
271 | == Usage | 272 | == Features |
273 | |||
274 | include::./generated_features.adoc[] | ||
275 | |||
276 | == Assists (Code Actions) | ||
277 | |||
278 | Assists, or code actions, are small local refactorings, available in a particular context. | ||
279 | They are usually triggered by a shortcut or by clicking a light bulb icon in the editor. | ||
280 | Cursor position or selection is signified by `┃` character. | ||
272 | 281 | ||
273 | See https://github.com/rust-analyzer/rust-analyzer/blob/master/docs/user/features.md[features.md]. | 282 | include::./generated_assists.adoc[] |
diff --git a/editors/code/src/run.ts b/editors/code/src/run.ts index 401cb76af..113354bab 100644 --- a/editors/code/src/run.ts +++ b/editors/code/src/run.ts | |||
@@ -178,7 +178,7 @@ export function createTask(spec: ra.Runnable): vscode.Task { | |||
178 | label: spec.label, | 178 | label: spec.label, |
179 | command: toolchain.getPathForExecutable(spec.kind), | 179 | command: toolchain.getPathForExecutable(spec.kind), |
180 | args: spec.extraArgs ? [...spec.args, '--', ...spec.extraArgs] : spec.args, | 180 | args: spec.extraArgs ? [...spec.args, '--', ...spec.extraArgs] : spec.args, |
181 | env: spec.env, | 181 | env: Object.assign({}, process.env, spec.env), |
182 | }; | 182 | }; |
183 | 183 | ||
184 | const execOption: vscode.ShellExecutionOptions = { | 184 | const execOption: vscode.ShellExecutionOptions = { |
diff --git a/xtask/src/ast_src.rs b/xtask/src/ast_src.rs index d4621930e..f60f0fb16 100644 --- a/xtask/src/ast_src.rs +++ b/xtask/src/ast_src.rs | |||
@@ -1058,7 +1058,7 @@ pub(crate) const AST_SRC: AstSrc = AstSrc { | |||
1058 | /// [Reference](https://doc.rust-lang.org/reference/expressions/block-expr.html) | 1058 | /// [Reference](https://doc.rust-lang.org/reference/expressions/block-expr.html) |
1059 | /// [Labels for blocks RFC](https://github.com/rust-lang/rfcs/blob/master/text/2046-label-break-value.md) | 1059 | /// [Labels for blocks RFC](https://github.com/rust-lang/rfcs/blob/master/text/2046-label-break-value.md) |
1060 | struct BlockExpr: AttrsOwner, ModuleItemOwner { | 1060 | struct BlockExpr: AttrsOwner, ModuleItemOwner { |
1061 | T!['{'], statements: [Stmt], Expr, T!['}'], | 1061 | Label, T!['{'], statements: [Stmt], Expr, T!['}'], |
1062 | } | 1062 | } |
1063 | 1063 | ||
1064 | /// Return expression. | 1064 | /// Return expression. |
diff --git a/xtask/src/codegen.rs b/xtask/src/codegen.rs index b4907f4b2..5511c01d5 100644 --- a/xtask/src/codegen.rs +++ b/xtask/src/codegen.rs | |||
@@ -8,14 +8,18 @@ | |||
8 | mod gen_syntax; | 8 | mod gen_syntax; |
9 | mod gen_parser_tests; | 9 | mod gen_parser_tests; |
10 | mod gen_assists_docs; | 10 | mod gen_assists_docs; |
11 | mod gen_feature_docs; | ||
11 | 12 | ||
12 | use std::{mem, path::Path}; | 13 | use std::{ |
14 | fmt, mem, | ||
15 | path::{Path, PathBuf}, | ||
16 | }; | ||
13 | 17 | ||
14 | use crate::{not_bash::fs2, Result}; | 18 | use crate::{not_bash::fs2, project_root, Result}; |
15 | 19 | ||
16 | pub use self::{ | 20 | pub use self::{ |
17 | gen_assists_docs::generate_assists_docs, gen_parser_tests::generate_parser_tests, | 21 | gen_assists_docs::generate_assists_docs, gen_feature_docs::generate_feature_docs, |
18 | gen_syntax::generate_syntax, | 22 | gen_parser_tests::generate_parser_tests, gen_syntax::generate_syntax, |
19 | }; | 23 | }; |
20 | 24 | ||
21 | const GRAMMAR_DIR: &str = "crates/ra_parser/src/grammar"; | 25 | const GRAMMAR_DIR: &str = "crates/ra_parser/src/grammar"; |
@@ -28,7 +32,6 @@ const AST_TOKENS: &str = "crates/ra_syntax/src/ast/generated/tokens.rs"; | |||
28 | 32 | ||
29 | const ASSISTS_DIR: &str = "crates/ra_assists/src/handlers"; | 33 | const ASSISTS_DIR: &str = "crates/ra_assists/src/handlers"; |
30 | const ASSISTS_TESTS: &str = "crates/ra_assists/src/tests/generated.rs"; | 34 | const ASSISTS_TESTS: &str = "crates/ra_assists/src/tests/generated.rs"; |
31 | const ASSISTS_DOCS: &str = "docs/user/assists.md"; | ||
32 | 35 | ||
33 | #[derive(Debug, PartialEq, Eq, Clone, Copy)] | 36 | #[derive(Debug, PartialEq, Eq, Clone, Copy)] |
34 | pub enum Mode { | 37 | pub enum Mode { |
@@ -40,7 +43,7 @@ pub enum Mode { | |||
40 | /// With verify = false, | 43 | /// With verify = false, |
41 | fn update(path: &Path, contents: &str, mode: Mode) -> Result<()> { | 44 | fn update(path: &Path, contents: &str, mode: Mode) -> Result<()> { |
42 | match fs2::read_to_string(path) { | 45 | match fs2::read_to_string(path) { |
43 | Ok(ref old_contents) if normalize(old_contents) == normalize(contents) => { | 46 | Ok(old_contents) if normalize(&old_contents) == normalize(contents) => { |
44 | return Ok(()); | 47 | return Ok(()); |
45 | } | 48 | } |
46 | _ => (), | 49 | _ => (), |
@@ -58,35 +61,85 @@ fn update(path: &Path, contents: &str, mode: Mode) -> Result<()> { | |||
58 | } | 61 | } |
59 | 62 | ||
60 | fn extract_comment_blocks(text: &str) -> Vec<Vec<String>> { | 63 | fn extract_comment_blocks(text: &str) -> Vec<Vec<String>> { |
61 | do_extract_comment_blocks(text, false) | 64 | do_extract_comment_blocks(text, false).into_iter().map(|(_line, block)| block).collect() |
65 | } | ||
66 | |||
67 | fn extract_comment_blocks_with_empty_lines(tag: &str, text: &str) -> Vec<CommentBlock> { | ||
68 | assert!(tag.starts_with(char::is_uppercase)); | ||
69 | let tag = format!("{}:", tag); | ||
70 | let mut res = Vec::new(); | ||
71 | for (line, mut block) in do_extract_comment_blocks(text, true) { | ||
72 | let first = block.remove(0); | ||
73 | if first.starts_with(&tag) { | ||
74 | let id = first[tag.len()..].trim().to_string(); | ||
75 | let block = CommentBlock { id, line, contents: block }; | ||
76 | res.push(block); | ||
77 | } | ||
78 | } | ||
79 | res | ||
62 | } | 80 | } |
63 | 81 | ||
64 | fn extract_comment_blocks_with_empty_lines(text: &str) -> Vec<Vec<String>> { | 82 | struct CommentBlock { |
65 | do_extract_comment_blocks(text, true) | 83 | id: String, |
84 | line: usize, | ||
85 | contents: Vec<String>, | ||
66 | } | 86 | } |
67 | 87 | ||
68 | fn do_extract_comment_blocks(text: &str, allow_blocks_with_empty_lines: bool) -> Vec<Vec<String>> { | 88 | fn do_extract_comment_blocks( |
89 | text: &str, | ||
90 | allow_blocks_with_empty_lines: bool, | ||
91 | ) -> Vec<(usize, Vec<String>)> { | ||
69 | let mut res = Vec::new(); | 92 | let mut res = Vec::new(); |
70 | 93 | ||
71 | let prefix = "// "; | 94 | let prefix = "// "; |
72 | let lines = text.lines().map(str::trim_start); | 95 | let lines = text.lines().map(str::trim_start); |
73 | 96 | ||
74 | let mut block = vec![]; | 97 | let mut block = (0, vec![]); |
75 | for line in lines { | 98 | for (line_num, line) in lines.enumerate() { |
76 | if line == "//" && allow_blocks_with_empty_lines { | 99 | if line == "//" && allow_blocks_with_empty_lines { |
77 | block.push(String::new()); | 100 | block.1.push(String::new()); |
78 | continue; | 101 | continue; |
79 | } | 102 | } |
80 | 103 | ||
81 | let is_comment = line.starts_with(prefix); | 104 | let is_comment = line.starts_with(prefix); |
82 | if is_comment { | 105 | if is_comment { |
83 | block.push(line[prefix.len()..].to_string()); | 106 | block.1.push(line[prefix.len()..].to_string()); |
84 | } else if !block.is_empty() { | 107 | } else { |
85 | res.push(mem::replace(&mut block, Vec::new())); | 108 | if !block.1.is_empty() { |
109 | res.push(mem::take(&mut block)); | ||
110 | } | ||
111 | block.0 = line_num + 2; | ||
86 | } | 112 | } |
87 | } | 113 | } |
88 | if !block.is_empty() { | 114 | if !block.1.is_empty() { |
89 | res.push(mem::replace(&mut block, Vec::new())) | 115 | res.push(block) |
90 | } | 116 | } |
91 | res | 117 | res |
92 | } | 118 | } |
119 | |||
120 | #[derive(Debug)] | ||
121 | struct Location { | ||
122 | file: PathBuf, | ||
123 | line: usize, | ||
124 | } | ||
125 | |||
126 | impl Location { | ||
127 | fn new(file: PathBuf, line: usize) -> Self { | ||
128 | Self { file, line } | ||
129 | } | ||
130 | } | ||
131 | |||
132 | impl fmt::Display for Location { | ||
133 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | ||
134 | let path = self.file.strip_prefix(&project_root()).unwrap().display().to_string(); | ||
135 | let path = path.replace('\\', "/"); | ||
136 | let name = self.file.file_name().unwrap(); | ||
137 | write!( | ||
138 | f, | ||
139 | "https://github.com/rust-analyzer/rust-analyzer/blob/master/{}#L{}[{}]", | ||
140 | path, | ||
141 | self.line, | ||
142 | name.to_str().unwrap() | ||
143 | ) | ||
144 | } | ||
145 | } | ||
diff --git a/xtask/src/codegen/gen_assists_docs.rs b/xtask/src/codegen/gen_assists_docs.rs index 20dcde820..6c1be5350 100644 --- a/xtask/src/codegen/gen_assists_docs.rs +++ b/xtask/src/codegen/gen_assists_docs.rs | |||
@@ -1,22 +1,28 @@ | |||
1 | //! Generates `assists.md` documentation. | 1 | //! Generates `assists.md` documentation. |
2 | 2 | ||
3 | use std::{fs, path::Path}; | 3 | use std::{fmt, fs, path::Path}; |
4 | 4 | ||
5 | use crate::{ | 5 | use crate::{ |
6 | codegen::{self, extract_comment_blocks_with_empty_lines, Mode}, | 6 | codegen::{self, extract_comment_blocks_with_empty_lines, Location, Mode}, |
7 | project_root, rust_files, Result, | 7 | project_root, rust_files, Result, |
8 | }; | 8 | }; |
9 | 9 | ||
10 | pub fn generate_assists_docs(mode: Mode) -> Result<()> { | 10 | pub fn generate_assists_docs(mode: Mode) -> Result<()> { |
11 | let assists = Assist::collect()?; | 11 | let assists = Assist::collect()?; |
12 | generate_tests(&assists, mode)?; | 12 | generate_tests(&assists, mode)?; |
13 | generate_docs(&assists, mode)?; | 13 | |
14 | let contents = assists.into_iter().map(|it| it.to_string()).collect::<Vec<_>>().join("\n\n"); | ||
15 | let contents = contents.trim().to_string() + "\n"; | ||
16 | let dst = project_root().join("docs/user/generated_assists.adoc"); | ||
17 | codegen::update(&dst, &contents, mode)?; | ||
18 | |||
14 | Ok(()) | 19 | Ok(()) |
15 | } | 20 | } |
16 | 21 | ||
17 | #[derive(Debug)] | 22 | #[derive(Debug)] |
18 | struct Assist { | 23 | struct Assist { |
19 | id: String, | 24 | id: String, |
25 | location: Location, | ||
20 | doc: String, | 26 | doc: String, |
21 | before: String, | 27 | before: String, |
22 | after: String, | 28 | after: String, |
@@ -33,22 +39,18 @@ impl Assist { | |||
33 | 39 | ||
34 | fn collect_file(acc: &mut Vec<Assist>, path: &Path) -> Result<()> { | 40 | fn collect_file(acc: &mut Vec<Assist>, path: &Path) -> Result<()> { |
35 | let text = fs::read_to_string(path)?; | 41 | let text = fs::read_to_string(path)?; |
36 | let comment_blocks = extract_comment_blocks_with_empty_lines(&text); | 42 | let comment_blocks = extract_comment_blocks_with_empty_lines("Assist", &text); |
37 | 43 | ||
38 | for block in comment_blocks { | 44 | for block in comment_blocks { |
39 | // FIXME: doesn't support blank lines yet, need to tweak | 45 | // FIXME: doesn't support blank lines yet, need to tweak |
40 | // `extract_comment_blocks` for that. | 46 | // `extract_comment_blocks` for that. |
41 | let mut lines = block.iter(); | 47 | let id = block.id; |
42 | let first_line = lines.next().unwrap(); | ||
43 | if !first_line.starts_with("Assist: ") { | ||
44 | continue; | ||
45 | } | ||
46 | let id = first_line["Assist: ".len()..].to_string(); | ||
47 | assert!( | 48 | assert!( |
48 | id.chars().all(|it| it.is_ascii_lowercase() || it == '_'), | 49 | id.chars().all(|it| it.is_ascii_lowercase() || it == '_'), |
49 | "invalid assist id: {:?}", | 50 | "invalid assist id: {:?}", |
50 | id | 51 | id |
51 | ); | 52 | ); |
53 | let mut lines = block.contents.iter(); | ||
52 | 54 | ||
53 | let doc = take_until(lines.by_ref(), "```").trim().to_string(); | 55 | let doc = take_until(lines.by_ref(), "```").trim().to_string(); |
54 | assert!( | 56 | assert!( |
@@ -62,7 +64,8 @@ impl Assist { | |||
62 | assert_eq!(lines.next().unwrap().as_str(), "->"); | 64 | assert_eq!(lines.next().unwrap().as_str(), "->"); |
63 | assert_eq!(lines.next().unwrap().as_str(), "```"); | 65 | assert_eq!(lines.next().unwrap().as_str(), "```"); |
64 | let after = take_until(lines.by_ref(), "```"); | 66 | let after = take_until(lines.by_ref(), "```"); |
65 | acc.push(Assist { id, doc, before, after }) | 67 | let location = Location::new(path.to_path_buf(), block.line); |
68 | acc.push(Assist { id, location, doc, before, after }) | ||
66 | } | 69 | } |
67 | 70 | ||
68 | fn take_until<'a>(lines: impl Iterator<Item = &'a String>, marker: &str) -> String { | 71 | fn take_until<'a>(lines: impl Iterator<Item = &'a String>, marker: &str) -> String { |
@@ -80,6 +83,33 @@ impl Assist { | |||
80 | } | 83 | } |
81 | } | 84 | } |
82 | 85 | ||
86 | impl fmt::Display for Assist { | ||
87 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | ||
88 | let before = self.before.replace("<|>", "┃"); // Unicode pseudo-graphics bar | ||
89 | let after = self.after.replace("<|>", "┃"); | ||
90 | writeln!( | ||
91 | f, | ||
92 | "[discrete]\n=== `{}` | ||
93 | **Source:** {} | ||
94 | |||
95 | {} | ||
96 | |||
97 | .Before | ||
98 | ```rust | ||
99 | {}``` | ||
100 | |||
101 | .After | ||
102 | ```rust | ||
103 | {}```", | ||
104 | self.id, | ||
105 | self.location, | ||
106 | self.doc, | ||
107 | hide_hash_comments(&before), | ||
108 | hide_hash_comments(&after) | ||
109 | ) | ||
110 | } | ||
111 | } | ||
112 | |||
83 | fn generate_tests(assists: &[Assist], mode: Mode) -> Result<()> { | 113 | fn generate_tests(assists: &[Assist], mode: Mode) -> Result<()> { |
84 | let mut buf = String::from("use super::check_doc_test;\n"); | 114 | let mut buf = String::from("use super::check_doc_test;\n"); |
85 | 115 | ||
@@ -107,37 +137,6 @@ r#####" | |||
107 | codegen::update(&project_root().join(codegen::ASSISTS_TESTS), &buf, mode) | 137 | codegen::update(&project_root().join(codegen::ASSISTS_TESTS), &buf, mode) |
108 | } | 138 | } |
109 | 139 | ||
110 | fn generate_docs(assists: &[Assist], mode: Mode) -> Result<()> { | ||
111 | let mut buf = String::from( | ||
112 | "# Assists\n\nCursor position or selection is signified by `┃` character.\n\n", | ||
113 | ); | ||
114 | |||
115 | for assist in assists { | ||
116 | let before = assist.before.replace("<|>", "┃"); // Unicode pseudo-graphics bar | ||
117 | let after = assist.after.replace("<|>", "┃"); | ||
118 | let docs = format!( | ||
119 | " | ||
120 | ## `{}` | ||
121 | |||
122 | {} | ||
123 | |||
124 | ```rust | ||
125 | // BEFORE | ||
126 | {} | ||
127 | // AFTER | ||
128 | {}``` | ||
129 | ", | ||
130 | assist.id, | ||
131 | assist.doc, | ||
132 | hide_hash_comments(&before), | ||
133 | hide_hash_comments(&after) | ||
134 | ); | ||
135 | buf.push_str(&docs); | ||
136 | } | ||
137 | |||
138 | codegen::update(&project_root().join(codegen::ASSISTS_DOCS), &buf, mode) | ||
139 | } | ||
140 | |||
141 | fn hide_hash_comments(text: &str) -> String { | 140 | fn hide_hash_comments(text: &str) -> String { |
142 | text.split('\n') // want final newline | 141 | text.split('\n') // want final newline |
143 | .filter(|&it| !(it.starts_with("# ") || it == "#")) | 142 | .filter(|&it| !(it.starts_with("# ") || it == "#")) |
diff --git a/xtask/src/codegen/gen_feature_docs.rs b/xtask/src/codegen/gen_feature_docs.rs new file mode 100644 index 000000000..31bc3839d --- /dev/null +++ b/xtask/src/codegen/gen_feature_docs.rs | |||
@@ -0,0 +1,75 @@ | |||
1 | //! Generates `assists.md` documentation. | ||
2 | |||
3 | use std::{fmt, fs, path::PathBuf}; | ||
4 | |||
5 | use crate::{ | ||
6 | codegen::{self, extract_comment_blocks_with_empty_lines, Location, Mode}, | ||
7 | project_root, rust_files, Result, | ||
8 | }; | ||
9 | |||
10 | pub fn generate_feature_docs(mode: Mode) -> Result<()> { | ||
11 | let features = Feature::collect()?; | ||
12 | let contents = features.into_iter().map(|it| it.to_string()).collect::<Vec<_>>().join("\n\n"); | ||
13 | let contents = contents.trim().to_string() + "\n"; | ||
14 | let dst = project_root().join("docs/user/generated_features.adoc"); | ||
15 | codegen::update(&dst, &contents, mode)?; | ||
16 | Ok(()) | ||
17 | } | ||
18 | |||
19 | #[derive(Debug)] | ||
20 | struct Feature { | ||
21 | id: String, | ||
22 | location: Location, | ||
23 | doc: String, | ||
24 | } | ||
25 | |||
26 | impl Feature { | ||
27 | fn collect() -> Result<Vec<Feature>> { | ||
28 | let mut res = Vec::new(); | ||
29 | for path in rust_files(&project_root()) { | ||
30 | collect_file(&mut res, path)?; | ||
31 | } | ||
32 | res.sort_by(|lhs, rhs| lhs.id.cmp(&rhs.id)); | ||
33 | return Ok(res); | ||
34 | |||
35 | fn collect_file(acc: &mut Vec<Feature>, path: PathBuf) -> Result<()> { | ||
36 | let text = fs::read_to_string(&path)?; | ||
37 | let comment_blocks = extract_comment_blocks_with_empty_lines("Feature", &text); | ||
38 | |||
39 | for block in comment_blocks { | ||
40 | let id = block.id; | ||
41 | assert!(is_valid_feature_name(&id), "invalid feature name: {:?}", id); | ||
42 | let doc = block.contents.join("\n"); | ||
43 | let location = Location::new(path.clone(), block.line); | ||
44 | acc.push(Feature { id, location, doc }) | ||
45 | } | ||
46 | |||
47 | Ok(()) | ||
48 | } | ||
49 | } | ||
50 | } | ||
51 | |||
52 | fn is_valid_feature_name(feature: &str) -> bool { | ||
53 | 'word: for word in feature.split_whitespace() { | ||
54 | for &short in ["to", "and"].iter() { | ||
55 | if word == short { | ||
56 | continue 'word; | ||
57 | } | ||
58 | } | ||
59 | for &short in ["To", "And"].iter() { | ||
60 | if word == short { | ||
61 | return false; | ||
62 | } | ||
63 | } | ||
64 | if !word.starts_with(char::is_uppercase) { | ||
65 | return false; | ||
66 | } | ||
67 | } | ||
68 | true | ||
69 | } | ||
70 | |||
71 | impl fmt::Display for Feature { | ||
72 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | ||
73 | writeln!(f, "=== {}\n**Source:** {}\n{}", self.id, self.location, self.doc) | ||
74 | } | ||
75 | } | ||
diff --git a/xtask/src/lib.rs b/xtask/src/lib.rs index 2b7a461e5..874957885 100644 --- a/xtask/src/lib.rs +++ b/xtask/src/lib.rs | |||
@@ -191,7 +191,11 @@ Release: release:{}[] | |||
191 | let path = changelog_dir.join(format!("{}-changelog-{}.adoc", today, changelog_n)); | 191 | let path = changelog_dir.join(format!("{}-changelog-{}.adoc", today, changelog_n)); |
192 | fs2::write(&path, &contents)?; | 192 | fs2::write(&path, &contents)?; |
193 | 193 | ||
194 | fs2::copy(project_root().join("./docs/user/readme.adoc"), website_root.join("manual.adoc"))?; | 194 | for &adoc in ["manual.adoc", "generated_features.adoc", "generated_assists.adoc"].iter() { |
195 | let src = project_root().join("./docs/user/").join(adoc); | ||
196 | let dst = website_root.join(adoc); | ||
197 | fs2::copy(src, dst)?; | ||
198 | } | ||
195 | 199 | ||
196 | let tags = run!("git tag --list"; echo = false)?; | 200 | let tags = run!("git tag --list"; echo = false)?; |
197 | let prev_tag = tags.lines().filter(|line| is_release_tag(line)).last().unwrap(); | 201 | let prev_tag = tags.lines().filter(|line| is_release_tag(line)).last().unwrap(); |
diff --git a/xtask/src/main.rs b/xtask/src/main.rs index dff3ce4a1..9d7cdd114 100644 --- a/xtask/src/main.rs +++ b/xtask/src/main.rs | |||
@@ -75,6 +75,7 @@ FLAGS: | |||
75 | codegen::generate_syntax(Mode::Overwrite)?; | 75 | codegen::generate_syntax(Mode::Overwrite)?; |
76 | codegen::generate_parser_tests(Mode::Overwrite)?; | 76 | codegen::generate_parser_tests(Mode::Overwrite)?; |
77 | codegen::generate_assists_docs(Mode::Overwrite)?; | 77 | codegen::generate_assists_docs(Mode::Overwrite)?; |
78 | codegen::generate_feature_docs(Mode::Overwrite)?; | ||
78 | Ok(()) | 79 | Ok(()) |
79 | } | 80 | } |
80 | "format" => { | 81 | "format" => { |
diff --git a/xtask/tests/tidy.rs b/xtask/tests/tidy.rs index 2e9fcf07c..4ac5d929f 100644 --- a/xtask/tests/tidy.rs +++ b/xtask/tests/tidy.rs | |||
@@ -31,6 +31,13 @@ fn generated_assists_are_fresh() { | |||
31 | } | 31 | } |
32 | 32 | ||
33 | #[test] | 33 | #[test] |
34 | fn generated_features_are_fresh() { | ||
35 | if let Err(error) = codegen::generate_feature_docs(Mode::Verify) { | ||
36 | panic!("{}. Please update features by running `cargo xtask codegen`", error); | ||
37 | } | ||
38 | } | ||
39 | |||
40 | #[test] | ||
34 | fn check_code_formatting() { | 41 | fn check_code_formatting() { |
35 | if let Err(error) = run_rustfmt(Mode::Verify) { | 42 | if let Err(error) = run_rustfmt(Mode::Verify) { |
36 | panic!("{}. Please format the code by running `cargo format`", error); | 43 | panic!("{}. Please format the code by running `cargo format`", error); |
@@ -95,7 +102,7 @@ impl TidyDocs { | |||
95 | fn visit(&mut self, path: &Path, text: &str) { | 102 | fn visit(&mut self, path: &Path, text: &str) { |
96 | // Test hopefully don't really need comments, and for assists we already | 103 | // Test hopefully don't really need comments, and for assists we already |
97 | // have special comments which are source of doc tests and user docs. | 104 | // have special comments which are source of doc tests and user docs. |
98 | if is_exclude_dir(path, &["tests", "test_data", "handlers"]) { | 105 | if is_exclude_dir(path, &["tests", "test_data"]) { |
99 | return; | 106 | return; |
100 | } | 107 | } |
101 | 108 | ||
@@ -110,9 +117,12 @@ impl TidyDocs { | |||
110 | 117 | ||
111 | if first_line.starts_with("//!") { | 118 | if first_line.starts_with("//!") { |
112 | if first_line.contains("FIXME") { | 119 | if first_line.contains("FIXME") { |
113 | self.contains_fixme.push(path.to_path_buf()) | 120 | self.contains_fixme.push(path.to_path_buf()); |
114 | } | 121 | } |
115 | } else { | 122 | } else { |
123 | if text.contains("// Feature:") || text.contains("// Assist:") { | ||
124 | return; | ||
125 | } | ||
116 | self.missing_docs.push(path.display().to_string()); | 126 | self.missing_docs.push(path.display().to_string()); |
117 | } | 127 | } |
118 | 128 | ||