diff options
-rw-r--r-- | crates/ide/src/link_rewrite.rs | 90 |
1 files changed, 83 insertions, 7 deletions
diff --git a/crates/ide/src/link_rewrite.rs b/crates/ide/src/link_rewrite.rs index 80005c06e..1e102997f 100644 --- a/crates/ide/src/link_rewrite.rs +++ b/crates/ide/src/link_rewrite.rs | |||
@@ -91,7 +91,6 @@ pub fn remove_links(markdown: &str) -> String { | |||
91 | } | 91 | } |
92 | 92 | ||
93 | pub fn get_doc_link<T: Resolvable + Clone>(db: &dyn HirDatabase, definition: &T) -> Option<String> { | 93 | pub fn get_doc_link<T: Resolvable + Clone>(db: &dyn HirDatabase, definition: &T) -> Option<String> { |
94 | eprintln!("hir::doc_links::get_doc_link"); | ||
95 | let module_def = definition.clone().try_into_module_def()?; | 94 | let module_def = definition.clone().try_into_module_def()?; |
96 | 95 | ||
97 | get_doc_link_impl(db, &module_def) | 96 | get_doc_link_impl(db, &module_def) |
@@ -105,8 +104,31 @@ pub fn get_doc_link<T: Resolvable + Clone>(db: &dyn HirDatabase, definition: &T) | |||
105 | // BUG: For methods | 104 | // BUG: For methods |
106 | // import_map.path_of(ns) fails, is not designed to resolve methods | 105 | // import_map.path_of(ns) fails, is not designed to resolve methods |
107 | fn get_doc_link_impl(db: &dyn HirDatabase, moddef: &ModuleDef) -> Option<String> { | 106 | fn get_doc_link_impl(db: &dyn HirDatabase, moddef: &ModuleDef) -> Option<String> { |
108 | eprintln!("get_doc_link_impl: {:#?}", moddef); | 107 | // Get the outermost definition for the moduledef. This is used to resolve the public path to the type, |
109 | let ns = ItemInNs::Types(moddef.clone().into()); | 108 | // then we can join the method, field, etc onto it if required. |
109 | let target_def: ModuleDef = match moddef { | ||
110 | ModuleDef::Function(f) => match f.as_assoc_item(db).map(|assoc| assoc.container(db)) { | ||
111 | Some(AssocItemContainer::Trait(t)) => t.into(), | ||
112 | Some(AssocItemContainer::ImplDef(imp)) => { | ||
113 | let resolver = ModuleId::from(imp.module(db)).resolver(db.upcast()); | ||
114 | let ctx = TyLoweringContext::new(db, &resolver); | ||
115 | Adt::from( | ||
116 | Ty::from_hir( | ||
117 | &ctx, | ||
118 | &imp.target_trait(db).unwrap_or_else(|| imp.target_type(db)), | ||
119 | ) | ||
120 | .as_adt() | ||
121 | .map(|t| t.0) | ||
122 | .unwrap(), | ||
123 | ) | ||
124 | .into() | ||
125 | } | ||
126 | None => ModuleDef::Function(*f), | ||
127 | }, | ||
128 | moddef => *moddef, | ||
129 | }; | ||
130 | |||
131 | let ns = ItemInNs::Types(target_def.clone().into()); | ||
110 | 132 | ||
111 | let module = moddef.module(db)?; | 133 | let module = moddef.module(db)?; |
112 | let krate = module.krate(); | 134 | let krate = module.krate(); |
@@ -117,7 +139,28 @@ fn get_doc_link_impl(db: &dyn HirDatabase, moddef: &ModuleDef) -> Option<String> | |||
117 | 139 | ||
118 | get_doc_url(db, &krate) | 140 | get_doc_url(db, &krate) |
119 | .and_then(|url| url.join(&base).ok()) | 141 | .and_then(|url| url.join(&base).ok()) |
120 | .and_then(|url| get_symbol_filename(db, &moddef).as_deref().and_then(|f| url.join(f).ok())) | 142 | .and_then(|url| { |
143 | get_symbol_filename(db, &target_def).as_deref().and_then(|f| url.join(f).ok()) | ||
144 | }) | ||
145 | .and_then(|url| match moddef { | ||
146 | ModuleDef::Function(f) => { | ||
147 | get_symbol_fragment(db, &FieldOrAssocItem::AssocItem(AssocItem::Function(*f))) | ||
148 | .as_deref() | ||
149 | .and_then(|f| url.join(f).ok()) | ||
150 | } | ||
151 | ModuleDef::Const(c) => { | ||
152 | get_symbol_fragment(db, &FieldOrAssocItem::AssocItem(AssocItem::Const(*c))) | ||
153 | .as_deref() | ||
154 | .and_then(|f| url.join(f).ok()) | ||
155 | } | ||
156 | ModuleDef::TypeAlias(ty) => { | ||
157 | get_symbol_fragment(db, &FieldOrAssocItem::AssocItem(AssocItem::TypeAlias(*ty))) | ||
158 | .as_deref() | ||
159 | .and_then(|f| url.join(f).ok()) | ||
160 | } | ||
161 | // TODO: Field <- this requires passing in a definition or something | ||
162 | _ => Some(url), | ||
163 | }) | ||
121 | .map(|url| url.into_string()) | 164 | .map(|url| url.into_string()) |
122 | } | 165 | } |
123 | 166 | ||
@@ -307,6 +350,12 @@ fn ns_from_intra_spec(s: &str) -> Option<hir::Namespace> { | |||
307 | .next() | 350 | .next() |
308 | } | 351 | } |
309 | 352 | ||
353 | /// Get the root URL for the documentation of a crate. | ||
354 | /// | ||
355 | /// ``` | ||
356 | /// https://doc.rust-lang.org/std/iter/trait.Iterator.html#tymethod.next | ||
357 | /// ^^^^^^^^^^^^^^^^^^^^^^^^^^ | ||
358 | /// ``` | ||
310 | fn get_doc_url(db: &RootDatabase, krate: &Crate) -> Option<Url> { | 359 | fn get_doc_url(db: &RootDatabase, krate: &Crate) -> Option<Url> { |
311 | krate | 360 | krate |
312 | .get_html_root_url(db) | 361 | .get_html_root_url(db) |
@@ -323,8 +372,11 @@ fn get_doc_url(db: &RootDatabase, krate: &Crate) -> Option<Url> { | |||
323 | 372 | ||
324 | /// Get the filename and extension generated for a symbol by rustdoc. | 373 | /// Get the filename and extension generated for a symbol by rustdoc. |
325 | /// | 374 | /// |
326 | /// Example: `struct.Shard.html` | 375 | /// ``` |
327 | fn get_symbol_filename(db: &RootDatabase, definition: &ModuleDef) -> Option<String> { | 376 | /// https://doc.rust-lang.org/std/iter/trait.Iterator.html#tymethod.next |
377 | /// ^^^^^^^^^^^^^^^^^^^ | ||
378 | /// ``` | ||
379 | fn get_symbol_filename(db: &dyn HirDatabase, definition: &ModuleDef) -> Option<String> { | ||
328 | Some(match definition { | 380 | Some(match definition { |
329 | ModuleDef::Adt(adt) => match adt { | 381 | ModuleDef::Adt(adt) => match adt { |
330 | Adt::Struct(s) => format!("struct.{}.html", s.name(db)), | 382 | Adt::Struct(s) => format!("struct.{}.html", s.name(db)), |
@@ -334,7 +386,7 @@ fn get_symbol_filename(db: &RootDatabase, definition: &ModuleDef) -> Option<Stri | |||
334 | ModuleDef::Module(_) => "index.html".to_string(), | 386 | ModuleDef::Module(_) => "index.html".to_string(), |
335 | ModuleDef::Trait(t) => format!("trait.{}.html", t.name(db)), | 387 | ModuleDef::Trait(t) => format!("trait.{}.html", t.name(db)), |
336 | ModuleDef::TypeAlias(t) => format!("type.{}.html", t.name(db)), | 388 | ModuleDef::TypeAlias(t) => format!("type.{}.html", t.name(db)), |
337 | ModuleDef::BuiltinType(t) => format!("primitive.{}.html", t), | 389 | ModuleDef::BuiltinType(t) => format!("primitive.{}.html", t.as_name()), |
338 | ModuleDef::Function(f) => format!("fn.{}.html", f.name(db)), | 390 | ModuleDef::Function(f) => format!("fn.{}.html", f.name(db)), |
339 | ModuleDef::EnumVariant(ev) => { | 391 | ModuleDef::EnumVariant(ev) => { |
340 | format!("enum.{}.html#variant.{}", ev.parent_enum(db).name(db), ev.name(db)) | 392 | format!("enum.{}.html#variant.{}", ev.parent_enum(db).name(db), ev.name(db)) |
@@ -344,6 +396,30 @@ fn get_symbol_filename(db: &RootDatabase, definition: &ModuleDef) -> Option<Stri | |||
344 | }) | 396 | }) |
345 | } | 397 | } |
346 | 398 | ||
399 | enum FieldOrAssocItem { | ||
400 | Field(Field), | ||
401 | AssocItem(AssocItem), | ||
402 | } | ||
403 | |||
404 | /// Get the fragment required to link to a specific field, method, associated type, or associated constant. | ||
405 | /// | ||
406 | /// ``` | ||
407 | /// https://doc.rust-lang.org/std/iter/trait.Iterator.html#tymethod.next | ||
408 | /// ^^^^^^^^^^^^^^ | ||
409 | /// ``` | ||
410 | fn get_symbol_fragment(db: &dyn HirDatabase, field_or_assoc: &FieldOrAssocItem) -> Option<String> { | ||
411 | Some(match field_or_assoc { | ||
412 | FieldOrAssocItem::Field(field) => format!("#structfield.{}", field.name(db)), | ||
413 | FieldOrAssocItem::AssocItem(assoc) => match assoc { | ||
414 | // TODO: Rustdoc sometimes uses tymethod instead of method. This case needs to be investigated. | ||
415 | AssocItem::Function(function) => format!("#method.{}", function.name(db)), | ||
416 | // TODO: This might be the old method for documenting associated constants, i32::MAX uses a separate page... | ||
417 | AssocItem::Const(constant) => format!("#associatedconstant.{}", constant.name(db)?), | ||
418 | AssocItem::TypeAlias(ty) => format!("#associatedtype.{}", ty.name(db)), | ||
419 | }, | ||
420 | }) | ||
421 | } | ||
422 | |||
347 | fn pick_best(tokens: TokenAtOffset<SyntaxToken>) -> Option<SyntaxToken> { | 423 | fn pick_best(tokens: TokenAtOffset<SyntaxToken>) -> Option<SyntaxToken> { |
348 | return tokens.max_by_key(priority); | 424 | return tokens.max_by_key(priority); |
349 | fn priority(n: &SyntaxToken) -> usize { | 425 | fn priority(n: &SyntaxToken) -> usize { |