diff options
Diffstat (limited to 'crates/ide/src')
-rw-r--r-- | crates/ide/src/doc_links.rs | 133 |
1 files changed, 51 insertions, 82 deletions
diff --git a/crates/ide/src/doc_links.rs b/crates/ide/src/doc_links.rs index f12c9d442..0cee741ac 100644 --- a/crates/ide/src/doc_links.rs +++ b/crates/ide/src/doc_links.rs | |||
@@ -114,7 +114,7 @@ pub(crate) fn extract_definitions_from_markdown( | |||
114 | for (event, range) in doc.into_offset_iter() { | 114 | for (event, range) in doc.into_offset_iter() { |
115 | if let Event::Start(Tag::Link(_, target, title)) = event { | 115 | if let Event::Start(Tag::Link(_, target, title)) = event { |
116 | let link = if target.is_empty() { title } else { target }; | 116 | let link = if target.is_empty() { title } else { target }; |
117 | let (link, ns) = parse_link(&link); | 117 | let (link, ns) = parse_intra_doc_link(&link); |
118 | res.push((range, link.to_string(), ns)); | 118 | res.push((range, link.to_string(), ns)); |
119 | } | 119 | } |
120 | } | 120 | } |
@@ -276,26 +276,8 @@ fn rewrite_intra_doc_link( | |||
276 | title: &str, | 276 | title: &str, |
277 | ) -> Option<(String, String)> { | 277 | ) -> Option<(String, String)> { |
278 | let link = if target.is_empty() { title } else { target }; | 278 | let link = if target.is_empty() { title } else { target }; |
279 | let (link, ns) = parse_link(link); | 279 | let (link, ns) = parse_intra_doc_link(link); |
280 | let resolved = match def { | 280 | let resolved = resolve_doc_path_for_def(db, def, link, ns)?; |
281 | Definition::ModuleDef(def) => match def { | ||
282 | ModuleDef::Module(it) => it.resolve_doc_path(db, link, ns), | ||
283 | ModuleDef::Function(it) => it.resolve_doc_path(db, link, ns), | ||
284 | ModuleDef::Adt(it) => it.resolve_doc_path(db, link, ns), | ||
285 | ModuleDef::Variant(it) => it.resolve_doc_path(db, link, ns), | ||
286 | ModuleDef::Const(it) => it.resolve_doc_path(db, link, ns), | ||
287 | ModuleDef::Static(it) => it.resolve_doc_path(db, link, ns), | ||
288 | ModuleDef::Trait(it) => it.resolve_doc_path(db, link, ns), | ||
289 | ModuleDef::TypeAlias(it) => it.resolve_doc_path(db, link, ns), | ||
290 | ModuleDef::BuiltinType(_) => return None, | ||
291 | }, | ||
292 | Definition::Macro(it) => it.resolve_doc_path(db, link, ns), | ||
293 | Definition::Field(it) => it.resolve_doc_path(db, link, ns), | ||
294 | Definition::SelfType(_) | ||
295 | | Definition::Local(_) | ||
296 | | Definition::GenericParam(_) | ||
297 | | Definition::Label(_) => return None, | ||
298 | }?; | ||
299 | let krate = resolved.module(db)?.krate(); | 281 | let krate = resolved.module(db)?.krate(); |
300 | let canonical_path = resolved.canonical_path(db)?; | 282 | let canonical_path = resolved.canonical_path(db)?; |
301 | let mut new_url = get_doc_url(db, &krate)? | 283 | let mut new_url = get_doc_url(db, &krate)? |
@@ -307,24 +289,23 @@ fn rewrite_intra_doc_link( | |||
307 | .ok()?; | 289 | .ok()?; |
308 | 290 | ||
309 | if let ModuleDef::Trait(t) = resolved { | 291 | if let ModuleDef::Trait(t) = resolved { |
310 | let items = t.items(db); | 292 | if let Some(assoc_item) = t.items(db).into_iter().find_map(|assoc_item| { |
311 | if let Some(field_or_assoc_item) = items.iter().find_map(|assoc_item| { | ||
312 | if let Some(name) = assoc_item.name(db) { | 293 | if let Some(name) = assoc_item.name(db) { |
313 | if *link == format!("{}::{}", canonical_path, name) { | 294 | if *link == format!("{}::{}", canonical_path, name) { |
314 | return Some(FieldOrAssocItem::AssocItem(*assoc_item)); | 295 | return Some(assoc_item); |
315 | } | 296 | } |
316 | } | 297 | } |
317 | None | 298 | None |
318 | }) { | 299 | }) { |
319 | if let Some(fragment) = get_symbol_fragment(db, &field_or_assoc_item) { | 300 | if let Some(fragment) = |
301 | get_symbol_fragment(db, &FieldOrAssocItem::AssocItem(assoc_item)) | ||
302 | { | ||
320 | new_url = new_url.join(&fragment).ok()?; | 303 | new_url = new_url.join(&fragment).ok()?; |
321 | } | 304 | } |
322 | }; | 305 | }; |
323 | } | 306 | } |
324 | 307 | ||
325 | let new_target = new_url.into_string(); | 308 | Some((new_url.into_string(), strip_prefixes_suffixes(title).to_string())) |
326 | let new_title = strip_prefixes_suffixes(title); | ||
327 | Some((new_target, new_title.to_string())) | ||
328 | } | 309 | } |
329 | 310 | ||
330 | /// Try to resolve path to local documentation via path-based links (i.e. `../gateway/struct.Shard.html`). | 311 | /// Try to resolve path to local documentation via path-based links (i.e. `../gateway/struct.Shard.html`). |
@@ -401,73 +382,61 @@ fn map_links<'e>( | |||
401 | }) | 382 | }) |
402 | } | 383 | } |
403 | 384 | ||
404 | fn parse_link(s: &str) -> (&str, Option<hir::Namespace>) { | 385 | const TYPES: ([&str; 9], [&str; 0]) = |
405 | let path = strip_prefixes_suffixes(s); | 386 | (["type", "struct", "enum", "mod", "trait", "union", "module", "prim", "primitive"], []); |
406 | let ns = ns_from_intra_spec(s); | 387 | const VALUES: ([&str; 8], [&str; 1]) = |
407 | (path, ns) | ||
408 | } | ||
409 | |||
410 | /// Strip prefixes, suffixes, and inline code marks from the given string. | ||
411 | fn strip_prefixes_suffixes(mut s: &str) -> &str { | ||
412 | s = s.trim_matches('`'); | ||
413 | |||
414 | [ | ||
415 | (TYPES.0.iter(), TYPES.1.iter()), | ||
416 | (VALUES.0.iter(), VALUES.1.iter()), | ||
417 | (MACROS.0.iter(), MACROS.1.iter()), | ||
418 | ] | ||
419 | .iter() | ||
420 | .for_each(|(prefixes, suffixes)| { | ||
421 | prefixes.clone().for_each(|prefix| s = s.trim_start_matches(*prefix)); | ||
422 | suffixes.clone().for_each(|suffix| s = s.trim_end_matches(*suffix)); | ||
423 | }); | ||
424 | s.trim_start_matches('@').trim() | ||
425 | } | ||
426 | |||
427 | static TYPES: ([&str; 7], [&str; 0]) = | ||
428 | (["type", "struct", "enum", "mod", "trait", "union", "module"], []); | ||
429 | static VALUES: ([&str; 8], [&str; 1]) = | ||
430 | (["value", "function", "fn", "method", "const", "static", "mod", "module"], ["()"]); | 388 | (["value", "function", "fn", "method", "const", "static", "mod", "module"], ["()"]); |
431 | static MACROS: ([&str; 1], [&str; 1]) = (["macro"], ["!"]); | 389 | const MACROS: ([&str; 2], [&str; 1]) = (["macro", "derive"], ["!"]); |
432 | 390 | ||
433 | /// Extract the specified namespace from an intra-doc-link if one exists. | 391 | /// Extract the specified namespace from an intra-doc-link if one exists. |
434 | /// | 392 | /// |
435 | /// # Examples | 393 | /// # Examples |
436 | /// | 394 | /// |
437 | /// * `struct MyStruct` -> `Namespace::Types` | 395 | /// * `struct MyStruct` -> ("MyStruct", `Namespace::Types`) |
438 | /// * `panic!` -> `Namespace::Macros` | 396 | /// * `panic!` -> ("panic", `Namespace::Macros`) |
439 | /// * `fn@from_intra_spec` -> `Namespace::Values` | 397 | /// * `fn@from_intra_spec` -> ("from_intra_spec", `Namespace::Values`) |
440 | fn ns_from_intra_spec(s: &str) -> Option<hir::Namespace> { | 398 | fn parse_intra_doc_link(s: &str) -> (&str, Option<hir::Namespace>) { |
399 | let s = s.trim_matches('`'); | ||
400 | |||
441 | [ | 401 | [ |
442 | (hir::Namespace::Types, (TYPES.0.iter(), TYPES.1.iter())), | 402 | (hir::Namespace::Types, (TYPES.0.iter(), TYPES.1.iter())), |
443 | (hir::Namespace::Values, (VALUES.0.iter(), VALUES.1.iter())), | 403 | (hir::Namespace::Values, (VALUES.0.iter(), VALUES.1.iter())), |
444 | (hir::Namespace::Macros, (MACROS.0.iter(), MACROS.1.iter())), | 404 | (hir::Namespace::Macros, (MACROS.0.iter(), MACROS.1.iter())), |
445 | ] | 405 | ] |
446 | .iter() | 406 | .iter() |
447 | .filter(|(_ns, (prefixes, suffixes))| { | 407 | .cloned() |
448 | prefixes | 408 | .find_map(|(ns, (mut prefixes, mut suffixes))| { |
449 | .clone() | 409 | if let Some(prefix) = prefixes.find(|&&prefix| { |
450 | .map(|prefix| { | 410 | s.starts_with(prefix) |
451 | s.starts_with(*prefix) | 411 | && s.chars().nth(prefix.len()).map_or(false, |c| c == '@' || c == ' ') |
452 | && s.chars() | 412 | }) { |
453 | .nth(prefix.len() + 1) | 413 | Some((&s[prefix.len() + 1..], ns)) |
454 | .map(|c| c == '@' || c == ' ') | 414 | } else { |
455 | .unwrap_or(false) | 415 | suffixes.find_map(|&suffix| s.strip_suffix(suffix).zip(Some(ns))) |
456 | }) | 416 | } |
457 | .any(|cond| cond) | 417 | }) |
458 | || suffixes | 418 | .map_or((s, None), |(s, ns)| (s, Some(ns))) |
459 | .clone() | 419 | } |
460 | .map(|suffix| { | 420 | |
461 | s.starts_with(*suffix) | 421 | fn strip_prefixes_suffixes(s: &str) -> &str { |
462 | && s.chars() | 422 | [ |
463 | .nth(suffix.len() + 1) | 423 | (TYPES.0.iter(), TYPES.1.iter()), |
464 | .map(|c| c == '@' || c == ' ') | 424 | (VALUES.0.iter(), VALUES.1.iter()), |
465 | .unwrap_or(false) | 425 | (MACROS.0.iter(), MACROS.1.iter()), |
466 | }) | 426 | ] |
467 | .any(|cond| cond) | 427 | .iter() |
428 | .cloned() | ||
429 | .find_map(|(mut prefixes, mut suffixes)| { | ||
430 | if let Some(prefix) = prefixes.find(|&&prefix| { | ||
431 | s.starts_with(prefix) | ||
432 | && s.chars().nth(prefix.len()).map_or(false, |c| c == '@' || c == ' ') | ||
433 | }) { | ||
434 | Some(&s[prefix.len() + 1..]) | ||
435 | } else { | ||
436 | suffixes.find_map(|&suffix| s.strip_suffix(suffix)) | ||
437 | } | ||
468 | }) | 438 | }) |
469 | .map(|(ns, (_, _))| *ns) | 439 | .unwrap_or(s) |
470 | .next() | ||
471 | } | 440 | } |
472 | 441 | ||
473 | /// Get the root URL for the documentation of a crate. | 442 | /// Get the root URL for the documentation of a crate. |