1use core::cmp::Ordering;
2use core::ops::Range;
3use std::string::String;
4use std::sync::Arc;
5use std::vec::Vec;
6
7use thiserror::Error;
8
9use crate::{Config, File};
10
11#[derive(Clone, Debug, Eq, Hash, PartialEq)]
13pub struct FileDocs {
14 file: Arc<File>,
16 docs: String,
18 remap: Vec<TextRemap>,
20}
21
22#[derive(Clone, Debug, Eq, Hash, PartialEq)]
24pub struct TextRemap {
25 pub source: Range<usize>,
27 pub target: Range<usize>,
29}
30
31impl FileDocs {
32 pub fn from_file(file: Arc<File>, config: &Config<'_>) -> Result<Self, FileDocsFromFileError> {
34 use crate::build_attr_docs;
35
36 let file_text = file.text();
37 let line_offsets: Vec<_> = file_text
38 .split('\n')
39 .map(|slice| slice.as_ptr() as usize - file_text.as_ptr() as usize)
40 .collect();
41
42 let ast = syn::parse_file(file_text)?;
43 let chunks: Result<Vec<_>, _> = ast
44 .attrs
45 .iter()
46 .map(|attr| build_attr_docs(attr, config))
47 .collect();
48 let chunks = chunks?;
49
50 let (docs, mut remap, _) = chunks.into_iter().flatten().fold(
51 (String::new(), Vec::new(), None),
52 |(text, mut remap, last), item| {
53 let range = item.span.map(|span| {
54 line_offsets[span.start.line] + span.start.column
55 ..line_offsets[span.end.line] + span.end.column
56 });
57 if let Some(range) = range.clone() {
58 remap.push(TextRemap {
59 source: text.len()..text.len() + item.text.len(),
60 target: range,
61 });
62 }
63 (
64 text + &item.text,
65 remap,
66 range.map_or_else(|| last, |range| Some(range.end)),
67 )
68 },
69 );
70
71 remap.sort();
72 Ok(FileDocs { file, docs, remap })
73 }
74
75 pub fn file(&self) -> &Arc<File> {
77 &self.file
78 }
79
80 pub fn docs(&self) -> &str {
82 &self.docs
83 }
84
85 pub fn remap(&self) -> &[TextRemap] {
87 &self.remap
88 }
89
90 pub fn remap_to_file(&self, range: Range<usize>) -> Option<Range<usize>> {
92 let remap_idx = self
93 .remap
94 .binary_search_by(|remap| {
95 if range.start < remap.source.start {
96 Ordering::Greater
97 } else if range.start < remap.source.end {
98 Ordering::Equal
99 } else {
100 Ordering::Less
101 }
102 })
103 .ok()?;
104
105 let remap = &self.remap[remap_idx];
106 Some(remap.target.start..remap.target.end)
107 }
108}
109
110impl PartialOrd for TextRemap {
111 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
112 Some(self.cmp(other))
113 }
114}
115
116impl Ord for TextRemap {
117 fn cmp(&self, other: &Self) -> Ordering {
118 self.source.start.cmp(&other.source.start)
119 }
120}
121
122#[derive(Clone, Debug, Error)]
124pub enum FileDocsFromFileError {
125 #[error("File parser error: {0}")]
127 SynError(#[from] syn::Error),
128 #[error(transparent)]
130 AttrError(#[from] crate::BuildAttrDocsError),
131}