aboutsummaryrefslogtreecommitdiff
path: root/crates/hir_def
diff options
context:
space:
mode:
authorbors[bot] <26634292+bors[bot]@users.noreply.github.com>2021-04-05 13:30:20 +0100
committerGitHub <[email protected]>2021-04-05 13:30:20 +0100
commitc2be91dcd826e1529ac6ac431b3f871ec72abebc (patch)
treee267eed3fc8966093fbd79389e47a051c435cd7d /crates/hir_def
parentd8ee25bb976f50c0c0c8c247ca8bb030d9167bdb (diff)
parent8d786dc4c3ce26dbb3432023c7461bd879993bfd (diff)
Merge #8245
8245: Properly resolve intra doc links in hover and goto_definition r=matklad a=Veykril Unfortunately involves a bit of weird workarounds due to pulldown_cmark's incorrect lifetimes on `BrokenLinkCallback`... I should probably open an issue there asking for the fixes to be pushed to a release since they already exist in the repo for quite some time it seems. Fixes #8258, Fixes #8238 Co-authored-by: Lukas Wirth <[email protected]>
Diffstat (limited to 'crates/hir_def')
-rw-r--r--crates/hir_def/src/attr.rs95
1 files changed, 93 insertions, 2 deletions
diff --git a/crates/hir_def/src/attr.rs b/crates/hir_def/src/attr.rs
index 442c5fb5b..ab77d924a 100644
--- a/crates/hir_def/src/attr.rs
+++ b/crates/hir_def/src/attr.rs
@@ -1,6 +1,10 @@
1//! A higher level attributes based on TokenTree, with also some shortcuts. 1//! A higher level attributes based on TokenTree, with also some shortcuts.
2 2
3use std::{ops, sync::Arc}; 3use std::{
4 convert::{TryFrom, TryInto},
5 ops,
6 sync::Arc,
7};
4 8
5use base_db::CrateId; 9use base_db::CrateId;
6use cfg::{CfgExpr, CfgOptions}; 10use cfg::{CfgExpr, CfgOptions};
@@ -12,7 +16,7 @@ use mbe::ast_to_token_tree;
12use smallvec::{smallvec, SmallVec}; 16use smallvec::{smallvec, SmallVec};
13use syntax::{ 17use syntax::{
14 ast::{self, AstNode, AttrsOwner}, 18 ast::{self, AstNode, AttrsOwner},
15 match_ast, AstToken, SmolStr, SyntaxNode, 19 match_ast, AstToken, SmolStr, SyntaxNode, TextRange, TextSize,
16}; 20};
17use tt::Subtree; 21use tt::Subtree;
18 22
@@ -452,6 +456,55 @@ impl AttrsWithOwner {
452 .collect(), 456 .collect(),
453 } 457 }
454 } 458 }
459
460 pub fn docs_with_rangemap(
461 &self,
462 db: &dyn DefDatabase,
463 ) -> Option<(Documentation, DocsRangeMap)> {
464 // FIXME: code duplication in `docs` above
465 let docs = self.by_key("doc").attrs().flat_map(|attr| match attr.input.as_ref()? {
466 AttrInput::Literal(s) => Some((s, attr.index)),
467 AttrInput::TokenTree(_) => None,
468 });
469 let indent = docs
470 .clone()
471 .flat_map(|(s, _)| s.lines())
472 .filter(|line| !line.chars().all(|c| c.is_whitespace()))
473 .map(|line| line.chars().take_while(|c| c.is_whitespace()).count())
474 .min()
475 .unwrap_or(0);
476 let mut buf = String::new();
477 let mut mapping = Vec::new();
478 for (doc, idx) in docs {
479 // str::lines doesn't yield anything for the empty string
480 if !doc.is_empty() {
481 for line in doc.split('\n') {
482 let line = line.trim_end();
483 let line_len = line.len();
484 let (offset, line) = match line.char_indices().nth(indent) {
485 Some((offset, _)) => (offset, &line[offset..]),
486 None => (0, line),
487 };
488 let buf_offset = buf.len();
489 buf.push_str(line);
490 mapping.push((
491 TextRange::new(buf_offset.try_into().ok()?, buf.len().try_into().ok()?),
492 idx,
493 TextRange::new(offset.try_into().ok()?, line_len.try_into().ok()?),
494 ));
495 buf.push('\n');
496 }
497 } else {
498 buf.push('\n');
499 }
500 }
501 buf.pop();
502 if buf.is_empty() {
503 None
504 } else {
505 Some((Documentation(buf), DocsRangeMap { mapping, source: self.source_map(db).attrs }))
506 }
507 }
455} 508}
456 509
457fn inner_attributes( 510fn inner_attributes(
@@ -508,6 +561,44 @@ impl AttrSourceMap {
508 } 561 }
509} 562}
510 563
564/// A struct to map text ranges from [`Documentation`] back to TextRanges in the syntax tree.
565pub struct DocsRangeMap {
566 source: Vec<InFile<Either<ast::Attr, ast::Comment>>>,
567 // (docstring-line-range, attr_index, attr-string-range)
568 // a mapping from the text range of a line of the [`Documentation`] to the attribute index and
569 // the original (untrimmed) syntax doc line
570 mapping: Vec<(TextRange, u32, TextRange)>,
571}
572
573impl DocsRangeMap {
574 pub fn map(&self, range: TextRange) -> Option<InFile<TextRange>> {
575 let found = self.mapping.binary_search_by(|(probe, ..)| probe.ordering(range)).ok()?;
576 let (line_docs_range, idx, original_line_src_range) = self.mapping[found].clone();
577 if !line_docs_range.contains_range(range) {
578 return None;
579 }
580
581 let relative_range = range - line_docs_range.start();
582
583 let &InFile { file_id, value: ref source } = &self.source[idx as usize];
584 match source {
585 Either::Left(_) => None, // FIXME, figure out a nice way to handle doc attributes here
586 // as well as for whats done in syntax highlight doc injection
587 Either::Right(comment) => {
588 let text_range = comment.syntax().text_range();
589 let range = TextRange::at(
590 text_range.start()
591 + TextSize::try_from(comment.prefix().len()).ok()?
592 + original_line_src_range.start()
593 + relative_range.start(),
594 text_range.len().min(range.len()),
595 );
596 Some(InFile { file_id, value: range })
597 }
598 }
599 }
600}
601
511#[derive(Debug, Clone, PartialEq, Eq)] 602#[derive(Debug, Clone, PartialEq, Eq)]
512pub struct Attr { 603pub struct Attr {
513 index: u32, 604 index: u32,