diff options
Diffstat (limited to 'crates/ra_ide')
-rw-r--r-- | crates/ra_ide/Cargo.toml | 5 | ||||
-rw-r--r-- | crates/ra_ide/src/hover.rs | 333 |
2 files changed, 245 insertions, 93 deletions
diff --git a/crates/ra_ide/Cargo.toml b/crates/ra_ide/Cargo.toml index 1bc095c5b..642b71937 100644 --- a/crates/ra_ide/Cargo.toml +++ b/crates/ra_ide/Cargo.toml | |||
@@ -17,9 +17,11 @@ itertools = "0.9.0" | |||
17 | log = "0.4.8" | 17 | log = "0.4.8" |
18 | rustc-hash = "1.1.0" | 18 | rustc-hash = "1.1.0" |
19 | rand = { version = "0.7.3", features = ["small_rng"] } | 19 | rand = { version = "0.7.3", features = ["small_rng"] } |
20 | comrak = "0.7.0" | ||
21 | url = "*" | 20 | url = "*" |
22 | maplit = "*" | 21 | maplit = "*" |
22 | lazy_static = "*" | ||
23 | pulldown-cmark-to-cmark = "4.0.2" | ||
24 | pulldown-cmark = "0.7.0" | ||
23 | 25 | ||
24 | stdx = { path = "../stdx" } | 26 | stdx = { path = "../stdx" } |
25 | 27 | ||
@@ -36,7 +38,6 @@ ra_ssr = { path = "../ra_ssr" } | |||
36 | ra_project_model = { path = "../ra_project_model" } | 38 | ra_project_model = { path = "../ra_project_model" } |
37 | ra_hir_def = { path = "../ra_hir_def" } | 39 | ra_hir_def = { path = "../ra_hir_def" } |
38 | ra_tt = { path = "../ra_tt" } | 40 | ra_tt = { path = "../ra_tt" } |
39 | ra_hir_expand = { path = "../ra_hir_expand" } | ||
40 | ra_parser = { path = "../ra_parser" } | 41 | ra_parser = { path = "../ra_parser" } |
41 | 42 | ||
42 | # ra_ide should depend only on the top-level `hir` package. if you need | 43 | # ra_ide should depend only on the top-level `hir` package. if you need |
diff --git a/crates/ra_ide/src/hover.rs b/crates/ra_ide/src/hover.rs index 34fc36a1f..82aa24f4f 100644 --- a/crates/ra_ide/src/hover.rs +++ b/crates/ra_ide/src/hover.rs | |||
@@ -1,23 +1,25 @@ | |||
1 | use std::collections::{HashMap, HashSet}; | ||
1 | use std::iter::once; | 2 | use std::iter::once; |
2 | 3 | ||
3 | use hir::{ | 4 | use hir::{ |
4 | Adt, AsAssocItem, AssocItemContainer, FieldSource, HasSource, HirDisplay, ModuleDef, | 5 | db::DefDatabase, Adt, AsAssocItem, AsName, AssocItemContainer, AttrDef, Crate, Documentation, |
5 | ModuleSource, Semantics, Documentation, AttrDef, Crate, ModPath, Hygiene | 6 | FieldSource, HasSource, HirDisplay, Hygiene, ItemInNs, ModPath, ModuleDef, ModuleSource, |
7 | Semantics, Module | ||
6 | }; | 8 | }; |
7 | use itertools::Itertools; | 9 | use itertools::Itertools; |
10 | use lazy_static::lazy_static; | ||
11 | use maplit::{hashmap, hashset}; | ||
12 | use pulldown_cmark::{CowStr, Event, Options, Parser, Tag}; | ||
13 | use pulldown_cmark_to_cmark::cmark; | ||
8 | use ra_db::SourceDatabase; | 14 | use ra_db::SourceDatabase; |
9 | use ra_ide_db::{ | 15 | use ra_ide_db::{ |
10 | defs::{classify_name, classify_name_ref, Definition}, | 16 | defs::{classify_name, classify_name_ref, Definition}, |
11 | RootDatabase, | 17 | RootDatabase, |
12 | }; | 18 | }; |
13 | use ra_syntax::{ast, match_ast, AstNode, SyntaxKind::*, SyntaxToken, SyntaxNode, TokenAtOffset, ast::Path}; | 19 | use ra_syntax::{ |
14 | use ra_hir_def::{item_scope::ItemInNs, db::DefDatabase, resolver::HasResolver}; | 20 | ast, ast::Path, match_ast, AstNode, SyntaxKind::*, SyntaxNode, SyntaxToken, TokenAtOffset, |
15 | use ra_tt::{Literal, Ident, Punct, TokenTree, Leaf}; | 21 | }; |
16 | use ra_hir_expand::name::AsName; | 22 | use ra_tt::{Ident, Leaf, Literal, Punct, TokenTree}; |
17 | use maplit::{hashset, hashmap}; | ||
18 | |||
19 | use comrak::{parse_document,format_commonmark, ComrakOptions, Arena}; | ||
20 | use comrak::nodes::NodeValue; | ||
21 | use url::Url; | 23 | use url::Url; |
22 | 24 | ||
23 | use crate::{ | 25 | use crate::{ |
@@ -389,80 +391,143 @@ fn hover_text_from_name_kind(db: &RootDatabase, def: Definition) -> Option<Strin | |||
389 | } | 391 | } |
390 | } | 392 | } |
391 | 393 | ||
394 | // Rewrites a markdown document, resolving links using `callback` and additionally striping prefixes/suffixes on link titles. | ||
395 | fn map_links<'e>( | ||
396 | events: impl Iterator<Item = Event<'e>>, | ||
397 | callback: impl Fn(&str, &str) -> String, | ||
398 | ) -> impl Iterator<Item = Event<'e>> { | ||
399 | let mut in_link = false; | ||
400 | let mut link_text = CowStr::Borrowed(""); | ||
401 | events.map(move |evt| match evt { | ||
402 | Event::Start(Tag::Link(..)) => { | ||
403 | in_link = true; | ||
404 | evt | ||
405 | } | ||
406 | Event::End(Tag::Link(link_type, target, _)) => { | ||
407 | in_link = false; | ||
408 | let target = callback(&target, &link_text); | ||
409 | Event::End(Tag::Link(link_type, CowStr::Boxed(target.into()), CowStr::Borrowed(""))) | ||
410 | } | ||
411 | Event::Text(s) if in_link => { | ||
412 | link_text = s.clone(); | ||
413 | Event::Text(CowStr::Boxed(strip_prefixes_suffixes(&s).into())) | ||
414 | } | ||
415 | Event::Code(s) if in_link => { | ||
416 | link_text = s.clone(); | ||
417 | Event::Code(CowStr::Boxed(strip_prefixes_suffixes(&s).into())) | ||
418 | } | ||
419 | _ => evt, | ||
420 | }) | ||
421 | } | ||
422 | |||
392 | /// Rewrite documentation links in markdown to point to local documentation/docs.rs | 423 | /// Rewrite documentation links in markdown to point to local documentation/docs.rs |
393 | fn rewrite_links(db: &RootDatabase, markdown: &str, definition: &Definition) -> Option<String> { | 424 | fn rewrite_links(db: &RootDatabase, markdown: &str, definition: &Definition) -> Option<String> { |
394 | let arena = Arena::new(); | 425 | let doc = Parser::new_with_broken_link_callback( |
395 | let doc = parse_document(&arena, markdown, &ComrakOptions::default()); | 426 | markdown, |
396 | 427 | Options::empty(), | |
397 | iter_nodes(doc, &|node| { | 428 | Some(&|label, _| Some((/*url*/ label.to_string(), /*title*/ label.to_string()))), |
398 | match &mut node.data.borrow_mut().value { | 429 | ); |
399 | &mut NodeValue::Link(ref mut link) => { | 430 | |
400 | match Url::parse(&String::from_utf8(link.url.clone()).unwrap()) { | 431 | let doc = map_links(doc, |target, title: &str| { |
401 | // If this is a valid absolute URL don't touch it | 432 | match Url::parse(target) { |
402 | Ok(_) => (), | 433 | // If this is a valid absolute URL don't touch it |
403 | // Otherwise there are two main possibilities | 434 | Ok(_) => target.to_string(), |
404 | // path-based links: `../../module/struct.MyStruct.html` | 435 | // Otherwise there are two main possibilities |
405 | // module-based links (AKA intra-doc links): `super::super::module::MyStruct` | 436 | // path-based links: `../../module/struct.MyStruct.html` |
406 | Err(_) => { | 437 | // module-based links (AKA intra-doc links): `super::super::module::MyStruct` |
407 | let link_str = String::from_utf8(link.url.clone()).unwrap(); | 438 | Err(_) => { |
408 | let link_text = String::from_utf8(link.title.clone()).unwrap(); | 439 | let resolved = try_resolve_intra(db, definition, title, &target) |
409 | let resolved = try_resolve_intra(db, definition, &link_text, &link_str) | 440 | .or_else(|| try_resolve_path(db, definition, &target)); |
410 | .or_else(|| try_resolve_path(db, definition, &link_str)); | 441 | |
411 | 442 | if let Some(resolved) = resolved { | |
412 | if let Some(resolved) = resolved { | 443 | resolved |
413 | link.url = resolved.as_bytes().to_vec(); | 444 | } else { |
414 | } | 445 | target.to_string() |
415 | |||
416 | } | ||
417 | } | 446 | } |
418 | }, | 447 | } |
419 | _ => () | ||
420 | } | 448 | } |
421 | }); | 449 | }); |
422 | let mut out = Vec::new(); | 450 | let mut out = String::new(); |
423 | format_commonmark(doc, &ComrakOptions::default(), &mut out).ok()?; | 451 | cmark(doc, &mut out, None).ok(); |
424 | Some(String::from_utf8(out).unwrap().trim().to_string()) | 452 | Some(out) |
425 | } | 453 | } |
426 | 454 | ||
427 | #[derive(PartialEq, Eq, Hash, Copy, Clone, Debug)] | 455 | #[derive(PartialEq, Eq, Hash, Copy, Clone, Debug)] |
428 | enum Namespace { | 456 | enum Namespace { |
429 | Types, | 457 | Types, |
430 | Values, | 458 | Values, |
431 | Macros | 459 | Macros, |
432 | } | 460 | } |
433 | 461 | ||
462 | lazy_static!( | ||
463 | /// Map of namespaces to identifying prefixes and suffixes as defined by RFC1946. | ||
464 | static ref NS_MAP: HashMap<Namespace, (HashSet<&'static str>, HashSet<&'static str>)> = hashmap!{ | ||
465 | Namespace::Types => (hashset!{"type", "struct", "enum", "mod", "trait", "union", "module"}, hashset!{}), | ||
466 | Namespace::Values => (hashset!{"value", "function", "fn", "method", "const", "static", "mod", "module"}, hashset!{"()"}), | ||
467 | Namespace::Macros => (hashset!{"macro"}, hashset!{"!"}) | ||
468 | }; | ||
469 | ); | ||
470 | |||
434 | impl Namespace { | 471 | impl Namespace { |
435 | /// Extract the specified namespace from an intra-doc-link if one exists. | 472 | /// Extract the specified namespace from an intra-doc-link if one exists. |
436 | fn from_intra_spec(s: &str) -> Option<Self> { | 473 | fn from_intra_spec(s: &str) -> Option<Self> { |
437 | let ns_map = hashmap!{ | 474 | NS_MAP |
438 | Self::Types => (hashset!{"type", "struct", "enum", "mod", "trait", "union", "module"}, hashset!{}), | ||
439 | Self::Values => (hashset!{"value", "function", "fn", "method", "const", "static", "mod", "module"}, hashset!{"()"}), | ||
440 | Self::Macros => (hashset!{"macro"}, hashset!{"!"}) | ||
441 | }; | ||
442 | |||
443 | ns_map | ||
444 | .iter() | 475 | .iter() |
445 | .filter(|(_ns, (prefixes, suffixes))| { | 476 | .filter(|(_ns, (prefixes, suffixes))| { |
446 | prefixes.iter().map(|prefix| s.starts_with(prefix) && s.chars().nth(prefix.len()+1).map(|c| c == '@' || c == ' ').unwrap_or(false)).any(|cond| cond) || | 477 | prefixes |
447 | suffixes.iter().map(|suffix| s.starts_with(suffix) && s.chars().nth(suffix.len()+1).map(|c| c == '@' || c == ' ').unwrap_or(false)).any(|cond| cond) | 478 | .iter() |
479 | .map(|prefix| { | ||
480 | s.starts_with(prefix) | ||
481 | && s.chars() | ||
482 | .nth(prefix.len() + 1) | ||
483 | .map(|c| c == '@' || c == ' ') | ||
484 | .unwrap_or(false) | ||
485 | }) | ||
486 | .any(|cond| cond) | ||
487 | || suffixes | ||
488 | .iter() | ||
489 | .map(|suffix| { | ||
490 | s.starts_with(suffix) | ||
491 | && s.chars() | ||
492 | .nth(suffix.len() + 1) | ||
493 | .map(|c| c == '@' || c == ' ') | ||
494 | .unwrap_or(false) | ||
495 | }) | ||
496 | .any(|cond| cond) | ||
448 | }) | 497 | }) |
449 | .map(|(ns, (_, _))| *ns) | 498 | .map(|(ns, (_, _))| *ns) |
450 | .next() | 499 | .next() |
451 | } | 500 | } |
452 | } | 501 | } |
453 | 502 | ||
503 | // Strip prefixes, suffixes, and inline code marks from the given string. | ||
504 | fn strip_prefixes_suffixes(mut s: &str) -> &str { | ||
505 | s = s.trim_matches('`'); | ||
506 | NS_MAP.iter().for_each(|(_, (prefixes, suffixes))| { | ||
507 | prefixes.iter().for_each(|prefix| s = s.trim_start_matches(prefix)); | ||
508 | suffixes.iter().for_each(|suffix| s = s.trim_end_matches(suffix)); | ||
509 | }); | ||
510 | s.trim_start_matches("@").trim() | ||
511 | } | ||
512 | |||
454 | /// Try to resolve path to local documentation via intra-doc-links (i.e. `super::gateway::Shard`). | 513 | /// Try to resolve path to local documentation via intra-doc-links (i.e. `super::gateway::Shard`). |
455 | /// | 514 | /// |
456 | /// See [RFC1946](https://github.com/rust-lang/rfcs/blob/master/text/1946-intra-rustdoc-links.md). | 515 | /// See [RFC1946](https://github.com/rust-lang/rfcs/blob/master/text/1946-intra-rustdoc-links.md). |
457 | fn try_resolve_intra(db: &RootDatabase, definition: &Definition, link_text: &str, link_target: &str) -> Option<String> { | 516 | fn try_resolve_intra( |
458 | eprintln!("try_resolve_intra"); | 517 | db: &RootDatabase, |
459 | 518 | definition: &Definition, | |
519 | link_text: &str, | ||
520 | link_target: &str, | ||
521 | ) -> Option<String> { | ||
460 | // Set link_target for implied shortlinks | 522 | // Set link_target for implied shortlinks |
461 | let link_target = if link_target.is_empty() { | 523 | let link_target = |
462 | link_text.trim_matches('`') | 524 | if link_target.is_empty() { link_text.trim_matches('`') } else { link_target }; |
463 | } else { | 525 | |
464 | link_target | 526 | // Namespace disambiguation |
465 | }; | 527 | let namespace = Namespace::from_intra_spec(link_target); |
528 | |||
529 | // Strip prefixes/suffixes | ||
530 | let link_target = strip_prefixes_suffixes(link_target); | ||
466 | 531 | ||
467 | // Parse link as a module path | 532 | // Parse link as a module path |
468 | // This expects a full document, which a single path isn't, but we can just ignore the errors. | 533 | // This expects a full document, which a single path isn't, but we can just ignore the errors. |
@@ -473,17 +538,17 @@ fn try_resolve_intra(db: &RootDatabase, definition: &Definition, link_text: &str | |||
473 | // Resolve it relative to symbol's location (according to the RFC this should consider small scopes | 538 | // Resolve it relative to symbol's location (according to the RFC this should consider small scopes |
474 | let resolver = definition.resolver(db)?; | 539 | let resolver = definition.resolver(db)?; |
475 | 540 | ||
476 | // Namespace disambiguation | ||
477 | let namespace = Namespace::from_intra_spec(link_target); | ||
478 | |||
479 | let resolved = resolver.resolve_module_path_in_items(db, &modpath); | 541 | let resolved = resolver.resolve_module_path_in_items(db, &modpath); |
480 | let (defid, namespace) = match namespace { | 542 | let (defid, namespace) = match namespace { |
481 | // TODO: .or(resolved.macros) | 543 | // TODO: .or(resolved.macros) |
482 | None => resolved.types.map(|t| (t.0, Namespace::Types)).or(resolved.values.map(|t| (t.0, Namespace::Values)))?, | 544 | None => resolved |
545 | .types | ||
546 | .map(|t| (t.0, Namespace::Types)) | ||
547 | .or(resolved.values.map(|t| (t.0, Namespace::Values)))?, | ||
483 | Some(ns @ Namespace::Types) => (resolved.types?.0, ns), | 548 | Some(ns @ Namespace::Types) => (resolved.types?.0, ns), |
484 | Some(ns @ Namespace::Values) => (resolved.values?.0, ns), | 549 | Some(ns @ Namespace::Values) => (resolved.values?.0, ns), |
485 | // TODO: | 550 | // TODO: |
486 | Some(Namespace::Macros) => None? | 551 | Some(Namespace::Macros) => None?, |
487 | }; | 552 | }; |
488 | 553 | ||
489 | // Get the filepath of the final symbol | 554 | // Get the filepath of the final symbol |
@@ -494,23 +559,28 @@ fn try_resolve_intra(db: &RootDatabase, definition: &Definition, link_text: &str | |||
494 | Namespace::Types => ItemInNs::Types(defid), | 559 | Namespace::Types => ItemInNs::Types(defid), |
495 | Namespace::Values => ItemInNs::Values(defid), | 560 | Namespace::Values => ItemInNs::Values(defid), |
496 | // TODO: | 561 | // TODO: |
497 | Namespace::Macros => None? | 562 | Namespace::Macros => None?, |
498 | }; | 563 | }; |
499 | let import_map = db.import_map(krate.into()); | 564 | let import_map = db.import_map(krate.into()); |
500 | let path = import_map.path_of(ns)?; | 565 | let path = import_map.path_of(ns)?; |
501 | 566 | ||
502 | Some( | 567 | Some( |
503 | get_doc_url(db, &krate)? | 568 | get_doc_url(db, &krate)? |
504 | .join(&format!("{}/", krate.display_name(db)?)).ok()? | 569 | .join(&format!("{}/", krate.display_name(db)?)) |
505 | .join(&path.segments.iter().map(|name| format!("{}", name)).join("/")).ok()? | 570 | .ok()? |
506 | .join(&get_symbol_filename(db, &Definition::ModuleDef(def))?).ok()? | 571 | .join(&path.segments.iter().map(|name| format!("{}", name)).join("/")) |
507 | .into_string() | 572 | .ok()? |
573 | .join(&get_symbol_filename(db, &Definition::ModuleDef(def))?) | ||
574 | .ok()? | ||
575 | .into_string(), | ||
508 | ) | 576 | ) |
509 | } | 577 | } |
510 | 578 | ||
511 | /// Try to resolve path to local documentation via path-based links (i.e. `../gateway/struct.Shard.html`). | 579 | /// Try to resolve path to local documentation via path-based links (i.e. `../gateway/struct.Shard.html`). |
512 | fn try_resolve_path(db: &RootDatabase, definition: &Definition, link: &str) -> Option<String> { | 580 | fn try_resolve_path(db: &RootDatabase, definition: &Definition, link: &str) -> Option<String> { |
513 | eprintln!("try_resolve_path"); | 581 | if !link.contains("#") && !link.contains(".html") { |
582 | return None; | ||
583 | } | ||
514 | let ns = if let Definition::ModuleDef(moddef) = definition { | 584 | let ns = if let Definition::ModuleDef(moddef) = definition { |
515 | ItemInNs::Types(moddef.clone().into()) | 585 | ItemInNs::Types(moddef.clone().into()) |
516 | } else { | 586 | } else { |
@@ -522,11 +592,15 @@ fn try_resolve_path(db: &RootDatabase, definition: &Definition, link: &str) -> O | |||
522 | // TODO: It should be possible to fall back to not-necessarilly-public paths if we can't find a public one, | 592 | // TODO: It should be possible to fall back to not-necessarilly-public paths if we can't find a public one, |
523 | // then hope rustdoc was run locally with `--document-private-items` | 593 | // then hope rustdoc was run locally with `--document-private-items` |
524 | let base = import_map.path_of(ns)?; | 594 | let base = import_map.path_of(ns)?; |
525 | let base = once(format!("{}", krate.display_name(db)?)).chain(base.segments.iter().map(|name| format!("{}", name))).join("/"); | 595 | let base = once(format!("{}", krate.display_name(db)?)) |
596 | .chain(base.segments.iter().map(|name| format!("{}", name))) | ||
597 | .join("/"); | ||
526 | 598 | ||
527 | get_doc_url(db, &krate) | 599 | get_doc_url(db, &krate) |
528 | .and_then(|url| url.join(&base).ok()) | 600 | .and_then(|url| url.join(&base).ok()) |
529 | .and_then(|url| get_symbol_filename(db, definition).as_deref().map(|f| url.join(f).ok()).flatten()) | 601 | .and_then(|url| { |
602 | get_symbol_filename(db, definition).as_deref().map(|f| url.join(f).ok()).flatten() | ||
603 | }) | ||
530 | .and_then(|url| url.join(link).ok()) | 604 | .and_then(|url| url.join(link).ok()) |
531 | .map(|url| url.into_string()) | 605 | .map(|url| url.into_string()) |
532 | } | 606 | } |
@@ -566,31 +640,25 @@ fn get_symbol_filename(db: &RootDatabase, definition: &Definition) -> Option<Str | |||
566 | ModuleDef::Adt(adt) => match adt { | 640 | ModuleDef::Adt(adt) => match adt { |
567 | Adt::Struct(s) => format!("struct.{}.html", s.name(db)), | 641 | Adt::Struct(s) => format!("struct.{}.html", s.name(db)), |
568 | Adt::Enum(e) => format!("enum.{}.html", e.name(db)), | 642 | Adt::Enum(e) => format!("enum.{}.html", e.name(db)), |
569 | Adt::Union(u) => format!("union.{}.html", u.name(db)) | 643 | Adt::Union(u) => format!("union.{}.html", u.name(db)), |
570 | }, | 644 | }, |
571 | ModuleDef::Module(_) => "index.html".to_string(), | 645 | ModuleDef::Module(_) => "index.html".to_string(), |
572 | ModuleDef::Trait(t) => format!("trait.{}.html", t.name(db)), | 646 | ModuleDef::Trait(t) => format!("trait.{}.html", t.name(db)), |
573 | ModuleDef::TypeAlias(t) => format!("type.{}.html", t.name(db)), | 647 | ModuleDef::TypeAlias(t) => format!("type.{}.html", t.name(db)), |
574 | ModuleDef::BuiltinType(t) => format!("primitive.{}.html", t.as_name()), | 648 | ModuleDef::BuiltinType(t) => format!("primitive.{}.html", t.as_name()), |
575 | ModuleDef::Function(f) => format!("fn.{}.html", f.name(db)), | 649 | ModuleDef::Function(f) => format!("fn.{}.html", f.name(db)), |
576 | ModuleDef::EnumVariant(ev) => format!("enum.{}.html#variant.{}", ev.parent_enum(db).name(db), ev.name(db)), | 650 | ModuleDef::EnumVariant(ev) => { |
651 | format!("enum.{}.html#variant.{}", ev.parent_enum(db).name(db), ev.name(db)) | ||
652 | } | ||
577 | ModuleDef::Const(c) => format!("const.{}.html", c.name(db)?), | 653 | ModuleDef::Const(c) => format!("const.{}.html", c.name(db)?), |
578 | // TODO: Check this is the right prefix | 654 | // TODO: Check this is the right prefix |
579 | ModuleDef::Static(s) => format!("static.{}.html", s.name(db)?) | 655 | ModuleDef::Static(s) => format!("static.{}.html", s.name(db)?), |
580 | }, | 656 | }, |
581 | Definition::Macro(m) => format!("macro.{}.html", m.name(db)?), | 657 | Definition::Macro(m) => format!("macro.{}.html", m.name(db)?), |
582 | _ => None? | 658 | _ => None?, |
583 | }) | 659 | }) |
584 | } | 660 | } |
585 | 661 | ||
586 | fn iter_nodes<'a, F>(node: &'a comrak::nodes::AstNode<'a>, f: &F) | ||
587 | where F : Fn(&'a comrak::nodes::AstNode<'a>) { | ||
588 | f(node); | ||
589 | for c in node.children() { | ||
590 | iter_nodes(c, f); | ||
591 | } | ||
592 | } | ||
593 | |||
594 | fn pick_best(tokens: TokenAtOffset<SyntaxToken>) -> Option<SyntaxToken> { | 662 | fn pick_best(tokens: TokenAtOffset<SyntaxToken>) -> Option<SyntaxToken> { |
595 | return tokens.max_by_key(priority); | 663 | return tokens.max_by_key(priority); |
596 | fn priority(n: &SyntaxToken) -> usize { | 664 | fn priority(n: &SyntaxToken) -> usize { |
@@ -614,12 +682,11 @@ mod tests { | |||
614 | use crate::mock_analysis::analysis_and_position; | 682 | use crate::mock_analysis::analysis_and_position; |
615 | 683 | ||
616 | fn trim_markup(s: &str) -> String { | 684 | fn trim_markup(s: &str) -> String { |
617 | s | 685 | s.trim() |
618 | .replace("``` rust", "```rust") | 686 | .replace("````", "```") |
619 | .replace("-----", "___") | 687 | .replace("---", "___") |
620 | .replace("\n\n___\n\n", "\n___\n\n") | ||
621 | .replace("\\<-", "<-") | 688 | .replace("\\<-", "<-") |
622 | .trim_start_matches("test\n```\n\n") | 689 | .replace("```\n\n___", "```\n___") |
623 | .trim_start_matches("```rust\n") | 690 | .trim_start_matches("```rust\n") |
624 | .trim_start_matches("test\n```\n\n```rust\n") | 691 | .trim_start_matches("test\n```\n\n```rust\n") |
625 | .trim_end_matches("\n```") | 692 | .trim_end_matches("\n```") |
@@ -873,7 +940,10 @@ fn main() { | |||
873 | ", | 940 | ", |
874 | ); | 941 | ); |
875 | let hover = analysis.hover(position).unwrap().unwrap(); | 942 | let hover = analysis.hover(position).unwrap().unwrap(); |
876 | assert_eq!(trim_markup_opt(hover.info.first()).as_deref(), Some("test::Option\n```\n\n```rust\nSome")); | 943 | assert_eq!( |
944 | trim_markup_opt(hover.info.first()).as_deref(), | ||
945 | Some("test::Option\n```\n\n```rust\nSome") | ||
946 | ); | ||
877 | 947 | ||
878 | let (analysis, position) = analysis_and_position( | 948 | let (analysis, position) = analysis_and_position( |
879 | " | 949 | " |
@@ -1408,7 +1478,7 @@ fn func(foo: i32) { if true { <|>foo; }; } | |||
1408 | /// [Foo](struct.Foo.html) | 1478 | /// [Foo](struct.Foo.html) |
1409 | pub struct B<|>ar | 1479 | pub struct B<|>ar |
1410 | ", | 1480 | ", |
1411 | &["pub struct Bar\n```\n___\n\n[Foo](https://docs.rs/test/*/test/struct.Foo.html)"] | 1481 | &["pub struct Bar\n```\n___\n\n[Foo](https://docs.rs/test/*/test/struct.Foo.html)"], |
1412 | ); | 1482 | ); |
1413 | } | 1483 | } |
1414 | 1484 | ||
@@ -1421,7 +1491,7 @@ fn func(foo: i32) { if true { <|>foo; }; } | |||
1421 | /// [Foo](Foo) | 1491 | /// [Foo](Foo) |
1422 | pub struct B<|>ar | 1492 | pub struct B<|>ar |
1423 | ", | 1493 | ", |
1424 | &["pub struct Bar\n```\n___\n\n[Foo](https://docs.rs/test/*/test/struct.Foo.html)"] | 1494 | &["pub struct Bar\n```\n___\n\n[Foo](https://docs.rs/test/*/test/struct.Foo.html)"], |
1425 | ); | 1495 | ); |
1426 | } | 1496 | } |
1427 | 1497 | ||
@@ -1434,7 +1504,20 @@ fn func(foo: i32) { if true { <|>foo; }; } | |||
1434 | /// [Foo] | 1504 | /// [Foo] |
1435 | pub struct B<|>ar | 1505 | pub struct B<|>ar |
1436 | ", | 1506 | ", |
1437 | &["pub struct Bar\n```\n___\n\n[Foo](https://docs.rs/test/*/test/struct.Foo.html)"] | 1507 | &["pub struct Bar\n```\n___\n\n[Foo](https://docs.rs/test/*/test/struct.Foo.html)"], |
1508 | ); | ||
1509 | } | ||
1510 | |||
1511 | #[test] | ||
1512 | fn test_hover_intra_link_shortlink_code() { | ||
1513 | check_hover_result( | ||
1514 | r" | ||
1515 | //- /lib.rs | ||
1516 | pub struct Foo; | ||
1517 | /// [`Foo`] | ||
1518 | pub struct B<|>ar | ||
1519 | ", | ||
1520 | &["pub struct Bar\n```\n___\n\n[`Foo`](https://docs.rs/test/*/test/struct.Foo.html)"], | ||
1438 | ); | 1521 | ); |
1439 | } | 1522 | } |
1440 | 1523 | ||
@@ -1448,7 +1531,75 @@ fn func(foo: i32) { if true { <|>foo; }; } | |||
1448 | /// [Foo()] | 1531 | /// [Foo()] |
1449 | pub struct B<|>ar | 1532 | pub struct B<|>ar |
1450 | ", | 1533 | ", |
1451 | &["pub struct Bar\n```\n___\n\n[Foo](https://docs.rs/test/*/test/struct.Foo.html)"] | 1534 | &["pub struct Bar\n```\n___\n\n[Foo](https://docs.rs/test/*/test/struct.Foo.html)"], |
1535 | ); | ||
1536 | } | ||
1537 | |||
1538 | #[test] | ||
1539 | fn test_hover_intra_link_shortlink_namspaced_code() { | ||
1540 | check_hover_result( | ||
1541 | r" | ||
1542 | //- /lib.rs | ||
1543 | pub struct Foo; | ||
1544 | /// [`struct Foo`] | ||
1545 | pub struct B<|>ar | ||
1546 | ", | ||
1547 | &["pub struct Bar\n```\n___\n\n[`Foo`](https://docs.rs/test/*/test/struct.Foo.html)"], | ||
1548 | ); | ||
1549 | } | ||
1550 | |||
1551 | #[test] | ||
1552 | fn test_hover_intra_link_shortlink_namspaced_code_with_at() { | ||
1553 | check_hover_result( | ||
1554 | r" | ||
1555 | //- /lib.rs | ||
1556 | pub struct Foo; | ||
1557 | /// [`struct@Foo`] | ||
1558 | pub struct B<|>ar | ||
1559 | ", | ||
1560 | &["pub struct Bar\n```\n___\n\n[`Foo`](https://docs.rs/test/*/test/struct.Foo.html)"], | ||
1561 | ); | ||
1562 | } | ||
1563 | |||
1564 | #[test] | ||
1565 | fn test_hover_intra_link_reference() { | ||
1566 | check_hover_result( | ||
1567 | r" | ||
1568 | //- /lib.rs | ||
1569 | pub struct Foo; | ||
1570 | /// [my Foo][foo] | ||
1571 | /// | ||
1572 | /// [foo]: Foo | ||
1573 | pub struct B<|>ar | ||
1574 | ", | ||
1575 | &["pub struct Bar\n```\n___\n\n[my Foo](https://docs.rs/test/*/test/struct.Foo.html)"], | ||
1576 | ); | ||
1577 | } | ||
1578 | |||
1579 | #[test] | ||
1580 | fn test_hover_external_url() { | ||
1581 | check_hover_result( | ||
1582 | r" | ||
1583 | //- /lib.rs | ||
1584 | pub struct Foo; | ||
1585 | /// [external](https://www.google.com) | ||
1586 | pub struct B<|>ar | ||
1587 | ", | ||
1588 | &["pub struct Bar\n```\n___\n\n[external](https://www.google.com)"], | ||
1589 | ); | ||
1590 | } | ||
1591 | |||
1592 | // Check that we don't rewrite links which we can't identify | ||
1593 | #[test] | ||
1594 | fn test_hover_unknown_target() { | ||
1595 | check_hover_result( | ||
1596 | r" | ||
1597 | //- /lib.rs | ||
1598 | pub struct Foo; | ||
1599 | /// [baz](Baz) | ||
1600 | pub struct B<|>ar | ||
1601 | ", | ||
1602 | &["pub struct Bar\n```\n___\n\n[baz](Baz)"], | ||
1452 | ); | 1603 | ); |
1453 | } | 1604 | } |
1454 | 1605 | ||