duat_term/
lib.rs

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