cursive_logger_view/
view.rs

1use cursive_core::{
2  view::{ScrollStrategy, Scrollable, View},
3  views::ScrollView,
4  Printer, Vec2,
5};
6use tap::Pipe;
7use unicode_width::UnicodeWidthStr;
8
9use crate::{
10  log_buffer::{self, GET_LOCK_ERR_MSG},
11  FlexiLoggerView,
12};
13
14impl FlexiLoggerView {
15  /// Wraps a `FlexiLoggerView` in a `ScrollView`.
16  ///
17  /// # Example
18  ///
19  /// ```
20  /// use cursive_logger_view::FlexiLoggerView;
21  ///
22  /// FlexiLoggerView::new()
23  ///     .with_indent(false)
24  ///     .wrap_scroll_view();
25  /// ```
26  pub fn wrap_scroll_view(self) -> ScrollView<Self> {
27    self
28      .scrollable()
29      .scroll_x(true)
30      .scroll_y(true)
31      .scroll_strategy(ScrollStrategy::StickToBottom)
32  }
33
34  /// Creates a new `FlexiLoggerView`.
35  pub fn new() -> Self {
36    FlexiLoggerView { indent: true }
37  }
38}
39
40impl View for FlexiLoggerView {
41  fn draw(&self, printer: &Printer<'_, '_>) {
42    let logs = log_buffer::static_logs()
43      .lock()
44      .expect(GET_LOCK_ERR_MSG);
45
46    // Only print the last logs, so skip what doesn't fit
47    let skipped = logs
48      .len()
49      .saturating_sub(printer.size.y);
50
51    logs
52      .iter()
53      .skip(skipped)
54      .fold(0, |y, msg| {
55        let log_msg_index = msg.spans_raw().len() - 1;
56
57        let x = msg
58          .spans()
59          .take(log_msg_index)
60          .fold(0, |x, span| {
61            printer.with_style(*span.attr, |p| p.print((x, y), span.content));
62            x + span.width
63          });
64
65        msg
66          .spans()
67          .nth(log_msg_index)
68          .map(|log_msg| {
69            log_msg
70              .content
71              .split('\n')
72              .enumerate()
73              .fold(y, |current_y, (i, part)| {
74                let x_pos = if !self.indent && i > 0 { 0 } else { x };
75                printer
76                  .with_style(*log_msg.attr, |p| p.print((x_pos, current_y), part));
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}