duat_term/
lib.rs

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