1use crate::widget::logformatter::LogFormatter;
2use crate::widget::standard_formatter::LogStandardFormatter;
3use parking_lot::Mutex;
4use std::sync::Arc;
5
6use ratatui::{
7 buffer::Buffer,
8 layout::Rect,
9 style::Style,
10 widgets::{Block, Widget},
11};
12
13use crate::{CircularBuffer, TuiLoggerLevelOutput, TuiWidgetState, TUI_LOGGER};
14
15use super::inner::TuiWidgetInnerState;
16
17pub struct TuiLoggerWidget<'b> {
18 block: Option<Block<'b>>,
19 logformatter: Option<Box<dyn LogFormatter>>,
20 style: Style,
22 style_error: Option<Style>,
24 style_warn: Option<Style>,
25 style_debug: Option<Style>,
26 style_trace: Option<Style>,
27 style_info: Option<Style>,
28 format_separator: char,
29 format_timestamp: Option<String>,
30 format_output_level: Option<TuiLoggerLevelOutput>,
31 format_output_target: bool,
32 format_output_file: bool,
33 format_output_line: bool,
34 state: Arc<Mutex<TuiWidgetInnerState>>,
35}
36impl<'b> Default for TuiLoggerWidget<'b> {
37 fn default() -> TuiLoggerWidget<'b> {
38 TuiLoggerWidget {
40 block: None,
41 logformatter: None,
42 style: Default::default(),
43 style_error: None,
44 style_warn: None,
45 style_debug: None,
46 style_trace: None,
47 style_info: None,
48 format_separator: ':',
49 format_timestamp: Some("%H:%M:%S".to_string()),
50 format_output_level: Some(TuiLoggerLevelOutput::Long),
51 format_output_target: true,
52 format_output_file: true,
53 format_output_line: true,
54 state: Arc::new(Mutex::new(TuiWidgetInnerState::new())),
55 }
56 }
57}
58impl<'b> TuiLoggerWidget<'b> {
59 pub fn block(mut self, block: Block<'b>) -> Self {
60 self.block = Some(block);
61 self
62 }
63 pub fn opt_formatter(mut self, formatter: Option<Box<dyn LogFormatter>>) -> Self {
64 self.logformatter = formatter;
65 self
66 }
67 pub fn formatter(mut self, formatter: Box<dyn LogFormatter>) -> Self {
68 self.logformatter = Some(formatter);
69 self
70 }
71 pub fn opt_style(mut self, style: Option<Style>) -> Self {
72 if let Some(s) = style {
73 self.style = s;
74 }
75 self
76 }
77 pub fn opt_style_error(mut self, style: Option<Style>) -> Self {
78 if style.is_some() {
79 self.style_error = style;
80 }
81 self
82 }
83 pub fn opt_style_warn(mut self, style: Option<Style>) -> Self {
84 if style.is_some() {
85 self.style_warn = style;
86 }
87 self
88 }
89 pub fn opt_style_info(mut self, style: Option<Style>) -> Self {
90 if style.is_some() {
91 self.style_info = style;
92 }
93 self
94 }
95 pub fn opt_style_trace(mut self, style: Option<Style>) -> Self {
96 if style.is_some() {
97 self.style_trace = style;
98 }
99 self
100 }
101 pub fn opt_style_debug(mut self, style: Option<Style>) -> Self {
102 if style.is_some() {
103 self.style_debug = style;
104 }
105 self
106 }
107 pub fn style(mut self, style: Style) -> Self {
108 self.style = style;
109 self
110 }
111 pub fn style_error(mut self, style: Style) -> Self {
112 self.style_error = Some(style);
113 self
114 }
115 pub fn style_warn(mut self, style: Style) -> Self {
116 self.style_warn = Some(style);
117 self
118 }
119 pub fn style_info(mut self, style: Style) -> Self {
120 self.style_info = Some(style);
121 self
122 }
123 pub fn style_trace(mut self, style: Style) -> Self {
124 self.style_trace = Some(style);
125 self
126 }
127 pub fn style_debug(mut self, style: Style) -> Self {
128 self.style_debug = Some(style);
129 self
130 }
131 pub fn opt_output_separator(mut self, opt_sep: Option<char>) -> Self {
132 if let Some(ch) = opt_sep {
133 self.format_separator = ch;
134 }
135 self
136 }
137 pub fn output_separator(mut self, sep: char) -> Self {
140 self.format_separator = sep;
141 self
142 }
143 pub fn opt_output_timestamp(mut self, opt_fmt: Option<Option<String>>) -> Self {
144 if let Some(fmt) = opt_fmt {
145 self.format_timestamp = fmt;
146 }
147 self
148 }
149 pub fn output_timestamp(mut self, fmt: Option<String>) -> Self {
156 self.format_timestamp = fmt;
157 self
158 }
159 pub fn opt_output_level(mut self, opt_fmt: Option<Option<TuiLoggerLevelOutput>>) -> Self {
160 if let Some(fmt) = opt_fmt {
161 self.format_output_level = fmt;
162 }
163 self
164 }
165 pub fn output_level(mut self, level: Option<TuiLoggerLevelOutput>) -> Self {
173 self.format_output_level = level;
174 self
175 }
176 pub fn opt_output_target(mut self, opt_enabled: Option<bool>) -> Self {
177 if let Some(enabled) = opt_enabled {
178 self.format_output_target = enabled;
179 }
180 self
181 }
182 pub fn output_target(mut self, enabled: bool) -> Self {
186 self.format_output_target = enabled;
187 self
188 }
189 pub fn opt_output_file(mut self, opt_enabled: Option<bool>) -> Self {
190 if let Some(enabled) = opt_enabled {
191 self.format_output_file = enabled;
192 }
193 self
194 }
195 pub fn output_file(mut self, enabled: bool) -> Self {
199 self.format_output_file = enabled;
200 self
201 }
202 pub fn opt_output_line(mut self, opt_enabled: Option<bool>) -> Self {
203 if let Some(enabled) = opt_enabled {
204 self.format_output_line = enabled;
205 }
206 self
207 }
208 pub fn output_line(mut self, enabled: bool) -> Self {
212 self.format_output_line = enabled;
213 self
214 }
215 pub fn inner_state(mut self, state: Arc<Mutex<TuiWidgetInnerState>>) -> Self {
216 self.state = state;
217 self
218 }
219 pub fn state(mut self, state: &TuiWidgetState) -> Self {
220 self.state = state.inner.clone();
221 self
222 }
223}
224impl<'b> Widget for TuiLoggerWidget<'b> {
225 fn render(mut self, area: Rect, buf: &mut Buffer) {
226 let formatter = match self.logformatter.take() {
227 Some(fmt) => fmt,
228 None => {
229 let fmt = LogStandardFormatter {
230 style: self.style,
231 style_error: self.style_error,
232 style_warn: self.style_warn,
233 style_debug: self.style_debug,
234 style_trace: self.style_trace,
235 style_info: self.style_info,
236 format_separator: self.format_separator,
237 format_timestamp: self.format_timestamp,
238 format_output_level: self.format_output_level,
239 format_output_target: self.format_output_target,
240 format_output_file: self.format_output_file,
241 format_output_line: self.format_output_line,
242 };
243 Box::new(fmt)
244 }
245 };
246
247 buf.set_style(area, self.style);
248 let list_area = match self.block.take() {
249 Some(b) => {
250 let inner_area = b.inner(area);
251 b.render(area, buf);
252 inner_area
253 }
254 None => area,
255 };
256 if list_area.width < formatter.min_width() || list_area.height < 1 {
257 return;
258 }
259
260 let mut state = self.state.lock();
261 let la_height = list_area.height as usize;
262 let la_left = list_area.left();
263 let la_top = list_area.top();
264 let la_width = list_area.width as usize;
265 let mut lines = CircularBuffer::new(la_height);
267 {
268 state.opt_timestamp_next_page = None;
269 let opt_timestamp_bottom = state.opt_timestamp_bottom;
270 let mut opt_timestamp_prev_page = None;
271 let mut tui_lock = TUI_LOGGER.inner.lock();
272 let mut circular = CircularBuffer::new(10); for evt in tui_lock.events.rev_iter() {
274 if let Some(level) = state.config.get(&evt.target) {
275 if level < evt.level {
276 continue;
277 }
278 } else if let Some(level) = state.config.default_display_level {
279 if level < evt.level {
280 continue;
281 }
282 }
283 if state.focus_selected {
284 if let Some(target) = state.opt_selected_target.as_ref() {
285 if target != &evt.target {
286 continue;
287 }
288 }
289 }
290 if let Some(timestamp) = opt_timestamp_bottom.as_ref() {
293 if *timestamp < evt.timestamp {
294 circular.push(evt.timestamp);
295 continue;
296 }
297 }
298 if !circular.is_empty() {
299 state.opt_timestamp_next_page = circular.take().first().cloned();
300 }
301 let mut evt_lines = formatter.format(la_width, evt);
302 while let Some(line) = evt_lines.pop() {
303 lines.push(line);
304 }
305 if lines.len() >= la_height {
306 break;
307 }
308 if opt_timestamp_prev_page.is_none() && lines.len() >= la_height / 2 {
309 opt_timestamp_prev_page = Some(evt.timestamp);
310 }
311 }
312 state.opt_timestamp_prev_page = opt_timestamp_prev_page.or(state.opt_timestamp_bottom);
313 }
314
315 let offset: u16 = if state.opt_timestamp_bottom.is_none() {
317 0
318 } else {
319 let lines_cnt = lines.len();
320 std::cmp::max(0, la_height - lines_cnt) as u16
321 };
322
323 for (i, line) in lines.iter().rev().take(la_height).enumerate() {
324 line.render(
325 Rect {
326 x: la_left,
327 y: la_top + i as u16 + offset,
328 width: list_area.width,
329 height: 1,
330 },
331 buf,
332 )
333 }
334 }
335}