tui_lipan/widgets/log_view/
mod.rs1mod buffer;
4pub(crate) mod component;
5pub(crate) mod matching;
6
7pub use buffer::{LogBuffer, LogEntry, LogLevel};
8
9use std::sync::Arc;
10
11use crate::callback::Callback;
12use crate::core::element::Element;
13use crate::style::{BorderStyle, Length, Padding, ScrollbarConfig, Style, StyleSlot};
14
15#[derive(Clone, Debug, PartialEq, Eq)]
17pub struct LogViewEvent {
18 pub visible_index: usize,
20 pub source_index: usize,
22 pub entry: LogEntry,
24}
25
26pub use crate::utils::nucleo::MatchMode as LogFilterMode;
27
28#[derive(Clone, PartialEq)]
29pub struct LogViewProps {
30 pub entries: Arc<[LogEntry]>,
31 pub filter: Option<Arc<str>>,
32 pub filter_mode: LogFilterMode,
33 pub case_sensitive: bool,
34 pub show_level: bool,
35 pub auto_follow: bool,
36 pub paused: bool,
37 pub selected: usize,
38 pub style: Style,
39 pub hover_style: StyleSlot,
40 pub item_hover_style: StyleSlot,
41 pub selection_style: StyleSlot,
42 pub unfocused_selection_style: StyleSlot,
43 pub border: bool,
44 pub border_style: BorderStyle,
47 pub padding: Padding,
50 pub scrollbar: bool,
51 pub scrollbar_config: ScrollbarConfig,
52 pub show_scroll_indicators: bool,
53 pub scroll_indicator_style: Style,
54 pub width: Length,
57 pub height: Length,
60 pub empty_text: Option<Arc<str>>,
61 pub empty_text_style: Style,
62 pub trace_style: Style,
63 pub debug_style: Style,
64 pub info_style: Style,
65 pub warn_style: Style,
66 pub error_style: Style,
67 pub on_select: Option<Callback<LogViewEvent>>,
68 pub on_activate: Option<Callback<LogViewEvent>>,
69 pub activate_on_click: bool,
75}
76
77impl LogViewProps {
78 pub fn level_style(&self, level: LogLevel) -> Style {
79 match level {
80 LogLevel::Trace => self.trace_style,
81 LogLevel::Debug => self.debug_style,
82 LogLevel::Info => self.info_style,
83 LogLevel::Warn => self.warn_style,
84 LogLevel::Error => self.error_style,
85 }
86 }
87}
88
89#[derive(Clone)]
91pub struct LogView {
92 props: LogViewProps,
93}
94
95impl Default for LogView {
96 fn default() -> Self {
97 Self {
98 props: LogViewProps {
99 entries: Arc::new([]),
100 filter: None,
101 filter_mode: LogFilterMode::Fuzzy,
102 case_sensitive: true,
103 show_level: true,
104 auto_follow: true,
105 paused: false,
106 selected: 0,
107 style: Style::default(),
108 hover_style: StyleSlot::Inherit,
109 item_hover_style: StyleSlot::Inherit,
110 selection_style: StyleSlot::Inherit,
111 unfocused_selection_style: StyleSlot::Inherit,
112 border: false,
113 border_style: BorderStyle::default(),
114 padding: Padding::default(),
115 scrollbar: true,
116 scrollbar_config: ScrollbarConfig::default(),
117 show_scroll_indicators: false,
118 scroll_indicator_style: Style::default(),
119 width: Length::Flex(1),
120 height: Length::Flex(1),
121 empty_text: Some("No log lines".into()),
122 empty_text_style: Style::default(),
123 trace_style: Style::default(),
124 debug_style: Style::default(),
125 info_style: Style::default(),
126 warn_style: Style::default(),
127 error_style: Style::default(),
128 on_select: None,
129 on_activate: None,
130 activate_on_click: true,
131 },
132 }
133 }
134}
135
136impl LogView {
137 pub fn new() -> Self {
139 Self::default()
140 }
141
142 pub fn entries<I>(mut self, entries: I) -> Self
144 where
145 I: IntoIterator<Item = LogEntry>,
146 {
147 self.props.entries = entries.into_iter().collect();
148 self
149 }
150
151 pub fn entries_arc(mut self, entries: Arc<[LogEntry]>) -> Self {
153 self.props.entries = entries;
154 self
155 }
156
157 pub fn entry(mut self, entry: LogEntry) -> Self {
161 let mut entries = self.props.entries.to_vec();
162 entries.push(entry);
163 self.props.entries = entries.into();
164 self
165 }
166
167 pub fn filter(mut self, filter: impl Into<Arc<str>>) -> Self {
169 self.props.filter = Some(filter.into());
170 self
171 }
172
173 pub fn clear_filter(mut self) -> Self {
175 self.props.filter = None;
176 self
177 }
178
179 pub fn filter_mode(mut self, mode: LogFilterMode) -> Self {
181 self.props.filter_mode = mode;
182 self
183 }
184
185 pub fn fuzzy(mut self) -> Self {
187 self.props.filter_mode = LogFilterMode::Fuzzy;
188 self
189 }
190
191 pub fn substring(mut self) -> Self {
193 self.props.filter_mode = LogFilterMode::Substring;
194 self
195 }
196
197 pub fn exact(mut self) -> Self {
199 self.props.filter_mode = LogFilterMode::Exact;
200 self
201 }
202
203 pub fn case_sensitive(mut self, enabled: bool) -> Self {
205 self.props.case_sensitive = enabled;
206 self
207 }
208
209 pub fn show_level(mut self, show_level: bool) -> Self {
211 self.props.show_level = show_level;
212 self
213 }
214
215 pub fn auto_follow(mut self, auto_follow: bool) -> Self {
217 self.props.auto_follow = auto_follow;
218 self
219 }
220
221 pub fn paused(mut self, paused: bool) -> Self {
225 self.props.paused = paused;
226 self
227 }
228
229 pub fn selected(mut self, selected: usize) -> Self {
231 self.props.selected = selected;
232 self
233 }
234
235 pub fn style(mut self, style: Style) -> Self {
237 self.props.style = style;
238 self
239 }
240
241 pub fn hover_style(mut self, style: Style) -> Self {
243 self.props.hover_style = StyleSlot::Replace(style);
244 self
245 }
246
247 pub fn extend_hover_style(mut self, style: Style) -> Self {
249 self.props.hover_style = StyleSlot::Extend(style);
250 self
251 }
252
253 pub fn inherit_hover_style(mut self) -> Self {
255 self.props.hover_style = StyleSlot::Inherit;
256 self
257 }
258
259 pub fn hover_style_slot(mut self, slot: StyleSlot) -> Self {
261 self.props.hover_style = slot;
262 self
263 }
264
265 pub fn item_hover_style(mut self, style: Style) -> Self {
267 self.props.item_hover_style = StyleSlot::Replace(style);
268 self
269 }
270
271 pub fn extend_item_hover_style(mut self, style: Style) -> Self {
273 self.props.item_hover_style = StyleSlot::Extend(style);
274 self
275 }
276
277 pub fn inherit_item_hover_style(mut self) -> Self {
279 self.props.item_hover_style = StyleSlot::Inherit;
280 self
281 }
282
283 pub fn item_hover_style_slot(mut self, slot: StyleSlot) -> Self {
285 self.props.item_hover_style = slot;
286 self
287 }
288
289 pub fn selection_style(mut self, style: Style) -> Self {
291 self.props.selection_style = StyleSlot::Replace(style);
292 self
293 }
294
295 pub fn extend_selection_style(mut self, style: Style) -> Self {
297 self.props.selection_style = StyleSlot::Extend(style);
298 self
299 }
300
301 pub fn inherit_selection_style(mut self) -> Self {
303 self.props.selection_style = StyleSlot::Inherit;
304 self
305 }
306
307 pub fn selection_style_slot(mut self, slot: StyleSlot) -> Self {
309 self.props.selection_style = slot;
310 self
311 }
312
313 pub fn unfocused_selection_style(mut self, style: Style) -> Self {
315 self.props.unfocused_selection_style = StyleSlot::Replace(style);
316 self
317 }
318
319 pub fn extend_unfocused_selection_style(mut self, style: Style) -> Self {
321 self.props.unfocused_selection_style = StyleSlot::Extend(style);
322 self
323 }
324
325 pub fn inherit_unfocused_selection_style(mut self) -> Self {
327 self.props.unfocused_selection_style = StyleSlot::Inherit;
328 self
329 }
330
331 pub fn unfocused_selection_style_slot(mut self, slot: StyleSlot) -> Self {
333 self.props.unfocused_selection_style = slot;
334 self
335 }
336
337 pub fn border(mut self, border: bool) -> Self {
339 self.props.border = border;
340 self
341 }
342
343 pub fn border_style(mut self, border_style: BorderStyle) -> Self {
345 self.props.border_style = border_style;
346 self
347 }
348
349 pub fn padding(mut self, padding: impl Into<Padding>) -> Self {
351 self.props.padding = padding.into();
352 self
353 }
354
355 pub fn scrollbar(mut self, scrollbar: bool) -> Self {
357 self.props.scrollbar = scrollbar;
358 self
359 }
360
361 pub fn scrollbar_config(mut self, config: ScrollbarConfig) -> Self {
363 self.props.scrollbar_config = config;
364 self
365 }
366
367 pub fn show_scroll_indicators(mut self, show: bool) -> Self {
369 self.props.show_scroll_indicators = show;
370 self
371 }
372
373 pub fn scroll_indicator_style(mut self, style: Style) -> Self {
375 self.props.scroll_indicator_style = style;
376 self
377 }
378
379 pub fn width(mut self, width: Length) -> Self {
381 self.props.width = width;
382 self
383 }
384
385 pub fn height(mut self, height: Length) -> Self {
387 self.props.height = height;
388 self
389 }
390
391 pub fn empty_text(mut self, text: impl Into<Arc<str>>) -> Self {
393 self.props.empty_text = Some(text.into());
394 self
395 }
396
397 pub fn empty_text_style(mut self, style: Style) -> Self {
399 self.props.empty_text_style = style;
400 self
401 }
402
403 pub fn trace_style(mut self, style: Style) -> Self {
405 self.props.trace_style = style;
406 self
407 }
408
409 pub fn debug_style(mut self, style: Style) -> Self {
411 self.props.debug_style = style;
412 self
413 }
414
415 pub fn info_style(mut self, style: Style) -> Self {
417 self.props.info_style = style;
418 self
419 }
420
421 pub fn warn_style(mut self, style: Style) -> Self {
423 self.props.warn_style = style;
424 self
425 }
426
427 pub fn error_style(mut self, style: Style) -> Self {
429 self.props.error_style = style;
430 self
431 }
432
433 pub fn on_select(mut self, cb: Callback<LogViewEvent>) -> Self {
435 self.props.on_select = Some(cb);
436 self
437 }
438
439 pub fn on_activate(mut self, cb: Callback<LogViewEvent>) -> Self {
441 self.props.on_activate = Some(cb);
442 self
443 }
444
445 pub fn activate_on_click(mut self, activate: bool) -> Self {
450 self.props.activate_on_click = activate;
451 self
452 }
453}
454
455impl From<LogView> for Element {
456 fn from(view: LogView) -> Self {
457 crate::child(component::LogViewComponent::new, view.props)
458 }
459}
460
461#[cfg(test)]
462mod tests {
463 use super::*;
464
465 #[test]
466 fn log_buffer_keeps_newest_entries() {
467 let mut buffer = LogBuffer::new(2);
468 buffer.push(LogEntry::info("a"));
469 buffer.push(LogEntry::info("b"));
470 buffer.push(LogEntry::info("c"));
471
472 let snapshot = buffer.snapshot();
473 assert_eq!(snapshot.len(), 2);
474 assert_eq!(snapshot[0].message.as_ref(), "b");
475 assert_eq!(snapshot[1].message.as_ref(), "c");
476 }
477
478 #[test]
479 fn paused_snapshot_stays_frozen() {
480 let mut buffer = LogBuffer::new(8);
481 buffer.push(LogEntry::info("one"));
482 buffer.push(LogEntry::info("two"));
483 buffer.set_paused(true);
484 buffer.push(LogEntry::info("three"));
485
486 let snapshot = buffer.snapshot();
487 assert_eq!(snapshot.len(), 2);
488 assert_eq!(snapshot[1].message.as_ref(), "two");
489 }
490}