1use std::marker::PhantomData;
2
3use duat_core::{
4 context::{Logs, Record},
5 prelude::*,
6 ui::{
7 Constraint::{self, Len, Ratio},
8 Side,
9 },
10};
11
12use crate::modes::Pager;
13
14pub struct LogBook {
16 logs: Logs,
17 len_of_taken: usize,
18 text: Text,
19 format_rec: Box<dyn FnMut(Record) -> Option<Text> + Send>,
20 close_on_unfocus: bool,
21}
22
23impl<U: Ui> Widget<U> for LogBook {
24 type Cfg = LogBookCfg<U>;
25
26 fn cfg() -> Self::Cfg {
27 LogBookCfg {
28 format_rec: Box::new(|rec| {
29 use context::Level::*;
30 let mut builder = match rec.level() {
31 Error => txt!("[log_book.error][[ERROR]][log_book.colon]: "),
32 Warn => txt!("[log_book.warn][[WARNING]][log_book.colon]: "),
33 Info => txt!("[log_book.info][[INFO]][log_book.colon]: "),
34 Debug => txt!("[log_book.debug][[DEBUG]][log_book.colon]: "),
35 Trace => unreachable!("Trace is not meant to be useable"),
36 };
37
38 builder.push(txt!(
39 "[log_book.bracket]([log_book.target]{}[log_book.bracket])[] {}",
40 rec.metadata().target(),
41 rec.text().clone()
42 ));
43
44 Some(builder.build())
45 }),
46 close_on_unfocus: true,
47 hidden: true,
48 side: Side::Below,
49 height: Ratio(1, 4),
50 width: Ratio(2, 7),
51 _ghost: PhantomData,
52 }
53 }
54
55 fn update(pa: &mut Pass, handle: &Handle<Self, U>)
56 where
57 Self: Sized,
58 {
59 let (lb, area) = handle.write_with_area(pa);
60 let Some(new_records) = lb.logs.get(lb.len_of_taken..) else {
61 return;
62 };
63
64 let records_were_added = !new_records.is_empty();
65 lb.len_of_taken += new_records.len();
66
67 for rec_text in new_records.into_iter().filter_map(&mut lb.format_rec) {
68 lb.text.insert_text(lb.text.len(), rec_text);
69 }
70
71 if records_were_added {
72 area.scroll_to_points(&lb.text, lb.text.len(), Widget::<U>::print_cfg(lb));
73 }
74 }
75
76 fn needs_update(&self, _: &Pass) -> bool {
77 self.logs.has_changed()
78 }
79
80 fn text(&self) -> &Text {
81 &self.text
82 }
83
84 fn text_mut(&mut self) -> &mut Text {
85 &mut self.text
86 }
87
88 fn once() -> Result<(), Text> {
89 form::set_weak("log_book.error", "default.error");
90 form::set_weak("log_book.warn", "default.warn");
91 form::set_weak("log_book.info", "default.info");
92 form::set_weak("log_book.debug", "default.debug");
93 form::set_weak("log_book.colon", "prompt.colon");
94 form::set_weak("log_book.bracket", "punctuation.bracket");
95 form::set_weak("log_book.target", "module");
96
97 cmd::add!("logs", |pa| {
98 mode::set(Pager::<LogBook, U>::new());
99 Ok(None)
100 });
101
102 Ok(())
103 }
104
105 fn print_cfg(&self) -> PrintCfg {
106 *PrintCfg::new().wrap_on_word().set_scrolloff(0, 0)
107 }
108
109 fn on_focus(pa: &mut Pass, handle: &Handle<Self, U>) {
110 handle.area(pa).reveal().unwrap();
111 }
112
113 fn on_unfocus(pa: &mut Pass, handle: &Handle<Self, U>) {
114 if handle.read(pa).close_on_unfocus {
115 handle.area(pa).hide().unwrap()
116 }
117 }
118}
119
120pub struct LogBookCfg<U> {
122 format_rec: Box<dyn FnMut(Record) -> Option<Text> + Send>,
123 close_on_unfocus: bool,
124 hidden: bool,
125 side: Side,
126 height: Constraint,
127 width: Constraint,
128 _ghost: PhantomData<U>,
129}
130
131impl<U> LogBookCfg<U> {
132 pub fn open_by_default(self) -> Self {
134 Self { hidden: false, ..self }
135 }
136
137 pub fn keep_open_on_unfocus(self) -> Self {
140 Self { close_on_unfocus: false, ..self }
141 }
142
143 pub fn formatted(self, fmt: impl FnMut(Record) -> Option<Text> + Send + 'static) -> Self {
151 Self { format_rec: Box::new(fmt), ..self }
152 }
153
154 pub fn on_the_right(self) -> Self {
156 Self { side: Side::Right, ..self }
157 }
158
159 pub fn on_the_left(self) -> Self {
161 Self { side: Side::Left, ..self }
162 }
163
164 pub fn above(self) -> Self {
166 Self { side: Side::Above, ..self }
167 }
168
169 pub fn height(self, height: usize) -> Self {
173 Self { height: Len(height as f32), ..self }
174 }
175
176 pub fn width(self, width: usize) -> Self {
180 Self { width: Len(width as f32), ..self }
181 }
182
183 pub fn height_ratio(self, den: u16, div: u16) -> Self {
187 Self { height: Ratio(den, div), ..self }
188 }
189
190 pub fn width_ratio(self, den: u16, div: u16) -> Self {
194 Self { width: Ratio(den, div), ..self }
195 }
196}
197
198impl<U: Ui> WidgetCfg<U> for LogBookCfg<U> {
199 type Widget = LogBook;
200
201 fn build(mut self, _: &mut Pass, _: BuildInfo<U>) -> (Self::Widget, PushSpecs) {
202 let logs = context::logs();
203
204 let mut text = Text::new();
205
206 let records = logs.get(..).unwrap();
207 let len_of_taken = records.len();
208 for rec_text in records.into_iter().filter_map(&mut self.format_rec) {
209 text.insert_text(text.len(), rec_text);
210 }
211
212 let lb = LogBook {
213 logs,
214 len_of_taken,
215 text,
216 format_rec: self.format_rec,
217 close_on_unfocus: self.close_on_unfocus,
218 };
219
220 let specs = match self.side {
221 Side::Right => PushSpecs::right().constrain_hor(self.width),
222 Side::Left => PushSpecs::left().constrain_hor(self.width),
223 Side::Above => PushSpecs::above().constrain_ver(self.height),
224 Side::Below => PushSpecs::below().constrain_ver(self.height),
225 };
226
227 (lb, if self.hidden { specs.hidden() } else { specs })
228 }
229}