duat_utils/widgets/
log_book.rs1use std::marker::PhantomData;
2
3use duat_core::{
4 context::{Logs, Record},
5 prelude::*,
6 ui::Side,
7};
8
9use crate::modes::Pager;
10
11pub struct LogBook {
13 logs: Logs,
14 len_of_taken: usize,
15 text: Text,
16 format_rec: Box<dyn FnMut(Record) -> Option<Text>>,
17 close_on_unfocus: bool,
18}
19
20impl<U: Ui> Widget<U> for LogBook {
21 type Cfg = LogBookCfg<U>;
22
23 fn cfg() -> Self::Cfg {
24 LogBookCfg {
25 format_rec: Box::new(|rec| {
26 use context::Level::*;
27 let mut builder = match rec.level() {
28 Error => txt!("[log_book.error][[ERROR]][log_book.colon]: "),
29 Warn => txt!("[log_book.warn][[WARNING]][log_book.colon]: "),
30 Info => txt!("[log_book.info][[INFO]][log_book.colon]: "),
31 Debug => txt!("[log_book.debug][[DEBUG]][log_book.colon]: "),
32 Trace => unreachable!("Trace is not meant to be useable"),
33 };
34
35 builder.push(txt!(
36 "[log_book.bracket]([log_book.target]{}[log_book.bracket])[] {}",
37 rec.target(),
38 rec.text().clone()
39 ));
40
41 Some(builder.build())
42 }),
43 close_on_unfocus: true,
44 hidden: true,
45 side: Side::Below,
46 _ghost: PhantomData,
47 }
48 }
49
50 fn update(pa: &mut Pass, handle: Handle<Self, U>)
51 where
52 Self: Sized,
53 {
54 handle.write(pa, |lb, area| {
55 let Some(new_records) = lb.logs.get(lb.len_of_taken..) else {
56 return;
57 };
58
59 let records_were_added = !new_records.is_empty();
60 lb.len_of_taken += new_records.len();
61
62 for rec_text in new_records.into_iter().filter_map(&mut lb.format_rec) {
63 lb.text.insert_text(lb.text.len(), rec_text);
64 }
65
66 if records_were_added {
67 area.scroll_to_points(&lb.text, lb.text.len(), Widget::<U>::print_cfg(lb));
68 }
69 });
70 }
71
72 fn needs_update(&self) -> bool {
73 self.logs.has_changed()
74 }
75
76 fn text(&self) -> &Text {
77 &self.text
78 }
79
80 fn text_mut(&mut self) -> &mut Text {
81 &mut self.text
82 }
83
84 fn once() -> Result<(), Text> {
85 form::set_weak("log_book.error", "default.error");
86 form::set_weak("log_book.warn", "default.warn");
87 form::set_weak("log_book.info", "default.info");
88 form::set_weak("log_book.debug", "default.debug");
89 form::set_weak("log_book.colon", "prompt.colon");
90 form::set_weak("log_book.bracket", "punctuation.bracket");
91 form::set_weak("log_book.target", "module");
92
93 cmd::add!("logs", |pa| {
94 mode::set(Pager::<LogBook, U>::new());
95 Ok(None)
96 })
97 .unwrap();
98
99 Ok(())
100 }
101
102 fn print_cfg(&self) -> PrintCfg {
103 PrintCfg::new().edge_wrapped().with_scrolloff(0, 0)
104 }
105
106 fn on_focus(_: &mut Pass, handle: Handle<Self, U>) {
107 handle.area().reveal().unwrap();
108 }
109
110 fn on_unfocus(pa: &mut Pass, handle: Handle<Self, U>) {
111 handle.read(pa, |lb, area| {
112 if lb.close_on_unfocus {
113 area.hide().unwrap()
114 }
115 });
116 }
117}
118
119pub struct LogBookCfg<U> {
121 format_rec: Box<dyn FnMut(Record) -> Option<Text>>,
122 close_on_unfocus: bool,
123 hidden: bool,
124 side: Side,
125 _ghost: PhantomData<U>,
126}
127
128impl<U> LogBookCfg<U> {
129 pub fn open_by_default(self) -> Self {
131 Self { hidden: false, ..self }
132 }
133
134 pub fn keep_open_on_unfocus(self) -> Self {
137 Self { close_on_unfocus: false, ..self }
138 }
139
140 pub fn formatted(self, format_rec: impl FnMut(Record) -> Option<Text> + 'static) -> Self {
148 Self { format_rec: Box::new(format_rec), ..self }
149 }
150
151 pub fn on_the_right(self) -> Self {
153 Self { side: Side::Right, ..self }
154 }
155
156 pub fn on_the_left(self) -> Self {
158 Self { side: Side::Left, ..self }
159 }
160
161 pub fn above(self) -> Self {
163 Self { side: Side::Above, ..self }
164 }
165}
166
167impl<U: Ui> WidgetCfg<U> for LogBookCfg<U> {
168 type Widget = LogBook;
169
170 fn build(mut self, _: &mut Pass, _: Option<FileHandle<U>>) -> (Self::Widget, PushSpecs) {
171 let logs = context::logs();
172
173 let mut text = Text::new();
174
175 let records = logs.get(..).unwrap();
176 let len_of_taken = records.len();
177 for rec_text in records.into_iter().filter_map(&mut self.format_rec) {
178 text.insert_text(text.len(), rec_text);
179 }
180
181 let lb = LogBook {
182 logs,
183 len_of_taken,
184 text,
185 format_rec: self.format_rec,
186 close_on_unfocus: self.close_on_unfocus,
187 };
188
189 let specs = match self.side {
190 Side::Right => PushSpecs::right().with_hor_len(30.0),
191 Side::Left => PushSpecs::left().with_hor_len(30.0),
192 Side::Above => PushSpecs::above().with_ver_len(10.0),
193 Side::Below => PushSpecs::below().with_ver_len(10.0),
194 };
195
196 (lb, if self.hidden { specs.hidden() } else { specs })
197 }
198}