duat_term/
lib.rs

1#![feature(
2    iter_collect_into,
3    let_chains,
4    if_let_guard,
5    decl_macro,
6    debug_closure_helpers
7)]
8use std::{
9    fmt::Debug,
10    io::{self, Write},
11    sync::{Mutex, mpsc},
12    time::Duration,
13};
14
15pub use area::{Area, Coords};
16use crossterm::{
17    cursor,
18    event::{self, PopKeyboardEnhancementFlags},
19    execute,
20    style::ContentStyle,
21    terminal::{self, ClearType},
22};
23use duat_core::{
24    DuatError,
25    data::RwData,
26    form::Color,
27    text::err,
28    ui::{self, Sender, UiEvent},
29};
30
31use self::{layout::Layout, print::Printer};
32pub use self::{
33    print::{Brush, Frame},
34    rules::{VertRule, VertRuleCfg},
35};
36
37mod area;
38mod layout;
39mod print;
40mod rules;
41
42pub struct Ui {
43    windows: Vec<(Area, RwData<Printer>)>,
44    layouts: RwData<Vec<Layout>>,
45    win: usize,
46    fr: Frame,
47    printer_fn: fn() -> RwData<Printer>,
48}
49
50impl Ui {
51    fn cur_printer(&self) -> &RwData<Printer> {
52        if let Some((_, printer)) = self.windows.get(self.win) {
53            printer
54        } else {
55            unreachable!("Started printing before a window was created");
56        }
57    }
58}
59
60impl Default for Ui {
61    fn default() -> Self {
62        Self {
63            windows: Vec::new(),
64            layouts: RwData::default(),
65            win: 0,
66            fr: Frame::default(),
67            printer_fn: RwData::default,
68        }
69    }
70}
71
72impl ui::Ui for Ui {
73    type Area = Area;
74    type MetaStatics = Mutex<Ui>;
75
76    fn open(ms: &'static Self::MetaStatics, tx: Sender, rx: mpsc::Receiver<UiEvent>) {
77        // Initial terminal setup
78        use crossterm::event::{KeyboardEnhancementFlags as KEF, PushKeyboardEnhancementFlags};
79        execute!(
80            io::stdout(),
81            terminal::EnterAlternateScreen,
82            terminal::DisableLineWrap
83        )
84        .unwrap();
85        // Some key chords (like alt+shift+o for some reason) don't work
86        // without this.
87        if terminal::supports_keyboard_enhancement().is_ok() {
88            execute!(
89                io::stdout(),
90                PushKeyboardEnhancementFlags(KEF::DISAMBIGUATE_ESCAPE_CODES)
91            )
92            .unwrap()
93        }
94        terminal::enable_raw_mode().unwrap();
95
96        // The main application input loop
97        let thread = std::thread::Builder::new().name("print loop".to_string());
98        let _ = thread.spawn(move || {
99            'outer: loop {
100                let Ok(UiEvent::ResumePrinting) = rx.recv() else {
101                    break;
102                };
103
104                let printer = ms.lock().unwrap().cur_printer().clone();
105
106                loop {
107                    if let Ok(true) = event::poll(Duration::from_millis(13)) {
108                        let res = match event::read().unwrap() {
109                            event::Event::Key(key) => tx.send_key(key),
110                            event::Event::Resize(..) => {
111                                printer.write().update(true);
112                                tx.send_resize()
113                            }
114                            event::Event::FocusGained
115                            | event::Event::FocusLost
116                            | event::Event::Mouse(_)
117                            | event::Event::Paste(_) => Ok(()),
118                        };
119                        if res.is_err() {
120                            break;
121                        }
122                    }
123
124                    printer.try_inspect(|p| p.print());
125
126                    if let Ok(event) = rx.try_recv() {
127                        match event {
128                            UiEvent::ResumePrinting => {
129                                unreachable!("Wrong order")
130                            }
131                            UiEvent::PausePrinting => break,
132                            UiEvent::Quit => break 'outer,
133                        }
134                    }
135                }
136            }
137        });
138    }
139
140    fn close(_ms: &'static Self::MetaStatics) {
141        terminal::disable_raw_mode().unwrap();
142        execute!(
143            io::stdout(),
144            terminal::Clear(ClearType::All),
145            terminal::LeaveAlternateScreen,
146            terminal::EnableLineWrap,
147            cursor::Show,
148            PopKeyboardEnhancementFlags
149        )
150        .unwrap();
151    }
152
153    fn load(_ms: &'static Self::MetaStatics) {
154        // Hook for returning to regular terminal state
155        std::panic::set_hook(Box::new(|info| {
156            let trace = std::backtrace::Backtrace::capture();
157            terminal::disable_raw_mode().unwrap();
158            queue!(
159                io::stdout(),
160                terminal::Clear(ClearType::All),
161                terminal::LeaveAlternateScreen,
162                cursor::MoveToColumn(0),
163                terminal::Clear(ClearType::FromCursorDown),
164                terminal::EnableLineWrap,
165                cursor::Show,
166                PopKeyboardEnhancementFlags
167            );
168            if let std::backtrace::BacktraceStatus::Captured = trace.status() {
169                for line in trace.to_string().lines() {
170                    println!("{line}");
171                    queue!(io::stdout(), cursor::MoveToColumn(0));
172                }
173            }
174            for line in info.to_string().lines() {
175                println!("{line}");
176                queue!(io::stdout(), cursor::MoveToColumn(0));
177            }
178        }));
179    }
180
181    fn new_root(
182        ms: &'static Self::MetaStatics,
183        cache: <Self::Area as ui::Area>::Cache,
184    ) -> Self::Area {
185        let mut ui = ms.lock().unwrap();
186        let printer = (ui.printer_fn)();
187
188        let root = ui.layouts.mutate(|layouts| {
189            let layout = Layout::new(ui.fr, printer.clone(), cache);
190            let main_id = layout.main_id();
191            layouts.push(layout);
192
193            Area::new(main_id, ui.layouts.clone())
194        });
195        let area = root.clone();
196
197        ui.windows.push((root, printer));
198
199        area
200    }
201
202    fn switch_window(ms: &'static Self::MetaStatics, win: usize) {
203        ms.lock().unwrap().win = win;
204    }
205
206    fn flush_layout(ms: &'static Self::MetaStatics) {
207        let ui = ms.lock().unwrap();
208        ui.cur_printer().write().flush_equalities().unwrap();
209    }
210
211    fn unload(ms: &'static Self::MetaStatics) {
212        let mut ui = ms.lock().unwrap();
213        ui.windows = Vec::new();
214        *ui.layouts.write() = Vec::new();
215        ui.win = 0;
216    }
217
218    fn remove_window(ms: &'static Self::MetaStatics, win: usize) {
219        let mut ui = ms.lock().unwrap();
220        ui.windows.remove(win);
221        ui.layouts.write().remove(win);
222        if ui.win > win {
223            ui.win -= 1;
224        }
225    }
226}
227
228pub enum ConstraintErr {
229    NoParent,
230    Impossible,
231}
232
233impl std::fmt::Display for ConstraintErr {
234    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
235        <Self as std::fmt::Debug>::fmt(self, f)
236    }
237}
238
239impl std::fmt::Debug for ConstraintErr {
240    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
241        match self {
242            // NOTE: Might not be true in the future.
243            ConstraintErr::NoParent => {
244                write!(f, "No parents, so its constraint can't be changed.")
245            }
246            ConstraintErr::Impossible => {
247                write!(f, "The constraint change is impossible.")
248            }
249        }
250    }
251}
252
253impl DuatError for ConstraintErr {
254    fn into_text(self) -> Box<duat_core::text::Text> {
255        Box::new(match self {
256            ConstraintErr::NoParent => {
257                err!("The constraints of the master node " [*a] "can't" [] " change.")
258            }
259            ConstraintErr::Impossible => {
260                err!("The requested constraint change is impossible.")
261            }
262        })
263    }
264}
265
266#[derive(Debug)]
267pub enum Anchor {
268    TopLeft,
269    TopRight,
270    BottomLeft,
271    BottomRight,
272}
273
274impl std::error::Error for ConstraintErr {}
275
276#[derive(Clone, Copy, PartialEq)]
277pub struct AreaId(usize);
278
279impl AreaId {
280    /// Generates a unique index for [`Rect`]s.
281    fn new() -> Self {
282        use std::sync::atomic::{AtomicUsize, Ordering};
283        static INDEX_COUNTER: AtomicUsize = AtomicUsize::new(0);
284
285        AreaId(INDEX_COUNTER.fetch_add(1, Ordering::Relaxed))
286    }
287}
288
289impl std::fmt::Debug for AreaId {
290    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
291        f.write_fmt(format_args!("{}", self.0))
292    }
293}
294
295type Equality = cassowary::Constraint;
296
297fn print_style(w: &mut impl Write, style: ContentStyle) {
298    use crossterm::style::Attribute::{self, *};
299    const ATTRIBUTES: [(Attribute, &[u8]); 10] = [
300        (Reset, b"0"),
301        (Bold, b"1"),
302        (Dim, b"2"),
303        (Italic, b"3"),
304        (Underlined, b"4"),
305        (DoubleUnderlined, b"4;2"),
306        (Undercurled, b"4;3"),
307        (Underdotted, b"4;4"),
308        (Underdashed, b"4;5"),
309        (Reverse, b"7"),
310    ];
311    color_values!(U8, "", "");
312    color_values!(U8_SC, "", ";");
313    color_values!(U8_FG_RGB, "38;2;", ";");
314    color_values!(U8_FG_ANSI, "38;5;", ";");
315    color_values!(U8_BG_RGB, "48;2;", ";");
316    color_values!(U8_BG_ANSI, "48;5;", ";");
317    color_values!(U8_UL_RGB, "58;2;", ";");
318    color_values!(U8_UL_ANSI, "58;5;", ";");
319
320    if style == ContentStyle::default() {
321        return;
322    }
323    let _ = w.write_all(b"\x1b[");
324
325    let mut semicolon = false;
326    if !style.attributes.is_empty() {
327        for (attr, ansi) in ATTRIBUTES {
328            if style.attributes.has(attr) {
329                if semicolon {
330                    w.write_all(b";").unwrap()
331                }
332                w.write_all(ansi).unwrap();
333                semicolon = true;
334            }
335        }
336    }
337
338    let semicolon = if let Some(color) = style.foreground_color {
339        if semicolon {
340            w.write_all(b";").unwrap()
341        }
342        let _ = match color {
343            Color::Reset => w.write_all(b"39"),
344            Color::Black => w.write_all(b"30"),
345            Color::DarkRed => w.write_all(b"31"),
346            Color::DarkGreen => w.write_all(b"32"),
347            Color::DarkYellow => w.write_all(b"33"),
348            Color::DarkBlue => w.write_all(b"34"),
349            Color::DarkMagenta => w.write_all(b"35"),
350            Color::DarkCyan => w.write_all(b"36"),
351            Color::Grey => w.write_all(b"37"),
352            Color::DarkGrey => w.write_all(b"90"),
353            Color::Red => w.write_all(b"91"),
354            Color::Green => w.write_all(b"92"),
355            Color::Yellow => w.write_all(b"93"),
356            Color::Blue => w.write_all(b"94"),
357            Color::Magenta => w.write_all(b"95"),
358            Color::Cyan => w.write_all(b"96"),
359            Color::White => w.write_all(b"97"),
360            Color::Rgb { r, g, b } => {
361                let _ = w.write_all(U8_FG_RGB[r as usize].as_bytes());
362                let _ = w.write_all(U8_SC[g as usize].as_bytes());
363                w.write_all(U8[b as usize].as_bytes())
364            }
365            Color::AnsiValue(ansi) => w.write_all(U8_FG_ANSI[ansi as usize].as_bytes()),
366        };
367        true
368    } else {
369        semicolon
370    };
371
372    let semicolon = if let Some(color) = style.background_color {
373        if semicolon {
374            w.write_all(b";").unwrap()
375        }
376        match color {
377            Color::Reset => w.write_all(b"49").unwrap(),
378            Color::Black => w.write_all(b"40").unwrap(),
379            Color::DarkRed => w.write_all(b"41").unwrap(),
380            Color::DarkGreen => w.write_all(b"42").unwrap(),
381            Color::DarkYellow => w.write_all(b"43").unwrap(),
382            Color::DarkBlue => w.write_all(b"44").unwrap(),
383            Color::DarkMagenta => w.write_all(b"45").unwrap(),
384            Color::DarkCyan => w.write_all(b"46").unwrap(),
385            Color::Grey => w.write_all(b"47").unwrap(),
386            Color::DarkGrey => w.write_all(b"100").unwrap(),
387            Color::Red => w.write_all(b"101").unwrap(),
388            Color::Green => w.write_all(b"102").unwrap(),
389            Color::Yellow => w.write_all(b"103").unwrap(),
390            Color::Blue => w.write_all(b"104").unwrap(),
391            Color::Magenta => w.write_all(b"105").unwrap(),
392            Color::Cyan => w.write_all(b"106").unwrap(),
393            Color::White => w.write_all(b"107").unwrap(),
394            Color::Rgb { r, g, b } => {
395                w.write_all(U8_BG_RGB[r as usize].as_bytes()).unwrap();
396                w.write_all(U8_SC[g as usize].as_bytes()).unwrap();
397                w.write_all(U8[b as usize].as_bytes()).unwrap()
398            }
399            Color::AnsiValue(ansi) => w.write_all(U8_BG_ANSI[ansi as usize].as_bytes()).unwrap(),
400        };
401        true
402    } else {
403        semicolon
404    };
405
406    if let Some(color) = style.underline_color {
407        if semicolon {
408            w.write_all(b";").unwrap()
409        }
410        match color {
411            Color::Reset => w.write_all(b"59").unwrap(),
412            Color::Black => w.write_all(b"58;0").unwrap(),
413            Color::DarkRed => w.write_all(b"58;1").unwrap(),
414            Color::DarkGreen => w.write_all(b"58;2").unwrap(),
415            Color::DarkYellow => w.write_all(b"58;3").unwrap(),
416            Color::DarkBlue => w.write_all(b"58;4").unwrap(),
417            Color::DarkMagenta => w.write_all(b"58;5").unwrap(),
418            Color::DarkCyan => w.write_all(b"58;6").unwrap(),
419            Color::Grey => w.write_all(b"58;7").unwrap(),
420            Color::DarkGrey => w.write_all(b"58;8").unwrap(),
421            Color::Red => w.write_all(b"58;9").unwrap(),
422            Color::Green => w.write_all(b"58;10").unwrap(),
423            Color::Yellow => w.write_all(b"58;11").unwrap(),
424            Color::Blue => w.write_all(b"58;12").unwrap(),
425            Color::Magenta => w.write_all(b"58;13").unwrap(),
426            Color::Cyan => w.write_all(b"58;14").unwrap(),
427            Color::White => w.write_all(b"58;15").unwrap(),
428            Color::Rgb { r, g, b } => {
429                w.write_all(U8_UL_RGB[r as usize].as_bytes()).unwrap();
430                w.write_all(U8_SC[g as usize].as_bytes()).unwrap();
431                w.write_all(U8[b as usize].as_bytes()).unwrap()
432            }
433            Color::AnsiValue(ansi) => w.write_all(U8_UL_ANSI[ansi as usize].as_bytes()).unwrap(),
434        };
435    }
436
437    w.write_all(b"m").unwrap();
438}
439
440macro queue($writer:expr $(, $command:expr)* $(,)?) {
441    crossterm::queue!($writer $(, $command)*).unwrap()
442}
443
444macro style($lines:expr, $style:expr) {{
445    #[cfg(unix)]
446    print_style(&mut $lines, $style);
447    #[cfg(not(unix))]
448    queue!(
449        $lines,
450        crossterm::style::ResetColor,
451        crossterm::style::SetStyle($style)
452    );
453}}
454
455#[rustfmt::skip]
456macro color_values($name:ident, $p:literal, $s:literal) {
457    macro c($n:literal) {
458        concat!($p, $n, $s)
459    }
460    const $name: [&str; 256] = [
461        c!(0), c!(1), c!(2), c!(3), c!(4), c!(5), c!(6), c!(7), c!(8), c!(9), c!(10), c!(11),
462        c!(12), c!(13), c!(14), c!(15), c!(16), c!(17), c!(18), c!(19), c!(20), c!(21), c!(22),
463        c!(23), c!(24), c!(25), c!(26), c!(27), c!(28), c!(29), c!(30), c!(31), c!(32), c!(33),
464        c!(34), c!(35), c!(36), c!(37), c!(38), c!(39), c!(40), c!(41), c!(42), c!(43), c!(44),
465        c!(45), c!(46), c!(47), c!(48), c!(49), c!(50), c!(51), c!(52), c!(53), c!(54), c!(55),
466        c!(56), c!(57), c!(58), c!(59), c!(60), c!(61), c!(62), c!(63), c!(64), c!(65), c!(66),
467        c!(67), c!(68), c!(69), c!(70), c!(71), c!(72), c!(73), c!(74), c!(75), c!(76), c!(77),
468        c!(78), c!(79), c!(80), c!(81), c!(82), c!(83), c!(84), c!(85), c!(86), c!(87), c!(88),
469        c!(89), c!(90), c!(91), c!(92), c!(93), c!(94), c!(95), c!(96), c!(97), c!(98), c!(99),
470        c!(100), c!(101), c!(102), c!(103), c!(104), c!(105), c!(106), c!(107), c!(108), c!(109),
471        c!(110), c!(111), c!(112), c!(113), c!(114), c!(115), c!(116), c!(117), c!(118), c!(119),
472        c!(120), c!(121), c!(122), c!(123), c!(124), c!(125), c!(126), c!(127), c!(128), c!(129),
473        c!(130), c!(131), c!(132), c!(133), c!(134), c!(135), c!(136), c!(137), c!(138), c!(139),
474        c!(140), c!(141), c!(142), c!(143), c!(144), c!(145), c!(146), c!(147), c!(148), c!(149),
475        c!(150), c!(151), c!(152), c!(153), c!(154), c!(155), c!(156), c!(157), c!(158), c!(159),
476        c!(160), c!(161), c!(162), c!(163), c!(164), c!(165), c!(166), c!(167), c!(168), c!(169),
477        c!(170), c!(171), c!(172), c!(173), c!(174), c!(175), c!(176), c!(177), c!(178), c!(179),
478        c!(180), c!(181), c!(182), c!(183), c!(184), c!(185), c!(186), c!(187), c!(188), c!(189),
479        c!(190), c!(191), c!(192), c!(193), c!(194), c!(195), c!(196), c!(197), c!(198), c!(199),
480        c!(200), c!(201), c!(202), c!(203), c!(204), c!(205), c!(206), c!(207), c!(208), c!(209),
481        c!(210), c!(211), c!(212), c!(213), c!(214), c!(215), c!(216), c!(217), c!(218), c!(219),
482        c!(220), c!(221), c!(222), c!(223), c!(224), c!(225), c!(226), c!(227), c!(228), c!(229),
483        c!(230), c!(231), c!(232), c!(233), c!(234), c!(235), c!(236), c!(237), c!(238), c!(239),
484        c!(240), c!(241), c!(242), c!(243), c!(244), c!(245), c!(246), c!(247), c!(248), c!(249),
485        c!(250), c!(251), c!(252), c!(253), c!(254), c!(255),
486    ];
487}