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 let Ok(Event::NewPrinter(mut printer)) = rx.recv() else {
50 unreachable!("Failed to load the Ui");
51 };
52
53 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 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 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 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 *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 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 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}