iris_reader/renderer/
text.rs1use 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}