cursive_logger_view/
view.rs

1use crate::{
2    log_buffer::{self, GET_LOCK_ERR_MSG},
3    FlexiLoggerView,
4};
5use cursive_core::{
6    view::{ScrollStrategy, Scrollable, View},
7    views::ScrollView,
8    Printer, Vec2,
9};
10use tap::Pipe;
11use unicode_width::UnicodeWidthStr;
12
13impl FlexiLoggerView {
14    /// Wraps a `FlexiLoggerView` in a `ScrollView`.
15    ///
16    /// # Example
17    ///
18    /// ```
19    /// use cursive_logger_view::FlexiLoggerView;
20    ///
21    /// FlexiLoggerView::new()
22    ///     .with_indent(false)
23    ///     .wrap_scroll_view();
24    /// ```
25    pub fn wrap_scroll_view(self) -> ScrollView<Self> {
26        self.scrollable()
27            .scroll_x(true)
28            .scroll_y(true)
29            .scroll_strategy(ScrollStrategy::StickToBottom)
30    }
31
32    /// Creates a new `FlexiLoggerView`.
33    pub fn new() -> Self {
34        FlexiLoggerView { indent: true }
35    }
36}
37
38impl View for FlexiLoggerView {
39    fn draw(&self, printer: &Printer<'_, '_>) {
40        let logs = log_buffer::static_logs()
41            .lock()
42            .expect(GET_LOCK_ERR_MSG);
43
44        // Only print the last logs, so skip what doesn't fit
45        let skipped = logs
46            .len()
47            .saturating_sub(printer.size.y);
48
49        logs.iter()
50            .skip(skipped)
51            .fold(0, |y, msg| {
52                let log_msg_index = msg.spans_raw().len() - 1;
53
54                let x = msg
55                    .spans()
56                    .take(log_msg_index)
57                    .fold(0, |x, span| {
58                        printer.with_style(*span.attr, |p| {
59                            p.print((x, y), span.content)
60                        });
61                        x + span.width
62                    });
63
64                msg.spans()
65                    .nth(log_msg_index)
66                    .map(|log_msg| {
67                        log_msg
68                            .content
69                            .split('\n')
70                            .enumerate()
71                            .fold(y, |current_y, (i, part)| {
72                                let x_pos =
73                                    if !self.indent && i > 0 { 0 } else { x };
74                                printer.with_style(*log_msg.attr, |p| {
75                                    p.print((x_pos, current_y), part)
76                                });
77                                current_y + 1
78                            })
79                    })
80                    .unwrap_or(y)
81            });
82    }
83
84    fn required_size(&mut self, constraint: Vec2) -> Vec2 {
85        let logs = log_buffer::static_logs()
86            .lock()
87            .expect(GET_LOCK_ERR_MSG);
88
89        // The longest line sets the width
90        let width = logs
91            .iter()
92            .map(|msg| {
93                msg.spans()
94                    .map(|x|
95                    // if the log message contains more than one line,
96                    // only the longest line should be considered
97                    // (definitely not the total content.len())
98                    x.content.split('\n').map(|x| x.width()).max().expect("ERR: required_size(), failed to get width"))
99                    .sum::<usize>()
100            })
101            .max()
102            .unwrap_or(1)
103            .pipe(|w|core::cmp::max(w, constraint.x));
104
105        let height = logs
106            .iter()
107            .map(|msg| {
108                msg.spans()
109                    .last()
110                    .map(|x| x.content.split('\n').count())
111                    .expect("ERR: required_size(), the last span message is invalid, and failed to get height.")
112            })
113            .sum::<usize>()
114            .pipe(|h| core::cmp::max(h, constraint.y));
115
116        Vec2::new(width, height)
117    }
118}