duat_term/
lib.rs

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