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