Skip to main content

iris_reader/renderer/
text.rs

1use ratatui::text::Line;
2
3use super::{NodeId, RenderedDocument, RenderedLine};
4use crate::{
5    renderer::markdown::wrap::{StyledPiece, wrap_pieces},
6    search::{SearchMatch, SearchMatcher},
7    theme::Theme,
8};
9
10#[derive(Debug, Clone)]
11pub struct TextLine {
12    pub id: NodeId,
13    pub content: String,
14}
15
16#[derive(Debug, Clone)]
17pub struct TextDocument {
18    lines: Vec<TextLine>,
19}
20
21impl TextDocument {
22    pub fn new(source: &str) -> Self {
23        let lines = source
24            .split('\n')
25            .enumerate()
26            .map(|(index, content)| TextLine {
27                id: index + 1,
28                content: content.trim_end_matches('\r').to_string(),
29            })
30            .collect();
31        Self { lines }
32    }
33
34    pub fn search(&self, matcher: &SearchMatcher) -> Vec<SearchMatch> {
35        let mut output = Vec::new();
36        for line in &self.lines {
37            for occurrence in 0..matcher.count(&line.content) {
38                output.push(SearchMatch {
39                    node_id: line.id,
40                    occurrence,
41                });
42            }
43        }
44        output
45    }
46}
47
48pub fn render(
49    document: &TextDocument,
50    width: u16,
51    theme: &Theme,
52    wrap: bool,
53    tab_width: usize,
54) -> RenderedDocument {
55    let mut output = RenderedDocument::default();
56    let width = width.max(1) as usize;
57
58    for source in &document.lines {
59        let pieces = vec![StyledPiece::new(source.content.clone(), theme.document)];
60        let wrapped = wrap_pieces(&pieces, width, wrap, tab_width);
61        for line in wrapped {
62            output.lines.push(RenderedLine {
63                line: Line::from(line.spans),
64                plain: line.plain,
65                source_id: Some(source.id),
66            });
67        }
68    }
69
70    if output.lines.is_empty() {
71        output.lines.push(RenderedLine::empty());
72    }
73    output.finish()
74}
75
76#[cfg(test)]
77mod tests {
78    use super::*;
79
80    #[test]
81    fn empty_text_is_still_a_document() {
82        let doc = TextDocument::new("");
83        assert_eq!(doc.lines.len(), 1);
84    }
85}