aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Cargo.lock54
-rw-r--r--Cargo.toml12
-rw-r--r--crates/assists/src/handlers/fix_visibility.rs10
-rw-r--r--crates/hir/src/code_model.rs17
-rw-r--r--crates/hir_def/src/adt.rs28
-rw-r--r--crates/hir_def/src/body/lower.rs5
-rw-r--r--crates/hir_def/src/data.rs2
-rw-r--r--crates/hir_def/src/item_tree.rs1
-rw-r--r--crates/hir_def/src/item_tree/lower.rs3
-rw-r--r--crates/hir_def/src/item_tree/tests.rs24
-rw-r--r--crates/hir_ty/Cargo.toml6
-rw-r--r--crates/ide/src/completion/complete_postfix/format_like.rs2
-rw-r--r--crates/ide/src/doc_links.rs (renamed from crates/ide/src/link_rewrite.rs)285
-rw-r--r--crates/ide/src/hover.rs2
-rw-r--r--crates/ide/src/lib.rs16
-rw-r--r--crates/ide/src/references.rs25
-rw-r--r--crates/ide/src/references/rename.rs175
-rw-r--r--crates/ide/src/syntax_highlighting/test_data/highlighting.html2
-rw-r--r--crates/rust-analyzer/src/handlers.rs29
-rw-r--r--crates/rust-analyzer/src/lsp_ext.rs8
-rw-r--r--crates/rust-analyzer/src/main_loop.rs1
-rw-r--r--crates/stdx/src/macros.rs8
-rw-r--r--docs/dev/lsp-extensions.md25
-rw-r--r--editors/code/package.json9
-rw-r--r--editors/code/src/commands.ts25
-rw-r--r--editors/code/src/lsp_ext.ts2
-rw-r--r--editors/code/src/main.ts1
27 files changed, 625 insertions, 152 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 4cfa4c519..fdb62e6ea 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -82,12 +82,12 @@ checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
82 82
83[[package]] 83[[package]]
84name = "backtrace" 84name = "backtrace"
85version = "0.3.51" 85version = "0.3.53"
86source = "registry+https://github.com/rust-lang/crates.io-index" 86source = "registry+https://github.com/rust-lang/crates.io-index"
87checksum = "ec1931848a574faa8f7c71a12ea00453ff5effbb5f51afe7f77d7a48cace6ac1" 87checksum = "707b586e0e2f247cbde68cdd2c3ce69ea7b7be43e1c5b426e37c9319c4b9838e"
88dependencies = [ 88dependencies = [
89 "addr2line", 89 "addr2line",
90 "cfg-if 0.1.10", 90 "cfg-if 1.0.0",
91 "libc", 91 "libc",
92 "miniz_oxide", 92 "miniz_oxide",
93 "object", 93 "object",
@@ -140,9 +140,9 @@ dependencies = [
140 140
141[[package]] 141[[package]]
142name = "cc" 142name = "cc"
143version = "1.0.60" 143version = "1.0.61"
144source = "registry+https://github.com/rust-lang/crates.io-index" 144source = "registry+https://github.com/rust-lang/crates.io-index"
145checksum = "ef611cc68ff783f18535d77ddd080185275713d852c4f5cbb6122c462a7a825c" 145checksum = "ed67cbde08356238e75fc4656be4749481eeffb09e19f320a25237d5221c985d"
146 146
147[[package]] 147[[package]]
148name = "cfg" 148name = "cfg"
@@ -168,9 +168,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
168 168
169[[package]] 169[[package]]
170name = "chalk-derive" 170name = "chalk-derive"
171version = "0.32.0" 171version = "0.33.0"
172source = "registry+https://github.com/rust-lang/crates.io-index" 172source = "registry+https://github.com/rust-lang/crates.io-index"
173checksum = "2d072b2ba723f0bada7c515d8b3725224bc4f5052d2a92dcbeb0b118ff37084a" 173checksum = "569014cab9084a6b826fe2507cc6d08f7897ba144fb1bc74e71b593dc8a0b952"
174dependencies = [ 174dependencies = [
175 "proc-macro2", 175 "proc-macro2",
176 "quote", 176 "quote",
@@ -180,9 +180,9 @@ dependencies = [
180 180
181[[package]] 181[[package]]
182name = "chalk-ir" 182name = "chalk-ir"
183version = "0.32.0" 183version = "0.33.0"
184source = "registry+https://github.com/rust-lang/crates.io-index" 184source = "registry+https://github.com/rust-lang/crates.io-index"
185checksum = "f60cdb0e18c5455cb6a85e8464aad3622b70476018edfa8845691df66f7e9a05" 185checksum = "8d9eab2a6590b696419f89c9ca3616fe8e8266ef676e6a6da8818c94963c9541"
186dependencies = [ 186dependencies = [
187 "chalk-derive", 187 "chalk-derive",
188 "lazy_static", 188 "lazy_static",
@@ -190,9 +190,9 @@ dependencies = [
190 190
191[[package]] 191[[package]]
192name = "chalk-recursive" 192name = "chalk-recursive"
193version = "0.32.0" 193version = "0.33.0"
194source = "registry+https://github.com/rust-lang/crates.io-index" 194source = "registry+https://github.com/rust-lang/crates.io-index"
195checksum = "b14f40242102e7c0e2791a2cc86dbbc213a1d7b7acc0e22b7da329f4957d1722" 195checksum = "4a4671bcc70aa2d7e12ff4fe03f91d0c3c9ce387de915915e57fdf0c91dc5abd"
196dependencies = [ 196dependencies = [
197 "chalk-derive", 197 "chalk-derive",
198 "chalk-ir", 198 "chalk-ir",
@@ -203,9 +203,9 @@ dependencies = [
203 203
204[[package]] 204[[package]]
205name = "chalk-solve" 205name = "chalk-solve"
206version = "0.32.0" 206version = "0.33.0"
207source = "registry+https://github.com/rust-lang/crates.io-index" 207source = "registry+https://github.com/rust-lang/crates.io-index"
208checksum = "981534d499a8476ecc0b520be4d3864757f96211826a75360fbf2cb6fae362ab" 208checksum = "45f75cc603f2fd302576c8b2976437f334e159e26d0afbb108a565b96c52184e"
209dependencies = [ 209dependencies = [
210 "chalk-derive", 210 "chalk-derive",
211 "chalk-ir", 211 "chalk-ir",
@@ -737,9 +737,9 @@ checksum = "2448f6066e80e3bfc792e9c98bf705b4b0fc6e8ef5b43e5889aff0eaa9c58743"
737 737
738[[package]] 738[[package]]
739name = "libloading" 739name = "libloading"
740version = "0.6.3" 740version = "0.6.4"
741source = "registry+https://github.com/rust-lang/crates.io-index" 741source = "registry+https://github.com/rust-lang/crates.io-index"
742checksum = "2443d8f0478b16759158b2f66d525991a05491138bc05814ef52a250148ef4f9" 742checksum = "3557c9384f7f757f6d139cd3a4c62ef4e850696c16bf27924a5538c8a09717a1"
743dependencies = [ 743dependencies = [
744 "cfg-if 0.1.10", 744 "cfg-if 0.1.10",
745 "winapi 0.3.9", 745 "winapi 0.3.9",
@@ -747,9 +747,9 @@ dependencies = [
747 747
748[[package]] 748[[package]]
749name = "libmimalloc-sys" 749name = "libmimalloc-sys"
750version = "0.1.17" 750version = "0.1.18"
751source = "registry+https://github.com/rust-lang/crates.io-index" 751source = "registry+https://github.com/rust-lang/crates.io-index"
752checksum = "2bd65748dfb74a807e8379fd49bf4d38964d92b8637b56f173f87e1d70433368" 752checksum = "82151ff13433c4d403cb15d0e6fbda14b24d65bd1a5b33f7d52ec983cc00752d"
753dependencies = [ 753dependencies = [
754 "cmake", 754 "cmake",
755] 755]
@@ -859,9 +859,9 @@ dependencies = [
859 859
860[[package]] 860[[package]]
861name = "mimalloc" 861name = "mimalloc"
862version = "0.1.21" 862version = "0.1.22"
863source = "registry+https://github.com/rust-lang/crates.io-index" 863source = "registry+https://github.com/rust-lang/crates.io-index"
864checksum = "2e18b3d01186c4a7bec0b56cff9a4b9d5a1461aa162c7b8eaf2e5ee10b265b02" 864checksum = "4a5d2c9cb18f9cdc6d88f4aca6d3d8ea89c4c8202d6facfc7e56efdee97b80fa"
865dependencies = [ 865dependencies = [
866 "libmimalloc-sys", 866 "libmimalloc-sys",
867] 867]
@@ -981,9 +981,9 @@ dependencies = [
981 981
982[[package]] 982[[package]]
983name = "object" 983name = "object"
984version = "0.20.0" 984version = "0.21.1"
985source = "registry+https://github.com/rust-lang/crates.io-index" 985source = "registry+https://github.com/rust-lang/crates.io-index"
986checksum = "1ab52be62400ca80aa00285d25253d7f7c437b7375c4de678f5405d3afe82ca5" 986checksum = "37fd5004feb2ce328a52b0b3d01dbf4ffff72583493900ed15f22d4111c51693"
987 987
988[[package]] 988[[package]]
989name = "once_cell" 989name = "once_cell"
@@ -1383,18 +1383,18 @@ checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
1383 1383
1384[[package]] 1384[[package]]
1385name = "scroll" 1385name = "scroll"
1386version = "0.10.1" 1386version = "0.10.2"
1387source = "registry+https://github.com/rust-lang/crates.io-index" 1387source = "registry+https://github.com/rust-lang/crates.io-index"
1388checksum = "abb2332cb595d33f7edd5700f4cbf94892e680c7f0ae56adab58a35190b66cb1" 1388checksum = "fda28d4b4830b807a8b43f7b0e6b5df875311b3e7621d84577188c175b6ec1ec"
1389dependencies = [ 1389dependencies = [
1390 "scroll_derive", 1390 "scroll_derive",
1391] 1391]
1392 1392
1393[[package]] 1393[[package]]
1394name = "scroll_derive" 1394name = "scroll_derive"
1395version = "0.10.2" 1395version = "0.10.3"
1396source = "registry+https://github.com/rust-lang/crates.io-index" 1396source = "registry+https://github.com/rust-lang/crates.io-index"
1397checksum = "e367622f934864ffa1c704ba2b82280aab856e3d8213c84c5720257eb34b15b9" 1397checksum = "6dfde5d1531034db129e95c76ac857e2baecea3443579d493d02224950b0fb6d"
1398dependencies = [ 1398dependencies = [
1399 "proc-macro2", 1399 "proc-macro2",
1400 "quote", 1400 "quote",
@@ -1510,9 +1510,9 @@ version = "0.0.0"
1510 1510
1511[[package]] 1511[[package]]
1512name = "syn" 1512name = "syn"
1513version = "1.0.42" 1513version = "1.0.44"
1514source = "registry+https://github.com/rust-lang/crates.io-index" 1514source = "registry+https://github.com/rust-lang/crates.io-index"
1515checksum = "9c51d92969d209b54a98397e1b91c8ae82d8c87a7bb87df0b29aa2ad81454228" 1515checksum = "e03e57e4fcbfe7749842d53e24ccb9aa12b7252dbe5e91d2acad31834c8b8fdd"
1516dependencies = [ 1516dependencies = [
1517 "proc-macro2", 1517 "proc-macro2",
1518 "quote", 1518 "quote",
diff --git a/Cargo.toml b/Cargo.toml
index 218581d9d..87d33f06c 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -19,18 +19,6 @@ miniz_oxide.opt-level = 3
19incremental = true 19incremental = true
20debug = 0 # Set this to 1 or 2 to get more useful backtraces in debugger. 20debug = 0 # Set this to 1 or 2 to get more useful backtraces in debugger.
21 21
22# Ideally, we would use `build-override` here, but some crates are also
23# needed at run-time and we end up compiling them twice.
24[profile.release.package]
25chalk-derive.opt-level = 0
26proc-macro2.opt-level = 0
27quote.opt-level = 0
28salsa-macros.opt-level = 0
29serde_derive.opt-level = 0
30syn.opt-level = 0
31tracing-attributes.opt-level = 0
32xtask.opt-level = 0
33
34[patch.'crates-io'] 22[patch.'crates-io']
35# rowan = { path = "../rowan" } 23# rowan = { path = "../rowan" }
36 24
diff --git a/crates/assists/src/handlers/fix_visibility.rs b/crates/assists/src/handlers/fix_visibility.rs
index 7cd76ea06..d505e9444 100644
--- a/crates/assists/src/handlers/fix_visibility.rs
+++ b/crates/assists/src/handlers/fix_visibility.rs
@@ -324,14 +324,14 @@ pub struct Foo { pub bar: () }
324 324
325 #[test] 325 #[test]
326 fn fix_visibility_of_enum_variant_field() { 326 fn fix_visibility_of_enum_variant_field() {
327 check_assist( 327 // Enum variants, as well as their fields, always get the enum's visibility. In fact, rustc
328 // rejects any visibility specifiers on them, so this assist should never fire on them.
329 check_assist_not_applicable(
328 fix_visibility, 330 fix_visibility,
329 r"mod foo { pub enum Foo { Bar { bar: () } } } 331 r"mod foo { pub enum Foo { Bar { bar: () } } }
330 fn main() { foo::Foo::Bar { <|>bar: () }; } ", 332 fn main() { foo::Foo::Bar { <|>bar: () }; } ",
331 r"mod foo { pub enum Foo { Bar { $0pub(crate) bar: () } } }
332 fn main() { foo::Foo::Bar { bar: () }; } ",
333 ); 333 );
334 check_assist( 334 check_assist_not_applicable(
335 fix_visibility, 335 fix_visibility,
336 r" 336 r"
337//- /lib.rs 337//- /lib.rs
@@ -340,8 +340,6 @@ fn main() { foo::Foo::Bar { <|>bar: () }; }
340//- /foo.rs 340//- /foo.rs
341pub enum Foo { Bar { bar: () } } 341pub enum Foo { Bar { bar: () } }
342", 342",
343 r"pub enum Foo { Bar { $0pub(crate) bar: () } }
344",
345 ); 343 );
346 check_assist_not_applicable( 344 check_assist_not_applicable(
347 fix_visibility, 345 fix_visibility,
diff --git a/crates/hir/src/code_model.rs b/crates/hir/src/code_model.rs
index 031c91ccf..650b4fa40 100644
--- a/crates/hir/src/code_model.rs
+++ b/crates/hir/src/code_model.rs
@@ -186,6 +186,16 @@ impl_from!(
186 for ModuleDef 186 for ModuleDef
187); 187);
188 188
189impl From<VariantDef> for ModuleDef {
190 fn from(var: VariantDef) -> Self {
191 match var {
192 VariantDef::Struct(t) => Adt::from(t).into(),
193 VariantDef::Union(t) => Adt::from(t).into(),
194 VariantDef::EnumVariant(t) => t.into(),
195 }
196 }
197}
198
189impl ModuleDef { 199impl ModuleDef {
190 pub fn module(self, db: &dyn HirDatabase) -> Option<Module> { 200 pub fn module(self, db: &dyn HirDatabase) -> Option<Module> {
191 match self { 201 match self {
@@ -752,6 +762,13 @@ impl Function {
752 pub fn diagnostics(self, db: &dyn HirDatabase, sink: &mut DiagnosticSink) { 762 pub fn diagnostics(self, db: &dyn HirDatabase, sink: &mut DiagnosticSink) {
753 hir_ty::diagnostics::validate_body(db, self.id.into(), sink) 763 hir_ty::diagnostics::validate_body(db, self.id.into(), sink)
754 } 764 }
765
766 /// Whether this function declaration has a definition.
767 ///
768 /// This is false in the case of required (not provided) trait methods.
769 pub fn has_body(self, db: &dyn HirDatabase) -> bool {
770 db.function_data(self.id).has_body
771 }
755} 772}
756 773
757// Note: logically, this belongs to `hir_ty`, but we are not using it there yet. 774// Note: logically, this belongs to `hir_ty`, but we are not using it there yet.
diff --git a/crates/hir_def/src/adt.rs b/crates/hir_def/src/adt.rs
index d69ff2fc7..6539959c3 100644
--- a/crates/hir_def/src/adt.rs
+++ b/crates/hir_def/src/adt.rs
@@ -14,7 +14,7 @@ use tt::{Delimiter, DelimiterKind, Leaf, Subtree, TokenTree};
14use crate::{ 14use crate::{
15 body::{CfgExpander, LowerCtx}, 15 body::{CfgExpander, LowerCtx},
16 db::DefDatabase, 16 db::DefDatabase,
17 item_tree::{AttrOwner, Field, Fields, ItemTree, ModItem}, 17 item_tree::{AttrOwner, Field, Fields, ItemTree, ModItem, RawVisibilityId},
18 src::HasChildSource, 18 src::HasChildSource,
19 src::HasSource, 19 src::HasSource,
20 trace::Trace, 20 trace::Trace,
@@ -91,7 +91,7 @@ impl StructData {
91 let cfg_options = db.crate_graph()[loc.container.module(db).krate].cfg_options.clone(); 91 let cfg_options = db.crate_graph()[loc.container.module(db).krate].cfg_options.clone();
92 92
93 let strukt = &item_tree[loc.id.value]; 93 let strukt = &item_tree[loc.id.value];
94 let variant_data = lower_fields(&item_tree, &cfg_options, &strukt.fields); 94 let variant_data = lower_fields(&item_tree, &cfg_options, &strukt.fields, None);
95 Arc::new(StructData { 95 Arc::new(StructData {
96 name: strukt.name.clone(), 96 name: strukt.name.clone(),
97 variant_data: Arc::new(variant_data), 97 variant_data: Arc::new(variant_data),
@@ -105,7 +105,7 @@ impl StructData {
105 let cfg_options = db.crate_graph()[loc.container.module(db).krate].cfg_options.clone(); 105 let cfg_options = db.crate_graph()[loc.container.module(db).krate].cfg_options.clone();
106 106
107 let union = &item_tree[loc.id.value]; 107 let union = &item_tree[loc.id.value];
108 let variant_data = lower_fields(&item_tree, &cfg_options, &union.fields); 108 let variant_data = lower_fields(&item_tree, &cfg_options, &union.fields, None);
109 109
110 Arc::new(StructData { 110 Arc::new(StructData {
111 name: union.name.clone(), 111 name: union.name.clone(),
@@ -126,7 +126,8 @@ impl EnumData {
126 for var_id in enum_.variants.clone() { 126 for var_id in enum_.variants.clone() {
127 if item_tree.attrs(var_id.into()).is_cfg_enabled(&cfg_options) { 127 if item_tree.attrs(var_id.into()).is_cfg_enabled(&cfg_options) {
128 let var = &item_tree[var_id]; 128 let var = &item_tree[var_id];
129 let var_data = lower_fields(&item_tree, &cfg_options, &var.fields); 129 let var_data =
130 lower_fields(&item_tree, &cfg_options, &var.fields, Some(enum_.visibility));
130 131
131 variants.alloc(EnumVariantData { 132 variants.alloc(EnumVariantData {
132 name: var.name.clone(), 133 name: var.name.clone(),
@@ -296,13 +297,18 @@ fn lower_struct(
296 } 297 }
297} 298}
298 299
299fn lower_fields(item_tree: &ItemTree, cfg_options: &CfgOptions, fields: &Fields) -> VariantData { 300fn lower_fields(
301 item_tree: &ItemTree,
302 cfg_options: &CfgOptions,
303 fields: &Fields,
304 override_visibility: Option<RawVisibilityId>,
305) -> VariantData {
300 match fields { 306 match fields {
301 Fields::Record(flds) => { 307 Fields::Record(flds) => {
302 let mut arena = Arena::new(); 308 let mut arena = Arena::new();
303 for field_id in flds.clone() { 309 for field_id in flds.clone() {
304 if item_tree.attrs(field_id.into()).is_cfg_enabled(cfg_options) { 310 if item_tree.attrs(field_id.into()).is_cfg_enabled(cfg_options) {
305 arena.alloc(lower_field(item_tree, &item_tree[field_id])); 311 arena.alloc(lower_field(item_tree, &item_tree[field_id], override_visibility));
306 } 312 }
307 } 313 }
308 VariantData::Record(arena) 314 VariantData::Record(arena)
@@ -311,7 +317,7 @@ fn lower_fields(item_tree: &ItemTree, cfg_options: &CfgOptions, fields: &Fields)
311 let mut arena = Arena::new(); 317 let mut arena = Arena::new();
312 for field_id in flds.clone() { 318 for field_id in flds.clone() {
313 if item_tree.attrs(field_id.into()).is_cfg_enabled(cfg_options) { 319 if item_tree.attrs(field_id.into()).is_cfg_enabled(cfg_options) {
314 arena.alloc(lower_field(item_tree, &item_tree[field_id])); 320 arena.alloc(lower_field(item_tree, &item_tree[field_id], override_visibility));
315 } 321 }
316 } 322 }
317 VariantData::Tuple(arena) 323 VariantData::Tuple(arena)
@@ -320,10 +326,14 @@ fn lower_fields(item_tree: &ItemTree, cfg_options: &CfgOptions, fields: &Fields)
320 } 326 }
321} 327}
322 328
323fn lower_field(item_tree: &ItemTree, field: &Field) -> FieldData { 329fn lower_field(
330 item_tree: &ItemTree,
331 field: &Field,
332 override_visibility: Option<RawVisibilityId>,
333) -> FieldData {
324 FieldData { 334 FieldData {
325 name: field.name.clone(), 335 name: field.name.clone(),
326 type_ref: field.type_ref.clone(), 336 type_ref: field.type_ref.clone(),
327 visibility: item_tree[field.visibility].clone(), 337 visibility: item_tree[override_visibility.unwrap_or(field.visibility)].clone(),
328 } 338 }
329} 339}
diff --git a/crates/hir_def/src/body/lower.rs b/crates/hir_def/src/body/lower.rs
index 2d91bb21f..01e72690a 100644
--- a/crates/hir_def/src/body/lower.rs
+++ b/crates/hir_def/src/body/lower.rs
@@ -107,7 +107,10 @@ impl ExprCollector<'_> {
107 let param_pat = self.alloc_pat( 107 let param_pat = self.alloc_pat(
108 Pat::Bind { 108 Pat::Bind {
109 name: name![self], 109 name: name![self],
110 mode: BindingAnnotation::Unannotated, 110 mode: BindingAnnotation::new(
111 self_param.mut_token().is_some() && self_param.amp_token().is_none(),
112 false,
113 ),
111 subpat: None, 114 subpat: None,
112 }, 115 },
113 Either::Right(ptr), 116 Either::Right(ptr),
diff --git a/crates/hir_def/src/data.rs b/crates/hir_def/src/data.rs
index 6190906da..ff1ef0df6 100644
--- a/crates/hir_def/src/data.rs
+++ b/crates/hir_def/src/data.rs
@@ -25,6 +25,7 @@ pub struct FunctionData {
25 /// True if the first param is `self`. This is relevant to decide whether this 25 /// True if the first param is `self`. This is relevant to decide whether this
26 /// can be called as a method. 26 /// can be called as a method.
27 pub has_self_param: bool, 27 pub has_self_param: bool,
28 pub has_body: bool,
28 pub is_unsafe: bool, 29 pub is_unsafe: bool,
29 pub is_varargs: bool, 30 pub is_varargs: bool,
30 pub visibility: RawVisibility, 31 pub visibility: RawVisibility,
@@ -42,6 +43,7 @@ impl FunctionData {
42 ret_type: func.ret_type.clone(), 43 ret_type: func.ret_type.clone(),
43 attrs: item_tree.attrs(ModItem::from(loc.id.value).into()).clone(), 44 attrs: item_tree.attrs(ModItem::from(loc.id.value).into()).clone(),
44 has_self_param: func.has_self_param, 45 has_self_param: func.has_self_param,
46 has_body: func.has_body,
45 is_unsafe: func.is_unsafe, 47 is_unsafe: func.is_unsafe,
46 is_varargs: func.is_varargs, 48 is_varargs: func.is_varargs,
47 visibility: item_tree[func.visibility].clone(), 49 visibility: item_tree[func.visibility].clone(),
diff --git a/crates/hir_def/src/item_tree.rs b/crates/hir_def/src/item_tree.rs
index 0fd91b9d0..8a1121bbd 100644
--- a/crates/hir_def/src/item_tree.rs
+++ b/crates/hir_def/src/item_tree.rs
@@ -505,6 +505,7 @@ pub struct Function {
505 pub visibility: RawVisibilityId, 505 pub visibility: RawVisibilityId,
506 pub generic_params: GenericParamsId, 506 pub generic_params: GenericParamsId,
507 pub has_self_param: bool, 507 pub has_self_param: bool,
508 pub has_body: bool,
508 pub is_unsafe: bool, 509 pub is_unsafe: bool,
509 pub params: Box<[TypeRef]>, 510 pub params: Box<[TypeRef]>,
510 pub is_varargs: bool, 511 pub is_varargs: bool,
diff --git a/crates/hir_def/src/item_tree/lower.rs b/crates/hir_def/src/item_tree/lower.rs
index 54814f141..3328639cf 100644
--- a/crates/hir_def/src/item_tree/lower.rs
+++ b/crates/hir_def/src/item_tree/lower.rs
@@ -330,12 +330,15 @@ impl Ctx {
330 ret_type 330 ret_type
331 }; 331 };
332 332
333 let has_body = func.body().is_some();
334
333 let ast_id = self.source_ast_id_map.ast_id(func); 335 let ast_id = self.source_ast_id_map.ast_id(func);
334 let mut res = Function { 336 let mut res = Function {
335 name, 337 name,
336 visibility, 338 visibility,
337 generic_params: GenericParamsId::EMPTY, 339 generic_params: GenericParamsId::EMPTY,
338 has_self_param, 340 has_self_param,
341 has_body,
339 is_unsafe: func.unsafe_token().is_some(), 342 is_unsafe: func.unsafe_token().is_some(),
340 params: params.into_boxed_slice(), 343 params: params.into_boxed_slice(),
341 is_varargs, 344 is_varargs,
diff --git a/crates/hir_def/src/item_tree/tests.rs b/crates/hir_def/src/item_tree/tests.rs
index 1a806cda5..4b354c4c1 100644
--- a/crates/hir_def/src/item_tree/tests.rs
+++ b/crates/hir_def/src/item_tree/tests.rs
@@ -240,9 +240,9 @@ fn smoke() {
240 > #[Attrs { entries: Some([Attr { path: ModPath { kind: Plain, segments: [Name(Text("assoc_const"))] }, input: None }]) }] 240 > #[Attrs { entries: Some([Attr { path: ModPath { kind: Plain, segments: [Name(Text("assoc_const"))] }, input: None }]) }]
241 > Const { name: Some(Name(Text("CONST"))), visibility: RawVisibilityId("pub(self)"), type_ref: Path(Path { type_anchor: None, mod_path: ModPath { kind: Plain, segments: [Name(Text("u8"))] }, generic_args: [None] }), ast_id: FileAstId::<syntax::ast::generated::nodes::Const>(9) } 241 > Const { name: Some(Name(Text("CONST"))), visibility: RawVisibilityId("pub(self)"), type_ref: Path(Path { type_anchor: None, mod_path: ModPath { kind: Plain, segments: [Name(Text("u8"))] }, generic_args: [None] }), ast_id: FileAstId::<syntax::ast::generated::nodes::Const>(9) }
242 > #[Attrs { entries: Some([Attr { path: ModPath { kind: Plain, segments: [Name(Text("assoc_method"))] }, input: None }]) }] 242 > #[Attrs { entries: Some([Attr { path: ModPath { kind: Plain, segments: [Name(Text("assoc_method"))] }, input: None }]) }]
243 > Function { name: Name(Text("method")), visibility: RawVisibilityId("pub(self)"), generic_params: GenericParamsId(4294967295), has_self_param: true, is_unsafe: false, params: [Reference(Path(Path { type_anchor: None, mod_path: ModPath { kind: Plain, segments: [Name(Text("Self"))] }, generic_args: [None] }), Shared)], is_varargs: false, ret_type: Tuple([]), ast_id: FileAstId::<syntax::ast::generated::nodes::Fn>(10) } 243 > Function { name: Name(Text("method")), visibility: RawVisibilityId("pub(self)"), generic_params: GenericParamsId(4294967295), has_self_param: true, has_body: false, is_unsafe: false, params: [Reference(Path(Path { type_anchor: None, mod_path: ModPath { kind: Plain, segments: [Name(Text("Self"))] }, generic_args: [None] }), Shared)], is_varargs: false, ret_type: Tuple([]), ast_id: FileAstId::<syntax::ast::generated::nodes::Fn>(10) }
244 > #[Attrs { entries: Some([Attr { path: ModPath { kind: Plain, segments: [Name(Text("assoc_dfl_method"))] }, input: None }]) }] 244 > #[Attrs { entries: Some([Attr { path: ModPath { kind: Plain, segments: [Name(Text("assoc_dfl_method"))] }, input: None }]) }]
245 > Function { name: Name(Text("dfl_method")), visibility: RawVisibilityId("pub(self)"), generic_params: GenericParamsId(4294967295), has_self_param: true, is_unsafe: false, params: [Reference(Path(Path { type_anchor: None, mod_path: ModPath { kind: Plain, segments: [Name(Text("Self"))] }, generic_args: [None] }), Mut)], is_varargs: false, ret_type: Tuple([]), ast_id: FileAstId::<syntax::ast::generated::nodes::Fn>(11) } 245 > Function { name: Name(Text("dfl_method")), visibility: RawVisibilityId("pub(self)"), generic_params: GenericParamsId(4294967295), has_self_param: true, has_body: true, is_unsafe: false, params: [Reference(Path(Path { type_anchor: None, mod_path: ModPath { kind: Plain, segments: [Name(Text("Self"))] }, generic_args: [None] }), Mut)], is_varargs: false, ret_type: Tuple([]), ast_id: FileAstId::<syntax::ast::generated::nodes::Fn>(11) }
246 #[Attrs { entries: Some([Attr { path: ModPath { kind: Plain, segments: [Name(Text("struct0"))] }, input: None }]) }] 246 #[Attrs { entries: Some([Attr { path: ModPath { kind: Plain, segments: [Name(Text("struct0"))] }, input: None }]) }]
247 Struct { name: Name(Text("Struct0")), visibility: RawVisibilityId("pub(self)"), generic_params: GenericParamsId(1), fields: Unit, ast_id: FileAstId::<syntax::ast::generated::nodes::Struct>(3), kind: Unit } 247 Struct { name: Name(Text("Struct0")), visibility: RawVisibilityId("pub(self)"), generic_params: GenericParamsId(1), fields: Unit, ast_id: FileAstId::<syntax::ast::generated::nodes::Struct>(3), kind: Unit }
248 #[Attrs { entries: Some([Attr { path: ModPath { kind: Plain, segments: [Name(Text("struct1"))] }, input: None }]) }] 248 #[Attrs { entries: Some([Attr { path: ModPath { kind: Plain, segments: [Name(Text("struct1"))] }, input: None }]) }]
@@ -275,12 +275,12 @@ fn simple_inner_items() {
275 275
276 top-level items: 276 top-level items:
277 Impl { generic_params: GenericParamsId(0), target_trait: Some(Path(Path { type_anchor: None, mod_path: ModPath { kind: Plain, segments: [Name(Text("D"))] }, generic_args: [None] })), target_type: Path(Path { type_anchor: None, mod_path: ModPath { kind: Plain, segments: [Name(Text("Response"))] }, generic_args: [Some(GenericArgs { args: [Type(Path(Path { type_anchor: None, mod_path: ModPath { kind: Plain, segments: [Name(Text("T"))] }, generic_args: [None] }))], has_self_type: false, bindings: [] })] }), is_negative: false, items: [Function(Idx::<Function>(1))], ast_id: FileAstId::<syntax::ast::generated::nodes::Impl>(0) } 277 Impl { generic_params: GenericParamsId(0), target_trait: Some(Path(Path { type_anchor: None, mod_path: ModPath { kind: Plain, segments: [Name(Text("D"))] }, generic_args: [None] })), target_type: Path(Path { type_anchor: None, mod_path: ModPath { kind: Plain, segments: [Name(Text("Response"))] }, generic_args: [Some(GenericArgs { args: [Type(Path(Path { type_anchor: None, mod_path: ModPath { kind: Plain, segments: [Name(Text("T"))] }, generic_args: [None] }))], has_self_type: false, bindings: [] })] }), is_negative: false, items: [Function(Idx::<Function>(1))], ast_id: FileAstId::<syntax::ast::generated::nodes::Impl>(0) }
278 > Function { name: Name(Text("foo")), visibility: RawVisibilityId("pub(self)"), generic_params: GenericParamsId(4294967295), has_self_param: false, is_unsafe: false, params: [], is_varargs: false, ret_type: Tuple([]), ast_id: FileAstId::<syntax::ast::generated::nodes::Fn>(1) } 278 > Function { name: Name(Text("foo")), visibility: RawVisibilityId("pub(self)"), generic_params: GenericParamsId(4294967295), has_self_param: false, has_body: true, is_unsafe: false, params: [], is_varargs: false, ret_type: Tuple([]), ast_id: FileAstId::<syntax::ast::generated::nodes::Fn>(1) }
279 279
280 inner items: 280 inner items:
281 281
282 for AST FileAstId::<syntax::ast::generated::nodes::Item>(2): 282 for AST FileAstId::<syntax::ast::generated::nodes::Item>(2):
283 Function { name: Name(Text("end")), visibility: RawVisibilityId("pub(self)"), generic_params: GenericParamsId(1), has_self_param: false, is_unsafe: false, params: [], is_varargs: false, ret_type: Tuple([]), ast_id: FileAstId::<syntax::ast::generated::nodes::Fn>(2) } 283 Function { name: Name(Text("end")), visibility: RawVisibilityId("pub(self)"), generic_params: GenericParamsId(1), has_self_param: false, has_body: true, is_unsafe: false, params: [], is_varargs: false, ret_type: Tuple([]), ast_id: FileAstId::<syntax::ast::generated::nodes::Fn>(2) }
284 284
285 "#]], 285 "#]],
286 ); 286 );
@@ -303,9 +303,9 @@ fn extern_attrs() {
303 303
304 top-level items: 304 top-level items:
305 #[Attrs { entries: Some([Attr { path: ModPath { kind: Plain, segments: [Name(Text("attr_a"))] }, input: None }, Attr { path: ModPath { kind: Plain, segments: [Name(Text("block_attr"))] }, input: None }]) }] 305 #[Attrs { entries: Some([Attr { path: ModPath { kind: Plain, segments: [Name(Text("attr_a"))] }, input: None }, Attr { path: ModPath { kind: Plain, segments: [Name(Text("block_attr"))] }, input: None }]) }]
306 Function { name: Name(Text("a")), visibility: RawVisibilityId("pub(self)"), generic_params: GenericParamsId(4294967295), has_self_param: false, is_unsafe: true, params: [], is_varargs: false, ret_type: Tuple([]), ast_id: FileAstId::<syntax::ast::generated::nodes::Fn>(1) } 306 Function { name: Name(Text("a")), visibility: RawVisibilityId("pub(self)"), generic_params: GenericParamsId(4294967295), has_self_param: false, has_body: true, is_unsafe: true, params: [], is_varargs: false, ret_type: Tuple([]), ast_id: FileAstId::<syntax::ast::generated::nodes::Fn>(1) }
307 #[Attrs { entries: Some([Attr { path: ModPath { kind: Plain, segments: [Name(Text("attr_b"))] }, input: None }, Attr { path: ModPath { kind: Plain, segments: [Name(Text("block_attr"))] }, input: None }]) }] 307 #[Attrs { entries: Some([Attr { path: ModPath { kind: Plain, segments: [Name(Text("attr_b"))] }, input: None }, Attr { path: ModPath { kind: Plain, segments: [Name(Text("block_attr"))] }, input: None }]) }]
308 Function { name: Name(Text("b")), visibility: RawVisibilityId("pub(self)"), generic_params: GenericParamsId(4294967295), has_self_param: false, is_unsafe: true, params: [], is_varargs: false, ret_type: Tuple([]), ast_id: FileAstId::<syntax::ast::generated::nodes::Fn>(2) } 308 Function { name: Name(Text("b")), visibility: RawVisibilityId("pub(self)"), generic_params: GenericParamsId(4294967295), has_self_param: false, has_body: true, is_unsafe: true, params: [], is_varargs: false, ret_type: Tuple([]), ast_id: FileAstId::<syntax::ast::generated::nodes::Fn>(2) }
309 "##]], 309 "##]],
310 ); 310 );
311} 311}
@@ -329,9 +329,9 @@ fn trait_attrs() {
329 #[Attrs { entries: Some([Attr { path: ModPath { kind: Plain, segments: [Name(Text("trait_attr"))] }, input: None }]) }] 329 #[Attrs { entries: Some([Attr { path: ModPath { kind: Plain, segments: [Name(Text("trait_attr"))] }, input: None }]) }]
330 Trait { name: Name(Text("Tr")), visibility: RawVisibilityId("pub(self)"), generic_params: GenericParamsId(0), auto: false, items: [Function(Idx::<Function>(0)), Function(Idx::<Function>(1))], ast_id: FileAstId::<syntax::ast::generated::nodes::Trait>(0) } 330 Trait { name: Name(Text("Tr")), visibility: RawVisibilityId("pub(self)"), generic_params: GenericParamsId(0), auto: false, items: [Function(Idx::<Function>(0)), Function(Idx::<Function>(1))], ast_id: FileAstId::<syntax::ast::generated::nodes::Trait>(0) }
331 > #[Attrs { entries: Some([Attr { path: ModPath { kind: Plain, segments: [Name(Text("attr_a"))] }, input: None }]) }] 331 > #[Attrs { entries: Some([Attr { path: ModPath { kind: Plain, segments: [Name(Text("attr_a"))] }, input: None }]) }]
332 > Function { name: Name(Text("a")), visibility: RawVisibilityId("pub(self)"), generic_params: GenericParamsId(4294967295), has_self_param: false, is_unsafe: false, params: [], is_varargs: false, ret_type: Tuple([]), ast_id: FileAstId::<syntax::ast::generated::nodes::Fn>(1) } 332 > Function { name: Name(Text("a")), visibility: RawVisibilityId("pub(self)"), generic_params: GenericParamsId(4294967295), has_self_param: false, has_body: true, is_unsafe: false, params: [], is_varargs: false, ret_type: Tuple([]), ast_id: FileAstId::<syntax::ast::generated::nodes::Fn>(1) }
333 > #[Attrs { entries: Some([Attr { path: ModPath { kind: Plain, segments: [Name(Text("attr_b"))] }, input: None }]) }] 333 > #[Attrs { entries: Some([Attr { path: ModPath { kind: Plain, segments: [Name(Text("attr_b"))] }, input: None }]) }]
334 > Function { name: Name(Text("b")), visibility: RawVisibilityId("pub(self)"), generic_params: GenericParamsId(4294967295), has_self_param: false, is_unsafe: false, params: [], is_varargs: false, ret_type: Tuple([]), ast_id: FileAstId::<syntax::ast::generated::nodes::Fn>(2) } 334 > Function { name: Name(Text("b")), visibility: RawVisibilityId("pub(self)"), generic_params: GenericParamsId(4294967295), has_self_param: false, has_body: true, is_unsafe: false, params: [], is_varargs: false, ret_type: Tuple([]), ast_id: FileAstId::<syntax::ast::generated::nodes::Fn>(2) }
335 "##]], 335 "##]],
336 ); 336 );
337} 337}
@@ -355,9 +355,9 @@ fn impl_attrs() {
355 #[Attrs { entries: Some([Attr { path: ModPath { kind: Plain, segments: [Name(Text("impl_attr"))] }, input: None }]) }] 355 #[Attrs { entries: Some([Attr { path: ModPath { kind: Plain, segments: [Name(Text("impl_attr"))] }, input: None }]) }]
356 Impl { generic_params: GenericParamsId(4294967295), target_trait: None, target_type: Path(Path { type_anchor: None, mod_path: ModPath { kind: Plain, segments: [Name(Text("Ty"))] }, generic_args: [None] }), is_negative: false, items: [Function(Idx::<Function>(0)), Function(Idx::<Function>(1))], ast_id: FileAstId::<syntax::ast::generated::nodes::Impl>(0) } 356 Impl { generic_params: GenericParamsId(4294967295), target_trait: None, target_type: Path(Path { type_anchor: None, mod_path: ModPath { kind: Plain, segments: [Name(Text("Ty"))] }, generic_args: [None] }), is_negative: false, items: [Function(Idx::<Function>(0)), Function(Idx::<Function>(1))], ast_id: FileAstId::<syntax::ast::generated::nodes::Impl>(0) }
357 > #[Attrs { entries: Some([Attr { path: ModPath { kind: Plain, segments: [Name(Text("attr_a"))] }, input: None }]) }] 357 > #[Attrs { entries: Some([Attr { path: ModPath { kind: Plain, segments: [Name(Text("attr_a"))] }, input: None }]) }]
358 > Function { name: Name(Text("a")), visibility: RawVisibilityId("pub(self)"), generic_params: GenericParamsId(4294967295), has_self_param: false, is_unsafe: false, params: [], is_varargs: false, ret_type: Tuple([]), ast_id: FileAstId::<syntax::ast::generated::nodes::Fn>(1) } 358 > Function { name: Name(Text("a")), visibility: RawVisibilityId("pub(self)"), generic_params: GenericParamsId(4294967295), has_self_param: false, has_body: true, is_unsafe: false, params: [], is_varargs: false, ret_type: Tuple([]), ast_id: FileAstId::<syntax::ast::generated::nodes::Fn>(1) }
359 > #[Attrs { entries: Some([Attr { path: ModPath { kind: Plain, segments: [Name(Text("attr_b"))] }, input: None }]) }] 359 > #[Attrs { entries: Some([Attr { path: ModPath { kind: Plain, segments: [Name(Text("attr_b"))] }, input: None }]) }]
360 > Function { name: Name(Text("b")), visibility: RawVisibilityId("pub(self)"), generic_params: GenericParamsId(4294967295), has_self_param: false, is_unsafe: false, params: [], is_varargs: false, ret_type: Tuple([]), ast_id: FileAstId::<syntax::ast::generated::nodes::Fn>(2) } 360 > Function { name: Name(Text("b")), visibility: RawVisibilityId("pub(self)"), generic_params: GenericParamsId(4294967295), has_self_param: false, has_body: true, is_unsafe: false, params: [], is_varargs: false, ret_type: Tuple([]), ast_id: FileAstId::<syntax::ast::generated::nodes::Fn>(2) }
361 "##]], 361 "##]],
362 ); 362 );
363} 363}
@@ -408,13 +408,13 @@ fn inner_item_attrs() {
408 inner attrs: Attrs { entries: None } 408 inner attrs: Attrs { entries: None }
409 409
410 top-level items: 410 top-level items:
411 Function { name: Name(Text("foo")), visibility: RawVisibilityId("pub(self)"), generic_params: GenericParamsId(4294967295), has_self_param: false, is_unsafe: false, params: [], is_varargs: false, ret_type: Tuple([]), ast_id: FileAstId::<syntax::ast::generated::nodes::Fn>(0) } 411 Function { name: Name(Text("foo")), visibility: RawVisibilityId("pub(self)"), generic_params: GenericParamsId(4294967295), has_self_param: false, has_body: true, is_unsafe: false, params: [], is_varargs: false, ret_type: Tuple([]), ast_id: FileAstId::<syntax::ast::generated::nodes::Fn>(0) }
412 412
413 inner items: 413 inner items:
414 414
415 for AST FileAstId::<syntax::ast::generated::nodes::Item>(1): 415 for AST FileAstId::<syntax::ast::generated::nodes::Item>(1):
416 #[Attrs { entries: Some([Attr { path: ModPath { kind: Plain, segments: [Name(Text("on_inner"))] }, input: None }]) }] 416 #[Attrs { entries: Some([Attr { path: ModPath { kind: Plain, segments: [Name(Text("on_inner"))] }, input: None }]) }]
417 Function { name: Name(Text("inner")), visibility: RawVisibilityId("pub(self)"), generic_params: GenericParamsId(4294967295), has_self_param: false, is_unsafe: false, params: [], is_varargs: false, ret_type: Tuple([]), ast_id: FileAstId::<syntax::ast::generated::nodes::Fn>(1) } 417 Function { name: Name(Text("inner")), visibility: RawVisibilityId("pub(self)"), generic_params: GenericParamsId(4294967295), has_self_param: false, has_body: true, is_unsafe: false, params: [], is_varargs: false, ret_type: Tuple([]), ast_id: FileAstId::<syntax::ast::generated::nodes::Fn>(1) }
418 418
419 "##]], 419 "##]],
420 ); 420 );
diff --git a/crates/hir_ty/Cargo.toml b/crates/hir_ty/Cargo.toml
index 0f3c85926..e9c62c6aa 100644
--- a/crates/hir_ty/Cargo.toml
+++ b/crates/hir_ty/Cargo.toml
@@ -17,9 +17,9 @@ ena = "0.14.0"
17log = "0.4.8" 17log = "0.4.8"
18rustc-hash = "1.1.0" 18rustc-hash = "1.1.0"
19scoped-tls = "1" 19scoped-tls = "1"
20chalk-solve = "0.32" 20chalk-solve = "0.33"
21chalk-ir = "0.32" 21chalk-ir = "0.33"
22chalk-recursive = "0.32" 22chalk-recursive = "0.33"
23 23
24stdx = { path = "../stdx", version = "0.0.0" } 24stdx = { path = "../stdx", version = "0.0.0" }
25hir_def = { path = "../hir_def", version = "0.0.0" } 25hir_def = { path = "../hir_def", version = "0.0.0" }
diff --git a/crates/ide/src/completion/complete_postfix/format_like.rs b/crates/ide/src/completion/complete_postfix/format_like.rs
index 81c33bf3a..50d1e5c81 100644
--- a/crates/ide/src/completion/complete_postfix/format_like.rs
+++ b/crates/ide/src/completion/complete_postfix/format_like.rs
@@ -25,6 +25,7 @@ static KINDS: &[(&str, &str)] = &[
25 ("fmt", "format!"), 25 ("fmt", "format!"),
26 ("panic", "panic!"), 26 ("panic", "panic!"),
27 ("println", "println!"), 27 ("println", "println!"),
28 ("eprintln", "eprintln!"),
28 ("logd", "log::debug!"), 29 ("logd", "log::debug!"),
29 ("logt", "log::trace!"), 30 ("logt", "log::trace!"),
30 ("logi", "log::info!"), 31 ("logi", "log::info!"),
@@ -259,6 +260,7 @@ mod tests {
259 fn test_into_suggestion() { 260 fn test_into_suggestion() {
260 let test_vector = &[ 261 let test_vector = &[
261 ("println!", "{}", r#"println!("{}", $1)"#), 262 ("println!", "{}", r#"println!("{}", $1)"#),
263 ("eprintln!", "{}", r#"eprintln!("{}", $1)"#),
262 ( 264 (
263 "log::info!", 265 "log::info!",
264 "{} {expr} {} {2 + 2}", 266 "{} {expr} {} {2 + 2}",
diff --git a/crates/ide/src/link_rewrite.rs b/crates/ide/src/doc_links.rs
index c317a2379..06af36b73 100644
--- a/crates/ide/src/link_rewrite.rs
+++ b/crates/ide/src/doc_links.rs
@@ -1,13 +1,27 @@
1//! Resolves and rewrites links in markdown documentation. 1//! Resolves and rewrites links in markdown documentation.
2//!
3//! Most of the implementation can be found in [`hir::doc_links`].
4 2
5use hir::{Adt, Crate, HasAttrs, ModuleDef}; 3use std::iter::once;
6use ide_db::{defs::Definition, RootDatabase}; 4
5use itertools::Itertools;
7use pulldown_cmark::{CowStr, Event, LinkType, Options, Parser, Tag}; 6use pulldown_cmark::{CowStr, Event, LinkType, Options, Parser, Tag};
8use pulldown_cmark_to_cmark::{cmark_with_options, Options as CmarkOptions}; 7use pulldown_cmark_to_cmark::{cmark_with_options, Options as CmarkOptions};
9use url::Url; 8use url::Url;
10 9
10use hir::{
11 db::{DefDatabase, HirDatabase},
12 Adt, AsAssocItem, AsName, AssocItem, AssocItemContainer, Crate, Field, HasAttrs, ItemInNs,
13 ModuleDef,
14};
15use ide_db::{
16 defs::{classify_name, classify_name_ref, Definition},
17 RootDatabase,
18};
19use syntax::{ast, match_ast, AstNode, SyntaxKind::*, SyntaxToken, TokenAtOffset, T};
20
21use crate::{FilePosition, Semantics};
22
23pub type DocumentationLink = String;
24
11/// Rewrite documentation links in markdown to point to an online host (e.g. docs.rs) 25/// Rewrite documentation links in markdown to point to an online host (e.g. docs.rs)
12pub fn rewrite_links(db: &RootDatabase, markdown: &str, definition: &Definition) -> String { 26pub fn rewrite_links(db: &RootDatabase, markdown: &str, definition: &Definition) -> String {
13 let doc = Parser::new_with_broken_link_callback( 27 let doc = Parser::new_with_broken_link_callback(
@@ -80,6 +94,70 @@ pub fn remove_links(markdown: &str) -> String {
80 out 94 out
81} 95}
82 96
97// FIXME:
98// BUG: For Option::Some
99// Returns https://doc.rust-lang.org/nightly/core/prelude/v1/enum.Option.html#variant.Some
100// Instead of https://doc.rust-lang.org/nightly/core/option/enum.Option.html
101//
102// This should cease to be a problem if RFC2988 (Stable Rustdoc URLs) is implemented
103// https://github.com/rust-lang/rfcs/pull/2988
104fn get_doc_link(db: &RootDatabase, definition: Definition) -> Option<String> {
105 // Get the outermost definition for the moduledef. This is used to resolve the public path to the type,
106 // then we can join the method, field, etc onto it if required.
107 let target_def: ModuleDef = match definition {
108 Definition::ModuleDef(moddef) => match moddef {
109 ModuleDef::Function(f) => f
110 .as_assoc_item(db)
111 .and_then(|assoc| match assoc.container(db) {
112 AssocItemContainer::Trait(t) => Some(t.into()),
113 AssocItemContainer::ImplDef(impld) => {
114 impld.target_ty(db).as_adt().map(|adt| adt.into())
115 }
116 })
117 .unwrap_or_else(|| f.clone().into()),
118 moddef => moddef,
119 },
120 Definition::Field(f) => f.parent_def(db).into(),
121 // FIXME: Handle macros
122 _ => return None,
123 };
124
125 let ns = ItemInNs::from(target_def.clone());
126
127 let module = definition.module(db)?;
128 let krate = module.krate();
129 let import_map = db.import_map(krate.into());
130 let base = once(krate.declaration_name(db)?.to_string())
131 .chain(import_map.path_of(ns)?.segments.iter().map(|name| name.to_string()))
132 .join("/");
133
134 let filename = get_symbol_filename(db, &target_def);
135 let fragment = match definition {
136 Definition::ModuleDef(moddef) => match moddef {
137 ModuleDef::Function(f) => {
138 get_symbol_fragment(db, &FieldOrAssocItem::AssocItem(AssocItem::Function(f)))
139 }
140 ModuleDef::Const(c) => {
141 get_symbol_fragment(db, &FieldOrAssocItem::AssocItem(AssocItem::Const(c)))
142 }
143 ModuleDef::TypeAlias(ty) => {
144 get_symbol_fragment(db, &FieldOrAssocItem::AssocItem(AssocItem::TypeAlias(ty)))
145 }
146 _ => None,
147 },
148 Definition::Field(field) => get_symbol_fragment(db, &FieldOrAssocItem::Field(field)),
149 _ => None,
150 };
151
152 get_doc_url(db, &krate)
153 .and_then(|url| url.join(&base).ok())
154 .and_then(|url| filename.as_deref().and_then(|f| url.join(f).ok()))
155 .and_then(
156 |url| if let Some(fragment) = fragment { url.join(&fragment).ok() } else { Some(url) },
157 )
158 .map(|url| url.into_string())
159}
160
83fn rewrite_intra_doc_link( 161fn rewrite_intra_doc_link(
84 db: &RootDatabase, 162 db: &RootDatabase,
85 def: Definition, 163 def: Definition,
@@ -138,7 +216,29 @@ fn rewrite_url_link(db: &RootDatabase, def: ModuleDef, target: &str) -> Option<S
138 .map(|url| url.into_string()) 216 .map(|url| url.into_string())
139} 217}
140 218
141// Rewrites a markdown document, resolving links using `callback` and additionally striping prefixes/suffixes on link titles. 219/// Retrieve a link to documentation for the given symbol.
220pub(crate) fn external_docs(
221 db: &RootDatabase,
222 position: &FilePosition,
223) -> Option<DocumentationLink> {
224 let sema = Semantics::new(db);
225 let file = sema.parse(position.file_id).syntax().clone();
226 let token = pick_best(file.token_at_offset(position.offset))?;
227 let token = sema.descend_into_macros(token);
228
229 let node = token.parent();
230 let definition = match_ast! {
231 match node {
232 ast::NameRef(name_ref) => classify_name_ref(&sema, &name_ref).map(|d| d.definition(sema.db)),
233 ast::Name(name) => classify_name(&sema, &name).map(|d| d.definition(sema.db)),
234 _ => None,
235 }
236 };
237
238 get_doc_link(db, definition?)
239}
240
241/// Rewrites a markdown document, applying 'callback' to each link.
142fn map_links<'e>( 242fn map_links<'e>(
143 events: impl Iterator<Item = Event<'e>>, 243 events: impl Iterator<Item = Event<'e>>,
144 callback: impl Fn(&str, &str) -> (String, String), 244 callback: impl Fn(&str, &str) -> (String, String),
@@ -239,6 +339,12 @@ fn ns_from_intra_spec(s: &str) -> Option<hir::Namespace> {
239 .next() 339 .next()
240} 340}
241 341
342/// Get the root URL for the documentation of a crate.
343///
344/// ```
345/// https://doc.rust-lang.org/std/iter/trait.Iterator.html#tymethod.next
346/// ^^^^^^^^^^^^^^^^^^^^^^^^^^
347/// ```
242fn get_doc_url(db: &RootDatabase, krate: &Crate) -> Option<Url> { 348fn get_doc_url(db: &RootDatabase, krate: &Crate) -> Option<Url> {
243 krate 349 krate
244 .get_html_root_url(db) 350 .get_html_root_url(db)
@@ -255,8 +361,11 @@ fn get_doc_url(db: &RootDatabase, krate: &Crate) -> Option<Url> {
255 361
256/// Get the filename and extension generated for a symbol by rustdoc. 362/// Get the filename and extension generated for a symbol by rustdoc.
257/// 363///
258/// Example: `struct.Shard.html` 364/// ```
259fn get_symbol_filename(db: &RootDatabase, definition: &ModuleDef) -> Option<String> { 365/// https://doc.rust-lang.org/std/iter/trait.Iterator.html#tymethod.next
366/// ^^^^^^^^^^^^^^^^^^^
367/// ```
368fn get_symbol_filename(db: &dyn HirDatabase, definition: &ModuleDef) -> Option<String> {
260 Some(match definition { 369 Some(match definition {
261 ModuleDef::Adt(adt) => match adt { 370 ModuleDef::Adt(adt) => match adt {
262 Adt::Struct(s) => format!("struct.{}.html", s.name(db)), 371 Adt::Struct(s) => format!("struct.{}.html", s.name(db)),
@@ -266,7 +375,7 @@ fn get_symbol_filename(db: &RootDatabase, definition: &ModuleDef) -> Option<Stri
266 ModuleDef::Module(_) => "index.html".to_string(), 375 ModuleDef::Module(_) => "index.html".to_string(),
267 ModuleDef::Trait(t) => format!("trait.{}.html", t.name(db)), 376 ModuleDef::Trait(t) => format!("trait.{}.html", t.name(db)),
268 ModuleDef::TypeAlias(t) => format!("type.{}.html", t.name(db)), 377 ModuleDef::TypeAlias(t) => format!("type.{}.html", t.name(db)),
269 ModuleDef::BuiltinType(t) => format!("primitive.{}.html", t), 378 ModuleDef::BuiltinType(t) => format!("primitive.{}.html", t.as_name()),
270 ModuleDef::Function(f) => format!("fn.{}.html", f.name(db)), 379 ModuleDef::Function(f) => format!("fn.{}.html", f.name(db)),
271 ModuleDef::EnumVariant(ev) => { 380 ModuleDef::EnumVariant(ev) => {
272 format!("enum.{}.html#variant.{}", ev.parent_enum(db).name(db), ev.name(db)) 381 format!("enum.{}.html#variant.{}", ev.parent_enum(db).name(db), ev.name(db))
@@ -275,3 +384,163 @@ fn get_symbol_filename(db: &RootDatabase, definition: &ModuleDef) -> Option<Stri
275 ModuleDef::Static(s) => format!("static.{}.html", s.name(db)?), 384 ModuleDef::Static(s) => format!("static.{}.html", s.name(db)?),
276 }) 385 })
277} 386}
387
388enum FieldOrAssocItem {
389 Field(Field),
390 AssocItem(AssocItem),
391}
392
393/// Get the fragment required to link to a specific field, method, associated type, or associated constant.
394///
395/// ```
396/// https://doc.rust-lang.org/std/iter/trait.Iterator.html#tymethod.next
397/// ^^^^^^^^^^^^^^
398/// ```
399fn get_symbol_fragment(db: &dyn HirDatabase, field_or_assoc: &FieldOrAssocItem) -> Option<String> {
400 Some(match field_or_assoc {
401 FieldOrAssocItem::Field(field) => format!("#structfield.{}", field.name(db)),
402 FieldOrAssocItem::AssocItem(assoc) => match assoc {
403 AssocItem::Function(function) => {
404 let is_trait_method = matches!(
405 function.as_assoc_item(db).map(|assoc| assoc.container(db)),
406 Some(AssocItemContainer::Trait(..))
407 );
408 // This distinction may get more complicated when specialisation is available.
409 // Rustdoc makes this decision based on whether a method 'has defaultness'.
410 // Currently this is only the case for provided trait methods.
411 if is_trait_method && !function.has_body(db) {
412 format!("#tymethod.{}", function.name(db))
413 } else {
414 format!("#method.{}", function.name(db))
415 }
416 }
417 AssocItem::Const(constant) => format!("#associatedconstant.{}", constant.name(db)?),
418 AssocItem::TypeAlias(ty) => format!("#associatedtype.{}", ty.name(db)),
419 },
420 })
421}
422
423fn pick_best(tokens: TokenAtOffset<SyntaxToken>) -> Option<SyntaxToken> {
424 return tokens.max_by_key(priority);
425 fn priority(n: &SyntaxToken) -> usize {
426 match n.kind() {
427 IDENT | INT_NUMBER => 3,
428 T!['('] | T![')'] => 2,
429 kind if kind.is_trivia() => 0,
430 _ => 1,
431 }
432 }
433}
434
435#[cfg(test)]
436mod tests {
437 use expect_test::{expect, Expect};
438
439 use crate::fixture;
440
441 fn check(ra_fixture: &str, expect: Expect) {
442 let (analysis, position) = fixture::position(ra_fixture);
443 let url = analysis.external_docs(position).unwrap().expect("could not find url for symbol");
444
445 expect.assert_eq(&url)
446 }
447
448 #[test]
449 fn test_doc_url_struct() {
450 check(
451 r#"
452pub struct Fo<|>o;
453"#,
454 expect![[r#"https://docs.rs/test/*/test/struct.Foo.html"#]],
455 );
456 }
457
458 #[test]
459 fn test_doc_url_fn() {
460 check(
461 r#"
462pub fn fo<|>o() {}
463"#,
464 expect![[r##"https://docs.rs/test/*/test/fn.foo.html#method.foo"##]],
465 );
466 }
467
468 #[test]
469 fn test_doc_url_inherent_method() {
470 check(
471 r#"
472pub struct Foo;
473
474impl Foo {
475 pub fn met<|>hod() {}
476}
477
478"#,
479 expect![[r##"https://docs.rs/test/*/test/struct.Foo.html#method.method"##]],
480 );
481 }
482
483 #[test]
484 fn test_doc_url_trait_provided_method() {
485 check(
486 r#"
487pub trait Bar {
488 fn met<|>hod() {}
489}
490
491"#,
492 expect![[r##"https://docs.rs/test/*/test/trait.Bar.html#method.method"##]],
493 );
494 }
495
496 #[test]
497 fn test_doc_url_trait_required_method() {
498 check(
499 r#"
500pub trait Foo {
501 fn met<|>hod();
502}
503
504"#,
505 expect![[r##"https://docs.rs/test/*/test/trait.Foo.html#tymethod.method"##]],
506 );
507 }
508
509 #[test]
510 fn test_doc_url_field() {
511 check(
512 r#"
513pub struct Foo {
514 pub fie<|>ld: ()
515}
516
517"#,
518 expect![[r##"https://docs.rs/test/*/test/struct.Foo.html#structfield.field"##]],
519 );
520 }
521
522 // FIXME: ImportMap will return re-export paths instead of public module
523 // paths. The correct path to documentation will never be a re-export.
524 // This problem stops us from resolving stdlib items included in the prelude
525 // such as `Option::Some` correctly.
526 #[ignore = "ImportMap may return re-exports"]
527 #[test]
528 fn test_reexport_order() {
529 check(
530 r#"
531pub mod wrapper {
532 pub use module::Item;
533
534 pub mod module {
535 pub struct Item;
536 }
537}
538
539fn foo() {
540 let bar: wrapper::It<|>em;
541}
542 "#,
543 expect![[r#"https://docs.rs/test/*/test/wrapper/module/struct.Item.html"#]],
544 )
545 }
546}
diff --git a/crates/ide/src/hover.rs b/crates/ide/src/hover.rs
index 53265488e..6290b35bd 100644
--- a/crates/ide/src/hover.rs
+++ b/crates/ide/src/hover.rs
@@ -14,7 +14,7 @@ use test_utils::mark;
14 14
15use crate::{ 15use crate::{
16 display::{macro_label, ShortLabel, ToNav, TryToNav}, 16 display::{macro_label, ShortLabel, ToNav, TryToNav},
17 link_rewrite::{remove_links, rewrite_links}, 17 doc_links::{remove_links, rewrite_links},
18 markdown_remove::remove_markdown, 18 markdown_remove::remove_markdown,
19 markup::Markup, 19 markup::Markup,
20 runnables::runnable, 20 runnables::runnable,
diff --git a/crates/ide/src/lib.rs b/crates/ide/src/lib.rs
index 57f3581b6..686cee3a1 100644
--- a/crates/ide/src/lib.rs
+++ b/crates/ide/src/lib.rs
@@ -45,8 +45,8 @@ mod status;
45mod syntax_highlighting; 45mod syntax_highlighting;
46mod syntax_tree; 46mod syntax_tree;
47mod typing; 47mod typing;
48mod link_rewrite;
49mod markdown_remove; 48mod markdown_remove;
49mod doc_links;
50 50
51use std::sync::Arc; 51use std::sync::Arc;
52 52
@@ -77,7 +77,9 @@ pub use crate::{
77 hover::{HoverAction, HoverConfig, HoverGotoTypeData, HoverResult}, 77 hover::{HoverAction, HoverConfig, HoverGotoTypeData, HoverResult},
78 inlay_hints::{InlayHint, InlayHintsConfig, InlayKind}, 78 inlay_hints::{InlayHint, InlayHintsConfig, InlayKind},
79 markup::Markup, 79 markup::Markup,
80 references::{Declaration, Reference, ReferenceAccess, ReferenceKind, ReferenceSearchResult}, 80 references::{
81 Declaration, Reference, ReferenceAccess, ReferenceKind, ReferenceSearchResult, RenameError,
82 },
81 runnables::{Runnable, RunnableKind, TestId}, 83 runnables::{Runnable, RunnableKind, TestId},
82 syntax_highlighting::{ 84 syntax_highlighting::{
83 Highlight, HighlightModifier, HighlightModifiers, HighlightTag, HighlightedRange, 85 Highlight, HighlightModifier, HighlightModifiers, HighlightTag, HighlightedRange,
@@ -382,6 +384,14 @@ impl Analysis {
382 self.with_db(|db| hover::hover(db, position, links_in_hover, markdown)) 384 self.with_db(|db| hover::hover(db, position, links_in_hover, markdown))
383 } 385 }
384 386
387 /// Return URL(s) for the documentation of the symbol under the cursor.
388 pub fn external_docs(
389 &self,
390 position: FilePosition,
391 ) -> Cancelable<Option<doc_links::DocumentationLink>> {
392 self.with_db(|db| doc_links::external_docs(db, &position))
393 }
394
385 /// Computes parameter information for the given call expression. 395 /// Computes parameter information for the given call expression.
386 pub fn call_info(&self, position: FilePosition) -> Cancelable<Option<CallInfo>> { 396 pub fn call_info(&self, position: FilePosition) -> Cancelable<Option<CallInfo>> {
387 self.with_db(|db| call_info::call_info(db, position)) 397 self.with_db(|db| call_info::call_info(db, position))
@@ -490,7 +500,7 @@ impl Analysis {
490 &self, 500 &self,
491 position: FilePosition, 501 position: FilePosition,
492 new_name: &str, 502 new_name: &str,
493 ) -> Cancelable<Option<RangeInfo<SourceChange>>> { 503 ) -> Cancelable<Result<RangeInfo<SourceChange>, RenameError>> {
494 self.with_db(|db| references::rename(db, position, new_name)) 504 self.with_db(|db| references::rename(db, position, new_name))
495 } 505 }
496 506
diff --git a/crates/ide/src/references.rs b/crates/ide/src/references.rs
index 571dd5452..f65a05ea3 100644
--- a/crates/ide/src/references.rs
+++ b/crates/ide/src/references.rs
@@ -26,6 +26,7 @@ use syntax::{
26use crate::{display::TryToNav, FilePosition, FileRange, NavigationTarget, RangeInfo}; 26use crate::{display::TryToNav, FilePosition, FileRange, NavigationTarget, RangeInfo};
27 27
28pub(crate) use self::rename::rename; 28pub(crate) use self::rename::rename;
29pub use self::rename::RenameError;
29 30
30pub use ide_db::search::{Reference, ReferenceAccess, ReferenceKind}; 31pub use ide_db::search::{Reference, ReferenceAccess, ReferenceKind};
31 32
@@ -732,6 +733,30 @@ fn f(e: En) {
732 ); 733 );
733 } 734 }
734 735
736 #[test]
737 fn test_find_all_refs_enum_var_privacy() {
738 check(
739 r#"
740mod m {
741 pub enum En {
742 Variant {
743 field<|>: u8,
744 }
745 }
746}
747
748fn f() -> m::En {
749 m::En::Variant { field: 0 }
750}
751"#,
752 expect![[r#"
753 field RECORD_FIELD FileId(0) 56..65 56..61 Other
754
755 FileId(0) 125..130 Other Read
756 "#]],
757 );
758 }
759
735 fn check(ra_fixture: &str, expect: Expect) { 760 fn check(ra_fixture: &str, expect: Expect) {
736 check_with_scope(ra_fixture, None, expect) 761 check_with_scope(ra_fixture, None, expect)
737 } 762 }
diff --git a/crates/ide/src/references/rename.rs b/crates/ide/src/references/rename.rs
index 8cbe1ae5a..f3b5cfc8c 100644
--- a/crates/ide/src/references/rename.rs
+++ b/crates/ide/src/references/rename.rs
@@ -6,11 +6,16 @@ use ide_db::{
6 defs::{classify_name, classify_name_ref, Definition, NameClass, NameRefClass}, 6 defs::{classify_name, classify_name_ref, Definition, NameClass, NameRefClass},
7 RootDatabase, 7 RootDatabase,
8}; 8};
9use std::convert::TryInto; 9
10use std::{
11 convert::TryInto,
12 error::Error,
13 fmt::{self, Display},
14};
10use syntax::{ 15use syntax::{
11 algo::find_node_at_offset, 16 algo::find_node_at_offset,
12 ast::{self, NameOwner}, 17 ast::{self, NameOwner},
13 lex_single_valid_syntax_kind, match_ast, AstNode, SyntaxKind, SyntaxNode, SyntaxToken, 18 lex_single_syntax_kind, match_ast, AstNode, SyntaxKind, SyntaxNode, SyntaxToken,
14}; 19};
15use test_utils::mark; 20use test_utils::mark;
16use text_edit::TextEdit; 21use text_edit::TextEdit;
@@ -20,17 +25,37 @@ use crate::{
20 SourceChange, SourceFileEdit, TextRange, TextSize, 25 SourceChange, SourceFileEdit, TextRange, TextSize,
21}; 26};
22 27
28#[derive(Debug)]
29pub struct RenameError(pub(crate) String);
30
31impl fmt::Display for RenameError {
32 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
33 Display::fmt(&self.0, f)
34 }
35}
36
37impl Error for RenameError {}
38
23pub(crate) fn rename( 39pub(crate) fn rename(
24 db: &RootDatabase, 40 db: &RootDatabase,
25 position: FilePosition, 41 position: FilePosition,
26 new_name: &str, 42 new_name: &str,
27) -> Option<RangeInfo<SourceChange>> { 43) -> Result<RangeInfo<SourceChange>, RenameError> {
28 let sema = Semantics::new(db); 44 let sema = Semantics::new(db);
29 45
30 match lex_single_valid_syntax_kind(new_name)? { 46 match lex_single_syntax_kind(new_name) {
31 SyntaxKind::IDENT | SyntaxKind::UNDERSCORE => (), 47 Some(res) => match res {
32 SyntaxKind::SELF_KW => return rename_to_self(&sema, position), 48 (SyntaxKind::IDENT, _) => (),
33 _ => return None, 49 (SyntaxKind::UNDERSCORE, _) => (),
50 (SyntaxKind::SELF_KW, _) => return rename_to_self(&sema, position),
51 (_, Some(syntax_error)) => {
52 return Err(RenameError(format!("Invalid name `{}`: {}", new_name, syntax_error)))
53 }
54 (_, None) => {
55 return Err(RenameError(format!("Invalid name `{}`: not an identifier", new_name)))
56 }
57 },
58 None => return Err(RenameError(format!("Invalid name `{}`: not an identifier", new_name))),
34 } 59 }
35 60
36 let source_file = sema.parse(position.file_id); 61 let source_file = sema.parse(position.file_id);
@@ -103,7 +128,7 @@ fn rename_mod(
103 position: FilePosition, 128 position: FilePosition,
104 module: Module, 129 module: Module,
105 new_name: &str, 130 new_name: &str,
106) -> Option<RangeInfo<SourceChange>> { 131) -> Result<RangeInfo<SourceChange>, RenameError> {
107 let mut source_file_edits = Vec::new(); 132 let mut source_file_edits = Vec::new();
108 let mut file_system_edits = Vec::new(); 133 let mut file_system_edits = Vec::new();
109 134
@@ -125,7 +150,7 @@ fn rename_mod(
125 150
126 if let Some(src) = module.declaration_source(sema.db) { 151 if let Some(src) = module.declaration_source(sema.db) {
127 let file_id = src.file_id.original_file(sema.db); 152 let file_id = src.file_id.original_file(sema.db);
128 let name = src.value.name()?; 153 let name = src.value.name().unwrap();
129 let edit = SourceFileEdit { 154 let edit = SourceFileEdit {
130 file_id, 155 file_id,
131 edit: TextEdit::replace(name.syntax().text_range(), new_name.into()), 156 edit: TextEdit::replace(name.syntax().text_range(), new_name.into()),
@@ -133,35 +158,40 @@ fn rename_mod(
133 source_file_edits.push(edit); 158 source_file_edits.push(edit);
134 } 159 }
135 160
136 let RangeInfo { range, info: refs } = find_all_refs(sema, position, None)?; 161 let RangeInfo { range, info: refs } = find_all_refs(sema, position, None)
162 .ok_or_else(|| RenameError("No references found at position".to_string()))?;
137 let ref_edits = refs 163 let ref_edits = refs
138 .references 164 .references
139 .into_iter() 165 .into_iter()
140 .map(|reference| source_edit_from_reference(reference, new_name)); 166 .map(|reference| source_edit_from_reference(reference, new_name));
141 source_file_edits.extend(ref_edits); 167 source_file_edits.extend(ref_edits);
142 168
143 Some(RangeInfo::new(range, SourceChange::from_edits(source_file_edits, file_system_edits))) 169 Ok(RangeInfo::new(range, SourceChange::from_edits(source_file_edits, file_system_edits)))
144} 170}
145 171
146fn rename_to_self( 172fn rename_to_self(
147 sema: &Semantics<RootDatabase>, 173 sema: &Semantics<RootDatabase>,
148 position: FilePosition, 174 position: FilePosition,
149) -> Option<RangeInfo<SourceChange>> { 175) -> Result<RangeInfo<SourceChange>, RenameError> {
150 let source_file = sema.parse(position.file_id); 176 let source_file = sema.parse(position.file_id);
151 let syn = source_file.syntax(); 177 let syn = source_file.syntax();
152 178
153 let fn_def = find_node_at_offset::<ast::Fn>(syn, position.offset)?; 179 let fn_def = find_node_at_offset::<ast::Fn>(syn, position.offset)
154 let params = fn_def.param_list()?; 180 .ok_or_else(|| RenameError("No surrounding method declaration found".to_string()))?;
181 let params =
182 fn_def.param_list().ok_or_else(|| RenameError("Method has no parameters".to_string()))?;
155 if params.self_param().is_some() { 183 if params.self_param().is_some() {
156 return None; // method already has self param 184 return Err(RenameError("Method already has a self parameter".to_string()));
157 } 185 }
158 let first_param = params.params().next()?; 186 let first_param =
187 params.params().next().ok_or_else(|| RenameError("Method has no parameters".into()))?;
159 let mutable = match first_param.ty() { 188 let mutable = match first_param.ty() {
160 Some(ast::Type::RefType(rt)) => rt.mut_token().is_some(), 189 Some(ast::Type::RefType(rt)) => rt.mut_token().is_some(),
161 _ => return None, // not renaming other types 190 _ => return Err(RenameError("Not renaming other types".to_string())),
162 }; 191 };
163 192
164 let RangeInfo { range, info: refs } = find_all_refs(sema, position, None)?; 193 let RangeInfo { range, info: refs } = find_all_refs(sema, position, None)
194 .ok_or_else(|| RenameError("No reference found at position".to_string()))?;
165 195
166 let param_range = first_param.syntax().text_range(); 196 let param_range = first_param.syntax().text_range();
167 let (param_ref, usages): (Vec<Reference>, Vec<Reference>) = refs 197 let (param_ref, usages): (Vec<Reference>, Vec<Reference>) = refs
@@ -169,7 +199,7 @@ fn rename_to_self(
169 .partition(|reference| param_range.intersect(reference.file_range.range).is_some()); 199 .partition(|reference| param_range.intersect(reference.file_range.range).is_some());
170 200
171 if param_ref.is_empty() { 201 if param_ref.is_empty() {
172 return None; 202 return Err(RenameError("Parameter to rename not found".to_string()));
173 } 203 }
174 204
175 let mut edits = usages 205 let mut edits = usages
@@ -185,7 +215,7 @@ fn rename_to_self(
185 ), 215 ),
186 }); 216 });
187 217
188 Some(RangeInfo::new(range, SourceChange::from(edits))) 218 Ok(RangeInfo::new(range, SourceChange::from(edits)))
189} 219}
190 220
191fn text_edit_from_self_param( 221fn text_edit_from_self_param(
@@ -216,12 +246,13 @@ fn rename_self_to_param(
216 position: FilePosition, 246 position: FilePosition,
217 self_token: SyntaxToken, 247 self_token: SyntaxToken,
218 new_name: &str, 248 new_name: &str,
219) -> Option<RangeInfo<SourceChange>> { 249) -> Result<RangeInfo<SourceChange>, RenameError> {
220 let source_file = sema.parse(position.file_id); 250 let source_file = sema.parse(position.file_id);
221 let syn = source_file.syntax(); 251 let syn = source_file.syntax();
222 252
223 let text = sema.db.file_text(position.file_id); 253 let text = sema.db.file_text(position.file_id);
224 let fn_def = find_node_at_offset::<ast::Fn>(syn, position.offset)?; 254 let fn_def = find_node_at_offset::<ast::Fn>(syn, position.offset)
255 .ok_or_else(|| RenameError("No surrounding method declaration found".to_string()))?;
225 let search_range = fn_def.syntax().text_range(); 256 let search_range = fn_def.syntax().text_range();
226 257
227 let mut edits: Vec<SourceFileEdit> = vec![]; 258 let mut edits: Vec<SourceFileEdit> = vec![];
@@ -235,7 +266,8 @@ fn rename_self_to_param(
235 syn.token_at_offset(offset).find(|t| t.kind() == SyntaxKind::SELF_KW) 266 syn.token_at_offset(offset).find(|t| t.kind() == SyntaxKind::SELF_KW)
236 { 267 {
237 let edit = if let Some(ref self_param) = ast::SelfParam::cast(usage.parent()) { 268 let edit = if let Some(ref self_param) = ast::SelfParam::cast(usage.parent()) {
238 text_edit_from_self_param(syn, self_param, new_name)? 269 text_edit_from_self_param(syn, self_param, new_name)
270 .ok_or_else(|| RenameError("No target type found".to_string()))?
239 } else { 271 } else {
240 TextEdit::replace(usage.text_range(), String::from(new_name)) 272 TextEdit::replace(usage.text_range(), String::from(new_name))
241 }; 273 };
@@ -246,15 +278,18 @@ fn rename_self_to_param(
246 let range = ast::SelfParam::cast(self_token.parent()) 278 let range = ast::SelfParam::cast(self_token.parent())
247 .map_or(self_token.text_range(), |p| p.syntax().text_range()); 279 .map_or(self_token.text_range(), |p| p.syntax().text_range());
248 280
249 Some(RangeInfo::new(range, SourceChange::from(edits))) 281 Ok(RangeInfo::new(range, SourceChange::from(edits)))
250} 282}
251 283
252fn rename_reference( 284fn rename_reference(
253 sema: &Semantics<RootDatabase>, 285 sema: &Semantics<RootDatabase>,
254 position: FilePosition, 286 position: FilePosition,
255 new_name: &str, 287 new_name: &str,
256) -> Option<RangeInfo<SourceChange>> { 288) -> Result<RangeInfo<SourceChange>, RenameError> {
257 let RangeInfo { range, info: refs } = find_all_refs(sema, position, None)?; 289 let RangeInfo { range, info: refs } = match find_all_refs(sema, position, None) {
290 Some(range_info) => range_info,
291 None => return Err(RenameError("No references found at position".to_string())),
292 };
258 293
259 let edit = refs 294 let edit = refs
260 .into_iter() 295 .into_iter()
@@ -262,10 +297,10 @@ fn rename_reference(
262 .collect::<Vec<_>>(); 297 .collect::<Vec<_>>();
263 298
264 if edit.is_empty() { 299 if edit.is_empty() {
265 return None; 300 return Err(RenameError("No references found at position".to_string()));
266 } 301 }
267 302
268 Some(RangeInfo::new(range, SourceChange::from(edit))) 303 Ok(RangeInfo::new(range, SourceChange::from(edit)))
269} 304}
270 305
271#[cfg(test)] 306#[cfg(test)]
@@ -280,25 +315,45 @@ mod tests {
280 fn check(new_name: &str, ra_fixture_before: &str, ra_fixture_after: &str) { 315 fn check(new_name: &str, ra_fixture_before: &str, ra_fixture_after: &str) {
281 let ra_fixture_after = &trim_indent(ra_fixture_after); 316 let ra_fixture_after = &trim_indent(ra_fixture_after);
282 let (analysis, position) = fixture::position(ra_fixture_before); 317 let (analysis, position) = fixture::position(ra_fixture_before);
283 let source_change = analysis.rename(position, new_name).unwrap(); 318 let rename_result = analysis
284 let mut text_edit_builder = TextEdit::builder(); 319 .rename(position, new_name)
285 let mut file_id: Option<FileId> = None; 320 .unwrap_or_else(|err| panic!("Rename to '{}' was cancelled: {}", new_name, err));
286 if let Some(change) = source_change { 321 match rename_result {
287 for edit in change.info.source_file_edits { 322 Ok(source_change) => {
288 file_id = Some(edit.file_id); 323 let mut text_edit_builder = TextEdit::builder();
289 for indel in edit.edit.into_iter() { 324 let mut file_id: Option<FileId> = None;
290 text_edit_builder.replace(indel.delete, indel.insert); 325 for edit in source_change.info.source_file_edits {
326 file_id = Some(edit.file_id);
327 for indel in edit.edit.into_iter() {
328 text_edit_builder.replace(indel.delete, indel.insert);
329 }
291 } 330 }
331 let mut result = analysis.file_text(file_id.unwrap()).unwrap().to_string();
332 text_edit_builder.finish().apply(&mut result);
333 assert_eq_text!(ra_fixture_after, &*result);
292 } 334 }
293 } 335 Err(err) => {
294 let mut result = analysis.file_text(file_id.unwrap()).unwrap().to_string(); 336 if ra_fixture_after.starts_with("error:") {
295 text_edit_builder.finish().apply(&mut result); 337 let error_message = ra_fixture_after
296 assert_eq_text!(ra_fixture_after, &*result); 338 .chars()
339 .into_iter()
340 .skip("error:".len())
341 .collect::<String>();
342 assert_eq!(error_message.trim(), err.to_string());
343 return;
344 } else {
345 panic!("Rename to '{}' failed unexpectedly: {}", new_name, err)
346 }
347 }
348 };
297 } 349 }
298 350
299 fn check_expect(new_name: &str, ra_fixture: &str, expect: Expect) { 351 fn check_expect(new_name: &str, ra_fixture: &str, expect: Expect) {
300 let (analysis, position) = fixture::position(ra_fixture); 352 let (analysis, position) = fixture::position(ra_fixture);
301 let source_change = analysis.rename(position, new_name).unwrap().unwrap(); 353 let source_change = analysis
354 .rename(position, new_name)
355 .unwrap()
356 .expect("Expect returned RangeInfo to be Some, but was None");
302 expect.assert_debug_eq(&source_change) 357 expect.assert_debug_eq(&source_change)
303 } 358 }
304 359
@@ -313,11 +368,30 @@ mod tests {
313 } 368 }
314 369
315 #[test] 370 #[test]
316 fn test_rename_to_invalid_identifier() { 371 fn test_rename_to_invalid_identifier1() {
317 let (analysis, position) = fixture::position(r#"fn main() { let i<|> = 1; }"#); 372 check(
318 let new_name = "invalid!"; 373 "invalid!",
319 let source_change = analysis.rename(position, new_name).unwrap(); 374 r#"fn main() { let i<|> = 1; }"#,
320 assert!(source_change.is_none()); 375 "error: Invalid name `invalid!`: not an identifier",
376 );
377 }
378
379 #[test]
380 fn test_rename_to_invalid_identifier2() {
381 check(
382 "multiple tokens",
383 r#"fn main() { let i<|> = 1; }"#,
384 "error: Invalid name `multiple tokens`: not an identifier",
385 );
386 }
387
388 #[test]
389 fn test_rename_to_invalid_identifier3() {
390 check(
391 "let",
392 r#"fn main() { let i<|> = 1; }"#,
393 "error: Invalid name `let`: not an identifier",
394 );
321 } 395 }
322 396
323 #[test] 397 #[test]
@@ -350,6 +424,15 @@ fn main() {
350 } 424 }
351 425
352 #[test] 426 #[test]
427 fn test_rename_unresolved_reference() {
428 check(
429 "new_name",
430 r#"fn main() { let _ = unresolved_ref<|>; }"#,
431 "error: No references found at position",
432 );
433 }
434
435 #[test]
353 fn test_rename_for_macro_args() { 436 fn test_rename_for_macro_args() {
354 check( 437 check(
355 "b", 438 "b",
diff --git a/crates/ide/src/syntax_highlighting/test_data/highlighting.html b/crates/ide/src/syntax_highlighting/test_data/highlighting.html
index 4f47e1be9..0bb0928e4 100644
--- a/crates/ide/src/syntax_highlighting/test_data/highlighting.html
+++ b/crates/ide/src/syntax_highlighting/test_data/highlighting.html
@@ -62,7 +62,7 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
62 62
63<span class="keyword">impl</span> <span class="struct">Foo</span> <span class="punctuation">{</span> 63<span class="keyword">impl</span> <span class="struct">Foo</span> <span class="punctuation">{</span>
64 <span class="keyword">fn</span> <span class="function declaration">baz</span><span class="punctuation">(</span><span class="keyword">mut</span> <span class="self_keyword mutable">self</span><span class="punctuation">,</span> <span class="value_param declaration">f</span><span class="punctuation">:</span> <span class="struct">Foo</span><span class="punctuation">)</span> <span class="operator">-&gt;</span> <span class="builtin_type">i32</span> <span class="punctuation">{</span> 64 <span class="keyword">fn</span> <span class="function declaration">baz</span><span class="punctuation">(</span><span class="keyword">mut</span> <span class="self_keyword mutable">self</span><span class="punctuation">,</span> <span class="value_param declaration">f</span><span class="punctuation">:</span> <span class="struct">Foo</span><span class="punctuation">)</span> <span class="operator">-&gt;</span> <span class="builtin_type">i32</span> <span class="punctuation">{</span>
65 <span class="value_param">f</span><span class="punctuation">.</span><span class="function consuming">baz</span><span class="punctuation">(</span><span class="self_keyword consuming">self</span><span class="punctuation">)</span> 65 <span class="value_param">f</span><span class="punctuation">.</span><span class="function consuming">baz</span><span class="punctuation">(</span><span class="self_keyword mutable consuming">self</span><span class="punctuation">)</span>
66 <span class="punctuation">}</span> 66 <span class="punctuation">}</span>
67 67
68 <span class="keyword">fn</span> <span class="function declaration">qux</span><span class="punctuation">(</span><span class="operator">&</span><span class="keyword">mut</span> <span class="self_keyword mutable">self</span><span class="punctuation">)</span> <span class="punctuation">{</span> 68 <span class="keyword">fn</span> <span class="function declaration">qux</span><span class="punctuation">(</span><span class="operator">&</span><span class="keyword">mut</span> <span class="self_keyword mutable">self</span><span class="punctuation">)</span> <span class="punctuation">{</span>
diff --git a/crates/rust-analyzer/src/handlers.rs b/crates/rust-analyzer/src/handlers.rs
index 468655f9c..215be850f 100644
--- a/crates/rust-analyzer/src/handlers.rs
+++ b/crates/rust-analyzer/src/handlers.rs
@@ -646,14 +646,9 @@ pub(crate) fn handle_prepare_rename(
646 let _p = profile::span("handle_prepare_rename"); 646 let _p = profile::span("handle_prepare_rename");
647 let position = from_proto::file_position(&snap, params)?; 647 let position = from_proto::file_position(&snap, params)?;
648 648
649 let optional_change = snap.analysis.rename(position, "dummy")?; 649 let change = snap.analysis.rename(position, "dummy")??;
650 let range = match optional_change {
651 None => return Ok(None),
652 Some(it) => it.range,
653 };
654
655 let line_index = snap.analysis.file_line_index(position.file_id)?; 650 let line_index = snap.analysis.file_line_index(position.file_id)?;
656 let range = to_proto::range(&line_index, range); 651 let range = to_proto::range(&line_index, change.range);
657 Ok(Some(PrepareRenameResponse::Range(range))) 652 Ok(Some(PrepareRenameResponse::Range(range)))
658} 653}
659 654
@@ -672,12 +667,8 @@ pub(crate) fn handle_rename(
672 .into()); 667 .into());
673 } 668 }
674 669
675 let optional_change = snap.analysis.rename(position, &*params.new_name)?; 670 let change = snap.analysis.rename(position, &*params.new_name)??;
676 let source_change = match optional_change { 671 let workspace_edit = to_proto::workspace_edit(&snap, change.info)?;
677 None => return Ok(None),
678 Some(it) => it.info,
679 };
680 let workspace_edit = to_proto::workspace_edit(&snap, source_change)?;
681 Ok(Some(workspace_edit)) 672 Ok(Some(workspace_edit))
682} 673}
683 674
@@ -1310,6 +1301,18 @@ pub(crate) fn handle_semantic_tokens_range(
1310 Ok(Some(semantic_tokens.into())) 1301 Ok(Some(semantic_tokens.into()))
1311} 1302}
1312 1303
1304pub(crate) fn handle_open_docs(
1305 snap: GlobalStateSnapshot,
1306 params: lsp_types::TextDocumentPositionParams,
1307) -> Result<Option<lsp_types::Url>> {
1308 let _p = profile::span("handle_open_docs");
1309 let position = from_proto::file_position(&snap, params)?;
1310
1311 let remote = snap.analysis.external_docs(position)?;
1312
1313 Ok(remote.and_then(|remote| Url::parse(&remote).ok()))
1314}
1315
1313fn implementation_title(count: usize) -> String { 1316fn implementation_title(count: usize) -> String {
1314 if count == 1 { 1317 if count == 1 {
1315 "1 implementation".into() 1318 "1 implementation".into()
diff --git a/crates/rust-analyzer/src/lsp_ext.rs b/crates/rust-analyzer/src/lsp_ext.rs
index fee0bb69c..f31f8d900 100644
--- a/crates/rust-analyzer/src/lsp_ext.rs
+++ b/crates/rust-analyzer/src/lsp_ext.rs
@@ -347,3 +347,11 @@ pub struct CommandLink {
347 #[serde(skip_serializing_if = "Option::is_none")] 347 #[serde(skip_serializing_if = "Option::is_none")]
348 pub tooltip: Option<String>, 348 pub tooltip: Option<String>,
349} 349}
350
351pub enum ExternalDocs {}
352
353impl Request for ExternalDocs {
354 type Params = lsp_types::TextDocumentPositionParams;
355 type Result = Option<lsp_types::Url>;
356 const METHOD: &'static str = "experimental/externalDocs";
357}
diff --git a/crates/rust-analyzer/src/main_loop.rs b/crates/rust-analyzer/src/main_loop.rs
index 4b7ac8224..06b38d99c 100644
--- a/crates/rust-analyzer/src/main_loop.rs
+++ b/crates/rust-analyzer/src/main_loop.rs
@@ -384,6 +384,7 @@ impl GlobalState {
384 .on::<lsp_ext::CodeActionRequest>(handlers::handle_code_action)? 384 .on::<lsp_ext::CodeActionRequest>(handlers::handle_code_action)?
385 .on::<lsp_ext::ResolveCodeActionRequest>(handlers::handle_resolve_code_action)? 385 .on::<lsp_ext::ResolveCodeActionRequest>(handlers::handle_resolve_code_action)?
386 .on::<lsp_ext::HoverRequest>(handlers::handle_hover)? 386 .on::<lsp_ext::HoverRequest>(handlers::handle_hover)?
387 .on::<lsp_ext::ExternalDocs>(handlers::handle_open_docs)?
387 .on::<lsp_types::request::OnTypeFormatting>(handlers::handle_on_type_formatting)? 388 .on::<lsp_types::request::OnTypeFormatting>(handlers::handle_on_type_formatting)?
388 .on::<lsp_types::request::DocumentSymbolRequest>(handlers::handle_document_symbol)? 389 .on::<lsp_types::request::DocumentSymbolRequest>(handlers::handle_document_symbol)?
389 .on::<lsp_types::request::WorkspaceSymbol>(handlers::handle_workspace_symbol)? 390 .on::<lsp_types::request::WorkspaceSymbol>(handlers::handle_workspace_symbol)?
diff --git a/crates/stdx/src/macros.rs b/crates/stdx/src/macros.rs
index bf298460f..f5ee3484b 100644
--- a/crates/stdx/src/macros.rs
+++ b/crates/stdx/src/macros.rs
@@ -18,7 +18,13 @@ macro_rules! format_to {
18 }; 18 };
19} 19}
20 20
21// Generates `From` impls for `Enum E { Foo(Foo), Bar(Bar) }` enums 21/// Generates `From` impls for `Enum E { Foo(Foo), Bar(Bar) }` enums
22///
23/// # Example
24///
25/// ```rust
26/// impl_from!(Struct, Union, Enum for Adt);
27/// ```
22#[macro_export] 28#[macro_export]
23macro_rules! impl_from { 29macro_rules! impl_from {
24 ($($variant:ident $(($($sub_variant:ident),*))?),* for $enum:ident) => { 30 ($($variant:ident $(($($sub_variant:ident),*))?),* for $enum:ident) => {
diff --git a/docs/dev/lsp-extensions.md b/docs/dev/lsp-extensions.md
index f1160bb1c..3f861f3e0 100644
--- a/docs/dev/lsp-extensions.md
+++ b/docs/dev/lsp-extensions.md
@@ -129,7 +129,7 @@ As a result of the command call the client will get the respective workspace edi
129 129
130**Server Capability:** `{ "parentModule": boolean }` 130**Server Capability:** `{ "parentModule": boolean }`
131 131
132This request is send from client to server to handle "Goto Parent Module" editor action. 132This request is sent from client to server to handle "Goto Parent Module" editor action.
133 133
134**Method:** `experimental/parentModule` 134**Method:** `experimental/parentModule`
135 135
@@ -163,7 +163,7 @@ mod foo;
163 163
164**Server Capability:** `{ "joinLines": boolean }` 164**Server Capability:** `{ "joinLines": boolean }`
165 165
166This request is send from client to server to handle "Join Lines" editor action. 166This request is sent from client to server to handle "Join Lines" editor action.
167 167
168**Method:** `experimental/joinLines` 168**Method:** `experimental/joinLines`
169 169
@@ -210,7 +210,7 @@ fn main() {
210 210
211**Server Capability:** `{ "onEnter": boolean }` 211**Server Capability:** `{ "onEnter": boolean }`
212 212
213This request is send from client to server to handle <kbd>Enter</kbd> keypress. 213This request is sent from client to server to handle <kbd>Enter</kbd> keypress.
214 214
215**Method:** `experimental/onEnter` 215**Method:** `experimental/onEnter`
216 216
@@ -261,7 +261,7 @@ As proper cursor positioning is raison-d'etat for `onEnter`, it uses `SnippetTex
261 261
262**Server Capability:** `{ "ssr": boolean }` 262**Server Capability:** `{ "ssr": boolean }`
263 263
264This request is send from client to server to handle structural search replace -- automated syntax tree based transformation of the source. 264This request is sent from client to server to handle structural search replace -- automated syntax tree based transformation of the source.
265 265
266**Method:** `experimental/ssr` 266**Method:** `experimental/ssr`
267 267
@@ -303,7 +303,7 @@ SSR with query `foo($a, $b) ==>> ($a).foo($b)` will transform, eg `foo(y + 5, z)
303 303
304**Server Capability:** `{ "matchingBrace": boolean }` 304**Server Capability:** `{ "matchingBrace": boolean }`
305 305
306This request is send from client to server to handle "Matching Brace" editor action. 306This request is sent from client to server to handle "Matching Brace" editor action.
307 307
308**Method:** `experimental/matchingBrace` 308**Method:** `experimental/matchingBrace`
309 309
@@ -348,7 +348,7 @@ Moreover, it would be cool if editors didn't need to implement even basic langua
348 348
349**Server Capability:** `{ "runnables": { "kinds": string[] } }` 349**Server Capability:** `{ "runnables": { "kinds": string[] } }`
350 350
351This request is send from client to server to get the list of things that can be run (tests, binaries, `cargo check -p`). 351This request is sent from client to server to get the list of things that can be run (tests, binaries, `cargo check -p`).
352 352
353**Method:** `experimental/runnables` 353**Method:** `experimental/runnables`
354 354
@@ -386,6 +386,17 @@ rust-analyzer supports only one `kind`, `"cargo"`. The `args` for `"cargo"` look
386} 386}
387``` 387```
388 388
389## Open External Documentation
390
391This request is sent from client to server to get a URL to documentation for the symbol under the cursor, if available.
392
393**Method** `experimental/externalDocs`
394
395**Request:**: `TextDocumentPositionParams`
396
397**Response** `string | null`
398
399
389## Analyzer Status 400## Analyzer Status
390 401
391**Method:** `rust-analyzer/analyzerStatus` 402**Method:** `rust-analyzer/analyzerStatus`
@@ -477,7 +488,7 @@ Expands macro call at a given position.
477 488
478**Method:** `rust-analyzer/inlayHints` 489**Method:** `rust-analyzer/inlayHints`
479 490
480This request is send from client to server to render "inlay hints" -- virtual text inserted into editor to show things like inferred types. 491This request is sent from client to server to render "inlay hints" -- virtual text inserted into editor to show things like inferred types.
481Generally, the client should re-query inlay hints after every modification. 492Generally, the client should re-query inlay hints after every modification.
482Note that we plan to move this request to `experimental/inlayHints`, as it is not really Rust-specific, but the current API is not necessary the right one. 493Note that we plan to move this request to `experimental/inlayHints`, as it is not really Rust-specific, but the current API is not necessary the right one.
483Upstream issue: https://github.com/microsoft/language-server-protocol/issues/956 494Upstream issue: https://github.com/microsoft/language-server-protocol/issues/956
diff --git a/editors/code/package.json b/editors/code/package.json
index 6a712a8a8..4bd3117fc 100644
--- a/editors/code/package.json
+++ b/editors/code/package.json
@@ -182,6 +182,11 @@
182 "command": "rust-analyzer.toggleInlayHints", 182 "command": "rust-analyzer.toggleInlayHints",
183 "title": "Toggle inlay hints", 183 "title": "Toggle inlay hints",
184 "category": "Rust Analyzer" 184 "category": "Rust Analyzer"
185 },
186 {
187 "command": "rust-analyzer.openDocs",
188 "title": "Open docs under cursor",
189 "category": "Rust Analyzer"
185 } 190 }
186 ], 191 ],
187 "keybindings": [ 192 "keybindings": [
@@ -1044,6 +1049,10 @@
1044 { 1049 {
1045 "command": "rust-analyzer.toggleInlayHints", 1050 "command": "rust-analyzer.toggleInlayHints",
1046 "when": "inRustProject" 1051 "when": "inRustProject"
1052 },
1053 {
1054 "command": "rust-analyzer.openDocs",
1055 "when": "inRustProject"
1047 } 1056 }
1048 ] 1057 ]
1049 } 1058 }
diff --git a/editors/code/src/commands.ts b/editors/code/src/commands.ts
index 1a90f1b7d..1445e41d3 100644
--- a/editors/code/src/commands.ts
+++ b/editors/code/src/commands.ts
@@ -419,10 +419,31 @@ export function gotoLocation(ctx: Ctx): Cmd {
419 }; 419 };
420} 420}
421 421
422export function openDocs(ctx: Ctx): Cmd {
423 return async () => {
424
425 const client = ctx.client;
426 const editor = vscode.window.activeTextEditor;
427 if (!editor || !client) {
428 return;
429 };
430
431 const position = editor.selection.active;
432 const textDocument = { uri: editor.document.uri.toString() };
433
434 const doclink = await client.sendRequest(ra.openDocs, { position, textDocument });
435
436 if (doclink != null) {
437 vscode.commands.executeCommand("vscode.open", vscode.Uri.parse(doclink));
438 }
439 };
440
441}
442
422export function resolveCodeAction(ctx: Ctx): Cmd { 443export function resolveCodeAction(ctx: Ctx): Cmd {
423 const client = ctx.client; 444 const client = ctx.client;
424 return async (params: ra.ResolveCodeActionParams) => { 445 return async () => {
425 const item: lc.WorkspaceEdit = await client.sendRequest(ra.resolveCodeAction, params); 446 const item: lc.WorkspaceEdit = await client.sendRequest(ra.resolveCodeAction, null);
426 if (!item) { 447 if (!item) {
427 return; 448 return;
428 } 449 }
diff --git a/editors/code/src/lsp_ext.ts b/editors/code/src/lsp_ext.ts
index f286b68a6..fc8e120b3 100644
--- a/editors/code/src/lsp_ext.ts
+++ b/editors/code/src/lsp_ext.ts
@@ -118,3 +118,5 @@ export interface CommandLinkGroup {
118 title?: string; 118 title?: string;
119 commands: CommandLink[]; 119 commands: CommandLink[];
120} 120}
121
122export const openDocs = new lc.RequestType<lc.TextDocumentPositionParams, string | void, void>('experimental/externalDocs');
diff --git a/editors/code/src/main.ts b/editors/code/src/main.ts
index 2896d90ac..09543e348 100644
--- a/editors/code/src/main.ts
+++ b/editors/code/src/main.ts
@@ -110,6 +110,7 @@ async function tryActivate(context: vscode.ExtensionContext) {
110 ctx.registerCommand('run', commands.run); 110 ctx.registerCommand('run', commands.run);
111 ctx.registerCommand('debug', commands.debug); 111 ctx.registerCommand('debug', commands.debug);
112 ctx.registerCommand('newDebugConfig', commands.newDebugConfig); 112 ctx.registerCommand('newDebugConfig', commands.newDebugConfig);
113 ctx.registerCommand('openDocs', commands.openDocs);
113 114
114 defaultOnEnter.dispose(); 115 defaultOnEnter.dispose();
115 ctx.registerCommand('onEnter', commands.onEnter); 116 ctx.registerCommand('onEnter', commands.onEnter);