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