aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.github/workflows/release.yaml34
-rw-r--r--crates/assists/src/handlers/generate_from_impl_for_enum.rs131
-rw-r--r--crates/assists/src/handlers/generate_getter.rs8
-rw-r--r--crates/assists/src/handlers/generate_getter_mut.rs8
-rw-r--r--crates/assists/src/handlers/generate_impl.rs70
-rw-r--r--crates/assists/src/handlers/generate_setter.rs8
-rw-r--r--crates/assists/src/handlers/replace_derive_with_manual_impl.rs26
-rw-r--r--crates/assists/src/tests/generated.rs2
-rw-r--r--crates/assists/src/utils.rs52
-rw-r--r--crates/ide/src/annotations.rs937
-rw-r--r--crates/ide/src/lib.rs14
-rw-r--r--crates/ide/src/references.rs35
-rw-r--r--crates/ide/src/references/rename.rs179
-rw-r--r--crates/ide_db/src/defs.rs14
-rw-r--r--crates/project_model/src/cargo_workspace.rs11
-rw-r--r--crates/project_model/src/lib.rs4
-rw-r--r--crates/project_model/src/sysroot.rs40
-rw-r--r--crates/project_model/src/workspace.rs13
-rw-r--r--crates/rust-analyzer/src/config.rs15
-rw-r--r--crates/rust-analyzer/src/from_proto.rs40
-rw-r--r--crates/rust-analyzer/src/handlers.rs228
-rw-r--r--crates/rust-analyzer/src/lsp_ext.rs8
-rw-r--r--crates/rust-analyzer/src/to_proto.rs145
-rw-r--r--crates/syntax/src/ast/node_ext.rs5
-rw-r--r--docs/dev/README.md3
-rw-r--r--docs/dev/lsp-extensions.md2
-rw-r--r--docs/user/generated_config.adoc2
-rw-r--r--editors/code/package.json2
-rw-r--r--editors/code/src/main.ts6
-rw-r--r--xtask/src/dist.rs2
-rw-r--r--xtask/src/release.rs55
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;
3use syntax::ast::{self, AstNode, NameOwner}; 3use syntax::ast::{self, AstNode, NameOwner};
4use test_utils::mark; 4use test_utils::mark;
5 5
6use crate::{AssistContext, AssistId, AssistKind, Assists}; 6use 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// ```
25pub(crate) fn generate_from_impl_for_enum(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { 25pub(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!(
57impl 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
107impl From<u32> for A { 119impl 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
122impl From<foo::bar::baz::Boo> for A { 134impl 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
165impl 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
158impl From<u32> for A { 180impl 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
184impl From<u32> for A { 206impl 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
231impl 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
246impl<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
261impl<'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 @@
1use stdx::{format_to, to_lower_snake_case}; 1use stdx::{format_to, to_lower_snake_case};
2use syntax::ast::VisibilityOwner; 2use syntax::ast::{self, AstNode, NameOwner, VisibilityOwner};
3use syntax::ast::{self, AstNode, NameOwner};
4 3
5use crate::{ 4use 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 @@
1use stdx::{format_to, to_lower_snake_case}; 1use stdx::{format_to, to_lower_snake_case};
2use syntax::ast::VisibilityOwner; 2use syntax::ast::{self, AstNode, NameOwner, VisibilityOwner};
3use syntax::ast::{self, AstNode, NameOwner};
4 3
5use crate::{ 4use 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 @@
1use itertools::Itertools; 1use syntax::ast::{self, AstNode, NameOwner};
2use stdx::format_to;
3use syntax::{
4 ast::{self, AstNode, AttrsOwner, GenericParamsOwner, NameOwner},
5 SmolStr,
6};
7 2
8use crate::{AssistContext, AssistId, AssistKind, Assists}; 3use 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 @@
1use stdx::{format_to, to_lower_snake_case}; 1use stdx::{format_to, to_lower_snake_case};
2use syntax::ast::VisibilityOwner; 2use syntax::ast::{self, AstNode, NameOwner, VisibilityOwner};
3use syntax::ast::{self, AstNode, NameOwner};
4 3
5use crate::{ 4use 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;
2use ide_db::imports_locator; 2use ide_db::imports_locator;
3use itertools::Itertools; 3use itertools::Itertools;
4use syntax::{ 4use 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::{
11use crate::{ 10use 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
500impl From<u32> for A { 500impl 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
3use std::ops; 3use std::ops;
4 4
5use ast::TypeBoundsOwner;
5use hir::{Adt, HasSource}; 6use hir::{Adt, HasSource};
6use ide_db::{helpers::SnippetCap, RootDatabase}; 7use ide_db::{helpers::SnippetCap, RootDatabase};
7use itertools::Itertools; 8use 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
369pub(crate) fn generate_impl_text(adt: &ast::Adt, code: &str) -> String { 370pub(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
376pub(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
380fn 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 @@
1use hir::Semantics;
2use ide_db::{
3 base_db::{FileId, FilePosition, FileRange, SourceDatabase},
4 RootDatabase, SymbolKind,
5};
6use syntax::TextRange;
7
8use 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)]
22pub struct Annotation {
23 pub range: TextRange,
24 pub kind: AnnotationKind,
25}
26
27#[derive(Debug)]
28pub 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
34pub 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
44pub(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
125pub(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
148fn 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)]
156mod 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#"
189const DEMO: i32 = 123;
190
191const UNUSED: i32 = 123;
192
193fn 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#"
295struct Test;
296
297fn 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#"
399struct Test;
400
401trait MyCoolTrait {}
402
403impl MyCoolTrait for Test {}
404
405fn 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#"
568fn 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#"
633struct Test;
634
635impl Test {
636 fn self_by_ref(&self) {}
637}
638
639fn 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#"
778fn main() {}
779
780mod 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;
22mod prime_caches; 22mod prime_caches;
23mod display; 23mod display;
24 24
25mod annotations;
25mod call_hierarchy; 26mod call_hierarchy;
26mod diagnostics; 27mod diagnostics;
27mod expand_macro; 28mod expand_macro;
@@ -63,6 +64,7 @@ use syntax::SourceFile;
63use crate::display::ToNav; 64use crate::display::ToNav;
64 65
65pub use crate::{ 66pub 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]
1206fn 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]
1223fn my_proc_macro() {}
1224
1225#[my_proc_macro$0]
1226fn 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
166fn source_edit_from_references( 177fn 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
204fn 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
186fn source_edit_from_name_ref( 216fn 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
260fn rename_mod( 288fn 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 {
1485struct Foo { i$0: i32 } 1513struct Foo { i$0: i32 }
1486 1514
1487fn foo(foo: Foo) { 1515fn 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#"
1493struct Foo { baz: i32 } 1521struct Foo { baz: i32 }
1494 1522
1495fn foo(foo: Foo) { 1523fn 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#"
1603struct Foo { i$0: i32 }
1604
1605fn foo(foo: Foo) {
1606 let Foo { ref i } = foo;
1607}
1608"#,
1609 r#"
1610struct Foo { baz: i32 }
1611
1612fn 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#"
1733macro_rules! foo {
1734 ($pattern:pat) => {
1735 let $pattern = loop {};
1736 };
1737}
1738struct Foo {
1739 bar$0: u32,
1740}
1741fn foo() {
1742 foo!(Foo { bar: baz });
1743}
1744"#,
1745 r#"
1746macro_rules! foo {
1747 ($pattern:pat) => {
1748 let $pattern = loop {};
1749 };
1750}
1751struct Foo {
1752 baz: u32,
1753}
1754fn 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
8use hir::{ 8use 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};
12use syntax::{ 12use 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)]
49pub 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)]
48pub struct CargoConfig { 57pub 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
70pub type Package = Idx<PackageData>; 79pub 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;
21pub use crate::{ 21pub 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
113fn discover_sysroot_src_dir(current_dir: &AbsPath) -> Result<AbsPathBuf> { 120fn 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
128fn 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
162fn 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
152fn get_rust_src(sysroot_path: &AbsPath) -> Option<AbsPathBuf> { 172fn 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};
19use itertools::Itertools; 19use itertools::Itertools;
20use lsp_types::{ClientCapabilities, MarkupKind}; 20use lsp_types::{ClientCapabilities, MarkupKind};
21use project_model::{CargoConfig, ProjectJson, ProjectJsonData, ProjectManifest}; 21use project_model::{CargoConfig, ProjectJson, ProjectJsonData, ProjectManifest, RustcSource};
22use rustc_hash::FxHashSet; 22use rustc_hash::FxHashSet;
23use serde::{de::DeserializeOwned, Deserialize}; 23use serde::{de::DeserializeOwned, Deserialize};
24use vfs::AbsPathBuf; 24use 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.
2use std::convert::TryFrom; 2use std::convert::TryFrom;
3 3
4use ide::{AssistKind, LineCol, LineIndex}; 4use ide::{Annotation, AnnotationKind, AssistKind, LineCol, LineIndex};
5use ide_db::base_db::{FileId, FilePosition, FileRange}; 5use ide_db::base_db::{FileId, FilePosition, FileRange};
6use syntax::{TextRange, TextSize}; 6use syntax::{TextRange, TextSize};
7use vfs::AbsPathBuf; 7use vfs::AbsPathBuf;
8 8
9use crate::{global_state::GlobalStateSnapshot, Result}; 9use crate::{from_json, global_state::GlobalStateSnapshot, lsp_ext, Result};
10 10
11pub(crate) fn abs_path(url: &lsp_types::Url) -> Result<AbsPathBuf> { 11pub(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
70pub(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(&params.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(&params.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
11use ide::{ 11use 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};
15use ide_db::SymbolKind; 16use ide_db::SymbolKind;
16use itertools::Itertools; 17use 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, &params.text_document.uri)?; 1089 let file_id = from_proto::file_id(&snap, &params.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")]
1181enum CodeLensResolveData {
1182 Impls(lsp_types::request::GotoImplementationParams),
1183 References(lsp_types::TextDocumentPositionParams),
1184}
1185
1186pub(crate) fn handle_code_lens_resolve( 1120pub(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
1254pub(crate) fn handle_document_highlight( 1129pub(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
1550fn implementation_title(count: usize) -> String {
1551 if count == 1 {
1552 "1 implementation".into()
1553 } else {
1554 format!("{} implementations", count)
1555 }
1556}
1557
1558fn reference_title(count: usize) -> String {
1559 if count == 1 {
1560 "1 reference".into()
1561 } else {
1562 format!("{} references", count)
1563 }
1564}
1565
1566fn 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
1587fn run_single_command(runnable: &lsp_ext::Runnable, title: &str) -> Command { 1425fn 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 {
377pub struct OpenCargoTomlParams { 377pub 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")]
384pub(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
7use ide::{ 7use 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};
13use ide_db::SymbolKind; 14use ide_db::SymbolKind;
14use itertools::Itertools; 15use itertools::Itertools;
16use serde_json::to_value;
15 17
16use crate::{ 18use 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
868pub(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
966pub(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
987pub(crate) fn implementation_title(count: usize) -> String {
988 if count == 1 {
989 "1 implementation".into()
990 } else {
991 format!("{} implementations", count)
992 }
993}
994
995pub(crate) fn reference_title(count: usize) -> String {
996 if count == 1 {
997 "1 reference".into()
998 } else {
999 format!("{} references", count)
1000 }
1001}
1002
866pub(crate) fn markup_content(markup: Markup) -> lsp_types::MarkupContent { 1003pub(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
4use std::fmt; 4use std::fmt;
5 5
6use ast::AttrsOwner;
7use itertools::Itertools; 6use itertools::Itertools;
8use parser::SyntaxKind; 7use parser::SyntaxKind;
9 8
10use crate::{ 9use 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 2212. While the release is in progress, fill in the changelog
2222. While the release is in progress, fill-in the changelog using `git.log`
2233. Commit & push the changelog 2223. Commit & push the changelog
2244. Tweet 2234. Tweet
2255. Inside `rust-analyzer`, run `cargo xtask promote` -- this will create a PR to rust-lang/rust updating rust-analyzer's submodule. 2245. 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<!---
2lsp_ext.rs hash: 8f1ae8530f69e3a3 2lsp_ext.rs hash: 34aec6bfeaeb97a
3 3
4If you need to change the above hash to make the test pass, please check if you 4If you need to change the above hash to make the test pass, please check if you
5need to adjust this doc as well and ping this issue: 5need 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
60fn dist_server() -> Result<()> { 60fn 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 @@
1use std::fmt::Write;
2
1use xshell::{cmd, cp, pushd, read_dir, write_file}; 3use xshell::{cmd, cp, pushd, read_dir, write_file};
2 4
3use crate::{codegen, date_iso, is_release_tag, project_root, Mode, Result}; 5use 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}