1use std::sync::Arc;
2
3use parking_lot::Mutex;
4use ratatui::{
5 buffer::Buffer,
6 layout::Rect,
7 style::{Modifier, Style},
8 widgets::{Block, Widget},
9};
10
11use crate::logger::TUI_LOGGER;
12use crate::widget::inner::TuiWidgetInnerState;
13use crate::TuiWidgetState;
14use log::Level;
15use log::LevelFilter;
16
17fn advance_levelfilter(levelfilter: LevelFilter) -> (Option<LevelFilter>, Option<LevelFilter>) {
18 match levelfilter {
19 LevelFilter::Trace => (None, Some(LevelFilter::Debug)),
20 LevelFilter::Debug => (Some(LevelFilter::Trace), Some(LevelFilter::Info)),
21 LevelFilter::Info => (Some(LevelFilter::Debug), Some(LevelFilter::Warn)),
22 LevelFilter::Warn => (Some(LevelFilter::Info), Some(LevelFilter::Error)),
23 LevelFilter::Error => (Some(LevelFilter::Warn), Some(LevelFilter::Off)),
24 LevelFilter::Off => (Some(LevelFilter::Error), None),
25 }
26}
27
28pub struct TuiLoggerTargetWidget<'b> {
31 block: Option<Block<'b>>,
32 style: Style,
34 style_show: Style,
35 style_hide: Style,
36 style_off: Option<Style>,
37 highlight_style: Style,
38 state: Arc<Mutex<TuiWidgetInnerState>>,
39 targets: Vec<String>,
40}
41impl<'b> Default for TuiLoggerTargetWidget<'b> {
42 fn default() -> TuiLoggerTargetWidget<'b> {
43 TuiLoggerTargetWidget {
44 block: None,
45 style: Default::default(),
46 style_off: None,
47 style_hide: Style::default(),
48 style_show: Style::default().add_modifier(Modifier::REVERSED),
49 highlight_style: Style::default().add_modifier(Modifier::REVERSED),
50 state: Arc::new(Mutex::new(TuiWidgetInnerState::new())),
51 targets: vec![],
52 }
53 }
54}
55impl<'b> TuiLoggerTargetWidget<'b> {
56 pub fn block(mut self, block: Block<'b>) -> TuiLoggerTargetWidget<'b> {
57 self.block = Some(block);
58 self
59 }
60 pub fn opt_style(mut self, style: Option<Style>) -> TuiLoggerTargetWidget<'b> {
61 if let Some(s) = style {
62 self.style = s;
63 }
64 self
65 }
66 pub fn opt_style_off(mut self, style: Option<Style>) -> TuiLoggerTargetWidget<'b> {
67 if style.is_some() {
68 self.style_off = style;
69 }
70 self
71 }
72 pub fn opt_style_hide(mut self, style: Option<Style>) -> TuiLoggerTargetWidget<'b> {
73 if let Some(s) = style {
74 self.style_hide = s;
75 }
76 self
77 }
78 pub fn opt_style_show(mut self, style: Option<Style>) -> TuiLoggerTargetWidget<'b> {
79 if let Some(s) = style {
80 self.style_show = s;
81 }
82 self
83 }
84 pub fn opt_highlight_style(mut self, style: Option<Style>) -> TuiLoggerTargetWidget<'b> {
85 if let Some(s) = style {
86 self.highlight_style = s;
87 }
88 self
89 }
90 pub fn style(mut self, style: Style) -> TuiLoggerTargetWidget<'b> {
91 self.style = style;
92 self
93 }
94 pub fn style_off(mut self, style: Style) -> TuiLoggerTargetWidget<'b> {
95 self.style_off = Some(style);
96 self
97 }
98 pub fn style_hide(mut self, style: Style) -> TuiLoggerTargetWidget<'b> {
99 self.style_hide = style;
100 self
101 }
102 pub fn style_show(mut self, style: Style) -> TuiLoggerTargetWidget<'b> {
103 self.style_show = style;
104 self
105 }
106 pub fn highlight_style(mut self, style: Style) -> TuiLoggerTargetWidget<'b> {
107 self.highlight_style = style;
108 self
109 }
110 pub(crate) fn inner_state(
111 mut self,
112 state: Arc<Mutex<TuiWidgetInnerState>>,
113 ) -> TuiLoggerTargetWidget<'b> {
114 self.state = state;
115 self
116 }
117 pub fn state(mut self, state: &TuiWidgetState) -> TuiLoggerTargetWidget<'b> {
118 self.state = state.clone_state();
119 self
120 }
121}
122impl<'b> Widget for TuiLoggerTargetWidget<'b> {
123 fn render(mut self, area: Rect, buf: &mut Buffer) {
124 buf.set_style(area, self.style);
125 let list_area = match self.block.take() {
126 Some(b) => {
127 let inner_area = b.inner(area);
128 b.render(area, buf);
129 inner_area
130 }
131 None => area,
132 };
133 if list_area.width < 8 || list_area.height < 1 {
134 return;
135 }
136
137 let la_left = list_area.left();
138 let la_top = list_area.top();
139 let la_width = list_area.width as usize;
140
141 {
142 let inner = &TUI_LOGGER.inner.lock();
143 let hot_targets = &inner.targets;
144 let mut state = self.state.lock();
145 let hide_off = state.hide_off;
146 let offset = state.offset;
147 let focus_selected = state.focus_selected;
148 {
149 let targets = &mut state.config;
150 targets.merge(hot_targets);
151 self.targets.clear();
152 for (t, levelfilter) in targets.iter() {
153 if hide_off && levelfilter == &LevelFilter::Off {
154 continue;
155 }
156 self.targets.push(t.clone());
157 }
158 self.targets.sort();
159 }
160 state.nr_items = self.targets.len();
161 if state.selected >= state.nr_items {
162 state.selected = state.nr_items.max(1) - 1;
163 }
164 if state.selected < state.nr_items {
165 state.opt_selected_target = Some(self.targets[state.selected].clone());
166 let t = &self.targets[state.selected];
167 let (more, less) = if let Some(levelfilter) = state.config.get(t) {
168 advance_levelfilter(levelfilter)
169 } else {
170 (None, None)
171 };
172 state.opt_selected_visibility_less = less;
173 state.opt_selected_visibility_more = more;
174 let (more, less) = if let Some(levelfilter) = hot_targets.get(t) {
175 advance_levelfilter(levelfilter)
176 } else {
177 (None, None)
178 };
179 state.opt_selected_recording_less = less;
180 state.opt_selected_recording_more = more;
181 }
182 let list_height = (list_area.height as usize).min(self.targets.len());
183 let offset = if list_height > self.targets.len() {
184 0
185 } else if state.selected < state.nr_items {
186 let sel = state.selected;
187 if sel >= offset + list_height {
188 sel - list_height + 1
190 } else if sel.min(offset) + list_height > self.targets.len() {
191 self.targets.len() - list_height
192 } else {
193 sel.min(offset)
194 }
195 } else {
196 0
197 };
198 state.offset = offset;
199
200 let targets = &(&state.config);
201 let default_level = inner.default;
202 for i in 0..list_height {
203 let t = &self.targets[i + offset];
204 let hot_level_filter = hot_targets.get(t).unwrap_or(default_level);
214 let level_filter = targets.get(t).unwrap_or(default_level);
215 for (j, sym, lev) in &[
216 (0, "E", Level::Error),
217 (1, "W", Level::Warn),
218 (2, "I", Level::Info),
219 (3, "D", Level::Debug),
220 (4, "T", Level::Trace),
221 ] {
222 if let Some(cell) = buf.cell_mut((la_left + j, la_top + i as u16)) {
223 let cell_style = if hot_level_filter >= *lev {
224 if level_filter >= *lev {
225 if !focus_selected || i + offset == state.selected {
226 self.style_show
227 } else {
228 self.style_hide
229 }
230 } else {
231 self.style_hide
232 }
233 } else if let Some(style_off) = self.style_off {
234 style_off
235 } else {
236 cell.set_symbol(" ");
237 continue;
238 };
239 cell.set_style(cell_style);
240 cell.set_symbol(sym);
241 }
242 }
243 buf.set_stringn(la_left + 5, la_top + i as u16, ":", la_width, self.style);
244 buf.set_stringn(
245 la_left + 6,
246 la_top + i as u16,
247 t,
248 la_width,
249 if i + offset == state.selected {
250 self.highlight_style
251 } else {
252 self.style
253 },
254 );
255 }
256 }
257 }
258}