duat_term/
lib.rs

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