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
59unsafe 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 let Ok(Event::NewPrinter(mut printer)) = term_rx.recv() else {
99 unreachable!("Failed to load the Ui");
100 };
101
102 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 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 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 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 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 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
522const EDGE_PRIO: kasuari::Strength = kasuari::Strength::REQUIRED;
524const MANUAL_LEN_PRIO: kasuari::Strength = kasuari::Strength::new(8.0);
526const LEN_PRIO: kasuari::Strength = kasuari::Strength::new(7.0);
528const FRAME_PRIO: kasuari::Strength = kasuari::Strength::new(6.0);
530const HIDDEN_PRIO: kasuari::Strength = kasuari::Strength::new(5.0);
532const SPAWN_POS_PRIO: kasuari::Strength = kasuari::Strength::new(4.0);
534const SPAWN_DIMS_PRIO: kasuari::Strength = kasuari::Strength::new(3.0);
536const SPAWN_LEN_PRIO: kasuari::Strength = kasuari::Strength::new(3.0);
538const SPAWN_ALIGN_PRIO: kasuari::Strength = kasuari::Strength::new(2.0);
540const EQ_LEN_PRIO: kasuari::Strength = kasuari::Strength::new(1.0);