diff options
Diffstat (limited to 'crates/hir_def')
-rw-r--r-- | crates/hir_def/src/attr.rs | 95 |
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 | ||
3 | use std::{ops, sync::Arc}; | 3 | use std::{ |
4 | convert::{TryFrom, TryInto}, | ||
5 | ops, | ||
6 | sync::Arc, | ||
7 | }; | ||
4 | 8 | ||
5 | use base_db::CrateId; | 9 | use base_db::CrateId; |
6 | use cfg::{CfgExpr, CfgOptions}; | 10 | use cfg::{CfgExpr, CfgOptions}; |
@@ -12,7 +16,7 @@ use mbe::ast_to_token_tree; | |||
12 | use smallvec::{smallvec, SmallVec}; | 16 | use smallvec::{smallvec, SmallVec}; |
13 | use syntax::{ | 17 | use 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 | }; |
17 | use tt::Subtree; | 21 | use 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 | ||
457 | fn inner_attributes( | 510 | fn 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. | ||
565 | pub 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 | |||
573 | impl 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)] |
512 | pub struct Attr { | 603 | pub struct Attr { |
513 | index: u32, | 604 | index: u32, |