diff options
Diffstat (limited to 'crates/ide/src')
-rw-r--r-- | crates/ide/src/doc_links.rs | 99 | ||||
-rw-r--r-- | crates/ide/src/lib.rs | 2 |
2 files changed, 47 insertions, 54 deletions
diff --git a/crates/ide/src/doc_links.rs b/crates/ide/src/doc_links.rs index 7fe88577d..512c42c4d 100644 --- a/crates/ide/src/doc_links.rs +++ b/crates/ide/src/doc_links.rs | |||
@@ -2,20 +2,27 @@ | |||
2 | //! | 2 | //! |
3 | //! Most of the implementation can be found in [`hir::doc_links`]. | 3 | //! Most of the implementation can be found in [`hir::doc_links`]. |
4 | 4 | ||
5 | use hir::{Adt, Crate, HasAttrs, ModuleDef}; | 5 | use std::iter::once; |
6 | use ide_db::{defs::Definition, RootDatabase}; | 6 | |
7 | use pulldown_cmark::{CowStr, Event, LinkType, Options, Parser, Tag}; | 7 | use itertools::Itertools; |
8 | use pulldown_cmark_to_cmark::{cmark_with_options, Options as CmarkOptions}; | 8 | use pulldown_cmark_to_cmark::{cmark_with_options, Options as CmarkOptions}; |
9 | use pulldown_cmark::{CowStr, Event, LinkType, Options, Parser, Tag}; | ||
9 | use url::Url; | 10 | use url::Url; |
10 | 11 | ||
11 | use crate::{FilePosition, Semantics}; | 12 | use ide_db::{defs::Definition, RootDatabase}; |
12 | use hir::{get_doc_link, resolve_doc_link}; | 13 | |
14 | use hir::{ | ||
15 | db::{DefDatabase, HirDatabase}, | ||
16 | Adt, AsName, AssocItem, Crate, Field, HasAttrs, ItemInNs, ModuleDef, | ||
17 | }; | ||
13 | use ide_db::{ | 18 | use ide_db::{ |
14 | defs::{classify_name, classify_name_ref, Definition}, | 19 | defs::{classify_name, classify_name_ref, Definition}, |
15 | RootDatabase, | 20 | RootDatabase, |
16 | }; | 21 | }; |
17 | use syntax::{ast, match_ast, AstNode, SyntaxKind::*, SyntaxToken, TokenAtOffset, T}; | 22 | use syntax::{ast, match_ast, AstNode, SyntaxKind::*, SyntaxToken, TokenAtOffset, T}; |
18 | 23 | ||
24 | use crate::{FilePosition, Semantics}; | ||
25 | |||
19 | pub type DocumentationLink = String; | 26 | pub type DocumentationLink = String; |
20 | 27 | ||
21 | /// Rewrite documentation links in markdown to point to an online host (e.g. docs.rs) | 28 | /// Rewrite documentation links in markdown to point to an online host (e.g. docs.rs) |
@@ -100,64 +107,58 @@ pub fn get_doc_link<T: Resolvable + Clone>(db: &dyn HirDatabase, definition: &T) | |||
100 | // BUG: For Option | 107 | // BUG: For Option |
101 | // Returns https://doc.rust-lang.org/nightly/core/prelude/v1/enum.Option.html#variant.Some | 108 | // Returns https://doc.rust-lang.org/nightly/core/prelude/v1/enum.Option.html#variant.Some |
102 | // Instead of https://doc.rust-lang.org/nightly/core/option/enum.Option.html | 109 | // Instead of https://doc.rust-lang.org/nightly/core/option/enum.Option.html |
103 | fn get_doc_link_impl(db: &dyn HirDatabase, moddef: &ModuleDef) -> Option<String> { | 110 | // This could be worked around by turning the `EnumVariant` into `Enum` before attempting resolution, |
111 | // but it's really just working around the problem. Ideally we need to implement a slightly different | ||
112 | // version of import map which follows the same process as rustdoc. Otherwise there'll always be some | ||
113 | // edge cases where we select the wrong import path. | ||
114 | fn get_doc_link(db: &RootDatabase, definition: Definition) -> Option<String> { | ||
104 | // Get the outermost definition for the moduledef. This is used to resolve the public path to the type, | 115 | // Get the outermost definition for the moduledef. This is used to resolve the public path to the type, |
105 | // then we can join the method, field, etc onto it if required. | 116 | // then we can join the method, field, etc onto it if required. |
106 | let target_def: ModuleDef = match moddef { | 117 | let target_def: ModuleDef = match definition { |
107 | ModuleDef::Function(f) => match f.as_assoc_item(db).map(|assoc| assoc.container(db)) { | 118 | Definition::ModuleDef(moddef) => match moddef { |
108 | Some(AssocItemContainer::Trait(t)) => t.into(), | 119 | ModuleDef::Function(f) => { |
109 | Some(AssocItemContainer::ImplDef(imp)) => { | 120 | f.parent_def(db).map(|mowner| mowner.into()).unwrap_or_else(|| f.clone().into()) |
110 | let resolver = ModuleId::from(imp.module(db)).resolver(db.upcast()); | ||
111 | let ctx = TyLoweringContext::new(db, &resolver); | ||
112 | Adt::from( | ||
113 | Ty::from_hir( | ||
114 | &ctx, | ||
115 | &imp.target_trait(db).unwrap_or_else(|| imp.target_type(db)), | ||
116 | ) | ||
117 | .as_adt() | ||
118 | .map(|t| t.0) | ||
119 | .unwrap(), | ||
120 | ) | ||
121 | .into() | ||
122 | } | 121 | } |
123 | None => ModuleDef::Function(*f), | 122 | moddef => moddef, |
124 | }, | 123 | }, |
125 | moddef => *moddef, | 124 | Definition::Field(f) => f.parent_def(db).into(), |
125 | // FIXME: Handle macros | ||
126 | _ => return None, | ||
126 | }; | 127 | }; |
127 | 128 | ||
128 | let ns = ItemInNs::Types(target_def.clone().into()); | 129 | let ns = ItemInNs::Types(target_def.clone().into()); |
129 | 130 | ||
130 | let module = moddef.module(db)?; | 131 | let module = definition.module(db)?; |
131 | let krate = module.krate(); | 132 | let krate = module.krate(); |
132 | let import_map = db.import_map(krate.into()); | 133 | let import_map = db.import_map(krate.into()); |
133 | let base = once(krate.display_name(db).unwrap()) | 134 | let base = once(krate.display_name(db).unwrap()) |
134 | .chain(import_map.path_of(ns).unwrap().segments.iter().map(|name| format!("{}", name))) | 135 | .chain(import_map.path_of(ns).unwrap().segments.iter().map(|name| format!("{}", name))) |
135 | .join("/"); | 136 | .join("/"); |
136 | 137 | ||
137 | get_doc_url(db, &krate) | 138 | let filename = get_symbol_filename(db, &target_def); |
138 | .and_then(|url| url.join(&base).ok()) | 139 | let fragment = match definition { |
139 | .and_then(|url| { | 140 | Definition::ModuleDef(moddef) => match moddef { |
140 | get_symbol_filename(db, &target_def).as_deref().and_then(|f| url.join(f).ok()) | ||
141 | }) | ||
142 | .and_then(|url| match moddef { | ||
143 | ModuleDef::Function(f) => { | 141 | ModuleDef::Function(f) => { |
144 | get_symbol_fragment(db, &FieldOrAssocItem::AssocItem(AssocItem::Function(*f))) | 142 | get_symbol_fragment(db, &FieldOrAssocItem::AssocItem(AssocItem::Function(f))) |
145 | .as_deref() | ||
146 | .and_then(|f| url.join(f).ok()) | ||
147 | } | 143 | } |
148 | ModuleDef::Const(c) => { | 144 | ModuleDef::Const(c) => { |
149 | get_symbol_fragment(db, &FieldOrAssocItem::AssocItem(AssocItem::Const(*c))) | 145 | get_symbol_fragment(db, &FieldOrAssocItem::AssocItem(AssocItem::Const(c))) |
150 | .as_deref() | ||
151 | .and_then(|f| url.join(f).ok()) | ||
152 | } | 146 | } |
153 | ModuleDef::TypeAlias(ty) => { | 147 | ModuleDef::TypeAlias(ty) => { |
154 | get_symbol_fragment(db, &FieldOrAssocItem::AssocItem(AssocItem::TypeAlias(*ty))) | 148 | get_symbol_fragment(db, &FieldOrAssocItem::AssocItem(AssocItem::TypeAlias(ty))) |
155 | .as_deref() | ||
156 | .and_then(|f| url.join(f).ok()) | ||
157 | } | 149 | } |
158 | // TODO: Field <- this requires passing in a definition or something | 150 | _ => None, |
159 | _ => Some(url), | 151 | }, |
160 | }) | 152 | Definition::Field(field) => get_symbol_fragment(db, &FieldOrAssocItem::Field(field)), |
153 | _ => None, | ||
154 | }; | ||
155 | |||
156 | get_doc_url(db, &krate) | ||
157 | .and_then(|url| url.join(&base).ok()) | ||
158 | .and_then(|url| filename.as_deref().and_then(|f| url.join(f).ok())) | ||
159 | .and_then( | ||
160 | |url| if let Some(fragment) = fragment { url.join(&fragment).ok() } else { Some(url) }, | ||
161 | ) | ||
161 | .map(|url| url.into_string()) | 162 | .map(|url| url.into_string()) |
162 | } | 163 | } |
163 | 164 | ||
@@ -219,9 +220,8 @@ fn rewrite_url_link(db: &RootDatabase, def: ModuleDef, target: &str) -> Option<S | |||
219 | .map(|url| url.into_string()) | 220 | .map(|url| url.into_string()) |
220 | } | 221 | } |
221 | 222 | ||
222 | // FIXME: This should either be moved, or the module should be renamed. | ||
223 | /// Retrieve a link to documentation for the given symbol. | 223 | /// Retrieve a link to documentation for the given symbol. |
224 | pub fn get_doc_url(db: &RootDatabase, position: &FilePosition) -> Option<DocumentationLink> { | 224 | pub fn external_docs(db: &RootDatabase, position: &FilePosition) -> Option<DocumentationLink> { |
225 | let sema = Semantics::new(db); | 225 | let sema = Semantics::new(db); |
226 | let file = sema.parse(position.file_id).syntax().clone(); | 226 | let file = sema.parse(position.file_id).syntax().clone(); |
227 | let token = pick_best(file.token_at_offset(position.offset))?; | 227 | let token = pick_best(file.token_at_offset(position.offset))?; |
@@ -236,14 +236,7 @@ pub fn get_doc_url(db: &RootDatabase, position: &FilePosition) -> Option<Documen | |||
236 | } | 236 | } |
237 | }; | 237 | }; |
238 | 238 | ||
239 | match definition? { | 239 | get_doc_link(db, definition?) |
240 | Definition::Macro(t) => get_doc_link(db, &t), | ||
241 | Definition::Field(t) => get_doc_link(db, &t), | ||
242 | Definition::ModuleDef(t) => get_doc_link(db, &t), | ||
243 | Definition::SelfType(t) => get_doc_link(db, &t), | ||
244 | Definition::Local(t) => get_doc_link(db, &t), | ||
245 | Definition::TypeParam(t) => get_doc_link(db, &t), | ||
246 | } | ||
247 | } | 240 | } |
248 | 241 | ||
249 | /// Rewrites a markdown document, applying 'callback' to each link. | 242 | /// Rewrites a markdown document, applying 'callback' to each link. |
diff --git a/crates/ide/src/lib.rs b/crates/ide/src/lib.rs index 0580d2979..5db6e1311 100644 --- a/crates/ide/src/lib.rs +++ b/crates/ide/src/lib.rs | |||
@@ -387,7 +387,7 @@ impl Analysis { | |||
387 | &self, | 387 | &self, |
388 | position: FilePosition, | 388 | position: FilePosition, |
389 | ) -> Cancelable<Option<doc_links::DocumentationLink>> { | 389 | ) -> Cancelable<Option<doc_links::DocumentationLink>> { |
390 | self.with_db(|db| doc_links::get_doc_url(db, &position)) | 390 | self.with_db(|db| doc_links::external_docs(db, &position)) |
391 | } | 391 | } |
392 | 392 | ||
393 | /// Computes parameter information for the given call expression. | 393 | /// Computes parameter information for the given call expression. |