diff options
31 files changed, 1688 insertions, 411 deletions
diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 82448f719..32c7cf7ef 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml | |||
@@ -121,6 +121,34 @@ jobs: | |||
121 | name: dist-x86_64-unknown-linux-gnu | 121 | name: dist-x86_64-unknown-linux-gnu |
122 | path: ./dist | 122 | path: ./dist |
123 | 123 | ||
124 | dist-x86_64-unknown-linux-musl: | ||
125 | name: dist (x86_64-unknown-linux-musl) | ||
126 | runs-on: ubuntu-20.04 | ||
127 | env: | ||
128 | RA_TARGET: x86_64-unknown-linux-musl | ||
129 | # For some reason `-crt-static` is not working for clang without lld | ||
130 | RUSTFLAGS: "-C link-arg=-fuse-ld=lld -C target-feature=-crt-static" | ||
131 | container: | ||
132 | image: rust:alpine | ||
133 | volumes: | ||
134 | - /usr/local/cargo/registry | ||
135 | |||
136 | steps: | ||
137 | - name: Install dependencies | ||
138 | run: apk add --no-cache git clang lld musl-dev | ||
139 | |||
140 | - name: Checkout repository | ||
141 | uses: actions/checkout@v2 | ||
142 | |||
143 | - name: Dist | ||
144 | run: cargo xtask dist | ||
145 | |||
146 | - name: Upload artifacts | ||
147 | uses: actions/upload-artifact@v1 | ||
148 | with: | ||
149 | name: dist-x86_64-unknown-linux-musl | ||
150 | path: ./dist | ||
151 | |||
124 | dist-aarch64-unknown-linux-gnu: | 152 | dist-aarch64-unknown-linux-gnu: |
125 | name: dist (aarch64-unknown-linux-gnu) | 153 | name: dist (aarch64-unknown-linux-gnu) |
126 | runs-on: ubuntu-16.04 | 154 | runs-on: ubuntu-16.04 |
@@ -216,7 +244,7 @@ jobs: | |||
216 | publish: | 244 | publish: |
217 | name: publish | 245 | name: publish |
218 | runs-on: ubuntu-16.04 | 246 | runs-on: ubuntu-16.04 |
219 | needs: ['dist-x86_64-pc-windows-msvc', 'dist-aarch64-pc-windows-msvc', 'dist-x86_64-unknown-linux-gnu', 'dist-aarch64-unknown-linux-gnu', 'dist-x86_64-apple-darwin', 'dist-aarch64-apple-darwin'] | 247 | needs: ['dist-x86_64-pc-windows-msvc', 'dist-aarch64-pc-windows-msvc', 'dist-x86_64-unknown-linux-gnu', 'dist-x86_64-unknown-linux-musl', 'dist-aarch64-unknown-linux-gnu', 'dist-x86_64-apple-darwin', 'dist-aarch64-apple-darwin'] |
220 | steps: | 248 | steps: |
221 | - name: Install Nodejs | 249 | - name: Install Nodejs |
222 | uses: actions/setup-node@v1 | 250 | uses: actions/setup-node@v1 |
@@ -249,6 +277,10 @@ jobs: | |||
249 | path: dist | 277 | path: dist |
250 | - uses: actions/download-artifact@v1 | 278 | - uses: actions/download-artifact@v1 |
251 | with: | 279 | with: |
280 | name: dist-x86_64-unknown-linux-musl | ||
281 | path: dist | ||
282 | - uses: actions/download-artifact@v1 | ||
283 | with: | ||
252 | name: dist-aarch64-unknown-linux-gnu | 284 | name: dist-aarch64-unknown-linux-gnu |
253 | path: dist | 285 | path: dist |
254 | - uses: actions/download-artifact@v1 | 286 | - uses: actions/download-artifact@v1 |
diff --git a/crates/assists/src/handlers/generate_from_impl_for_enum.rs b/crates/assists/src/handlers/generate_from_impl_for_enum.rs index d9af6ab11..d9388a737 100644 --- a/crates/assists/src/handlers/generate_from_impl_for_enum.rs +++ b/crates/assists/src/handlers/generate_from_impl_for_enum.rs | |||
@@ -3,7 +3,7 @@ use ide_db::RootDatabase; | |||
3 | use syntax::ast::{self, AstNode, NameOwner}; | 3 | use syntax::ast::{self, AstNode, NameOwner}; |
4 | use test_utils::mark; | 4 | use test_utils::mark; |
5 | 5 | ||
6 | use crate::{AssistContext, AssistId, AssistKind, Assists}; | 6 | use crate::{utils::generate_trait_impl_text, AssistContext, AssistId, AssistKind, Assists}; |
7 | 7 | ||
8 | // Assist: generate_from_impl_for_enum | 8 | // Assist: generate_from_impl_for_enum |
9 | // | 9 | // |
@@ -18,25 +18,29 @@ use crate::{AssistContext, AssistId, AssistKind, Assists}; | |||
18 | // | 18 | // |
19 | // impl From<u32> for A { | 19 | // impl From<u32> for A { |
20 | // fn from(v: u32) -> Self { | 20 | // fn from(v: u32) -> Self { |
21 | // A::One(v) | 21 | // Self::One(v) |
22 | // } | 22 | // } |
23 | // } | 23 | // } |
24 | // ``` | 24 | // ``` |
25 | pub(crate) fn generate_from_impl_for_enum(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { | 25 | pub(crate) fn generate_from_impl_for_enum(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { |
26 | let variant = ctx.find_node_at_offset::<ast::Variant>()?; | 26 | let variant = ctx.find_node_at_offset::<ast::Variant>()?; |
27 | let variant_name = variant.name()?; | 27 | let variant_name = variant.name()?; |
28 | let enum_name = variant.parent_enum().name()?; | 28 | let enum_ = ast::Adt::Enum(variant.parent_enum()); |
29 | let field_list = match variant.kind() { | 29 | let (field_name, field_type) = match variant.kind() { |
30 | ast::StructKind::Tuple(field_list) => field_list, | 30 | ast::StructKind::Tuple(field_list) => { |
31 | _ => return None, | 31 | if field_list.fields().count() != 1 { |
32 | }; | 32 | return None; |
33 | if field_list.fields().count() != 1 { | 33 | } |
34 | return None; | 34 | (None, field_list.fields().next()?.ty()?) |
35 | } | 35 | } |
36 | let field_type = field_list.fields().next()?.ty()?; | 36 | ast::StructKind::Record(field_list) => { |
37 | let path = match field_type { | 37 | if field_list.fields().count() != 1 { |
38 | ast::Type::PathType(it) => it, | 38 | return None; |
39 | _ => return None, | 39 | } |
40 | let field = field_list.fields().next()?; | ||
41 | (Some(field.name()?), field.ty()?) | ||
42 | } | ||
43 | ast::StructKind::Unit => return None, | ||
40 | }; | 44 | }; |
41 | 45 | ||
42 | if existing_from_impl(&ctx.sema, &variant).is_some() { | 46 | if existing_from_impl(&ctx.sema, &variant).is_some() { |
@@ -51,19 +55,27 @@ pub(crate) fn generate_from_impl_for_enum(acc: &mut Assists, ctx: &AssistContext | |||
51 | target, | 55 | target, |
52 | |edit| { | 56 | |edit| { |
53 | let start_offset = variant.parent_enum().syntax().text_range().end(); | 57 | let start_offset = variant.parent_enum().syntax().text_range().end(); |
54 | let buf = format!( | 58 | let from_trait = format!("From<{}>", field_type.syntax()); |
55 | r#" | 59 | let impl_code = if let Some(name) = field_name { |
56 | 60 | format!( | |
57 | impl From<{0}> for {1} {{ | 61 | r#" fn from({0}: {1}) -> Self {{ |
58 | fn from(v: {0}) -> Self {{ | 62 | Self::{2} {{ {0} }} |
59 | {1}::{2}(v) | 63 | }}"#, |
60 | }} | 64 | name.text(), |
61 | }}"#, | 65 | field_type.syntax(), |
62 | path.syntax(), | 66 | variant_name, |
63 | enum_name, | 67 | ) |
64 | variant_name | 68 | } else { |
65 | ); | 69 | format!( |
66 | edit.insert(start_offset, buf); | 70 | r#" fn from(v: {}) -> Self {{ |
71 | Self::{}(v) | ||
72 | }}"#, | ||
73 | field_type.syntax(), | ||
74 | variant_name, | ||
75 | ) | ||
76 | }; | ||
77 | let from_impl = generate_trait_impl_text(&enum_, &from_trait, &impl_code); | ||
78 | edit.insert(start_offset, from_impl); | ||
67 | }, | 79 | }, |
68 | ) | 80 | ) |
69 | } | 81 | } |
@@ -106,7 +118,7 @@ mod tests { | |||
106 | 118 | ||
107 | impl From<u32> for A { | 119 | impl From<u32> for A { |
108 | fn from(v: u32) -> Self { | 120 | fn from(v: u32) -> Self { |
109 | A::One(v) | 121 | Self::One(v) |
110 | } | 122 | } |
111 | }"#, | 123 | }"#, |
112 | ); | 124 | ); |
@@ -121,7 +133,7 @@ impl From<u32> for A { | |||
121 | 133 | ||
122 | impl From<foo::bar::baz::Boo> for A { | 134 | impl From<foo::bar::baz::Boo> for A { |
123 | fn from(v: foo::bar::baz::Boo) -> Self { | 135 | fn from(v: foo::bar::baz::Boo) -> Self { |
124 | A::One(v) | 136 | Self::One(v) |
125 | } | 137 | } |
126 | }"#, | 138 | }"#, |
127 | ); | 139 | ); |
@@ -145,7 +157,17 @@ impl From<foo::bar::baz::Boo> for A { | |||
145 | 157 | ||
146 | #[test] | 158 | #[test] |
147 | fn test_add_from_impl_struct_variant() { | 159 | fn test_add_from_impl_struct_variant() { |
148 | check_not_applicable("enum A { $0One { x: u32 } }"); | 160 | check_assist( |
161 | generate_from_impl_for_enum, | ||
162 | "enum A { $0One { x: u32 } }", | ||
163 | r#"enum A { One { x: u32 } } | ||
164 | |||
165 | impl From<u32> for A { | ||
166 | fn from(x: u32) -> Self { | ||
167 | Self::One { x } | ||
168 | } | ||
169 | }"#, | ||
170 | ); | ||
149 | } | 171 | } |
150 | 172 | ||
151 | #[test] | 173 | #[test] |
@@ -157,7 +179,7 @@ enum A { $0One(u32), } | |||
157 | 179 | ||
158 | impl From<u32> for A { | 180 | impl From<u32> for A { |
159 | fn from(v: u32) -> Self { | 181 | fn from(v: u32) -> Self { |
160 | A::One(v) | 182 | Self::One(v) |
161 | } | 183 | } |
162 | } | 184 | } |
163 | "#, | 185 | "#, |
@@ -183,7 +205,7 @@ pub trait From<T> { | |||
183 | 205 | ||
184 | impl From<u32> for A { | 206 | impl From<u32> for A { |
185 | fn from(v: u32) -> Self { | 207 | fn from(v: u32) -> Self { |
186 | A::One(v) | 208 | Self::One(v) |
187 | } | 209 | } |
188 | } | 210 | } |
189 | 211 | ||
@@ -198,4 +220,49 @@ pub trait From<T> { | |||
198 | }"#, | 220 | }"#, |
199 | ); | 221 | ); |
200 | } | 222 | } |
223 | |||
224 | #[test] | ||
225 | fn test_add_from_impl_static_str() { | ||
226 | check_assist( | ||
227 | generate_from_impl_for_enum, | ||
228 | "enum A { $0One(&'static str) }", | ||
229 | r#"enum A { One(&'static str) } | ||
230 | |||
231 | impl From<&'static str> for A { | ||
232 | fn from(v: &'static str) -> Self { | ||
233 | Self::One(v) | ||
234 | } | ||
235 | }"#, | ||
236 | ); | ||
237 | } | ||
238 | |||
239 | #[test] | ||
240 | fn test_add_from_impl_generic_enum() { | ||
241 | check_assist( | ||
242 | generate_from_impl_for_enum, | ||
243 | "enum Generic<T, U: Clone> { $0One(T), Two(U) }", | ||
244 | r#"enum Generic<T, U: Clone> { One(T), Two(U) } | ||
245 | |||
246 | impl<T, U: Clone> From<T> for Generic<T, U> { | ||
247 | fn from(v: T) -> Self { | ||
248 | Self::One(v) | ||
249 | } | ||
250 | }"#, | ||
251 | ); | ||
252 | } | ||
253 | |||
254 | #[test] | ||
255 | fn test_add_from_impl_with_lifetime() { | ||
256 | check_assist( | ||
257 | generate_from_impl_for_enum, | ||
258 | "enum Generic<'a> { $0One(&'a i32) }", | ||
259 | r#"enum Generic<'a> { One(&'a i32) } | ||
260 | |||
261 | impl<'a> From<&'a i32> for Generic<'a> { | ||
262 | fn from(v: &'a i32) -> Self { | ||
263 | Self::One(v) | ||
264 | } | ||
265 | }"#, | ||
266 | ); | ||
267 | } | ||
201 | } | 268 | } |
diff --git a/crates/assists/src/handlers/generate_getter.rs b/crates/assists/src/handlers/generate_getter.rs index fbcf8b069..df7d1bb95 100644 --- a/crates/assists/src/handlers/generate_getter.rs +++ b/crates/assists/src/handlers/generate_getter.rs | |||
@@ -1,10 +1,9 @@ | |||
1 | use stdx::{format_to, to_lower_snake_case}; | 1 | use stdx::{format_to, to_lower_snake_case}; |
2 | use syntax::ast::VisibilityOwner; | 2 | use syntax::ast::{self, AstNode, NameOwner, VisibilityOwner}; |
3 | use syntax::ast::{self, AstNode, NameOwner}; | ||
4 | 3 | ||
5 | use crate::{ | 4 | use crate::{ |
6 | utils::{find_impl_block_end, find_struct_impl, generate_impl_text}, | 5 | utils::{find_impl_block_end, find_struct_impl, generate_impl_text}, |
7 | AssistContext, AssistId, AssistKind, Assists, | 6 | AssistContext, AssistId, AssistKind, Assists, GroupLabel, |
8 | }; | 7 | }; |
9 | 8 | ||
10 | // Assist: generate_getter | 9 | // Assist: generate_getter |
@@ -42,7 +41,8 @@ pub(crate) fn generate_getter(acc: &mut Assists, ctx: &AssistContext) -> Option< | |||
42 | let impl_def = find_struct_impl(&ctx, &ast::Adt::Struct(strukt.clone()), fn_name.as_str())?; | 41 | let impl_def = find_struct_impl(&ctx, &ast::Adt::Struct(strukt.clone()), fn_name.as_str())?; |
43 | 42 | ||
44 | let target = field.syntax().text_range(); | 43 | let target = field.syntax().text_range(); |
45 | acc.add( | 44 | acc.add_group( |
45 | &GroupLabel("Generate getter/setter".to_owned()), | ||
46 | AssistId("generate_getter", AssistKind::Generate), | 46 | AssistId("generate_getter", AssistKind::Generate), |
47 | "Generate a getter method", | 47 | "Generate a getter method", |
48 | target, | 48 | target, |
diff --git a/crates/assists/src/handlers/generate_getter_mut.rs b/crates/assists/src/handlers/generate_getter_mut.rs index bf0d99881..821c2eed5 100644 --- a/crates/assists/src/handlers/generate_getter_mut.rs +++ b/crates/assists/src/handlers/generate_getter_mut.rs | |||
@@ -1,10 +1,9 @@ | |||
1 | use stdx::{format_to, to_lower_snake_case}; | 1 | use stdx::{format_to, to_lower_snake_case}; |
2 | use syntax::ast::VisibilityOwner; | 2 | use syntax::ast::{self, AstNode, NameOwner, VisibilityOwner}; |
3 | use syntax::ast::{self, AstNode, NameOwner}; | ||
4 | 3 | ||
5 | use crate::{ | 4 | use crate::{ |
6 | utils::{find_impl_block_end, find_struct_impl, generate_impl_text}, | 5 | utils::{find_impl_block_end, find_struct_impl, generate_impl_text}, |
7 | AssistContext, AssistId, AssistKind, Assists, | 6 | AssistContext, AssistId, AssistKind, Assists, GroupLabel, |
8 | }; | 7 | }; |
9 | 8 | ||
10 | // Assist: generate_getter_mut | 9 | // Assist: generate_getter_mut |
@@ -46,7 +45,8 @@ pub(crate) fn generate_getter_mut(acc: &mut Assists, ctx: &AssistContext) -> Opt | |||
46 | )?; | 45 | )?; |
47 | 46 | ||
48 | let target = field.syntax().text_range(); | 47 | let target = field.syntax().text_range(); |
49 | acc.add( | 48 | acc.add_group( |
49 | &GroupLabel("Generate getter/setter".to_owned()), | ||
50 | AssistId("generate_getter_mut", AssistKind::Generate), | 50 | AssistId("generate_getter_mut", AssistKind::Generate), |
51 | "Generate a mut getter method", | 51 | "Generate a mut getter method", |
52 | target, | 52 | target, |
diff --git a/crates/assists/src/handlers/generate_impl.rs b/crates/assists/src/handlers/generate_impl.rs index 61d1bd25c..16a600e6f 100644 --- a/crates/assists/src/handlers/generate_impl.rs +++ b/crates/assists/src/handlers/generate_impl.rs | |||
@@ -1,11 +1,6 @@ | |||
1 | use itertools::Itertools; | 1 | use syntax::ast::{self, AstNode, NameOwner}; |
2 | use stdx::format_to; | ||
3 | use syntax::{ | ||
4 | ast::{self, AstNode, AttrsOwner, GenericParamsOwner, NameOwner}, | ||
5 | SmolStr, | ||
6 | }; | ||
7 | 2 | ||
8 | use crate::{AssistContext, AssistId, AssistKind, Assists}; | 3 | use crate::{utils::generate_impl_text, AssistContext, AssistId, AssistKind, Assists}; |
9 | 4 | ||
10 | // Assist: generate_impl | 5 | // Assist: generate_impl |
11 | // | 6 | // |
@@ -36,44 +31,15 @@ pub(crate) fn generate_impl(acc: &mut Assists, ctx: &AssistContext) -> Option<() | |||
36 | format!("Generate impl for `{}`", name), | 31 | format!("Generate impl for `{}`", name), |
37 | target, | 32 | target, |
38 | |edit| { | 33 | |edit| { |
39 | let type_params = nominal.generic_param_list(); | ||
40 | let start_offset = nominal.syntax().text_range().end(); | 34 | let start_offset = nominal.syntax().text_range().end(); |
41 | let mut buf = String::new(); | ||
42 | buf.push_str("\n\n"); | ||
43 | nominal | ||
44 | .attrs() | ||
45 | .filter(|attr| { | ||
46 | attr.as_simple_call().map(|(name, _arg)| name == "cfg").unwrap_or(false) | ||
47 | }) | ||
48 | .for_each(|attr| buf.push_str(format!("{}\n", attr.to_string()).as_str())); | ||
49 | |||
50 | buf.push_str("impl"); | ||
51 | if let Some(type_params) = &type_params { | ||
52 | format_to!(buf, "{}", type_params.syntax()); | ||
53 | } | ||
54 | buf.push_str(" "); | ||
55 | buf.push_str(name.text()); | ||
56 | if let Some(type_params) = type_params { | ||
57 | let lifetime_params = type_params | ||
58 | .lifetime_params() | ||
59 | .filter_map(|it| it.lifetime()) | ||
60 | .map(|it| SmolStr::from(it.text())); | ||
61 | let type_params = type_params | ||
62 | .type_params() | ||
63 | .filter_map(|it| it.name()) | ||
64 | .map(|it| SmolStr::from(it.text())); | ||
65 | |||
66 | let generic_params = lifetime_params.chain(type_params).format(", "); | ||
67 | format_to!(buf, "<{}>", generic_params) | ||
68 | } | ||
69 | match ctx.config.snippet_cap { | 35 | match ctx.config.snippet_cap { |
70 | Some(cap) => { | 36 | Some(cap) => { |
71 | buf.push_str(" {\n $0\n}"); | 37 | let snippet = generate_impl_text(&nominal, " $0"); |
72 | edit.insert_snippet(cap, start_offset, buf); | 38 | edit.insert_snippet(cap, start_offset, snippet); |
73 | } | 39 | } |
74 | None => { | 40 | None => { |
75 | buf.push_str(" {\n}"); | 41 | let snippet = generate_impl_text(&nominal, ""); |
76 | edit.insert(start_offset, buf); | 42 | edit.insert(start_offset, snippet); |
77 | } | 43 | } |
78 | } | 44 | } |
79 | }, | 45 | }, |
@@ -132,6 +98,30 @@ mod tests { | |||
132 | $0 | 98 | $0 |
133 | }"#, | 99 | }"#, |
134 | ); | 100 | ); |
101 | |||
102 | check_assist( | ||
103 | generate_impl, | ||
104 | r#" | ||
105 | struct Defaulted<T = i32> {}$0"#, | ||
106 | r#" | ||
107 | struct Defaulted<T = i32> {} | ||
108 | |||
109 | impl<T> Defaulted<T> { | ||
110 | $0 | ||
111 | }"#, | ||
112 | ); | ||
113 | |||
114 | check_assist( | ||
115 | generate_impl, | ||
116 | r#" | ||
117 | struct Defaulted<'a, 'b: 'a, T: Debug + Clone + 'a + 'b = String> {}$0"#, | ||
118 | r#" | ||
119 | struct Defaulted<'a, 'b: 'a, T: Debug + Clone + 'a + 'b = String> {} | ||
120 | |||
121 | impl<'a, 'b: 'a, T: Debug + Clone + 'a + 'b> Defaulted<'a, 'b, T> { | ||
122 | $0 | ||
123 | }"#, | ||
124 | ); | ||
135 | } | 125 | } |
136 | 126 | ||
137 | #[test] | 127 | #[test] |
diff --git a/crates/assists/src/handlers/generate_setter.rs b/crates/assists/src/handlers/generate_setter.rs index b655f9b9c..288cf745d 100644 --- a/crates/assists/src/handlers/generate_setter.rs +++ b/crates/assists/src/handlers/generate_setter.rs | |||
@@ -1,10 +1,9 @@ | |||
1 | use stdx::{format_to, to_lower_snake_case}; | 1 | use stdx::{format_to, to_lower_snake_case}; |
2 | use syntax::ast::VisibilityOwner; | 2 | use syntax::ast::{self, AstNode, NameOwner, VisibilityOwner}; |
3 | use syntax::ast::{self, AstNode, NameOwner}; | ||
4 | 3 | ||
5 | use crate::{ | 4 | use crate::{ |
6 | utils::{find_impl_block_end, find_struct_impl, generate_impl_text}, | 5 | utils::{find_impl_block_end, find_struct_impl, generate_impl_text}, |
7 | AssistContext, AssistId, AssistKind, Assists, | 6 | AssistContext, AssistId, AssistKind, Assists, GroupLabel, |
8 | }; | 7 | }; |
9 | 8 | ||
10 | // Assist: generate_setter | 9 | // Assist: generate_setter |
@@ -46,7 +45,8 @@ pub(crate) fn generate_setter(acc: &mut Assists, ctx: &AssistContext) -> Option< | |||
46 | )?; | 45 | )?; |
47 | 46 | ||
48 | let target = field.syntax().text_range(); | 47 | let target = field.syntax().text_range(); |
49 | acc.add( | 48 | acc.add_group( |
49 | &GroupLabel("Generate getter/setter".to_owned()), | ||
50 | AssistId("generate_setter", AssistKind::Generate), | 50 | AssistId("generate_setter", AssistKind::Generate), |
51 | "Generate a setter method", | 51 | "Generate a setter method", |
52 | target, | 52 | target, |
diff --git a/crates/assists/src/handlers/replace_derive_with_manual_impl.rs b/crates/assists/src/handlers/replace_derive_with_manual_impl.rs index 6aa9d2f2c..c69bc5cac 100644 --- a/crates/assists/src/handlers/replace_derive_with_manual_impl.rs +++ b/crates/assists/src/handlers/replace_derive_with_manual_impl.rs | |||
@@ -2,8 +2,7 @@ use ide_db::helpers::mod_path_to_ast; | |||
2 | use ide_db::imports_locator; | 2 | use ide_db::imports_locator; |
3 | use itertools::Itertools; | 3 | use itertools::Itertools; |
4 | use syntax::{ | 4 | use syntax::{ |
5 | ast::{self, make, AstNode}, | 5 | ast::{self, make, AstNode, NameOwner}, |
6 | Direction, | ||
7 | SyntaxKind::{IDENT, WHITESPACE}, | 6 | SyntaxKind::{IDENT, WHITESPACE}, |
8 | TextSize, | 7 | TextSize, |
9 | }; | 8 | }; |
@@ -11,7 +10,8 @@ use syntax::{ | |||
11 | use crate::{ | 10 | use crate::{ |
12 | assist_context::{AssistBuilder, AssistContext, Assists}, | 11 | assist_context::{AssistBuilder, AssistContext, Assists}, |
13 | utils::{ | 12 | utils::{ |
14 | add_trait_assoc_items_to_impl, filter_assoc_items, render_snippet, Cursor, DefaultMethods, | 13 | add_trait_assoc_items_to_impl, filter_assoc_items, generate_trait_impl_text, |
14 | render_snippet, Cursor, DefaultMethods, | ||
15 | }, | 15 | }, |
16 | AssistId, AssistKind, | 16 | AssistId, AssistKind, |
17 | }; | 17 | }; |
@@ -57,8 +57,9 @@ pub(crate) fn replace_derive_with_manual_impl( | |||
57 | let trait_token = ctx.token_at_offset().find(|t| t.kind() == IDENT && t.text() != "derive")?; | 57 | let trait_token = ctx.token_at_offset().find(|t| t.kind() == IDENT && t.text() != "derive")?; |
58 | let trait_path = make::path_unqualified(make::path_segment(make::name_ref(trait_token.text()))); | 58 | let trait_path = make::path_unqualified(make::path_segment(make::name_ref(trait_token.text()))); |
59 | 59 | ||
60 | let annotated_name = attr.syntax().siblings(Direction::Next).find_map(ast::Name::cast)?; | 60 | let adt = attr.syntax().parent().and_then(ast::Adt::cast)?; |
61 | let insert_pos = annotated_name.syntax().parent()?.text_range().end(); | 61 | let annotated_name = adt.name()?; |
62 | let insert_pos = adt.syntax().text_range().end(); | ||
62 | 63 | ||
63 | let current_module = ctx.sema.scope(annotated_name.syntax()).module()?; | 64 | let current_module = ctx.sema.scope(annotated_name.syntax()).module()?; |
64 | let current_crate = current_module.krate(); | 65 | let current_crate = current_module.krate(); |
@@ -82,10 +83,10 @@ pub(crate) fn replace_derive_with_manual_impl( | |||
82 | 83 | ||
83 | let mut no_traits_found = true; | 84 | let mut no_traits_found = true; |
84 | for (trait_path, trait_) in found_traits.inspect(|_| no_traits_found = false) { | 85 | for (trait_path, trait_) in found_traits.inspect(|_| no_traits_found = false) { |
85 | add_assist(acc, ctx, &attr, &trait_path, Some(trait_), &annotated_name, insert_pos)?; | 86 | add_assist(acc, ctx, &attr, &trait_path, Some(trait_), &adt, &annotated_name, insert_pos)?; |
86 | } | 87 | } |
87 | if no_traits_found { | 88 | if no_traits_found { |
88 | add_assist(acc, ctx, &attr, &trait_path, None, &annotated_name, insert_pos)?; | 89 | add_assist(acc, ctx, &attr, &trait_path, None, &adt, &annotated_name, insert_pos)?; |
89 | } | 90 | } |
90 | Some(()) | 91 | Some(()) |
91 | } | 92 | } |
@@ -96,6 +97,7 @@ fn add_assist( | |||
96 | attr: &ast::Attr, | 97 | attr: &ast::Attr, |
97 | trait_path: &ast::Path, | 98 | trait_path: &ast::Path, |
98 | trait_: Option<hir::Trait>, | 99 | trait_: Option<hir::Trait>, |
100 | adt: &ast::Adt, | ||
99 | annotated_name: &ast::Name, | 101 | annotated_name: &ast::Name, |
100 | insert_pos: TextSize, | 102 | insert_pos: TextSize, |
101 | ) -> Option<()> { | 103 | ) -> Option<()> { |
@@ -112,15 +114,15 @@ fn add_assist( | |||
112 | let impl_def_with_items = | 114 | let impl_def_with_items = |
113 | impl_def_from_trait(&ctx.sema, annotated_name, trait_, trait_path); | 115 | impl_def_from_trait(&ctx.sema, annotated_name, trait_, trait_path); |
114 | update_attribute(builder, &input, &trait_name, &attr); | 116 | update_attribute(builder, &input, &trait_name, &attr); |
117 | let trait_path = format!("{}", trait_path); | ||
115 | match (ctx.config.snippet_cap, impl_def_with_items) { | 118 | match (ctx.config.snippet_cap, impl_def_with_items) { |
116 | (None, _) => builder.insert( | 119 | (None, _) => { |
117 | insert_pos, | 120 | builder.insert(insert_pos, generate_trait_impl_text(adt, &trait_path, "")) |
118 | format!("\n\nimpl {} for {} {{\n\n}}", trait_path, annotated_name), | 121 | } |
119 | ), | ||
120 | (Some(cap), None) => builder.insert_snippet( | 122 | (Some(cap), None) => builder.insert_snippet( |
121 | cap, | 123 | cap, |
122 | insert_pos, | 124 | insert_pos, |
123 | format!("\n\nimpl {} for {} {{\n $0\n}}", trait_path, annotated_name), | 125 | generate_trait_impl_text(adt, &trait_path, " $0"), |
124 | ), | 126 | ), |
125 | (Some(cap), Some((impl_def, first_assoc_item))) => { | 127 | (Some(cap), Some((impl_def, first_assoc_item))) => { |
126 | let mut cursor = Cursor::Before(first_assoc_item.syntax()); | 128 | let mut cursor = Cursor::Before(first_assoc_item.syntax()); |
diff --git a/crates/assists/src/tests/generated.rs b/crates/assists/src/tests/generated.rs index 6f2b22bc2..0516deaff 100644 --- a/crates/assists/src/tests/generated.rs +++ b/crates/assists/src/tests/generated.rs | |||
@@ -499,7 +499,7 @@ enum A { One(u32) } | |||
499 | 499 | ||
500 | impl From<u32> for A { | 500 | impl From<u32> for A { |
501 | fn from(v: u32) -> Self { | 501 | fn from(v: u32) -> Self { |
502 | A::One(v) | 502 | Self::One(v) |
503 | } | 503 | } |
504 | } | 504 | } |
505 | "#####, | 505 | "#####, |
diff --git a/crates/assists/src/utils.rs b/crates/assists/src/utils.rs index 5dd32aef1..8418e6e12 100644 --- a/crates/assists/src/utils.rs +++ b/crates/assists/src/utils.rs | |||
@@ -2,6 +2,7 @@ | |||
2 | 2 | ||
3 | use std::ops; | 3 | use std::ops; |
4 | 4 | ||
5 | use ast::TypeBoundsOwner; | ||
5 | use hir::{Adt, HasSource}; | 6 | use hir::{Adt, HasSource}; |
6 | use ide_db::{helpers::SnippetCap, RootDatabase}; | 7 | use ide_db::{helpers::SnippetCap, RootDatabase}; |
7 | use itertools::Itertools; | 8 | use itertools::Itertools; |
@@ -367,21 +368,56 @@ pub(crate) fn find_impl_block_end(impl_def: ast::Impl, buf: &mut String) -> Opti | |||
367 | // Generates the surrounding `impl Type { <code> }` including type and lifetime | 368 | // Generates the surrounding `impl Type { <code> }` including type and lifetime |
368 | // parameters | 369 | // parameters |
369 | pub(crate) fn generate_impl_text(adt: &ast::Adt, code: &str) -> String { | 370 | pub(crate) fn generate_impl_text(adt: &ast::Adt, code: &str) -> String { |
370 | let type_params = adt.generic_param_list(); | 371 | generate_impl_text_inner(adt, None, code) |
372 | } | ||
373 | |||
374 | // Generates the surrounding `impl <trait> for Type { <code> }` including type | ||
375 | // and lifetime parameters | ||
376 | pub(crate) fn generate_trait_impl_text(adt: &ast::Adt, trait_text: &str, code: &str) -> String { | ||
377 | generate_impl_text_inner(adt, Some(trait_text), code) | ||
378 | } | ||
379 | |||
380 | fn generate_impl_text_inner(adt: &ast::Adt, trait_text: Option<&str>, code: &str) -> String { | ||
381 | let generic_params = adt.generic_param_list(); | ||
371 | let mut buf = String::with_capacity(code.len()); | 382 | let mut buf = String::with_capacity(code.len()); |
372 | buf.push_str("\n\nimpl"); | 383 | buf.push_str("\n\n"); |
373 | if let Some(type_params) = &type_params { | 384 | adt.attrs() |
374 | format_to!(buf, "{}", type_params.syntax()); | 385 | .filter(|attr| attr.as_simple_call().map(|(name, _arg)| name == "cfg").unwrap_or(false)) |
386 | .for_each(|attr| buf.push_str(format!("{}\n", attr.to_string()).as_str())); | ||
387 | buf.push_str("impl"); | ||
388 | if let Some(generic_params) = &generic_params { | ||
389 | let lifetimes = generic_params.lifetime_params().map(|lt| format!("{}", lt.syntax())); | ||
390 | let type_params = generic_params.type_params().map(|type_param| { | ||
391 | let mut buf = String::new(); | ||
392 | if let Some(it) = type_param.name() { | ||
393 | format_to!(buf, "{}", it.syntax()); | ||
394 | } | ||
395 | if let Some(it) = type_param.colon_token() { | ||
396 | format_to!(buf, "{} ", it); | ||
397 | } | ||
398 | if let Some(it) = type_param.type_bound_list() { | ||
399 | format_to!(buf, "{}", it.syntax()); | ||
400 | } | ||
401 | buf | ||
402 | }); | ||
403 | let generics = lifetimes.chain(type_params).format(", "); | ||
404 | format_to!(buf, "<{}>", generics); | ||
375 | } | 405 | } |
376 | buf.push(' '); | 406 | buf.push(' '); |
407 | if let Some(trait_text) = trait_text { | ||
408 | buf.push_str(trait_text); | ||
409 | buf.push_str(" for "); | ||
410 | } | ||
377 | buf.push_str(adt.name().unwrap().text()); | 411 | buf.push_str(adt.name().unwrap().text()); |
378 | if let Some(type_params) = type_params { | 412 | if let Some(generic_params) = generic_params { |
379 | let lifetime_params = type_params | 413 | let lifetime_params = generic_params |
380 | .lifetime_params() | 414 | .lifetime_params() |
381 | .filter_map(|it| it.lifetime()) | 415 | .filter_map(|it| it.lifetime()) |
382 | .map(|it| SmolStr::from(it.text())); | 416 | .map(|it| SmolStr::from(it.text())); |
383 | let type_params = | 417 | let type_params = generic_params |
384 | type_params.type_params().filter_map(|it| it.name()).map(|it| SmolStr::from(it.text())); | 418 | .type_params() |
419 | .filter_map(|it| it.name()) | ||
420 | .map(|it| SmolStr::from(it.text())); | ||
385 | format_to!(buf, "<{}>", lifetime_params.chain(type_params).format(", ")) | 421 | format_to!(buf, "<{}>", lifetime_params.chain(type_params).format(", ")) |
386 | } | 422 | } |
387 | 423 | ||
diff --git a/crates/ide/src/annotations.rs b/crates/ide/src/annotations.rs new file mode 100644 index 000000000..414a60bed --- /dev/null +++ b/crates/ide/src/annotations.rs | |||
@@ -0,0 +1,937 @@ | |||
1 | use hir::Semantics; | ||
2 | use ide_db::{ | ||
3 | base_db::{FileId, FilePosition, FileRange, SourceDatabase}, | ||
4 | RootDatabase, SymbolKind, | ||
5 | }; | ||
6 | use syntax::TextRange; | ||
7 | |||
8 | use crate::{ | ||
9 | file_structure::file_structure, | ||
10 | fn_references::find_all_methods, | ||
11 | goto_implementation::goto_implementation, | ||
12 | references::find_all_refs, | ||
13 | runnables::{runnables, Runnable}, | ||
14 | NavigationTarget, RunnableKind, | ||
15 | }; | ||
16 | |||
17 | // Feature: Annotations | ||
18 | // | ||
19 | // Provides user with annotations above items for looking up references or impl blocks | ||
20 | // and running/debugging binaries. | ||
21 | #[derive(Debug)] | ||
22 | pub struct Annotation { | ||
23 | pub range: TextRange, | ||
24 | pub kind: AnnotationKind, | ||
25 | } | ||
26 | |||
27 | #[derive(Debug)] | ||
28 | pub enum AnnotationKind { | ||
29 | Runnable { debug: bool, runnable: Runnable }, | ||
30 | HasImpls { position: FilePosition, data: Option<Vec<NavigationTarget>> }, | ||
31 | HasReferences { position: FilePosition, data: Option<Vec<FileRange>> }, | ||
32 | } | ||
33 | |||
34 | pub struct AnnotationConfig { | ||
35 | pub binary_target: bool, | ||
36 | pub annotate_runnables: bool, | ||
37 | pub annotate_impls: bool, | ||
38 | pub annotate_references: bool, | ||
39 | pub annotate_method_references: bool, | ||
40 | pub run: bool, | ||
41 | pub debug: bool, | ||
42 | } | ||
43 | |||
44 | pub(crate) fn annotations( | ||
45 | db: &RootDatabase, | ||
46 | file_id: FileId, | ||
47 | config: AnnotationConfig, | ||
48 | ) -> Vec<Annotation> { | ||
49 | let mut annotations = Vec::default(); | ||
50 | |||
51 | if config.annotate_runnables { | ||
52 | for runnable in runnables(db, file_id) { | ||
53 | if should_skip_runnable(&runnable.kind, config.binary_target) { | ||
54 | continue; | ||
55 | } | ||
56 | |||
57 | let action = runnable.action(); | ||
58 | let range = runnable.nav.full_range; | ||
59 | |||
60 | if action.debugee && config.debug { | ||
61 | annotations.push(Annotation { | ||
62 | range, | ||
63 | |||
64 | // FIXME: This one allocates without reason if run is enabled, but debug is disabled | ||
65 | kind: AnnotationKind::Runnable { debug: true, runnable: runnable.clone() }, | ||
66 | }); | ||
67 | } | ||
68 | |||
69 | if config.run { | ||
70 | annotations.push(Annotation { | ||
71 | range, | ||
72 | kind: AnnotationKind::Runnable { debug: false, runnable }, | ||
73 | }); | ||
74 | } | ||
75 | } | ||
76 | } | ||
77 | |||
78 | file_structure(&db.parse(file_id).tree()) | ||
79 | .into_iter() | ||
80 | .filter(|node| { | ||
81 | matches!( | ||
82 | node.kind, | ||
83 | SymbolKind::Trait | ||
84 | | SymbolKind::Struct | ||
85 | | SymbolKind::Enum | ||
86 | | SymbolKind::Union | ||
87 | | SymbolKind::Const | ||
88 | ) | ||
89 | }) | ||
90 | .for_each(|node| { | ||
91 | if config.annotate_impls && node.kind != SymbolKind::Const { | ||
92 | annotations.push(Annotation { | ||
93 | range: node.node_range, | ||
94 | kind: AnnotationKind::HasImpls { | ||
95 | position: FilePosition { file_id, offset: node.navigation_range.start() }, | ||
96 | data: None, | ||
97 | }, | ||
98 | }); | ||
99 | } | ||
100 | |||
101 | if config.annotate_references { | ||
102 | annotations.push(Annotation { | ||
103 | range: node.node_range, | ||
104 | kind: AnnotationKind::HasReferences { | ||
105 | position: FilePosition { file_id, offset: node.navigation_range.start() }, | ||
106 | data: None, | ||
107 | }, | ||
108 | }); | ||
109 | } | ||
110 | }); | ||
111 | |||
112 | if config.annotate_method_references { | ||
113 | annotations.extend(find_all_methods(db, file_id).into_iter().map(|method| Annotation { | ||
114 | range: method.range, | ||
115 | kind: AnnotationKind::HasReferences { | ||
116 | position: FilePosition { file_id, offset: method.range.start() }, | ||
117 | data: None, | ||
118 | }, | ||
119 | })); | ||
120 | } | ||
121 | |||
122 | annotations | ||
123 | } | ||
124 | |||
125 | pub(crate) fn resolve_annotation(db: &RootDatabase, mut annotation: Annotation) -> Annotation { | ||
126 | match annotation.kind { | ||
127 | AnnotationKind::HasImpls { position, ref mut data } => { | ||
128 | *data = goto_implementation(db, position).map(|range| range.info); | ||
129 | } | ||
130 | AnnotationKind::HasReferences { position, ref mut data } => { | ||
131 | *data = find_all_refs(&Semantics::new(db), position, None).map(|result| { | ||
132 | result | ||
133 | .references | ||
134 | .into_iter() | ||
135 | .map(|(file_id, access)| { | ||
136 | access.into_iter().map(move |(range, _)| FileRange { file_id, range }) | ||
137 | }) | ||
138 | .flatten() | ||
139 | .collect() | ||
140 | }); | ||
141 | } | ||
142 | _ => {} | ||
143 | }; | ||
144 | |||
145 | annotation | ||
146 | } | ||
147 | |||
148 | fn should_skip_runnable(kind: &RunnableKind, binary_target: bool) -> bool { | ||
149 | match kind { | ||
150 | RunnableKind::Bin => !binary_target, | ||
151 | _ => false, | ||
152 | } | ||
153 | } | ||
154 | |||
155 | #[cfg(test)] | ||
156 | mod tests { | ||
157 | use expect_test::{expect, Expect}; | ||
158 | |||
159 | use crate::{fixture, Annotation, AnnotationConfig}; | ||
160 | |||
161 | fn check(ra_fixture: &str, expect: Expect) { | ||
162 | let (analysis, file_id) = fixture::file(ra_fixture); | ||
163 | |||
164 | let annotations: Vec<Annotation> = analysis | ||
165 | .annotations( | ||
166 | file_id, | ||
167 | AnnotationConfig { | ||
168 | binary_target: true, | ||
169 | annotate_runnables: true, | ||
170 | annotate_impls: true, | ||
171 | annotate_references: true, | ||
172 | annotate_method_references: true, | ||
173 | run: true, | ||
174 | debug: true, | ||
175 | }, | ||
176 | ) | ||
177 | .unwrap() | ||
178 | .into_iter() | ||
179 | .map(|annotation| analysis.resolve_annotation(annotation).unwrap()) | ||
180 | .collect(); | ||
181 | |||
182 | expect.assert_debug_eq(&annotations); | ||
183 | } | ||
184 | |||
185 | #[test] | ||
186 | fn const_annotations() { | ||
187 | check( | ||
188 | r#" | ||
189 | const DEMO: i32 = 123; | ||
190 | |||
191 | const UNUSED: i32 = 123; | ||
192 | |||
193 | fn main() { | ||
194 | let hello = DEMO; | ||
195 | } | ||
196 | "#, | ||
197 | expect![[r#" | ||
198 | [ | ||
199 | Annotation { | ||
200 | range: 50..85, | ||
201 | kind: Runnable { | ||
202 | debug: true, | ||
203 | runnable: Runnable { | ||
204 | nav: NavigationTarget { | ||
205 | file_id: FileId( | ||
206 | 0, | ||
207 | ), | ||
208 | full_range: 50..85, | ||
209 | focus_range: 53..57, | ||
210 | name: "main", | ||
211 | kind: Function, | ||
212 | }, | ||
213 | kind: Bin, | ||
214 | cfg: None, | ||
215 | }, | ||
216 | }, | ||
217 | }, | ||
218 | Annotation { | ||
219 | range: 50..85, | ||
220 | kind: Runnable { | ||
221 | debug: false, | ||
222 | runnable: Runnable { | ||
223 | nav: NavigationTarget { | ||
224 | file_id: FileId( | ||
225 | 0, | ||
226 | ), | ||
227 | full_range: 50..85, | ||
228 | focus_range: 53..57, | ||
229 | name: "main", | ||
230 | kind: Function, | ||
231 | }, | ||
232 | kind: Bin, | ||
233 | cfg: None, | ||
234 | }, | ||
235 | }, | ||
236 | }, | ||
237 | Annotation { | ||
238 | range: 0..22, | ||
239 | kind: HasReferences { | ||
240 | position: FilePosition { | ||
241 | file_id: FileId( | ||
242 | 0, | ||
243 | ), | ||
244 | offset: 6, | ||
245 | }, | ||
246 | data: Some( | ||
247 | [ | ||
248 | FileRange { | ||
249 | file_id: FileId( | ||
250 | 0, | ||
251 | ), | ||
252 | range: 78..82, | ||
253 | }, | ||
254 | ], | ||
255 | ), | ||
256 | }, | ||
257 | }, | ||
258 | Annotation { | ||
259 | range: 24..48, | ||
260 | kind: HasReferences { | ||
261 | position: FilePosition { | ||
262 | file_id: FileId( | ||
263 | 0, | ||
264 | ), | ||
265 | offset: 30, | ||
266 | }, | ||
267 | data: Some( | ||
268 | [], | ||
269 | ), | ||
270 | }, | ||
271 | }, | ||
272 | Annotation { | ||
273 | range: 53..57, | ||
274 | kind: HasReferences { | ||
275 | position: FilePosition { | ||
276 | file_id: FileId( | ||
277 | 0, | ||
278 | ), | ||
279 | offset: 53, | ||
280 | }, | ||
281 | data: Some( | ||
282 | [], | ||
283 | ), | ||
284 | }, | ||
285 | }, | ||
286 | ] | ||
287 | "#]], | ||
288 | ); | ||
289 | } | ||
290 | |||
291 | #[test] | ||
292 | fn struct_references_annotations() { | ||
293 | check( | ||
294 | r#" | ||
295 | struct Test; | ||
296 | |||
297 | fn main() { | ||
298 | let test = Test; | ||
299 | } | ||
300 | "#, | ||
301 | expect![[r#" | ||
302 | [ | ||
303 | Annotation { | ||
304 | range: 14..48, | ||
305 | kind: Runnable { | ||
306 | debug: true, | ||
307 | runnable: Runnable { | ||
308 | nav: NavigationTarget { | ||
309 | file_id: FileId( | ||
310 | 0, | ||
311 | ), | ||
312 | full_range: 14..48, | ||
313 | focus_range: 17..21, | ||
314 | name: "main", | ||
315 | kind: Function, | ||
316 | }, | ||
317 | kind: Bin, | ||
318 | cfg: None, | ||
319 | }, | ||
320 | }, | ||
321 | }, | ||
322 | Annotation { | ||
323 | range: 14..48, | ||
324 | kind: Runnable { | ||
325 | debug: false, | ||
326 | runnable: Runnable { | ||
327 | nav: NavigationTarget { | ||
328 | file_id: FileId( | ||
329 | 0, | ||
330 | ), | ||
331 | full_range: 14..48, | ||
332 | focus_range: 17..21, | ||
333 | name: "main", | ||
334 | kind: Function, | ||
335 | }, | ||
336 | kind: Bin, | ||
337 | cfg: None, | ||
338 | }, | ||
339 | }, | ||
340 | }, | ||
341 | Annotation { | ||
342 | range: 0..12, | ||
343 | kind: HasImpls { | ||
344 | position: FilePosition { | ||
345 | file_id: FileId( | ||
346 | 0, | ||
347 | ), | ||
348 | offset: 7, | ||
349 | }, | ||
350 | data: Some( | ||
351 | [], | ||
352 | ), | ||
353 | }, | ||
354 | }, | ||
355 | Annotation { | ||
356 | range: 0..12, | ||
357 | kind: HasReferences { | ||
358 | position: FilePosition { | ||
359 | file_id: FileId( | ||
360 | 0, | ||
361 | ), | ||
362 | offset: 7, | ||
363 | }, | ||
364 | data: Some( | ||
365 | [ | ||
366 | FileRange { | ||
367 | file_id: FileId( | ||
368 | 0, | ||
369 | ), | ||
370 | range: 41..45, | ||
371 | }, | ||
372 | ], | ||
373 | ), | ||
374 | }, | ||
375 | }, | ||
376 | Annotation { | ||
377 | range: 17..21, | ||
378 | kind: HasReferences { | ||
379 | position: FilePosition { | ||
380 | file_id: FileId( | ||
381 | 0, | ||
382 | ), | ||
383 | offset: 17, | ||
384 | }, | ||
385 | data: Some( | ||
386 | [], | ||
387 | ), | ||
388 | }, | ||
389 | }, | ||
390 | ] | ||
391 | "#]], | ||
392 | ); | ||
393 | } | ||
394 | |||
395 | #[test] | ||
396 | fn struct_and_trait_impls_annotations() { | ||
397 | check( | ||
398 | r#" | ||
399 | struct Test; | ||
400 | |||
401 | trait MyCoolTrait {} | ||
402 | |||
403 | impl MyCoolTrait for Test {} | ||
404 | |||
405 | fn main() { | ||
406 | let test = Test; | ||
407 | } | ||
408 | "#, | ||
409 | expect![[r#" | ||
410 | [ | ||
411 | Annotation { | ||
412 | range: 66..100, | ||
413 | kind: Runnable { | ||
414 | debug: true, | ||
415 | runnable: Runnable { | ||
416 | nav: NavigationTarget { | ||
417 | file_id: FileId( | ||
418 | 0, | ||
419 | ), | ||
420 | full_range: 66..100, | ||
421 | focus_range: 69..73, | ||
422 | name: "main", | ||
423 | kind: Function, | ||
424 | }, | ||
425 | kind: Bin, | ||
426 | cfg: None, | ||
427 | }, | ||
428 | }, | ||
429 | }, | ||
430 | Annotation { | ||
431 | range: 66..100, | ||
432 | kind: Runnable { | ||
433 | debug: false, | ||
434 | runnable: Runnable { | ||
435 | nav: NavigationTarget { | ||
436 | file_id: FileId( | ||
437 | 0, | ||
438 | ), | ||
439 | full_range: 66..100, | ||
440 | focus_range: 69..73, | ||
441 | name: "main", | ||
442 | kind: Function, | ||
443 | }, | ||
444 | kind: Bin, | ||
445 | cfg: None, | ||
446 | }, | ||
447 | }, | ||
448 | }, | ||
449 | Annotation { | ||
450 | range: 0..12, | ||
451 | kind: HasImpls { | ||
452 | position: FilePosition { | ||
453 | file_id: FileId( | ||
454 | 0, | ||
455 | ), | ||
456 | offset: 7, | ||
457 | }, | ||
458 | data: Some( | ||
459 | [ | ||
460 | NavigationTarget { | ||
461 | file_id: FileId( | ||
462 | 0, | ||
463 | ), | ||
464 | full_range: 36..64, | ||
465 | focus_range: 57..61, | ||
466 | name: "impl", | ||
467 | kind: Impl, | ||
468 | }, | ||
469 | ], | ||
470 | ), | ||
471 | }, | ||
472 | }, | ||
473 | Annotation { | ||
474 | range: 0..12, | ||
475 | kind: HasReferences { | ||
476 | position: FilePosition { | ||
477 | file_id: FileId( | ||
478 | 0, | ||
479 | ), | ||
480 | offset: 7, | ||
481 | }, | ||
482 | data: Some( | ||
483 | [ | ||
484 | FileRange { | ||
485 | file_id: FileId( | ||
486 | 0, | ||
487 | ), | ||
488 | range: 57..61, | ||
489 | }, | ||
490 | FileRange { | ||
491 | file_id: FileId( | ||
492 | 0, | ||
493 | ), | ||
494 | range: 93..97, | ||
495 | }, | ||
496 | ], | ||
497 | ), | ||
498 | }, | ||
499 | }, | ||
500 | Annotation { | ||
501 | range: 14..34, | ||
502 | kind: HasImpls { | ||
503 | position: FilePosition { | ||
504 | file_id: FileId( | ||
505 | 0, | ||
506 | ), | ||
507 | offset: 20, | ||
508 | }, | ||
509 | data: Some( | ||
510 | [ | ||
511 | NavigationTarget { | ||
512 | file_id: FileId( | ||
513 | 0, | ||
514 | ), | ||
515 | full_range: 36..64, | ||
516 | focus_range: 57..61, | ||
517 | name: "impl", | ||
518 | kind: Impl, | ||
519 | }, | ||
520 | ], | ||
521 | ), | ||
522 | }, | ||
523 | }, | ||
524 | Annotation { | ||
525 | range: 14..34, | ||
526 | kind: HasReferences { | ||
527 | position: FilePosition { | ||
528 | file_id: FileId( | ||
529 | 0, | ||
530 | ), | ||
531 | offset: 20, | ||
532 | }, | ||
533 | data: Some( | ||
534 | [ | ||
535 | FileRange { | ||
536 | file_id: FileId( | ||
537 | 0, | ||
538 | ), | ||
539 | range: 41..52, | ||
540 | }, | ||
541 | ], | ||
542 | ), | ||
543 | }, | ||
544 | }, | ||
545 | Annotation { | ||
546 | range: 69..73, | ||
547 | kind: HasReferences { | ||
548 | position: FilePosition { | ||
549 | file_id: FileId( | ||
550 | 0, | ||
551 | ), | ||
552 | offset: 69, | ||
553 | }, | ||
554 | data: Some( | ||
555 | [], | ||
556 | ), | ||
557 | }, | ||
558 | }, | ||
559 | ] | ||
560 | "#]], | ||
561 | ); | ||
562 | } | ||
563 | |||
564 | #[test] | ||
565 | fn runnable_annotation() { | ||
566 | check( | ||
567 | r#" | ||
568 | fn main() {} | ||
569 | "#, | ||
570 | expect![[r#" | ||
571 | [ | ||
572 | Annotation { | ||
573 | range: 0..12, | ||
574 | kind: Runnable { | ||
575 | debug: true, | ||
576 | runnable: Runnable { | ||
577 | nav: NavigationTarget { | ||
578 | file_id: FileId( | ||
579 | 0, | ||
580 | ), | ||
581 | full_range: 0..12, | ||
582 | focus_range: 3..7, | ||
583 | name: "main", | ||
584 | kind: Function, | ||
585 | }, | ||
586 | kind: Bin, | ||
587 | cfg: None, | ||
588 | }, | ||
589 | }, | ||
590 | }, | ||
591 | Annotation { | ||
592 | range: 0..12, | ||
593 | kind: Runnable { | ||
594 | debug: false, | ||
595 | runnable: Runnable { | ||
596 | nav: NavigationTarget { | ||
597 | file_id: FileId( | ||
598 | 0, | ||
599 | ), | ||
600 | full_range: 0..12, | ||
601 | focus_range: 3..7, | ||
602 | name: "main", | ||
603 | kind: Function, | ||
604 | }, | ||
605 | kind: Bin, | ||
606 | cfg: None, | ||
607 | }, | ||
608 | }, | ||
609 | }, | ||
610 | Annotation { | ||
611 | range: 3..7, | ||
612 | kind: HasReferences { | ||
613 | position: FilePosition { | ||
614 | file_id: FileId( | ||
615 | 0, | ||
616 | ), | ||
617 | offset: 3, | ||
618 | }, | ||
619 | data: Some( | ||
620 | [], | ||
621 | ), | ||
622 | }, | ||
623 | }, | ||
624 | ] | ||
625 | "#]], | ||
626 | ); | ||
627 | } | ||
628 | |||
629 | #[test] | ||
630 | fn method_annotations() { | ||
631 | check( | ||
632 | r#" | ||
633 | struct Test; | ||
634 | |||
635 | impl Test { | ||
636 | fn self_by_ref(&self) {} | ||
637 | } | ||
638 | |||
639 | fn main() { | ||
640 | Test.self_by_ref(); | ||
641 | } | ||
642 | "#, | ||
643 | expect![[r#" | ||
644 | [ | ||
645 | Annotation { | ||
646 | range: 58..95, | ||
647 | kind: Runnable { | ||
648 | debug: true, | ||
649 | runnable: Runnable { | ||
650 | nav: NavigationTarget { | ||
651 | file_id: FileId( | ||
652 | 0, | ||
653 | ), | ||
654 | full_range: 58..95, | ||
655 | focus_range: 61..65, | ||
656 | name: "main", | ||
657 | kind: Function, | ||
658 | }, | ||
659 | kind: Bin, | ||
660 | cfg: None, | ||
661 | }, | ||
662 | }, | ||
663 | }, | ||
664 | Annotation { | ||
665 | range: 58..95, | ||
666 | kind: Runnable { | ||
667 | debug: false, | ||
668 | runnable: Runnable { | ||
669 | nav: NavigationTarget { | ||
670 | file_id: FileId( | ||
671 | 0, | ||
672 | ), | ||
673 | full_range: 58..95, | ||
674 | focus_range: 61..65, | ||
675 | name: "main", | ||
676 | kind: Function, | ||
677 | }, | ||
678 | kind: Bin, | ||
679 | cfg: None, | ||
680 | }, | ||
681 | }, | ||
682 | }, | ||
683 | Annotation { | ||
684 | range: 0..12, | ||
685 | kind: HasImpls { | ||
686 | position: FilePosition { | ||
687 | file_id: FileId( | ||
688 | 0, | ||
689 | ), | ||
690 | offset: 7, | ||
691 | }, | ||
692 | data: Some( | ||
693 | [ | ||
694 | NavigationTarget { | ||
695 | file_id: FileId( | ||
696 | 0, | ||
697 | ), | ||
698 | full_range: 14..56, | ||
699 | focus_range: 19..23, | ||
700 | name: "impl", | ||
701 | kind: Impl, | ||
702 | }, | ||
703 | ], | ||
704 | ), | ||
705 | }, | ||
706 | }, | ||
707 | Annotation { | ||
708 | range: 0..12, | ||
709 | kind: HasReferences { | ||
710 | position: FilePosition { | ||
711 | file_id: FileId( | ||
712 | 0, | ||
713 | ), | ||
714 | offset: 7, | ||
715 | }, | ||
716 | data: Some( | ||
717 | [ | ||
718 | FileRange { | ||
719 | file_id: FileId( | ||
720 | 0, | ||
721 | ), | ||
722 | range: 19..23, | ||
723 | }, | ||
724 | FileRange { | ||
725 | file_id: FileId( | ||
726 | 0, | ||
727 | ), | ||
728 | range: 74..78, | ||
729 | }, | ||
730 | ], | ||
731 | ), | ||
732 | }, | ||
733 | }, | ||
734 | Annotation { | ||
735 | range: 33..44, | ||
736 | kind: HasReferences { | ||
737 | position: FilePosition { | ||
738 | file_id: FileId( | ||
739 | 0, | ||
740 | ), | ||
741 | offset: 33, | ||
742 | }, | ||
743 | data: Some( | ||
744 | [ | ||
745 | FileRange { | ||
746 | file_id: FileId( | ||
747 | 0, | ||
748 | ), | ||
749 | range: 79..90, | ||
750 | }, | ||
751 | ], | ||
752 | ), | ||
753 | }, | ||
754 | }, | ||
755 | Annotation { | ||
756 | range: 61..65, | ||
757 | kind: HasReferences { | ||
758 | position: FilePosition { | ||
759 | file_id: FileId( | ||
760 | 0, | ||
761 | ), | ||
762 | offset: 61, | ||
763 | }, | ||
764 | data: Some( | ||
765 | [], | ||
766 | ), | ||
767 | }, | ||
768 | }, | ||
769 | ] | ||
770 | "#]], | ||
771 | ); | ||
772 | } | ||
773 | |||
774 | #[test] | ||
775 | fn test_annotations() { | ||
776 | check( | ||
777 | r#" | ||
778 | fn main() {} | ||
779 | |||
780 | mod tests { | ||
781 | #[test] | ||
782 | fn my_cool_test() {} | ||
783 | } | ||
784 | "#, | ||
785 | expect![[r#" | ||
786 | [ | ||
787 | Annotation { | ||
788 | range: 0..12, | ||
789 | kind: Runnable { | ||
790 | debug: true, | ||
791 | runnable: Runnable { | ||
792 | nav: NavigationTarget { | ||
793 | file_id: FileId( | ||
794 | 0, | ||
795 | ), | ||
796 | full_range: 0..12, | ||
797 | focus_range: 3..7, | ||
798 | name: "main", | ||
799 | kind: Function, | ||
800 | }, | ||
801 | kind: Bin, | ||
802 | cfg: None, | ||
803 | }, | ||
804 | }, | ||
805 | }, | ||
806 | Annotation { | ||
807 | range: 0..12, | ||
808 | kind: Runnable { | ||
809 | debug: false, | ||
810 | runnable: Runnable { | ||
811 | nav: NavigationTarget { | ||
812 | file_id: FileId( | ||
813 | 0, | ||
814 | ), | ||
815 | full_range: 0..12, | ||
816 | focus_range: 3..7, | ||
817 | name: "main", | ||
818 | kind: Function, | ||
819 | }, | ||
820 | kind: Bin, | ||
821 | cfg: None, | ||
822 | }, | ||
823 | }, | ||
824 | }, | ||
825 | Annotation { | ||
826 | range: 14..64, | ||
827 | kind: Runnable { | ||
828 | debug: true, | ||
829 | runnable: Runnable { | ||
830 | nav: NavigationTarget { | ||
831 | file_id: FileId( | ||
832 | 0, | ||
833 | ), | ||
834 | full_range: 14..64, | ||
835 | focus_range: 18..23, | ||
836 | name: "tests", | ||
837 | kind: Module, | ||
838 | }, | ||
839 | kind: TestMod { | ||
840 | path: "tests", | ||
841 | }, | ||
842 | cfg: None, | ||
843 | }, | ||
844 | }, | ||
845 | }, | ||
846 | Annotation { | ||
847 | range: 14..64, | ||
848 | kind: Runnable { | ||
849 | debug: false, | ||
850 | runnable: Runnable { | ||
851 | nav: NavigationTarget { | ||
852 | file_id: FileId( | ||
853 | 0, | ||
854 | ), | ||
855 | full_range: 14..64, | ||
856 | focus_range: 18..23, | ||
857 | name: "tests", | ||
858 | kind: Module, | ||
859 | }, | ||
860 | kind: TestMod { | ||
861 | path: "tests", | ||
862 | }, | ||
863 | cfg: None, | ||
864 | }, | ||
865 | }, | ||
866 | }, | ||
867 | Annotation { | ||
868 | range: 30..62, | ||
869 | kind: Runnable { | ||
870 | debug: true, | ||
871 | runnable: Runnable { | ||
872 | nav: NavigationTarget { | ||
873 | file_id: FileId( | ||
874 | 0, | ||
875 | ), | ||
876 | full_range: 30..62, | ||
877 | focus_range: 45..57, | ||
878 | name: "my_cool_test", | ||
879 | kind: Function, | ||
880 | }, | ||
881 | kind: Test { | ||
882 | test_id: Path( | ||
883 | "tests::my_cool_test", | ||
884 | ), | ||
885 | attr: TestAttr { | ||
886 | ignore: false, | ||
887 | }, | ||
888 | }, | ||
889 | cfg: None, | ||
890 | }, | ||
891 | }, | ||
892 | }, | ||
893 | Annotation { | ||
894 | range: 30..62, | ||
895 | kind: Runnable { | ||
896 | debug: false, | ||
897 | runnable: Runnable { | ||
898 | nav: NavigationTarget { | ||
899 | file_id: FileId( | ||
900 | 0, | ||
901 | ), | ||
902 | full_range: 30..62, | ||
903 | focus_range: 45..57, | ||
904 | name: "my_cool_test", | ||
905 | kind: Function, | ||
906 | }, | ||
907 | kind: Test { | ||
908 | test_id: Path( | ||
909 | "tests::my_cool_test", | ||
910 | ), | ||
911 | attr: TestAttr { | ||
912 | ignore: false, | ||
913 | }, | ||
914 | }, | ||
915 | cfg: None, | ||
916 | }, | ||
917 | }, | ||
918 | }, | ||
919 | Annotation { | ||
920 | range: 3..7, | ||
921 | kind: HasReferences { | ||
922 | position: FilePosition { | ||
923 | file_id: FileId( | ||
924 | 0, | ||
925 | ), | ||
926 | offset: 3, | ||
927 | }, | ||
928 | data: Some( | ||
929 | [], | ||
930 | ), | ||
931 | }, | ||
932 | }, | ||
933 | ] | ||
934 | "#]], | ||
935 | ); | ||
936 | } | ||
937 | } | ||
diff --git a/crates/ide/src/lib.rs b/crates/ide/src/lib.rs index 592b12925..89e7bef7d 100644 --- a/crates/ide/src/lib.rs +++ b/crates/ide/src/lib.rs | |||
@@ -22,6 +22,7 @@ mod markup; | |||
22 | mod prime_caches; | 22 | mod prime_caches; |
23 | mod display; | 23 | mod display; |
24 | 24 | ||
25 | mod annotations; | ||
25 | mod call_hierarchy; | 26 | mod call_hierarchy; |
26 | mod diagnostics; | 27 | mod diagnostics; |
27 | mod expand_macro; | 28 | mod expand_macro; |
@@ -63,6 +64,7 @@ use syntax::SourceFile; | |||
63 | use crate::display::ToNav; | 64 | use crate::display::ToNav; |
64 | 65 | ||
65 | pub use crate::{ | 66 | pub use crate::{ |
67 | annotations::{Annotation, AnnotationConfig, AnnotationKind}, | ||
66 | call_hierarchy::CallItem, | 68 | call_hierarchy::CallItem, |
67 | diagnostics::{Diagnostic, DiagnosticsConfig, Fix, Severity}, | 69 | diagnostics::{Diagnostic, DiagnosticsConfig, Fix, Severity}, |
68 | display::navigation_target::NavigationTarget, | 70 | display::navigation_target::NavigationTarget, |
@@ -555,6 +557,18 @@ impl Analysis { | |||
555 | }) | 557 | }) |
556 | } | 558 | } |
557 | 559 | ||
560 | pub fn annotations( | ||
561 | &self, | ||
562 | file_id: FileId, | ||
563 | config: AnnotationConfig, | ||
564 | ) -> Cancelable<Vec<Annotation>> { | ||
565 | self.with_db(|db| annotations::annotations(db, file_id, config)) | ||
566 | } | ||
567 | |||
568 | pub fn resolve_annotation(&self, annotation: Annotation) -> Cancelable<Annotation> { | ||
569 | self.with_db(|db| annotations::resolve_annotation(db, annotation)) | ||
570 | } | ||
571 | |||
558 | /// Performs an operation on that may be Canceled. | 572 | /// Performs an operation on that may be Canceled. |
559 | fn with_db<F, T>(&self, f: F) -> Cancelable<T> | 573 | fn with_db<F, T>(&self, f: F) -> Cancelable<T> |
560 | where | 574 | where |
diff --git a/crates/ide/src/references.rs b/crates/ide/src/references.rs index 55a44ff01..a83b82f1b 100644 --- a/crates/ide/src/references.rs +++ b/crates/ide/src/references.rs | |||
@@ -1197,4 +1197,39 @@ impl Foo { | |||
1197 | "#]], | 1197 | "#]], |
1198 | ); | 1198 | ); |
1199 | } | 1199 | } |
1200 | |||
1201 | #[test] | ||
1202 | fn test_attr_differs_from_fn_with_same_name() { | ||
1203 | check( | ||
1204 | r#" | ||
1205 | #[test] | ||
1206 | fn test$0() { | ||
1207 | test(); | ||
1208 | } | ||
1209 | "#, | ||
1210 | expect![[r#" | ||
1211 | test Function FileId(0) 0..33 11..15 | ||
1212 | |||
1213 | FileId(0) 24..28 | ||
1214 | "#]], | ||
1215 | ); | ||
1216 | } | ||
1217 | |||
1218 | #[test] | ||
1219 | fn test_attr_matches_proc_macro_fn() { | ||
1220 | check( | ||
1221 | r#" | ||
1222 | #[proc_macro_attribute] | ||
1223 | fn my_proc_macro() {} | ||
1224 | |||
1225 | #[my_proc_macro$0] | ||
1226 | fn test() {} | ||
1227 | "#, | ||
1228 | expect![[r#" | ||
1229 | my_proc_macro Function FileId(0) 0..45 27..40 | ||
1230 | |||
1231 | FileId(0) 49..62 | ||
1232 | "#]], | ||
1233 | ); | ||
1234 | } | ||
1200 | } | 1235 | } |
diff --git a/crates/ide/src/references/rename.rs b/crates/ide/src/references/rename.rs index b04214291..08f16b54d 100644 --- a/crates/ide/src/references/rename.rs +++ b/crates/ide/src/references/rename.rs | |||
@@ -75,8 +75,7 @@ pub(crate) fn rename_with_semantics( | |||
75 | let source_file = sema.parse(position.file_id); | 75 | let source_file = sema.parse(position.file_id); |
76 | let syntax = source_file.syntax(); | 76 | let syntax = source_file.syntax(); |
77 | 77 | ||
78 | let def = find_definition(sema, syntax, position) | 78 | let def = find_definition(sema, syntax, position)?; |
79 | .ok_or_else(|| format_err!("No references found at position"))?; | ||
80 | match def { | 79 | match def { |
81 | Definition::ModuleDef(ModuleDef::Module(module)) => rename_mod(&sema, module, new_name), | 80 | Definition::ModuleDef(ModuleDef::Module(module)) => rename_mod(&sema, module, new_name), |
82 | def => rename_reference(sema, def, new_name), | 81 | def => rename_reference(sema, def, new_name), |
@@ -149,18 +148,30 @@ fn find_definition( | |||
149 | sema: &Semantics<RootDatabase>, | 148 | sema: &Semantics<RootDatabase>, |
150 | syntax: &SyntaxNode, | 149 | syntax: &SyntaxNode, |
151 | position: FilePosition, | 150 | position: FilePosition, |
152 | ) -> Option<Definition> { | 151 | ) -> RenameResult<Definition> { |
153 | let def = match find_name_like(sema, syntax, position)? { | 152 | match find_name_like(sema, syntax, position) |
154 | NameLike::Name(name) => NameClass::classify(sema, &name)?.referenced_or_defined(sema.db), | 153 | .ok_or_else(|| format_err!("No references found at position"))? |
155 | NameLike::NameRef(name_ref) => NameRefClass::classify(sema, &name_ref)?.referenced(sema.db), | 154 | { |
155 | // renaming aliases would rename the item being aliased as the HIR doesn't track aliases yet | ||
156 | NameLike::Name(name) | ||
157 | if name.syntax().parent().map_or(false, |it| ast::Rename::can_cast(it.kind())) => | ||
158 | { | ||
159 | bail!("Renaming aliases is currently unsupported") | ||
160 | } | ||
161 | NameLike::Name(name) => { | ||
162 | NameClass::classify(sema, &name).map(|class| class.referenced_or_defined(sema.db)) | ||
163 | } | ||
164 | NameLike::NameRef(name_ref) => { | ||
165 | NameRefClass::classify(sema, &name_ref).map(|class| class.referenced(sema.db)) | ||
166 | } | ||
156 | NameLike::Lifetime(lifetime) => NameRefClass::classify_lifetime(sema, &lifetime) | 167 | NameLike::Lifetime(lifetime) => NameRefClass::classify_lifetime(sema, &lifetime) |
157 | .map(|class| NameRefClass::referenced(class, sema.db)) | 168 | .map(|class| NameRefClass::referenced(class, sema.db)) |
158 | .or_else(|| { | 169 | .or_else(|| { |
159 | NameClass::classify_lifetime(sema, &lifetime) | 170 | NameClass::classify_lifetime(sema, &lifetime) |
160 | .map(|it| it.referenced_or_defined(sema.db)) | 171 | .map(|it| it.referenced_or_defined(sema.db)) |
161 | })?, | 172 | }), |
162 | }; | 173 | } |
163 | Some(def) | 174 | .ok_or_else(|| format_err!("No references found at position")) |
164 | } | 175 | } |
165 | 176 | ||
166 | fn source_edit_from_references( | 177 | fn source_edit_from_references( |
@@ -173,21 +184,40 @@ fn source_edit_from_references( | |||
173 | let mut edit = TextEdit::builder(); | 184 | let mut edit = TextEdit::builder(); |
174 | for reference in references { | 185 | for reference in references { |
175 | let (range, replacement) = match &reference.name { | 186 | let (range, replacement) = match &reference.name { |
176 | NameLike::Name(_) => (None, format!("{}", new_name)), | 187 | // if the ranges differ then the node is inside a macro call, we can't really attempt |
177 | NameLike::NameRef(name_ref) => source_edit_from_name_ref(name_ref, new_name, def), | 188 | // to make special rewrites like shorthand syntax and such, so just rename the node in |
178 | NameLike::Lifetime(_) => (None, format!("{}", new_name)), | 189 | // the macro input |
179 | }; | 190 | NameLike::NameRef(name_ref) if name_ref.syntax().text_range() == reference.range => { |
180 | // FIXME: Some(range) will be incorrect when we are inside macros | 191 | source_edit_from_name_ref(name_ref, new_name, def) |
181 | edit.replace(range.unwrap_or(reference.range), replacement); | 192 | } |
193 | NameLike::Name(name) if name.syntax().text_range() == reference.range => { | ||
194 | source_edit_from_name(name, new_name) | ||
195 | } | ||
196 | _ => None, | ||
197 | } | ||
198 | .unwrap_or_else(|| (reference.range, new_name.to_string())); | ||
199 | edit.replace(range, replacement); | ||
182 | } | 200 | } |
183 | (file_id, edit.finish()) | 201 | (file_id, edit.finish()) |
184 | } | 202 | } |
185 | 203 | ||
204 | fn source_edit_from_name(name: &ast::Name, new_name: &str) -> Option<(TextRange, String)> { | ||
205 | if let Some(_) = ast::RecordPatField::for_field_name(name) { | ||
206 | if let Some(ident_pat) = name.syntax().parent().and_then(ast::IdentPat::cast) { | ||
207 | return Some(( | ||
208 | TextRange::empty(ident_pat.syntax().text_range().start()), | ||
209 | format!("{}: ", new_name), | ||
210 | )); | ||
211 | } | ||
212 | } | ||
213 | None | ||
214 | } | ||
215 | |||
186 | fn source_edit_from_name_ref( | 216 | fn source_edit_from_name_ref( |
187 | name_ref: &ast::NameRef, | 217 | name_ref: &ast::NameRef, |
188 | new_name: &str, | 218 | new_name: &str, |
189 | def: Definition, | 219 | def: Definition, |
190 | ) -> (Option<TextRange>, String) { | 220 | ) -> Option<(TextRange, String)> { |
191 | if let Some(record_field) = ast::RecordExprField::for_name_ref(name_ref) { | 221 | if let Some(record_field) = ast::RecordExprField::for_name_ref(name_ref) { |
192 | let rcf_name_ref = record_field.name_ref(); | 222 | let rcf_name_ref = record_field.name_ref(); |
193 | let rcf_expr = record_field.expr(); | 223 | let rcf_expr = record_field.expr(); |
@@ -197,45 +227,40 @@ fn source_edit_from_name_ref( | |||
197 | if field_name == *name_ref { | 227 | if field_name == *name_ref { |
198 | if init.text() == new_name { | 228 | if init.text() == new_name { |
199 | mark::hit!(test_rename_field_put_init_shorthand); | 229 | mark::hit!(test_rename_field_put_init_shorthand); |
200 | // same names, we can use a shorthand here instead | 230 | // same names, we can use a shorthand here instead. |
201 | // we do not want to erase attributes hence this range start | 231 | // we do not want to erase attributes hence this range start |
202 | let s = field_name.syntax().text_range().start(); | 232 | let s = field_name.syntax().text_range().start(); |
203 | let e = record_field.syntax().text_range().end(); | 233 | let e = record_field.syntax().text_range().end(); |
204 | return (Some(TextRange::new(s, e)), format!("{}", new_name)); | 234 | return Some((TextRange::new(s, e), new_name.to_owned())); |
205 | } | 235 | } |
206 | } else if init == *name_ref { | 236 | } else if init == *name_ref { |
207 | if field_name.text() == new_name { | 237 | if field_name.text() == new_name { |
208 | mark::hit!(test_rename_local_put_init_shorthand); | 238 | mark::hit!(test_rename_local_put_init_shorthand); |
209 | // same names, we can use a shorthand here instead | 239 | // same names, we can use a shorthand here instead. |
210 | // we do not want to erase attributes hence this range start | 240 | // we do not want to erase attributes hence this range start |
211 | let s = field_name.syntax().text_range().start(); | 241 | let s = field_name.syntax().text_range().start(); |
212 | let e = record_field.syntax().text_range().end(); | 242 | let e = record_field.syntax().text_range().end(); |
213 | return (Some(TextRange::new(s, e)), format!("{}", new_name)); | 243 | return Some((TextRange::new(s, e), new_name.to_owned())); |
214 | } | 244 | } |
215 | } | 245 | } |
246 | None | ||
216 | } | 247 | } |
217 | // init shorthand | 248 | // init shorthand |
218 | (None, Some(_)) => { | 249 | // FIXME: instead of splitting the shorthand, recursively trigger a rename of the |
219 | // FIXME: instead of splitting the shorthand, recursively trigger a rename of the | 250 | // other name https://github.com/rust-analyzer/rust-analyzer/issues/6547 |
220 | // other name https://github.com/rust-analyzer/rust-analyzer/issues/6547 | 251 | (None, Some(_)) if matches!(def, Definition::Field(_)) => { |
221 | match def { | 252 | mark::hit!(test_rename_field_in_field_shorthand); |
222 | Definition::Field(_) => { | 253 | let s = name_ref.syntax().text_range().start(); |
223 | mark::hit!(test_rename_field_in_field_shorthand); | 254 | Some((TextRange::empty(s), format!("{}: ", new_name))) |
224 | let s = name_ref.syntax().text_range().start(); | ||
225 | return (Some(TextRange::empty(s)), format!("{}: ", new_name)); | ||
226 | } | ||
227 | Definition::Local(_) => { | ||
228 | mark::hit!(test_rename_local_in_field_shorthand); | ||
229 | let s = name_ref.syntax().text_range().end(); | ||
230 | return (Some(TextRange::empty(s)), format!(": {}", new_name)); | ||
231 | } | ||
232 | _ => {} | ||
233 | } | ||
234 | } | 255 | } |
235 | _ => {} | 256 | (None, Some(_)) if matches!(def, Definition::Local(_)) => { |
257 | mark::hit!(test_rename_local_in_field_shorthand); | ||
258 | let s = name_ref.syntax().text_range().end(); | ||
259 | Some((TextRange::empty(s), format!(": {}", new_name))) | ||
260 | } | ||
261 | _ => None, | ||
236 | } | 262 | } |
237 | } | 263 | } else if let Some(record_field) = ast::RecordPatField::for_field_name_ref(name_ref) { |
238 | if let Some(record_field) = ast::RecordPatField::for_field_name_ref(name_ref) { | ||
239 | let rcf_name_ref = record_field.name_ref(); | 264 | let rcf_name_ref = record_field.name_ref(); |
240 | let rcf_pat = record_field.pat(); | 265 | let rcf_pat = record_field.pat(); |
241 | match (rcf_name_ref, rcf_pat) { | 266 | match (rcf_name_ref, rcf_pat) { |
@@ -244,17 +269,20 @@ fn source_edit_from_name_ref( | |||
244 | // field name is being renamed | 269 | // field name is being renamed |
245 | if pat.name().map_or(false, |it| it.text() == new_name) { | 270 | if pat.name().map_or(false, |it| it.text() == new_name) { |
246 | mark::hit!(test_rename_field_put_init_shorthand_pat); | 271 | mark::hit!(test_rename_field_put_init_shorthand_pat); |
247 | // same names, we can use a shorthand here instead | 272 | // same names, we can use a shorthand here instead/ |
248 | // we do not want to erase attributes hence this range start | 273 | // we do not want to erase attributes hence this range start |
249 | let s = field_name.syntax().text_range().start(); | 274 | let s = field_name.syntax().text_range().start(); |
250 | let e = record_field.syntax().text_range().end(); | 275 | let e = record_field.syntax().text_range().end(); |
251 | return (Some(TextRange::new(s, e)), format!("{}", new_name)); | 276 | Some((TextRange::new(s, e), pat.to_string())) |
277 | } else { | ||
278 | None | ||
252 | } | 279 | } |
253 | } | 280 | } |
254 | _ => {} | 281 | _ => None, |
255 | } | 282 | } |
283 | } else { | ||
284 | None | ||
256 | } | 285 | } |
257 | (None, format!("{}", new_name)) | ||
258 | } | 286 | } |
259 | 287 | ||
260 | fn rename_mod( | 288 | fn rename_mod( |
@@ -1477,7 +1505,7 @@ fn foo(i: i32) -> Foo { | |||
1477 | } | 1505 | } |
1478 | 1506 | ||
1479 | #[test] | 1507 | #[test] |
1480 | fn test_struct_field_destructure_into_shorthand() { | 1508 | fn test_struct_field_pat_into_shorthand() { |
1481 | mark::check!(test_rename_field_put_init_shorthand_pat); | 1509 | mark::check!(test_rename_field_put_init_shorthand_pat); |
1482 | check( | 1510 | check( |
1483 | "baz", | 1511 | "baz", |
@@ -1485,16 +1513,16 @@ fn foo(i: i32) -> Foo { | |||
1485 | struct Foo { i$0: i32 } | 1513 | struct Foo { i$0: i32 } |
1486 | 1514 | ||
1487 | fn foo(foo: Foo) { | 1515 | fn foo(foo: Foo) { |
1488 | let Foo { i: baz } = foo; | 1516 | let Foo { i: ref baz @ qux } = foo; |
1489 | let _ = baz; | 1517 | let _ = qux; |
1490 | } | 1518 | } |
1491 | "#, | 1519 | "#, |
1492 | r#" | 1520 | r#" |
1493 | struct Foo { baz: i32 } | 1521 | struct Foo { baz: i32 } |
1494 | 1522 | ||
1495 | fn foo(foo: Foo) { | 1523 | fn foo(foo: Foo) { |
1496 | let Foo { baz } = foo; | 1524 | let Foo { ref baz @ qux } = foo; |
1497 | let _ = baz; | 1525 | let _ = qux; |
1498 | } | 1526 | } |
1499 | "#, | 1527 | "#, |
1500 | ); | 1528 | ); |
@@ -1568,6 +1596,27 @@ fn foo(Foo { i: bar }: foo) -> i32 { | |||
1568 | } | 1596 | } |
1569 | 1597 | ||
1570 | #[test] | 1598 | #[test] |
1599 | fn test_struct_field_complex_ident_pat() { | ||
1600 | check( | ||
1601 | "baz", | ||
1602 | r#" | ||
1603 | struct Foo { i$0: i32 } | ||
1604 | |||
1605 | fn foo(foo: Foo) { | ||
1606 | let Foo { ref i } = foo; | ||
1607 | } | ||
1608 | "#, | ||
1609 | r#" | ||
1610 | struct Foo { baz: i32 } | ||
1611 | |||
1612 | fn foo(foo: Foo) { | ||
1613 | let Foo { baz: ref i } = foo; | ||
1614 | } | ||
1615 | "#, | ||
1616 | ); | ||
1617 | } | ||
1618 | |||
1619 | #[test] | ||
1571 | fn test_rename_lifetimes() { | 1620 | fn test_rename_lifetimes() { |
1572 | mark::check!(rename_lifetime); | 1621 | mark::check!(rename_lifetime); |
1573 | check( | 1622 | check( |
@@ -1674,4 +1723,38 @@ impl Foo { | |||
1674 | "#, | 1723 | "#, |
1675 | ) | 1724 | ) |
1676 | } | 1725 | } |
1726 | |||
1727 | #[test] | ||
1728 | fn test_rename_field_in_pat_in_macro_doesnt_shorthand() { | ||
1729 | // ideally we would be able to make this emit a short hand, but I doubt this is easily possible | ||
1730 | check( | ||
1731 | "baz", | ||
1732 | r#" | ||
1733 | macro_rules! foo { | ||
1734 | ($pattern:pat) => { | ||
1735 | let $pattern = loop {}; | ||
1736 | }; | ||
1737 | } | ||
1738 | struct Foo { | ||
1739 | bar$0: u32, | ||
1740 | } | ||
1741 | fn foo() { | ||
1742 | foo!(Foo { bar: baz }); | ||
1743 | } | ||
1744 | "#, | ||
1745 | r#" | ||
1746 | macro_rules! foo { | ||
1747 | ($pattern:pat) => { | ||
1748 | let $pattern = loop {}; | ||
1749 | }; | ||
1750 | } | ||
1751 | struct Foo { | ||
1752 | baz: u32, | ||
1753 | } | ||
1754 | fn foo() { | ||
1755 | foo!(Foo { baz: baz }); | ||
1756 | } | ||
1757 | "#, | ||
1758 | ) | ||
1759 | } | ||
1677 | } | 1760 | } |
diff --git a/crates/ide_db/src/defs.rs b/crates/ide_db/src/defs.rs index a8091dbee..ff612b7d0 100644 --- a/crates/ide_db/src/defs.rs +++ b/crates/ide_db/src/defs.rs | |||
@@ -6,8 +6,8 @@ | |||
6 | // FIXME: this badly needs rename/rewrite (matklad, 2020-02-06). | 6 | // FIXME: this badly needs rename/rewrite (matklad, 2020-02-06). |
7 | 7 | ||
8 | use hir::{ | 8 | use hir::{ |
9 | db::HirDatabase, Crate, Field, GenericParam, HasVisibility, Impl, Label, Local, MacroDef, | 9 | db::HirDatabase, Crate, Field, GenericParam, HasAttrs, HasVisibility, Impl, Label, Local, |
10 | Module, ModuleDef, Name, PathResolution, Semantics, Visibility, | 10 | MacroDef, Module, ModuleDef, Name, PathResolution, Semantics, Visibility, |
11 | }; | 11 | }; |
12 | use syntax::{ | 12 | use syntax::{ |
13 | ast::{self, AstNode, PathSegmentKind}, | 13 | ast::{self, AstNode, PathSegmentKind}, |
@@ -366,7 +366,15 @@ impl NameRefClass { | |||
366 | 366 | ||
367 | if let Some(path) = name_ref.syntax().ancestors().find_map(ast::Path::cast) { | 367 | if let Some(path) = name_ref.syntax().ancestors().find_map(ast::Path::cast) { |
368 | if let Some(resolved) = sema.resolve_path(&path) { | 368 | if let Some(resolved) = sema.resolve_path(&path) { |
369 | return Some(NameRefClass::Definition(resolved.into())); | 369 | if path.syntax().parent().and_then(ast::Attr::cast).is_some() { |
370 | if let PathResolution::Def(ModuleDef::Function(func)) = resolved { | ||
371 | if func.attrs(sema.db).by_key("proc_macro_attribute").exists() { | ||
372 | return Some(NameRefClass::Definition(resolved.into())); | ||
373 | } | ||
374 | } | ||
375 | } else { | ||
376 | return Some(NameRefClass::Definition(resolved.into())); | ||
377 | } | ||
370 | } | 378 | } |
371 | } | 379 | } |
372 | 380 | ||
diff --git a/crates/project_model/src/cargo_workspace.rs b/crates/project_model/src/cargo_workspace.rs index f47898b9b..1d8d34a0b 100644 --- a/crates/project_model/src/cargo_workspace.rs +++ b/crates/project_model/src/cargo_workspace.rs | |||
@@ -44,6 +44,15 @@ impl ops::Index<Target> for CargoWorkspace { | |||
44 | } | 44 | } |
45 | } | 45 | } |
46 | 46 | ||
47 | /// Describes how to set the rustc source directory. | ||
48 | #[derive(Clone, Debug, PartialEq, Eq)] | ||
49 | pub enum RustcSource { | ||
50 | /// Explicit path for the rustc source directory. | ||
51 | Path(AbsPathBuf), | ||
52 | /// Try to automatically detect where the rustc source directory is. | ||
53 | Discover, | ||
54 | } | ||
55 | |||
47 | #[derive(Default, Clone, Debug, PartialEq, Eq)] | 56 | #[derive(Default, Clone, Debug, PartialEq, Eq)] |
48 | pub struct CargoConfig { | 57 | pub struct CargoConfig { |
49 | /// Do not activate the `default` feature. | 58 | /// Do not activate the `default` feature. |
@@ -64,7 +73,7 @@ pub struct CargoConfig { | |||
64 | pub no_sysroot: bool, | 73 | pub no_sysroot: bool, |
65 | 74 | ||
66 | /// rustc private crate source | 75 | /// rustc private crate source |
67 | pub rustc_source: Option<AbsPathBuf>, | 76 | pub rustc_source: Option<RustcSource>, |
68 | } | 77 | } |
69 | 78 | ||
70 | pub type Package = Idx<PackageData>; | 79 | pub type Package = Idx<PackageData>; |
diff --git a/crates/project_model/src/lib.rs b/crates/project_model/src/lib.rs index d712095a6..a5b35ed95 100644 --- a/crates/project_model/src/lib.rs +++ b/crates/project_model/src/lib.rs | |||
@@ -21,8 +21,8 @@ use rustc_hash::FxHashSet; | |||
21 | pub use crate::{ | 21 | pub use crate::{ |
22 | build_data::{BuildDataCollector, BuildDataResult}, | 22 | build_data::{BuildDataCollector, BuildDataResult}, |
23 | cargo_workspace::{ | 23 | cargo_workspace::{ |
24 | CargoConfig, CargoWorkspace, Package, PackageData, PackageDependency, Target, TargetData, | 24 | CargoConfig, CargoWorkspace, Package, PackageData, PackageDependency, RustcSource, Target, |
25 | TargetKind, | 25 | TargetData, TargetKind, |
26 | }, | 26 | }, |
27 | project_json::{ProjectJson, ProjectJsonData}, | 27 | project_json::{ProjectJson, ProjectJsonData}, |
28 | sysroot::Sysroot, | 28 | sysroot::Sysroot, |
diff --git a/crates/project_model/src/sysroot.rs b/crates/project_model/src/sysroot.rs index ff44dae4a..3b0ff506d 100644 --- a/crates/project_model/src/sysroot.rs +++ b/crates/project_model/src/sysroot.rs | |||
@@ -51,11 +51,18 @@ impl Sysroot { | |||
51 | pub fn discover(cargo_toml: &AbsPath) -> Result<Sysroot> { | 51 | pub fn discover(cargo_toml: &AbsPath) -> Result<Sysroot> { |
52 | log::debug!("Discovering sysroot for {}", cargo_toml.display()); | 52 | log::debug!("Discovering sysroot for {}", cargo_toml.display()); |
53 | let current_dir = cargo_toml.parent().unwrap(); | 53 | let current_dir = cargo_toml.parent().unwrap(); |
54 | let sysroot_src_dir = discover_sysroot_src_dir(current_dir)?; | 54 | let sysroot_dir = discover_sysroot_dir(current_dir)?; |
55 | let sysroot_src_dir = discover_sysroot_src_dir(&sysroot_dir, current_dir)?; | ||
55 | let res = Sysroot::load(&sysroot_src_dir)?; | 56 | let res = Sysroot::load(&sysroot_src_dir)?; |
56 | Ok(res) | 57 | Ok(res) |
57 | } | 58 | } |
58 | 59 | ||
60 | pub fn discover_rustc(cargo_toml: &AbsPath) -> Option<AbsPathBuf> { | ||
61 | log::debug!("Discovering rustc source for {}", cargo_toml.display()); | ||
62 | let current_dir = cargo_toml.parent().unwrap(); | ||
63 | discover_sysroot_dir(current_dir).ok().and_then(|sysroot_dir| get_rustc_src(&sysroot_dir)) | ||
64 | } | ||
65 | |||
59 | pub fn load(sysroot_src_dir: &AbsPath) -> Result<Sysroot> { | 66 | pub fn load(sysroot_src_dir: &AbsPath) -> Result<Sysroot> { |
60 | let mut sysroot = Sysroot { crates: Arena::default() }; | 67 | let mut sysroot = Sysroot { crates: Arena::default() }; |
61 | 68 | ||
@@ -110,7 +117,18 @@ impl Sysroot { | |||
110 | } | 117 | } |
111 | } | 118 | } |
112 | 119 | ||
113 | fn discover_sysroot_src_dir(current_dir: &AbsPath) -> Result<AbsPathBuf> { | 120 | fn discover_sysroot_dir(current_dir: &AbsPath) -> Result<AbsPathBuf> { |
121 | let mut rustc = Command::new(toolchain::rustc()); | ||
122 | rustc.current_dir(current_dir).args(&["--print", "sysroot"]); | ||
123 | log::debug!("Discovering sysroot by {:?}", rustc); | ||
124 | let stdout = utf8_stdout(rustc)?; | ||
125 | Ok(AbsPathBuf::assert(PathBuf::from(stdout))) | ||
126 | } | ||
127 | |||
128 | fn discover_sysroot_src_dir( | ||
129 | sysroot_path: &AbsPathBuf, | ||
130 | current_dir: &AbsPath, | ||
131 | ) -> Result<AbsPathBuf> { | ||
114 | if let Ok(path) = env::var("RUST_SRC_PATH") { | 132 | if let Ok(path) = env::var("RUST_SRC_PATH") { |
115 | let path = AbsPathBuf::try_from(path.as_str()) | 133 | let path = AbsPathBuf::try_from(path.as_str()) |
116 | .map_err(|path| format_err!("RUST_SRC_PATH must be absolute: {}", path.display()))?; | 134 | .map_err(|path| format_err!("RUST_SRC_PATH must be absolute: {}", path.display()))?; |
@@ -122,14 +140,6 @@ fn discover_sysroot_src_dir(current_dir: &AbsPath) -> Result<AbsPathBuf> { | |||
122 | log::debug!("RUST_SRC_PATH is set, but is invalid (no core: {:?}), ignoring", core); | 140 | log::debug!("RUST_SRC_PATH is set, but is invalid (no core: {:?}), ignoring", core); |
123 | } | 141 | } |
124 | 142 | ||
125 | let sysroot_path = { | ||
126 | let mut rustc = Command::new(toolchain::rustc()); | ||
127 | rustc.current_dir(current_dir).args(&["--print", "sysroot"]); | ||
128 | log::debug!("Discovering sysroot by {:?}", rustc); | ||
129 | let stdout = utf8_stdout(rustc)?; | ||
130 | AbsPathBuf::assert(PathBuf::from(stdout)) | ||
131 | }; | ||
132 | |||
133 | get_rust_src(&sysroot_path) | 143 | get_rust_src(&sysroot_path) |
134 | .or_else(|| { | 144 | .or_else(|| { |
135 | let mut rustup = Command::new(toolchain::rustup()); | 145 | let mut rustup = Command::new(toolchain::rustup()); |
@@ -149,6 +159,16 @@ try installing the Rust source the same way you installed rustc", | |||
149 | }) | 159 | }) |
150 | } | 160 | } |
151 | 161 | ||
162 | fn get_rustc_src(sysroot_path: &AbsPath) -> Option<AbsPathBuf> { | ||
163 | let rustc_src = sysroot_path.join("lib/rustlib/rustc-src/rust/compiler/rustc/Cargo.toml"); | ||
164 | log::debug!("Checking for rustc source code: {}", rustc_src.display()); | ||
165 | if rustc_src.exists() { | ||
166 | Some(rustc_src) | ||
167 | } else { | ||
168 | None | ||
169 | } | ||
170 | } | ||
171 | |||
152 | fn get_rust_src(sysroot_path: &AbsPath) -> Option<AbsPathBuf> { | 172 | fn get_rust_src(sysroot_path: &AbsPath) -> Option<AbsPathBuf> { |
153 | // Try the new path first since the old one still exists. | 173 | // Try the new path first since the old one still exists. |
154 | let rust_src = sysroot_path.join("lib/rustlib/src/rust"); | 174 | let rust_src = sysroot_path.join("lib/rustlib/src/rust"); |
diff --git a/crates/project_model/src/workspace.rs b/crates/project_model/src/workspace.rs index c30861976..0220efdb4 100644 --- a/crates/project_model/src/workspace.rs +++ b/crates/project_model/src/workspace.rs | |||
@@ -114,6 +114,7 @@ impl ProjectWorkspace { | |||
114 | cargo_version | 114 | cargo_version |
115 | ) | 115 | ) |
116 | })?; | 116 | })?; |
117 | |||
117 | let sysroot = if config.no_sysroot { | 118 | let sysroot = if config.no_sysroot { |
118 | Sysroot::default() | 119 | Sysroot::default() |
119 | } else { | 120 | } else { |
@@ -125,7 +126,17 @@ impl ProjectWorkspace { | |||
125 | })? | 126 | })? |
126 | }; | 127 | }; |
127 | 128 | ||
128 | let rustc = if let Some(rustc_dir) = &config.rustc_source { | 129 | let rustc_dir = if let Some(rustc_source) = &config.rustc_source { |
130 | use cargo_workspace::RustcSource; | ||
131 | match rustc_source { | ||
132 | RustcSource::Path(path) => Some(path.clone()), | ||
133 | RustcSource::Discover => Sysroot::discover_rustc(&cargo_toml), | ||
134 | } | ||
135 | } else { | ||
136 | None | ||
137 | }; | ||
138 | |||
139 | let rustc = if let Some(rustc_dir) = rustc_dir { | ||
129 | Some( | 140 | Some( |
130 | CargoWorkspace::from_cargo_metadata(&rustc_dir, config, progress) | 141 | CargoWorkspace::from_cargo_metadata(&rustc_dir, config, progress) |
131 | .with_context(|| { | 142 | .with_context(|| { |
diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs index cc0b22bff..f9098968a 100644 --- a/crates/rust-analyzer/src/config.rs +++ b/crates/rust-analyzer/src/config.rs | |||
@@ -18,7 +18,7 @@ use ide_db::helpers::{ | |||
18 | }; | 18 | }; |
19 | use itertools::Itertools; | 19 | use itertools::Itertools; |
20 | use lsp_types::{ClientCapabilities, MarkupKind}; | 20 | use lsp_types::{ClientCapabilities, MarkupKind}; |
21 | use project_model::{CargoConfig, ProjectJson, ProjectJsonData, ProjectManifest}; | 21 | use project_model::{CargoConfig, ProjectJson, ProjectJsonData, ProjectManifest, RustcSource}; |
22 | use rustc_hash::FxHashSet; | 22 | use rustc_hash::FxHashSet; |
23 | use serde::{de::DeserializeOwned, Deserialize}; | 23 | use serde::{de::DeserializeOwned, Deserialize}; |
24 | use vfs::AbsPathBuf; | 24 | use vfs::AbsPathBuf; |
@@ -177,8 +177,9 @@ config_data! { | |||
177 | /// tests or binaries.\nFor example, it may be `--release`. | 177 | /// tests or binaries.\nFor example, it may be `--release`. |
178 | runnables_cargoExtraArgs: Vec<String> = "[]", | 178 | runnables_cargoExtraArgs: Vec<String> = "[]", |
179 | 179 | ||
180 | /// Path to the rust compiler sources, for usage in rustc_private projects. | 180 | /// Path to the rust compiler sources, for usage in rustc_private projects, or "discover" |
181 | rustcSource : Option<PathBuf> = "null", | 181 | /// to try to automatically find it. |
182 | rustcSource : Option<String> = "null", | ||
182 | 183 | ||
183 | /// Additional arguments to `rustfmt`. | 184 | /// Additional arguments to `rustfmt`. |
184 | rustfmt_extraArgs: Vec<String> = "[]", | 185 | rustfmt_extraArgs: Vec<String> = "[]", |
@@ -473,7 +474,13 @@ impl Config { | |||
473 | self.data.cargo_loadOutDirsFromCheck | 474 | self.data.cargo_loadOutDirsFromCheck |
474 | } | 475 | } |
475 | pub fn cargo(&self) -> CargoConfig { | 476 | pub fn cargo(&self) -> CargoConfig { |
476 | let rustc_source = self.data.rustcSource.as_ref().map(|it| self.root_path.join(&it)); | 477 | let rustc_source = self.data.rustcSource.as_ref().map(|rustc_src| { |
478 | if rustc_src == "discover" { | ||
479 | RustcSource::Discover | ||
480 | } else { | ||
481 | RustcSource::Path(self.root_path.join(rustc_src)) | ||
482 | } | ||
483 | }); | ||
477 | 484 | ||
478 | CargoConfig { | 485 | CargoConfig { |
479 | no_default_features: self.data.cargo_noDefaultFeatures, | 486 | no_default_features: self.data.cargo_noDefaultFeatures, |
diff --git a/crates/rust-analyzer/src/from_proto.rs b/crates/rust-analyzer/src/from_proto.rs index aa6b808d6..6676eebf4 100644 --- a/crates/rust-analyzer/src/from_proto.rs +++ b/crates/rust-analyzer/src/from_proto.rs | |||
@@ -1,12 +1,12 @@ | |||
1 | //! Conversion lsp_types types to rust-analyzer specific ones. | 1 | //! Conversion lsp_types types to rust-analyzer specific ones. |
2 | use std::convert::TryFrom; | 2 | use std::convert::TryFrom; |
3 | 3 | ||
4 | use ide::{AssistKind, LineCol, LineIndex}; | 4 | use ide::{Annotation, AnnotationKind, AssistKind, LineCol, LineIndex}; |
5 | use ide_db::base_db::{FileId, FilePosition, FileRange}; | 5 | use ide_db::base_db::{FileId, FilePosition, FileRange}; |
6 | use syntax::{TextRange, TextSize}; | 6 | use syntax::{TextRange, TextSize}; |
7 | use vfs::AbsPathBuf; | 7 | use vfs::AbsPathBuf; |
8 | 8 | ||
9 | use crate::{global_state::GlobalStateSnapshot, Result}; | 9 | use crate::{from_json, global_state::GlobalStateSnapshot, lsp_ext, Result}; |
10 | 10 | ||
11 | pub(crate) fn abs_path(url: &lsp_types::Url) -> Result<AbsPathBuf> { | 11 | pub(crate) fn abs_path(url: &lsp_types::Url) -> Result<AbsPathBuf> { |
12 | let path = url.to_file_path().map_err(|()| "url is not a file")?; | 12 | let path = url.to_file_path().map_err(|()| "url is not a file")?; |
@@ -66,3 +66,39 @@ pub(crate) fn assist_kind(kind: lsp_types::CodeActionKind) -> Option<AssistKind> | |||
66 | 66 | ||
67 | Some(assist_kind) | 67 | Some(assist_kind) |
68 | } | 68 | } |
69 | |||
70 | pub(crate) fn annotation( | ||
71 | world: &GlobalStateSnapshot, | ||
72 | code_lens: lsp_types::CodeLens, | ||
73 | ) -> Result<Annotation> { | ||
74 | let data = code_lens.data.unwrap(); | ||
75 | let resolve = from_json::<lsp_ext::CodeLensResolveData>("CodeLensResolveData", data)?; | ||
76 | |||
77 | match resolve { | ||
78 | lsp_ext::CodeLensResolveData::Impls(params) => { | ||
79 | let file_id = | ||
80 | world.url_to_file_id(¶ms.text_document_position_params.text_document.uri)?; | ||
81 | let line_index = world.analysis.file_line_index(file_id)?; | ||
82 | |||
83 | Ok(Annotation { | ||
84 | range: text_range(&line_index, code_lens.range), | ||
85 | kind: AnnotationKind::HasImpls { | ||
86 | position: file_position(world, params.text_document_position_params)?, | ||
87 | data: None, | ||
88 | }, | ||
89 | }) | ||
90 | } | ||
91 | lsp_ext::CodeLensResolveData::References(params) => { | ||
92 | let file_id = world.url_to_file_id(¶ms.text_document.uri)?; | ||
93 | let line_index = world.analysis.file_line_index(file_id)?; | ||
94 | |||
95 | Ok(Annotation { | ||
96 | range: text_range(&line_index, code_lens.range), | ||
97 | kind: AnnotationKind::HasReferences { | ||
98 | position: file_position(world, params)?, | ||
99 | data: None, | ||
100 | }, | ||
101 | }) | ||
102 | } | ||
103 | } | ||
104 | } | ||
diff --git a/crates/rust-analyzer/src/handlers.rs b/crates/rust-analyzer/src/handlers.rs index 8898c12e3..b051c8f6c 100644 --- a/crates/rust-analyzer/src/handlers.rs +++ b/crates/rust-analyzer/src/handlers.rs | |||
@@ -9,8 +9,9 @@ use std::{ | |||
9 | }; | 9 | }; |
10 | 10 | ||
11 | use ide::{ | 11 | use ide::{ |
12 | FileId, FilePosition, FileRange, HoverAction, HoverGotoTypeData, LineIndex, NavigationTarget, | 12 | AnnotationConfig, FileId, FilePosition, FileRange, HoverAction, HoverGotoTypeData, LineIndex, |
13 | Query, RangeInfo, Runnable, RunnableKind, SearchScope, SourceChange, TextEdit, | 13 | NavigationTarget, Query, RangeInfo, Runnable, RunnableKind, SearchScope, SourceChange, |
14 | TextEdit, | ||
14 | }; | 15 | }; |
15 | use ide_db::SymbolKind; | 16 | use ide_db::SymbolKind; |
16 | use itertools::Itertools; | 17 | use itertools::Itertools; |
@@ -35,7 +36,7 @@ use crate::{ | |||
35 | cargo_target_spec::CargoTargetSpec, | 36 | cargo_target_spec::CargoTargetSpec, |
36 | config::RustfmtConfig, | 37 | config::RustfmtConfig, |
37 | diff::diff, | 38 | diff::diff, |
38 | from_json, from_proto, | 39 | from_proto, |
39 | global_state::{GlobalState, GlobalStateSnapshot}, | 40 | global_state::{GlobalState, GlobalStateSnapshot}, |
40 | line_endings::LineEndings, | 41 | line_endings::LineEndings, |
41 | lsp_ext::{self, InlayHint, InlayHintsParams}, | 42 | lsp_ext::{self, InlayHint, InlayHintsParams}, |
@@ -1078,177 +1079,51 @@ pub(crate) fn handle_code_lens( | |||
1078 | params: lsp_types::CodeLensParams, | 1079 | params: lsp_types::CodeLensParams, |
1079 | ) -> Result<Option<Vec<CodeLens>>> { | 1080 | ) -> Result<Option<Vec<CodeLens>>> { |
1080 | let _p = profile::span("handle_code_lens"); | 1081 | let _p = profile::span("handle_code_lens"); |
1081 | let mut lenses: Vec<CodeLens> = Default::default(); | ||
1082 | 1082 | ||
1083 | let lens_config = snap.config.lens(); | 1083 | let lens_config = snap.config.lens(); |
1084 | if lens_config.none() { | 1084 | if lens_config.none() { |
1085 | // early return before any db query! | 1085 | // early return before any db query! |
1086 | return Ok(Some(lenses)); | 1086 | return Ok(Some(Vec::default())); |
1087 | } | 1087 | } |
1088 | 1088 | ||
1089 | let file_id = from_proto::file_id(&snap, ¶ms.text_document.uri)?; | 1089 | let file_id = from_proto::file_id(&snap, ¶ms.text_document.uri)?; |
1090 | let line_index = snap.analysis.file_line_index(file_id)?; | 1090 | let cargo_target_spec = CargoTargetSpec::for_file(&snap, file_id)?; |
1091 | let cargo_spec = CargoTargetSpec::for_file(&snap, file_id)?; | ||
1092 | |||
1093 | if lens_config.runnable() { | ||
1094 | // Gather runnables | ||
1095 | for runnable in snap.analysis.runnables(file_id)? { | ||
1096 | if should_skip_target(&runnable, cargo_spec.as_ref()) { | ||
1097 | continue; | ||
1098 | } | ||
1099 | |||
1100 | let action = runnable.action(); | ||
1101 | let range = to_proto::range(&line_index, runnable.nav.full_range); | ||
1102 | let r = to_proto::runnable(&snap, file_id, runnable)?; | ||
1103 | if lens_config.run { | ||
1104 | let lens = CodeLens { | ||
1105 | range, | ||
1106 | command: Some(run_single_command(&r, action.run_title)), | ||
1107 | data: None, | ||
1108 | }; | ||
1109 | lenses.push(lens); | ||
1110 | } | ||
1111 | 1091 | ||
1112 | if action.debugee && lens_config.debug { | 1092 | let lenses = snap |
1113 | let debug_lens = | 1093 | .analysis |
1114 | CodeLens { range, command: Some(debug_single_command(&r)), data: None }; | 1094 | .annotations( |
1115 | lenses.push(debug_lens); | 1095 | file_id, |
1116 | } | 1096 | AnnotationConfig { |
1117 | } | 1097 | binary_target: cargo_target_spec |
1118 | } | 1098 | .map(|spec| { |
1119 | 1099 | matches!( | |
1120 | if lens_config.implementations || lens_config.refs { | 1100 | spec.target_kind, |
1121 | snap.analysis | 1101 | TargetKind::Bin | TargetKind::Example | TargetKind::Test |
1122 | .file_structure(file_id)? | 1102 | ) |
1123 | .into_iter() | ||
1124 | .filter(|it| { | ||
1125 | matches!( | ||
1126 | it.kind, | ||
1127 | SymbolKind::Trait | SymbolKind::Struct | SymbolKind::Enum | SymbolKind::Union | ||
1128 | ) | ||
1129 | }) | ||
1130 | .for_each(|it| { | ||
1131 | let range = to_proto::range(&line_index, it.node_range); | ||
1132 | let position = to_proto::position(&line_index, it.navigation_range.start()); | ||
1133 | let doc_pos = lsp_types::TextDocumentPositionParams::new( | ||
1134 | params.text_document.clone(), | ||
1135 | position, | ||
1136 | ); | ||
1137 | let goto_params = lsp_types::request::GotoImplementationParams { | ||
1138 | text_document_position_params: doc_pos.clone(), | ||
1139 | work_done_progress_params: Default::default(), | ||
1140 | partial_result_params: Default::default(), | ||
1141 | }; | ||
1142 | |||
1143 | if lens_config.implementations { | ||
1144 | lenses.push(CodeLens { | ||
1145 | range, | ||
1146 | command: None, | ||
1147 | data: Some(to_value(CodeLensResolveData::Impls(goto_params)).unwrap()), | ||
1148 | }) | ||
1149 | } | ||
1150 | |||
1151 | if lens_config.refs { | ||
1152 | lenses.push(CodeLens { | ||
1153 | range, | ||
1154 | command: None, | ||
1155 | data: Some(to_value(CodeLensResolveData::References(doc_pos)).unwrap()), | ||
1156 | }) | 1103 | }) |
1157 | } | 1104 | .unwrap_or(false), |
1158 | }); | 1105 | annotate_runnables: lens_config.runnable(), |
1159 | } | 1106 | annotate_impls: lens_config.implementations, |
1160 | 1107 | annotate_references: lens_config.refs, | |
1161 | if lens_config.method_refs { | 1108 | annotate_method_references: lens_config.method_refs, |
1162 | lenses.extend(snap.analysis.find_all_methods(file_id)?.into_iter().map(|it| { | 1109 | run: lens_config.run, |
1163 | let range = to_proto::range(&line_index, it.range); | 1110 | debug: lens_config.debug, |
1164 | let position = to_proto::position(&line_index, it.range.start()); | 1111 | }, |
1165 | let lens_params = | 1112 | )? |
1166 | lsp_types::TextDocumentPositionParams::new(params.text_document.clone(), position); | 1113 | .into_iter() |
1167 | 1114 | .map(|annotation| to_proto::code_lens(&snap, annotation).unwrap()) | |
1168 | CodeLens { | 1115 | .collect(); |
1169 | range, | ||
1170 | command: None, | ||
1171 | data: Some(to_value(CodeLensResolveData::References(lens_params)).unwrap()), | ||
1172 | } | ||
1173 | })); | ||
1174 | } | ||
1175 | 1116 | ||
1176 | Ok(Some(lenses)) | 1117 | Ok(Some(lenses)) |
1177 | } | 1118 | } |
1178 | 1119 | ||
1179 | #[derive(Debug, Serialize, Deserialize)] | ||
1180 | #[serde(rename_all = "camelCase")] | ||
1181 | enum CodeLensResolveData { | ||
1182 | Impls(lsp_types::request::GotoImplementationParams), | ||
1183 | References(lsp_types::TextDocumentPositionParams), | ||
1184 | } | ||
1185 | |||
1186 | pub(crate) fn handle_code_lens_resolve( | 1120 | pub(crate) fn handle_code_lens_resolve( |
1187 | snap: GlobalStateSnapshot, | 1121 | snap: GlobalStateSnapshot, |
1188 | code_lens: CodeLens, | 1122 | code_lens: CodeLens, |
1189 | ) -> Result<CodeLens> { | 1123 | ) -> Result<CodeLens> { |
1190 | let _p = profile::span("handle_code_lens_resolve"); | 1124 | let annotation = from_proto::annotation(&snap, code_lens)?; |
1191 | let data = code_lens.data.unwrap(); | ||
1192 | let resolve = from_json::<Option<CodeLensResolveData>>("CodeLensResolveData", data)?; | ||
1193 | match resolve { | ||
1194 | Some(CodeLensResolveData::Impls(lens_params)) => { | ||
1195 | let locations: Vec<Location> = | ||
1196 | match handle_goto_implementation(snap, lens_params.clone())? { | ||
1197 | Some(lsp_types::GotoDefinitionResponse::Scalar(loc)) => vec![loc], | ||
1198 | Some(lsp_types::GotoDefinitionResponse::Array(locs)) => locs, | ||
1199 | Some(lsp_types::GotoDefinitionResponse::Link(links)) => links | ||
1200 | .into_iter() | ||
1201 | .map(|link| Location::new(link.target_uri, link.target_selection_range)) | ||
1202 | .collect(), | ||
1203 | _ => vec![], | ||
1204 | }; | ||
1205 | |||
1206 | let title = implementation_title(locations.len()); | ||
1207 | let cmd = show_references_command( | ||
1208 | title, | ||
1209 | &lens_params.text_document_position_params.text_document.uri, | ||
1210 | code_lens.range.start, | ||
1211 | locations, | ||
1212 | ); | ||
1213 | Ok(CodeLens { range: code_lens.range, command: Some(cmd), data: None }) | ||
1214 | } | ||
1215 | Some(CodeLensResolveData::References(doc_position)) => { | ||
1216 | let position = from_proto::file_position(&snap, doc_position.clone())?; | ||
1217 | let locations = snap | ||
1218 | .analysis | ||
1219 | .find_all_refs(position, None) | ||
1220 | .unwrap_or(None) | ||
1221 | .map(|r| { | ||
1222 | r.references | ||
1223 | .into_iter() | ||
1224 | .flat_map(|(file_id, ranges)| { | ||
1225 | ranges.into_iter().map(move |(range, _)| FileRange { file_id, range }) | ||
1226 | }) | ||
1227 | .filter_map(|frange| to_proto::location(&snap, frange).ok()) | ||
1228 | .collect_vec() | ||
1229 | }) | ||
1230 | .unwrap_or_default(); | ||
1231 | |||
1232 | let title = reference_title(locations.len()); | ||
1233 | let cmd = if locations.is_empty() { | ||
1234 | Command { title, command: "".into(), arguments: None } | ||
1235 | } else { | ||
1236 | show_references_command( | ||
1237 | title, | ||
1238 | &doc_position.text_document.uri, | ||
1239 | code_lens.range.start, | ||
1240 | locations, | ||
1241 | ) | ||
1242 | }; | ||
1243 | 1125 | ||
1244 | Ok(CodeLens { range: code_lens.range, command: Some(cmd), data: None }) | 1126 | Ok(to_proto::code_lens(&snap, snap.analysis.resolve_annotation(annotation)?)?) |
1245 | } | ||
1246 | None => Ok(CodeLens { | ||
1247 | range: code_lens.range, | ||
1248 | command: Some(Command { title: "Error".into(), ..Default::default() }), | ||
1249 | data: None, | ||
1250 | }), | ||
1251 | } | ||
1252 | } | 1127 | } |
1253 | 1128 | ||
1254 | pub(crate) fn handle_document_highlight( | 1129 | pub(crate) fn handle_document_highlight( |
@@ -1547,43 +1422,6 @@ pub(crate) fn handle_open_cargo_toml( | |||
1547 | Ok(Some(res)) | 1422 | Ok(Some(res)) |
1548 | } | 1423 | } |
1549 | 1424 | ||
1550 | fn implementation_title(count: usize) -> String { | ||
1551 | if count == 1 { | ||
1552 | "1 implementation".into() | ||
1553 | } else { | ||
1554 | format!("{} implementations", count) | ||
1555 | } | ||
1556 | } | ||
1557 | |||
1558 | fn reference_title(count: usize) -> String { | ||
1559 | if count == 1 { | ||
1560 | "1 reference".into() | ||
1561 | } else { | ||
1562 | format!("{} references", count) | ||
1563 | } | ||
1564 | } | ||
1565 | |||
1566 | fn show_references_command( | ||
1567 | title: String, | ||
1568 | uri: &lsp_types::Url, | ||
1569 | position: lsp_types::Position, | ||
1570 | locations: Vec<lsp_types::Location>, | ||
1571 | ) -> Command { | ||
1572 | // We cannot use the 'editor.action.showReferences' command directly | ||
1573 | // because that command requires vscode types which we convert in the handler | ||
1574 | // on the client side. | ||
1575 | |||
1576 | Command { | ||
1577 | title, | ||
1578 | command: "rust-analyzer.showReferences".into(), | ||
1579 | arguments: Some(vec![ | ||
1580 | to_value(uri).unwrap(), | ||
1581 | to_value(position).unwrap(), | ||
1582 | to_value(locations).unwrap(), | ||
1583 | ]), | ||
1584 | } | ||
1585 | } | ||
1586 | |||
1587 | fn run_single_command(runnable: &lsp_ext::Runnable, title: &str) -> Command { | 1425 | fn run_single_command(runnable: &lsp_ext::Runnable, title: &str) -> Command { |
1588 | Command { | 1426 | Command { |
1589 | title: title.to_string(), | 1427 | title: title.to_string(), |
@@ -1635,8 +1473,8 @@ fn show_impl_command_link( | |||
1635 | .into_iter() | 1473 | .into_iter() |
1636 | .filter_map(|nav| to_proto::location_from_nav(snap, nav).ok()) | 1474 | .filter_map(|nav| to_proto::location_from_nav(snap, nav).ok()) |
1637 | .collect(); | 1475 | .collect(); |
1638 | let title = implementation_title(locations.len()); | 1476 | let title = to_proto::implementation_title(locations.len()); |
1639 | let command = show_references_command(title, &uri, position, locations); | 1477 | let command = to_proto::show_references_command(title, &uri, position, locations); |
1640 | 1478 | ||
1641 | return Some(lsp_ext::CommandLinkGroup { | 1479 | return Some(lsp_ext::CommandLinkGroup { |
1642 | commands: vec![to_command_link(command, "Go to implementations".into())], | 1480 | commands: vec![to_command_link(command, "Go to implementations".into())], |
diff --git a/crates/rust-analyzer/src/lsp_ext.rs b/crates/rust-analyzer/src/lsp_ext.rs index ce5a0e822..a1ad855c3 100644 --- a/crates/rust-analyzer/src/lsp_ext.rs +++ b/crates/rust-analyzer/src/lsp_ext.rs | |||
@@ -377,3 +377,11 @@ impl Request for OpenCargoToml { | |||
377 | pub struct OpenCargoTomlParams { | 377 | pub struct OpenCargoTomlParams { |
378 | pub text_document: TextDocumentIdentifier, | 378 | pub text_document: TextDocumentIdentifier, |
379 | } | 379 | } |
380 | |||
381 | /// Information about CodeLens, that is to be resolved. | ||
382 | #[derive(Debug, Serialize, Deserialize)] | ||
383 | #[serde(rename_all = "camelCase")] | ||
384 | pub(crate) enum CodeLensResolveData { | ||
385 | Impls(lsp_types::request::GotoImplementationParams), | ||
386 | References(lsp_types::TextDocumentPositionParams), | ||
387 | } | ||
diff --git a/crates/rust-analyzer/src/to_proto.rs b/crates/rust-analyzer/src/to_proto.rs index be10ac1ae..29fac96fb 100644 --- a/crates/rust-analyzer/src/to_proto.rs +++ b/crates/rust-analyzer/src/to_proto.rs | |||
@@ -5,13 +5,15 @@ use std::{ | |||
5 | }; | 5 | }; |
6 | 6 | ||
7 | use ide::{ | 7 | use ide::{ |
8 | Assist, AssistKind, CallInfo, CompletionItem, CompletionItemKind, Documentation, FileId, | 8 | Annotation, AnnotationKind, Assist, AssistKind, CallInfo, CompletionItem, CompletionItemKind, |
9 | FileRange, FileSystemEdit, Fold, FoldKind, Highlight, HlMod, HlPunct, HlRange, HlTag, Indel, | 9 | Documentation, FileId, FileRange, FileSystemEdit, Fold, FoldKind, Highlight, HlMod, HlPunct, |
10 | InlayHint, InlayKind, InsertTextFormat, LineIndex, Markup, NavigationTarget, ReferenceAccess, | 10 | HlRange, HlTag, Indel, InlayHint, InlayKind, InsertTextFormat, LineIndex, Markup, |
11 | RenameError, Runnable, Severity, SourceChange, TextEdit, TextRange, TextSize, | 11 | NavigationTarget, ReferenceAccess, RenameError, Runnable, Severity, SourceChange, TextEdit, |
12 | TextRange, TextSize, | ||
12 | }; | 13 | }; |
13 | use ide_db::SymbolKind; | 14 | use ide_db::SymbolKind; |
14 | use itertools::Itertools; | 15 | use itertools::Itertools; |
16 | use serde_json::to_value; | ||
15 | 17 | ||
16 | use crate::{ | 18 | use crate::{ |
17 | cargo_target_spec::CargoTargetSpec, global_state::GlobalStateSnapshot, | 19 | cargo_target_spec::CargoTargetSpec, global_state::GlobalStateSnapshot, |
@@ -863,6 +865,141 @@ pub(crate) fn runnable( | |||
863 | }) | 865 | }) |
864 | } | 866 | } |
865 | 867 | ||
868 | pub(crate) fn code_lens( | ||
869 | snap: &GlobalStateSnapshot, | ||
870 | annotation: Annotation, | ||
871 | ) -> Result<lsp_types::CodeLens> { | ||
872 | match annotation.kind { | ||
873 | AnnotationKind::Runnable { debug, runnable: run } => { | ||
874 | let line_index = snap.analysis.file_line_index(run.nav.file_id)?; | ||
875 | let annotation_range = range(&line_index, annotation.range); | ||
876 | |||
877 | let action = run.action(); | ||
878 | let r = runnable(&snap, run.nav.file_id, run)?; | ||
879 | |||
880 | let command = if debug { | ||
881 | lsp_types::Command { | ||
882 | title: action.run_title.to_string(), | ||
883 | command: "rust-analyzer.runSingle".into(), | ||
884 | arguments: Some(vec![to_value(r).unwrap()]), | ||
885 | } | ||
886 | } else { | ||
887 | lsp_types::Command { | ||
888 | title: "Debug".into(), | ||
889 | command: "rust-analyzer.debugSingle".into(), | ||
890 | arguments: Some(vec![to_value(r).unwrap()]), | ||
891 | } | ||
892 | }; | ||
893 | |||
894 | Ok(lsp_types::CodeLens { range: annotation_range, command: Some(command), data: None }) | ||
895 | } | ||
896 | AnnotationKind::HasImpls { position: file_position, data } => { | ||
897 | let line_index = snap.analysis.file_line_index(file_position.file_id)?; | ||
898 | let annotation_range = range(&line_index, annotation.range); | ||
899 | let url = url(snap, file_position.file_id); | ||
900 | |||
901 | let position = position(&line_index, file_position.offset); | ||
902 | |||
903 | let id = lsp_types::TextDocumentIdentifier { uri: url.clone() }; | ||
904 | |||
905 | let doc_pos = lsp_types::TextDocumentPositionParams::new(id.clone(), position); | ||
906 | |||
907 | let goto_params = lsp_types::request::GotoImplementationParams { | ||
908 | text_document_position_params: doc_pos.clone(), | ||
909 | work_done_progress_params: Default::default(), | ||
910 | partial_result_params: Default::default(), | ||
911 | }; | ||
912 | |||
913 | let command = data.map(|ranges| { | ||
914 | let locations: Vec<lsp_types::Location> = ranges | ||
915 | .into_iter() | ||
916 | .filter_map(|target| { | ||
917 | location( | ||
918 | snap, | ||
919 | FileRange { file_id: target.file_id, range: target.full_range }, | ||
920 | ) | ||
921 | .ok() | ||
922 | }) | ||
923 | .collect(); | ||
924 | |||
925 | show_references_command( | ||
926 | implementation_title(locations.len()), | ||
927 | &url, | ||
928 | position, | ||
929 | locations, | ||
930 | ) | ||
931 | }); | ||
932 | |||
933 | Ok(lsp_types::CodeLens { | ||
934 | range: annotation_range, | ||
935 | command, | ||
936 | data: Some(to_value(lsp_ext::CodeLensResolveData::Impls(goto_params)).unwrap()), | ||
937 | }) | ||
938 | } | ||
939 | AnnotationKind::HasReferences { position: file_position, data } => { | ||
940 | let line_index = snap.analysis.file_line_index(file_position.file_id)?; | ||
941 | let annotation_range = range(&line_index, annotation.range); | ||
942 | let url = url(snap, file_position.file_id); | ||
943 | |||
944 | let position = position(&line_index, file_position.offset); | ||
945 | |||
946 | let id = lsp_types::TextDocumentIdentifier { uri: url.clone() }; | ||
947 | |||
948 | let doc_pos = lsp_types::TextDocumentPositionParams::new(id, position); | ||
949 | |||
950 | let command = data.map(|ranges| { | ||
951 | let locations: Vec<lsp_types::Location> = | ||
952 | ranges.into_iter().filter_map(|range| location(snap, range).ok()).collect(); | ||
953 | |||
954 | show_references_command(reference_title(locations.len()), &url, position, locations) | ||
955 | }); | ||
956 | |||
957 | Ok(lsp_types::CodeLens { | ||
958 | range: annotation_range, | ||
959 | command, | ||
960 | data: Some(to_value(lsp_ext::CodeLensResolveData::References(doc_pos)).unwrap()), | ||
961 | }) | ||
962 | } | ||
963 | } | ||
964 | } | ||
965 | |||
966 | pub(crate) fn show_references_command( | ||
967 | title: String, | ||
968 | uri: &lsp_types::Url, | ||
969 | position: lsp_types::Position, | ||
970 | locations: Vec<lsp_types::Location>, | ||
971 | ) -> lsp_types::Command { | ||
972 | // We cannot use the 'editor.action.showReferences' command directly | ||
973 | // because that command requires vscode types which we convert in the handler | ||
974 | // on the client side. | ||
975 | |||
976 | lsp_types::Command { | ||
977 | title, | ||
978 | command: "rust-analyzer.showReferences".into(), | ||
979 | arguments: Some(vec![ | ||
980 | to_value(uri).unwrap(), | ||
981 | to_value(position).unwrap(), | ||
982 | to_value(locations).unwrap(), | ||
983 | ]), | ||
984 | } | ||
985 | } | ||
986 | |||
987 | pub(crate) fn implementation_title(count: usize) -> String { | ||
988 | if count == 1 { | ||
989 | "1 implementation".into() | ||
990 | } else { | ||
991 | format!("{} implementations", count) | ||
992 | } | ||
993 | } | ||
994 | |||
995 | pub(crate) fn reference_title(count: usize) -> String { | ||
996 | if count == 1 { | ||
997 | "1 reference".into() | ||
998 | } else { | ||
999 | format!("{} references", count) | ||
1000 | } | ||
1001 | } | ||
1002 | |||
866 | pub(crate) fn markup_content(markup: Markup) -> lsp_types::MarkupContent { | 1003 | pub(crate) fn markup_content(markup: Markup) -> lsp_types::MarkupContent { |
867 | let value = crate::markdown::format_docs(markup.as_str()); | 1004 | let value = crate::markdown::format_docs(markup.as_str()); |
868 | lsp_types::MarkupContent { kind: lsp_types::MarkupKind::Markdown, value } | 1005 | lsp_types::MarkupContent { kind: lsp_types::MarkupKind::Markdown, value } |
diff --git a/crates/syntax/src/ast/node_ext.rs b/crates/syntax/src/ast/node_ext.rs index b105cb0e0..307e150e9 100644 --- a/crates/syntax/src/ast/node_ext.rs +++ b/crates/syntax/src/ast/node_ext.rs | |||
@@ -3,12 +3,11 @@ | |||
3 | 3 | ||
4 | use std::fmt; | 4 | use std::fmt; |
5 | 5 | ||
6 | use ast::AttrsOwner; | ||
7 | use itertools::Itertools; | 6 | use itertools::Itertools; |
8 | use parser::SyntaxKind; | 7 | use parser::SyntaxKind; |
9 | 8 | ||
10 | use crate::{ | 9 | use crate::{ |
11 | ast::{self, support, AstNode, AstToken, NameOwner, SyntaxNode}, | 10 | ast::{self, support, AstNode, AstToken, AttrsOwner, NameOwner, SyntaxNode}, |
12 | SmolStr, SyntaxElement, SyntaxToken, T, | 11 | SmolStr, SyntaxElement, SyntaxToken, T, |
13 | }; | 12 | }; |
14 | 13 | ||
@@ -324,7 +323,7 @@ impl ast::RecordPatField { | |||
324 | 323 | ||
325 | pub fn for_field_name(field_name: &ast::Name) -> Option<ast::RecordPatField> { | 324 | pub fn for_field_name(field_name: &ast::Name) -> Option<ast::RecordPatField> { |
326 | let candidate = | 325 | let candidate = |
327 | field_name.syntax().ancestors().nth(3).and_then(ast::RecordPatField::cast)?; | 326 | field_name.syntax().ancestors().nth(2).and_then(ast::RecordPatField::cast)?; |
328 | match candidate.field_name()? { | 327 | match candidate.field_name()? { |
329 | NameOrNameRef::Name(name) if name == *field_name => Some(candidate), | 328 | NameOrNameRef::Name(name) if name == *field_name => Some(candidate), |
330 | _ => None, | 329 | _ => None, |
diff --git a/docs/dev/README.md b/docs/dev/README.md index 9b9b18102..d6fae5295 100644 --- a/docs/dev/README.md +++ b/docs/dev/README.md | |||
@@ -218,8 +218,7 @@ Release steps: | |||
218 | * makes a GitHub release | 218 | * makes a GitHub release |
219 | * pushes VS Code extension to the marketplace | 219 | * pushes VS Code extension to the marketplace |
220 | * create new changelog in `rust-analyzer.github.io` | 220 | * create new changelog in `rust-analyzer.github.io` |
221 | * create `rust-analyzer.github.io/git.log` file with the log of merge commits since last release | 221 | 2. While the release is in progress, fill in the changelog |
222 | 2. While the release is in progress, fill-in the changelog using `git.log` | ||
223 | 3. Commit & push the changelog | 222 | 3. Commit & push the changelog |
224 | 4. Tweet | 223 | 4. Tweet |
225 | 5. Inside `rust-analyzer`, run `cargo xtask promote` -- this will create a PR to rust-lang/rust updating rust-analyzer's submodule. | 224 | 5. Inside `rust-analyzer`, run `cargo xtask promote` -- this will create a PR to rust-lang/rust updating rust-analyzer's submodule. |
diff --git a/docs/dev/lsp-extensions.md b/docs/dev/lsp-extensions.md index 2a966a96d..a4e108724 100644 --- a/docs/dev/lsp-extensions.md +++ b/docs/dev/lsp-extensions.md | |||
@@ -1,5 +1,5 @@ | |||
1 | <!--- | 1 | <!--- |
2 | lsp_ext.rs hash: 8f1ae8530f69e3a3 | 2 | lsp_ext.rs hash: 34aec6bfeaeb97a |
3 | 3 | ||
4 | If you need to change the above hash to make the test pass, please check if you | 4 | If you need to change the above hash to make the test pass, please check if you |
5 | need to adjust this doc as well and ping this issue: | 5 | need to adjust this doc as well and ping this issue: |
diff --git a/docs/user/generated_config.adoc b/docs/user/generated_config.adoc index 55178c84c..f91e04c31 100644 --- a/docs/user/generated_config.adoc +++ b/docs/user/generated_config.adoc | |||
@@ -105,7 +105,7 @@ | |||
105 | [[rust-analyzer.runnables.cargoExtraArgs]]rust-analyzer.runnables.cargoExtraArgs (default: `[]`):: | 105 | [[rust-analyzer.runnables.cargoExtraArgs]]rust-analyzer.runnables.cargoExtraArgs (default: `[]`):: |
106 | Additional arguments to be passed to cargo for runnables such as tests or binaries.\nFor example, it may be `--release`. | 106 | Additional arguments to be passed to cargo for runnables such as tests or binaries.\nFor example, it may be `--release`. |
107 | [[rust-analyzer.rustcSource]]rust-analyzer.rustcSource (default: `null`):: | 107 | [[rust-analyzer.rustcSource]]rust-analyzer.rustcSource (default: `null`):: |
108 | Path to the rust compiler sources, for usage in rustc_private projects. | 108 | Path to the rust compiler sources, for usage in rustc_private projects, or "discover" to try to automatically find it. |
109 | [[rust-analyzer.rustfmt.extraArgs]]rust-analyzer.rustfmt.extraArgs (default: `[]`):: | 109 | [[rust-analyzer.rustfmt.extraArgs]]rust-analyzer.rustfmt.extraArgs (default: `[]`):: |
110 | Additional arguments to `rustfmt`. | 110 | Additional arguments to `rustfmt`. |
111 | [[rust-analyzer.rustfmt.overrideCommand]]rust-analyzer.rustfmt.overrideCommand (default: `null`):: | 111 | [[rust-analyzer.rustfmt.overrideCommand]]rust-analyzer.rustfmt.overrideCommand (default: `null`):: |
diff --git a/editors/code/package.json b/editors/code/package.json index 55825456e..defa108cb 100644 --- a/editors/code/package.json +++ b/editors/code/package.json | |||
@@ -707,7 +707,7 @@ | |||
707 | } | 707 | } |
708 | }, | 708 | }, |
709 | "rust-analyzer.rustcSource": { | 709 | "rust-analyzer.rustcSource": { |
710 | "markdownDescription": "Path to the rust compiler sources, for usage in rustc_private projects.", | 710 | "markdownDescription": "Path to the rust compiler sources, for usage in rustc_private projects, or \"discover\" to try to automatically find it.", |
711 | "default": null, | 711 | "default": null, |
712 | "type": [ | 712 | "type": [ |
713 | "null", | 713 | "null", |
diff --git a/editors/code/src/main.ts b/editors/code/src/main.ts index d18d6c8a9..620810d72 100644 --- a/editors/code/src/main.ts +++ b/editors/code/src/main.ts | |||
@@ -246,10 +246,10 @@ async function patchelf(dest: PathLike): Promise<void> { | |||
246 | }, | 246 | }, |
247 | async (progress, _) => { | 247 | async (progress, _) => { |
248 | const expression = ` | 248 | const expression = ` |
249 | {src, pkgs ? import <nixpkgs> {}}: | 249 | {srcStr, pkgs ? import <nixpkgs> {}}: |
250 | pkgs.stdenv.mkDerivation { | 250 | pkgs.stdenv.mkDerivation { |
251 | name = "rust-analyzer"; | 251 | name = "rust-analyzer"; |
252 | inherit src; | 252 | src = /. + srcStr; |
253 | phases = [ "installPhase" "fixupPhase" ]; | 253 | phases = [ "installPhase" "fixupPhase" ]; |
254 | installPhase = "cp $src $out"; | 254 | installPhase = "cp $src $out"; |
255 | fixupPhase = '' | 255 | fixupPhase = '' |
@@ -262,7 +262,7 @@ async function patchelf(dest: PathLike): Promise<void> { | |||
262 | await fs.rename(dest, origFile); | 262 | await fs.rename(dest, origFile); |
263 | progress.report({ message: "Patching executable", increment: 20 }); | 263 | progress.report({ message: "Patching executable", increment: 20 }); |
264 | await new Promise((resolve, reject) => { | 264 | await new Promise((resolve, reject) => { |
265 | const handle = exec(`nix-build -E - --arg src '${origFile}' -o ${dest}`, | 265 | const handle = exec(`nix-build -E - --argstr srcStr '${origFile}' -o '${dest}'`, |
266 | (err, stdout, stderr) => { | 266 | (err, stdout, stderr) => { |
267 | if (err != null) { | 267 | if (err != null) { |
268 | reject(Error(stderr)); | 268 | reject(Error(stderr)); |
diff --git a/xtask/src/dist.rs b/xtask/src/dist.rs index 6bc34106b..56bf9f99d 100644 --- a/xtask/src/dist.rs +++ b/xtask/src/dist.rs | |||
@@ -59,7 +59,7 @@ fn dist_client(version: &str, release_tag: &str) -> Result<()> { | |||
59 | 59 | ||
60 | fn dist_server() -> Result<()> { | 60 | fn dist_server() -> Result<()> { |
61 | let target = get_target(); | 61 | let target = get_target(); |
62 | if target.contains("-linux-gnu") { | 62 | if target.contains("-linux-gnu") || target.contains("-linux-musl") { |
63 | env::set_var("CC", "clang"); | 63 | env::set_var("CC", "clang"); |
64 | } | 64 | } |
65 | 65 | ||
diff --git a/xtask/src/release.rs b/xtask/src/release.rs index 93079b369..63556476d 100644 --- a/xtask/src/release.rs +++ b/xtask/src/release.rs | |||
@@ -1,3 +1,5 @@ | |||
1 | use std::fmt::Write; | ||
2 | |||
1 | use xshell::{cmd, cp, pushd, read_dir, write_file}; | 3 | use xshell::{cmd, cp, pushd, read_dir, write_file}; |
2 | 4 | ||
3 | use crate::{codegen, date_iso, is_release_tag, project_root, Mode, Result}; | 5 | use crate::{codegen, date_iso, is_release_tag, project_root, Mode, Result}; |
@@ -24,6 +26,34 @@ impl ReleaseCmd { | |||
24 | let commit = cmd!("git rev-parse HEAD").read()?; | 26 | let commit = cmd!("git rev-parse HEAD").read()?; |
25 | let changelog_n = read_dir(changelog_dir.as_path())?.len(); | 27 | let changelog_n = read_dir(changelog_dir.as_path())?.len(); |
26 | 28 | ||
29 | for &adoc in [ | ||
30 | "manual.adoc", | ||
31 | "generated_assists.adoc", | ||
32 | "generated_config.adoc", | ||
33 | "generated_diagnostic.adoc", | ||
34 | "generated_features.adoc", | ||
35 | ] | ||
36 | .iter() | ||
37 | { | ||
38 | let src = project_root().join("./docs/user/").join(adoc); | ||
39 | let dst = website_root.join(adoc); | ||
40 | cp(src, dst)?; | ||
41 | } | ||
42 | |||
43 | let tags = cmd!("git tag --list").read()?; | ||
44 | let prev_tag = tags.lines().filter(|line| is_release_tag(line)).last().unwrap(); | ||
45 | |||
46 | let git_log = cmd!("git log {prev_tag}..HEAD --merges --reverse").read()?; | ||
47 | let mut git_log_summary = String::new(); | ||
48 | for line in git_log.lines() { | ||
49 | let line = line.trim_start(); | ||
50 | if let Some(p) = line.find(':') { | ||
51 | if let Ok(pr) = line[..p].parse::<u32>() { | ||
52 | writeln!(git_log_summary, "* pr:{}[]{}", pr, &line[p + 1..]).unwrap(); | ||
53 | } | ||
54 | } | ||
55 | } | ||
56 | |||
27 | let contents = format!( | 57 | let contents = format!( |
28 | "\ | 58 | "\ |
29 | = Changelog #{} | 59 | = Changelog #{} |
@@ -40,39 +70,18 @@ https://github.com/sponsors/rust-analyzer[GitHub Sponsors]. | |||
40 | 70 | ||
41 | == New Features | 71 | == New Features |
42 | 72 | ||
43 | * pr:[] . | 73 | {} |
44 | 74 | ||
45 | == Fixes | 75 | == Fixes |
46 | 76 | ||
47 | == Internal Improvements | 77 | == Internal Improvements |
48 | ", | 78 | ", |
49 | changelog_n, commit, today | 79 | changelog_n, commit, today, git_log_summary |
50 | ); | 80 | ); |
51 | 81 | ||
52 | let path = changelog_dir.join(format!("{}-changelog-{}.adoc", today, changelog_n)); | 82 | let path = changelog_dir.join(format!("{}-changelog-{}.adoc", today, changelog_n)); |
53 | write_file(&path, &contents)?; | 83 | write_file(&path, &contents)?; |
54 | 84 | ||
55 | for &adoc in [ | ||
56 | "manual.adoc", | ||
57 | "generated_assists.adoc", | ||
58 | "generated_config.adoc", | ||
59 | "generated_diagnostic.adoc", | ||
60 | "generated_features.adoc", | ||
61 | ] | ||
62 | .iter() | ||
63 | { | ||
64 | let src = project_root().join("./docs/user/").join(adoc); | ||
65 | let dst = website_root.join(adoc); | ||
66 | cp(src, dst)?; | ||
67 | } | ||
68 | |||
69 | let tags = cmd!("git tag --list").read()?; | ||
70 | let prev_tag = tags.lines().filter(|line| is_release_tag(line)).last().unwrap(); | ||
71 | |||
72 | let git_log = cmd!("git log {prev_tag}..HEAD --merges --reverse").read()?; | ||
73 | let git_log_dst = website_root.join("git.log"); | ||
74 | write_file(git_log_dst, &git_log)?; | ||
75 | |||
76 | Ok(()) | 85 | Ok(()) |
77 | } | 86 | } |
78 | } | 87 | } |