term_rustdoc/tree/
textline.rs

1use self::fold::Fold;
2use crate::{
3    tree::{CrateDoc, DocTree, Tag},
4    util::XString,
5};
6use ratatui::style::{Color, Style};
7use std::{
8    fmt::{self, Write},
9    rc::Rc,
10};
11use termtree::Tree;
12use unicode_width::UnicodeWidthStr;
13
14mod fold;
15
16/// Tagged text including headings and nodes.
17#[derive(Clone, Default)]
18pub struct TextTag {
19    pub text: XString,
20    pub tag: Tag,
21    pub id: Option<XString>,
22}
23
24/// Show text only, which is used as a plain text Tree display.
25impl fmt::Display for TextTag {
26    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
27        write!(f, "{}", self.text)
28    }
29}
30
31/// Onwed [`ratatui::text::Span`], mainly being a small styled string.
32#[derive(Debug, Clone)]
33pub struct Text {
34    pub text: XString,
35    pub style: Style,
36}
37
38impl Text {
39    pub fn new(text: XString, style: Style) -> Self {
40        Self { text, style }
41    }
42
43    pub fn new_text(text: XString) -> Self {
44        Text {
45            text,
46            style: Style::default(),
47        }
48    }
49}
50
51#[derive(Clone)]
52pub struct TreeLine {
53    pub glyph: Text,
54    pub tag: Tag,
55    /// Identation level with range of 0..=u8::MAX
56    pub level: u8,
57    /// Node/Item id from Crate
58    pub id: Option<XString>,
59    pub name: Text,
60}
61
62impl fmt::Debug for TreeLine {
63    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
64        f.debug_struct("TreeLine")
65            .field("tag", &self.tag)
66            .field("level", &self.level)
67            .field("name.text", &self.name.text)
68            .finish()
69    }
70}
71
72impl TreeLine {
73    fn flatten(tree: DocTree) -> (Vec<TreeLine>, Tree<Empty>) {
74        let mut level = 0u8;
75        let mut emptytree = Tree::new(Empty).with_glyphs(tree.tree.root.tag.glyph());
76        let root = TreeLine::new(tree.tree.root, level);
77        let mut flatten = Vec::<TreeLine>::with_capacity(512);
78        flatten.push(root);
79        empty_tree_and_flatten(tree.tree.leaves, &mut level, &mut flatten, &mut emptytree);
80        (flatten, emptytree)
81    }
82
83    pub fn new(tt: TextTag, level: u8) -> Self {
84        let text = tt.text;
85        let tag = tt.tag;
86        let id = tt.id;
87        let style = tag.style();
88        let name = Text { text, style };
89
90        // stripe placeholder
91        let glyph = Text {
92            text: Default::default(),
93            style: Default::default(),
94        };
95        Self {
96            glyph,
97            tag,
98            level,
99            id,
100            name,
101        }
102    }
103
104    fn set_glyph(&mut self, glyph: XString) {
105        self.glyph.text = glyph;
106        self.glyph.style = Style::default().fg(Color::Gray);
107    }
108
109    /// texts and styles of glyph and name
110    pub fn glyph_name(&self) -> [(&str, Style); 2] {
111        [
112            (&self.glyph.text, self.glyph.style),
113            (&self.name.text, self.name.style),
114        ]
115    }
116
117    /// non-cjk unicode width including glyph and name
118    ///
119    /// reason for non-cjk:
120    /// * path or name usually doesn't contain CJK
121    /// * CJK width counts glyph width more, leading to wasteful space in outline
122    pub fn width(&self) -> u16 {
123        let (g, n) = (&*self.glyph.text, &*self.name.text);
124        (g.width() + n.width())
125            .try_into()
126            .unwrap_or_else(|_| panic!("The total width exceeds u16::MAX in `{g}{n}`"))
127    }
128}
129
130/// Outline tree for crate's public items with support of various folding.
131pub struct TreeLines {
132    doc: CrateDoc,
133    lines: Rc<[TreeLine]>,
134    fold: Fold,
135}
136
137impl TreeLines {
138    /// This also returns an identical ZST tree as the outline layout and tree glyph.
139    pub fn new_with(doc: CrateDoc, init: impl FnOnce(&CrateDoc) -> DocTree) -> (Self, Tree<Empty>) {
140        let doctree = init(&doc);
141        let (lines, layout) = doctree.cache_lines();
142
143        (
144            TreeLines {
145                doc,
146                lines,
147                fold: Fold::default(),
148            },
149            layout,
150        )
151    }
152
153    pub fn try_new_with(
154        doc: &CrateDoc,
155        init: impl FnOnce(&CrateDoc) -> Option<DocTree>,
156    ) -> Option<Self> {
157        let doctree = init(doc)?;
158        let (lines, _) = doctree.cache_lines();
159
160        Some(TreeLines {
161            doc: doc.clone(),
162            lines,
163            fold: Fold::default(),
164        })
165    }
166
167    pub fn new(doc: CrateDoc) -> Self {
168        // item tree is more concise and convenient for user
169        // because it directly can offer item's doc
170        let mut lines = TreeLines {
171            doc,
172            ..Default::default()
173        };
174        // default to zero level items by folding sub modules
175        lines.expand_zero_level();
176        info!("Outline TreeLines ready");
177        lines
178    }
179
180    pub fn all_lines(&self) -> &[TreeLine] {
181        &self.lines
182    }
183
184    /// This should exactly be the same as DocTree's display.
185    pub fn display_as_plain_text(&self) -> String {
186        struct DisplayPlain<'s> {
187            glyph: &'s str,
188            name: &'s str,
189        }
190        impl fmt::Display for DisplayPlain<'_> {
191            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
192                writeln!(f, "{}{}", self.glyph, self.name)
193            }
194        }
195        let mut buf = String::with_capacity(self.len() * 50);
196        for l in &*self.lines {
197            let plain = DisplayPlain {
198                glyph: &l.glyph.text,
199                name: &l.name.text,
200            };
201            write!(&mut buf, "{}", plain).unwrap();
202        }
203        buf.shrink_to_fit();
204        buf
205    }
206
207    pub fn doc(&self) -> CrateDoc {
208        self.doc.clone()
209    }
210
211    pub fn doc_ref(&self) -> &CrateDoc {
212        &self.doc
213    }
214}
215
216impl From<CrateDoc> for TreeLines {
217    fn from(doc: CrateDoc) -> Self {
218        TreeLines::new(doc)
219    }
220}
221
222impl DocTree {
223    fn cache_lines(self) -> (Rc<[TreeLine]>, Tree<Empty>) {
224        let (mut lines, layout) = TreeLine::flatten(self);
225        let tree_glyph = glyph(&layout);
226
227        let (len_nodes, len_glyph) = (lines.len(), tree_glyph.len());
228        assert_eq!(
229            len_nodes, len_glyph,
230            "the amount of nodes is {len_nodes}, but that of glyph is {len_glyph}"
231        );
232
233        lines
234            .iter_mut()
235            .zip(tree_glyph)
236            .for_each(|(l, g)| l.set_glyph(g));
237        (lines.into(), layout)
238    }
239}
240
241impl std::ops::Deref for TreeLines {
242    type Target = [TreeLine];
243
244    fn deref(&self) -> &Self::Target {
245        &self.lines
246    }
247}
248
249impl Default for TreeLines {
250    fn default() -> Self {
251        TreeLines {
252            doc: CrateDoc::default(),
253            lines: Rc::new([]),
254            fold: Fold::default(),
255        }
256    }
257}
258
259/// This type is displayed as empty because it's used
260/// as an empty node in Tree display to get the tree stripe glyph only.
261pub struct Empty;
262
263impl fmt::Display for Empty {
264    fn fmt(&self, _: &mut fmt::Formatter<'_>) -> fmt::Result {
265        Ok(())
266    }
267}
268
269/// Since the text is written into `flatten` vec, and glyph into `Tree<Empty>`,
270/// you need to write the glyph back into `flatten` vec.
271///
272/// This is because to generate the whole tree's stripe glyph without node text,
273/// we only have to know the whole tree structure. (Actually, it's possible to
274/// write our own glyph generation version based on levels :)
275fn empty_tree_and_flatten(
276    leaves: Vec<Tree<TextTag>>,
277    level: &mut u8,
278    flatten: &mut Vec<TreeLine>,
279    empty: &mut Tree<Empty>,
280) {
281    *level += 1;
282    // each tree node must have its root, so only looks into its leaves
283    for tree in leaves {
284        let mut current = *level;
285        let glyph = tree.root.tag.glyph();
286
287        // append the root of subtree
288        flatten.push(TreeLine::new(tree.root, current));
289
290        // construct new empty tree
291        let mut root = Tree::new(Empty).with_glyphs(glyph);
292        empty_tree_and_flatten(tree.leaves, &mut current, flatten, &mut root);
293        empty.push(root);
294    }
295}
296
297fn glyph(layout: &Tree<Empty>) -> Vec<XString> {
298    let mut buf = String::with_capacity(1024);
299    write!(&mut buf, "{layout}").expect("can't write Tree<Empty> to the string buf");
300    buf.lines().map(XString::from).collect()
301}