duat_term/area/
mod.rs

1mod iter;
2mod print_info;
3
4use std::{io::Write, sync::Arc};
5
6use crossterm::{
7    cursor, queue,
8    style::{Attribute, Attributes},
9};
10use duat_core::{
11    context::{self, Decode, Encode},
12    form::{CONTROL_CHAR_ID, Painter},
13    opts::PrintOpts,
14    text::{Item, Part, SpawnId, Text, TwoPoints, txt},
15    ui::{
16        Caret, DynSpawnSpecs, PushSpecs,
17        traits::{CoreAccess, RawArea},
18    },
19};
20use iter::{print_iter, rev_print_iter};
21
22pub use self::print_info::PrintInfo;
23use crate::{AreaId, CStyle, Mutex, layout::Layouts, print_style, printer::Lines};
24
25#[derive(Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Encode, Decode)]
26#[bincode(crate = "duat_core::context::bincode")]
27pub struct Coord {
28    pub x: u32,
29    pub y: u32,
30}
31
32impl std::fmt::Debug for Coord {
33    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
34        f.write_fmt(format_args!("x: {}, y: {}", self.x, self.y))
35    }
36}
37
38impl Coord {
39    pub fn new(x: u32, y: u32) -> Coord {
40        Coord { x, y }
41    }
42}
43
44#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Encode, Decode)]
45#[bincode(crate = "duat_core::context::bincode")]
46pub struct Coords {
47    pub tl: Coord,
48    pub br: Coord,
49}
50
51impl Coords {
52    pub fn new(tl: Coord, br: Coord) -> Self {
53        Coords { tl, br }
54    }
55
56    pub fn width(&self) -> u32 {
57        self.br.x - self.tl.x
58    }
59
60    pub fn height(&self) -> u32 {
61        self.br.y - self.tl.y
62    }
63
64    pub fn area(&self) -> u32 {
65        self.width() * self.height()
66    }
67
68    pub fn intersects(&self, other: Self) -> bool {
69        self.tl.x < other.br.x
70            && self.br.x > other.tl.x
71            && self.tl.y < other.br.y
72            && self.br.y > other.tl.y
73    }
74}
75
76#[derive(Clone)]
77pub struct Area {
78    layouts: Layouts,
79    id: AreaId,
80    ansi_codes: Arc<Mutex<micromap::Map<CStyle, String, 16>>>,
81}
82
83impl PartialEq for Area {
84    fn eq(&self, other: &Self) -> bool {
85        self.id == other.id
86    }
87}
88
89impl Area {
90    pub(crate) fn new(id: AreaId, layouts: Layouts) -> Self {
91        Self { layouts, id, ansi_codes: Arc::default() }
92    }
93
94    fn print<'a>(
95        &self,
96        text: &Text,
97        opts: PrintOpts,
98        mut painter: Painter,
99        mut f: impl FnMut(&Caret, &Item) + 'a,
100    ) {
101        const SPACES: &[u8] = &[b' '; 3000];
102
103        fn end_line(
104            lines: &mut Lines,
105            painter: &Painter,
106            ansi_codes: &mut micromap::Map<CStyle, String, 16>,
107            len: u32,
108            max_x: u32,
109        ) {
110            let mut default_style = painter.get_default();
111            default_style.style.foreground_color = None;
112            default_style.style.underline_color = None;
113            default_style.style.attributes = Attributes::from(Attribute::Reset);
114
115            print_style(lines, default_style.style, ansi_codes);
116            if lines.coords().br.x == max_x {
117                lines.write_all(b"\x1b[0K").unwrap();
118            } else {
119                lines
120                    .write_all(&SPACES[..(lines.coords().width() - len) as usize])
121                    .unwrap();
122            }
123            lines.flush().unwrap();
124        }
125
126        let (mut lines, iter, x_shift, max_x) = {
127            let Some(coords) = self.layouts.coords_of(self.id, true) else {
128                context::warn!("This Area was already deleted");
129                return;
130            };
131
132            let max = self
133                .layouts
134                .inspect(self.id, |_, layout| layout.max_value())
135                .unwrap();
136
137            if coords.width() == 0 || coords.height() == 0 {
138                return;
139            }
140
141            let (s_points, x_shift) = {
142                let mut info = self.layouts.get_info_of(self.id).unwrap();
143                let s_points = info.start_points(coords, text, opts);
144                self.layouts.set_info_of(self.id, info);
145                (s_points, info.x_shift())
146            };
147
148            let lines = Lines::new(coords);
149            let width = opts.wrap_width(coords.width()).unwrap_or(coords.width());
150            let iter = print_iter(text, s_points, width, opts);
151
152            (lines, iter, x_shift, max.x)
153        };
154
155        let mut observed_spawns = Vec::new();
156        let spawn_id = self
157            .layouts
158            .inspect(self.id, |rect, _| rect.spawn_id())
159            .unwrap();
160        let is_active = self.id == self.layouts.get_active_id();
161
162        let mut ansi_codes = self.ansi_codes.lock().unwrap();
163        let mut style_was_set = false;
164
165        enum Cursor {
166            Main,
167            Extra,
168        }
169
170        // The y here represents the bottom of the current row of cells.
171        let tl_y = lines.coords().tl.y;
172        let mut y = tl_y;
173        let mut cursor = None;
174        let mut spawns_for_next: Vec<SpawnId> = Vec::new();
175        let mut last_len = 0;
176
177        for (caret, item) in iter {
178            let (painter, lines, ansi_codes) = (&mut painter, &mut lines, &mut ansi_codes);
179            f(&caret, &item);
180
181            let Caret { x, len, wrap } = caret;
182            let Item { part, .. } = item;
183
184            if wrap {
185                if y == lines.coords().br.y {
186                    break;
187                }
188                if y > lines.coords().tl.y {
189                    end_line(lines, painter, ansi_codes, last_len, max_x);
190                }
191                let initial_space = x.saturating_sub(x_shift).min(lines.coords().width());
192                if initial_space > 0 {
193                    let mut default_style = painter.get_default().style;
194                    default_style.attributes.set(Attribute::Reset);
195                    print_style(lines, default_style, ansi_codes);
196                    lines.write_all(&SPACES[..initial_space as usize]).unwrap();
197                }
198                y += 1;
199
200                // Resetting space to prevent erroneous printing.
201                painter.reset_prev_style();
202                style_was_set = true;
203                last_len = initial_space;
204            }
205
206            let is_contained = x + len > x_shift && x < x_shift + lines.coords().width();
207
208            match part {
209                Part::Char(char) if is_contained => {
210                    if let Some(str) = get_control_str(char) {
211                        painter.apply(CONTROL_CHAR_ID, 100);
212                        if style_was_set && let Some(style) = painter.relative_style() {
213                            print_style(lines, style, ansi_codes);
214                        }
215                        lines.write_all(str.as_bytes()).unwrap();
216                        painter.remove(CONTROL_CHAR_ID)
217                    } else {
218                        if style_was_set && let Some(style) = painter.relative_style() {
219                            print_style(lines, style, ansi_codes);
220                        }
221                        match char {
222                            '\t' => {
223                                let truncated_start = x_shift.saturating_sub(x);
224                                let truncated_end =
225                                    (x + len).saturating_sub(lines.coords().width() + x_shift);
226                                let tab_len = len - (truncated_start + truncated_end);
227                                lines.write_all(&SPACES[..tab_len as usize]).unwrap()
228                            }
229                            '\n' if opts.print_new_line => lines.write_all(b" ").unwrap(),
230                            '\n' | '\r' => {}
231                            char => {
232                                let mut bytes = [0; 4];
233                                char.encode_utf8(&mut bytes);
234                                lines.write_all(&bytes[..char.len_utf8()]).unwrap();
235                            }
236                        }
237                    }
238
239                    if let Some(cursor) = cursor.take() {
240                        match cursor {
241                            Cursor::Main => painter.remove_main_caret(),
242                            Cursor::Extra => painter.remove_extra_caret(),
243                        }
244                        if let Some(style) = painter.relative_style() {
245                            print_style(lines, style, ansi_codes)
246                        }
247                    }
248                    for id in spawns_for_next.drain(..) {
249                        observed_spawns.push(id);
250                        self.layouts.move_spawn_to(
251                            id,
252                            Coord::new(lines.coords().tl.x + x, y - 1),
253                            len,
254                        );
255                    }
256
257                    last_len = x + len - x_shift;
258                    style_was_set = false;
259                }
260                Part::Char(_) => {
261                    match cursor.take() {
262                        Some(Cursor::Main) => painter.remove_main_caret(),
263                        Some(Cursor::Extra) => painter.remove_extra_caret(),
264                        None => {}
265                    }
266                    spawns_for_next.clear();
267                }
268                Part::PushForm(id, prio) => {
269                    painter.apply(id, prio);
270                    style_was_set = true;
271                }
272                Part::PopForm(id) => {
273                    painter.remove(id);
274                    style_was_set = true;
275                }
276                Part::MainCaret => {
277                    if let Some(shape) = painter.main_cursor()
278                        && is_active
279                    {
280                        lines.show_real_cursor();
281                        queue!(lines, shape, cursor::SavePosition).unwrap();
282                    } else {
283                        cursor = Some(Cursor::Main);
284                        lines.hide_real_cursor();
285                        painter.apply_main_cursor();
286                        style_was_set = true;
287                    }
288                }
289                Part::ExtraCaret => {
290                    cursor = Some(Cursor::Extra);
291                    painter.apply_extra_cursor();
292                    style_was_set = true;
293                }
294                Part::Spacer => {
295                    let truncated_start = x_shift.saturating_sub(x).min(len);
296                    let truncated_end = (x + len)
297                        .saturating_sub(lines.coords().width().saturating_sub(x_shift))
298                        .min(len);
299                    let spacer_len = len - (truncated_start + truncated_end);
300                    lines.write_all(&SPACES[0..spacer_len as usize]).unwrap();
301                    last_len = (x + len)
302                        .saturating_sub(x_shift)
303                        .min(lines.coords().width());
304                }
305                Part::ResetState => print_style(lines, painter.reset(), ansi_codes),
306                Part::SpawnedWidget(id) => spawns_for_next.push(id),
307                Part::ToggleStart(_) | Part::ToggleEnd(_) => {
308                    todo!("Toggles have not been implemented yet.")
309                }
310                Part::AlignLeft | Part::AlignCenter | Part::AlignRight => {}
311            }
312        }
313
314        end_line(&mut lines, &painter, &mut ansi_codes, last_len, max_x);
315
316        for _ in 0..lines.coords().br.y - y {
317            end_line(&mut lines, &painter, &mut ansi_codes, 0, max_x);
318        }
319
320        let spawns = text.get_spawned_ids();
321
322        self.layouts
323            .send_lines(self.id, spawn_id, lines, spawns, &observed_spawns);
324    }
325}
326
327impl RawArea for Area {
328    type Cache = PrintInfo;
329    type PrintInfo = PrintInfo;
330
331    /////////// Modification
332
333    fn push(
334        &self,
335        _: CoreAccess,
336        specs: PushSpecs,
337        on_files: bool,
338        cache: PrintInfo,
339    ) -> Option<(Area, Option<Area>)> {
340        let (child, parent) = self.layouts.push(self.id, specs, on_files, cache)?;
341
342        Some((
343            Self::new(child, self.layouts.clone()),
344            parent.map(|parent| Self::new(parent, self.layouts.clone())),
345        ))
346    }
347
348    fn delete(&self, _: CoreAccess) -> (bool, Vec<Self>) {
349        let (do_rm_window, rm_areas) = self.layouts.delete(self.id);
350        (
351            do_rm_window,
352            rm_areas
353                .into_iter()
354                .map(|id| Self::new(id, self.layouts.clone()))
355                .collect(),
356        )
357    }
358
359    fn swap(&self, _: CoreAccess, rhs: &Self) -> bool {
360        self.layouts.swap(self.id, rhs.id)
361    }
362
363    fn spawn(
364        &self,
365        _: CoreAccess,
366        spawn_id: SpawnId,
367        specs: DynSpawnSpecs,
368        cache: Self::Cache,
369    ) -> Option<Self> {
370        Some(Self::new(
371            self.layouts
372                .spawn_on_widget(self.id, spawn_id, specs, cache)?,
373            self.layouts.clone(),
374        ))
375    }
376
377    fn set_width(&self, _: CoreAccess, width: f32) -> Result<(), Text> {
378        if self
379            .layouts
380            .set_constraints(self.id, Some(width), None, None)
381        {
382            Ok(())
383        } else {
384            Err(txt!("Couldn't set the width to [a]{width}"))
385        }
386    }
387
388    fn set_height(&self, _: CoreAccess, height: f32) -> Result<(), Text> {
389        if self
390            .layouts
391            .set_constraints(self.id, None, Some(height), None)
392        {
393            Ok(())
394        } else {
395            Err(txt!("Couldn't set the height to [a]{height}"))
396        }
397    }
398
399    fn hide(&self, _: CoreAccess) -> Result<(), Text> {
400        if self
401            .layouts
402            .set_constraints(self.id, None, None, Some(true))
403        {
404            Ok(())
405        } else {
406            Err(txt!("Couldn't hide the Area"))
407        }
408    }
409
410    fn reveal(&self, _: CoreAccess) -> Result<(), Text> {
411        if self
412            .layouts
413            .set_constraints(self.id, None, None, Some(false))
414        {
415            Ok(())
416        } else {
417            Err(txt!("Couldn't reveal the Area"))
418        }
419    }
420
421    fn width_of_text(&self, _: CoreAccess, opts: PrintOpts, text: &Text) -> Result<f32, Text> {
422        let max = self
423            .layouts
424            .inspect(self.id, |_, layout| layout.max_value())
425            .ok_or_else(|| txt!("This Area was already deleted"))?;
426
427        let iter = iter::print_iter(text, TwoPoints::default(), max.x, opts);
428
429        // It can be None if there is total concalment.
430        Ok(iter.map(|(c, _)| c.x + c.len).max().unwrap_or(0) as f32)
431    }
432
433    fn scroll_ver(&self, _: CoreAccess, text: &Text, by: i32, opts: PrintOpts) {
434        if by == 0 {
435            return;
436        }
437
438        let Some(coords) = self.layouts.coords_of(self.id, false) else {
439            context::warn!("This Area was already deleted");
440            return;
441        };
442
443        if coords.width() == 0 || coords.height() == 0 {
444            return;
445        }
446
447        let mut info = self.layouts.get_info_of(self.id).unwrap();
448        info.scroll_ver(by, coords, text, opts);
449        self.layouts.set_info_of(self.id, info);
450    }
451
452    ////////// Printing
453
454    fn scroll_around_points(&self, _: CoreAccess, text: &Text, points: TwoPoints, opts: PrintOpts) {
455        let Some(coords) = self.layouts.coords_of(self.id, false) else {
456            context::warn!("This Area was already deleted");
457            return;
458        };
459
460        if coords.width() == 0 || coords.height() == 0 {
461            return;
462        }
463
464        let mut info = self.layouts.get_info_of(self.id).unwrap();
465        info.scroll_around(points.real, coords, text, opts);
466        self.layouts.set_info_of(self.id, info);
467    }
468
469    fn scroll_to_points(&self, _: CoreAccess, text: &Text, points: TwoPoints, opts: PrintOpts) {
470        let Some(coords) = self.layouts.coords_of(self.id, false) else {
471            context::warn!("This Area was already deleted");
472            return;
473        };
474
475        if coords.width() == 0 || coords.height() == 0 {
476            return;
477        }
478
479        let mut info = self.layouts.get_info_of(self.id).unwrap();
480        info.scroll_to_points(points, coords, text, opts);
481        self.layouts.set_info_of(self.id, info);
482    }
483
484    fn set_as_active(&self, _: CoreAccess) {
485        self.layouts.set_active_id(self.id);
486    }
487
488    fn print(&self, _: CoreAccess, text: &Text, opts: PrintOpts, painter: Painter) {
489        self.print(text, opts, painter, |_, _| {})
490    }
491
492    fn print_with<'a>(
493        &self,
494        _: CoreAccess,
495        text: &Text,
496        opts: PrintOpts,
497        painter: Painter,
498        f: impl FnMut(&Caret, &Item) + 'a,
499    ) {
500        self.print(text, opts, painter, f)
501    }
502
503    ////////// Queries
504
505    fn set_print_info(&self, _: CoreAccess, info: Self::PrintInfo) {
506        self.layouts.set_info_of(self.id, info);
507    }
508
509    fn print_iter<'a>(
510        &self,
511        ca: CoreAccess,
512        text: &'a Text,
513        points: TwoPoints,
514        opts: PrintOpts,
515    ) -> impl Iterator<Item = (Caret, Item)> + 'a {
516        let width = (self.bottom_right(ca).x - self.top_left(ca).x) as u32;
517        print_iter(text, points, width, opts)
518    }
519
520    fn rev_print_iter<'a>(
521        &self,
522        ca: CoreAccess,
523        text: &'a Text,
524        points: TwoPoints,
525        opts: PrintOpts,
526    ) -> impl Iterator<Item = (Caret, Item)> + 'a {
527        let width = (self.bottom_right(ca).x - self.top_left(ca).x) as u32;
528        rev_print_iter(text, points, opts.wrap_width(width).unwrap_or(width), opts)
529    }
530
531    fn has_changed(&self, _: CoreAccess) -> bool {
532        self.layouts
533            .inspect(self.id, |rect, layout| rect.has_changed(layout))
534            .unwrap_or(false)
535    }
536
537    fn is_master_of(&self, _: CoreAccess, other: &Self) -> bool {
538        if other.id == self.id {
539            return true;
540        }
541
542        let mut parent_id = other.id;
543
544        self.layouts.inspect(self.id, |_, layout| {
545            while let Some((_, parent)) = layout.get_parent(parent_id) {
546                parent_id = parent.id();
547                if parent.id() == self.id {
548                    break;
549                }
550            }
551        });
552
553        parent_id == self.id
554    }
555
556    fn get_cluster_master(&self, _: CoreAccess) -> Option<Self> {
557        let id = self
558            .layouts
559            .inspect(self.id, |_, layout| layout.get_cluster_master(self.id))??;
560
561        Some(Self {
562            layouts: self.layouts.clone(),
563            id,
564            ansi_codes: Arc::default(),
565        })
566    }
567
568    fn cache(&self, _: CoreAccess) -> Option<Self::Cache> {
569        let info = self.layouts.get_info_of(self.id)?.for_caching();
570        Some(info)
571    }
572
573    fn top_left(&self, _: CoreAccess) -> duat_core::ui::Coord {
574        self.layouts
575            .coords_of(self.id, false)
576            .map(|coords| duat_core::ui::Coord {
577                x: coords.tl.x as f32,
578                y: coords.tl.y as f32,
579            })
580            .unwrap_or_default()
581    }
582
583    fn bottom_right(&self, _: CoreAccess) -> duat_core::ui::Coord {
584        self.layouts
585            .coords_of(self.id, false)
586            .map(|coords| duat_core::ui::Coord {
587                x: coords.br.x as f32,
588                y: coords.br.y as f32,
589            })
590            .unwrap_or_default()
591    }
592
593    fn start_points(&self, _: CoreAccess, text: &Text, opts: PrintOpts) -> TwoPoints {
594        let Some(coords) = self.layouts.coords_of(self.id, false) else {
595            context::warn!("This Area was already deleted");
596            return Default::default();
597        };
598
599        let mut info = self.layouts.get_info_of(self.id).unwrap();
600        let start_points = info.start_points(coords, text, opts);
601        self.layouts.set_info_of(self.id, info);
602
603        start_points
604    }
605
606    fn end_points(&self, _: CoreAccess, text: &Text, opts: PrintOpts) -> TwoPoints {
607        let Some(coords) = self.layouts.coords_of(self.id, false) else {
608            context::warn!("This Area was already deleted");
609            return Default::default();
610        };
611
612        let mut info = self.layouts.get_info_of(self.id).unwrap();
613        let end_points = info.end_points(coords, text, opts);
614        self.layouts.set_info_of(self.id, info);
615
616        end_points
617    }
618
619    fn get_print_info(&self, _: CoreAccess) -> Self::PrintInfo {
620        self.layouts.get_info_of(self.id).unwrap_or_default()
621    }
622
623    fn is_active(&self, _: CoreAccess) -> bool {
624        self.layouts.get_active_id() == self.id
625    }
626}
627
628const fn get_control_str(char: char) -> Option<&'static str> {
629    match char {
630        '\0' => Some("^@"),
631        '\u{01}' => Some("^A"),
632        '\u{02}' => Some("^B"),
633        '\u{03}' => Some("^C"),
634        '\u{04}' => Some("^D"),
635        '\u{05}' => Some("^E"),
636        '\u{06}' => Some("^F"),
637        '\u{07}' => Some("^G"),
638        '\u{08}' => Some("^H"),
639        '\u{0b}' => Some("^K"),
640        '\u{0c}' => Some("^L"),
641        '\u{0e}' => Some("^N"),
642        '\u{0f}' => Some("^O"),
643        '\u{10}' => Some("^P"),
644        '\u{11}' => Some("^Q"),
645        '\u{12}' => Some("^R"),
646        '\u{13}' => Some("^S"),
647        '\u{14}' => Some("^T"),
648        '\u{15}' => Some("^U"),
649        '\u{16}' => Some("^V"),
650        '\u{17}' => Some("^W"),
651        '\u{18}' => Some("^X"),
652        '\u{19}' => Some("^Y"),
653        '\u{1a}' => Some("^Z"),
654        '\u{1b}' => Some("^["),
655        '\u{1c}' => Some("^\\"),
656        '\u{1d}' => Some("^]"),
657        '\u{1e}' => Some("^^"),
658        '\u{1f}' => Some("^_"),
659        '\u{80}' => Some("<80>"),
660        '\u{81}' => Some("<81>"),
661        '\u{82}' => Some("<82>"),
662        '\u{83}' => Some("<83>"),
663        '\u{84}' => Some("<84>"),
664        '\u{85}' => Some("<85>"),
665        '\u{86}' => Some("<86>"),
666        '\u{87}' => Some("<87>"),
667        '\u{88}' => Some("<88>"),
668        '\u{89}' => Some("<89>"),
669        '\u{8a}' => Some("<8a>"),
670        '\u{8b}' => Some("<8b>"),
671        '\u{8c}' => Some("<8c>"),
672        '\u{8d}' => Some("<8d>"),
673        '\u{8e}' => Some("<8e>"),
674        '\u{8f}' => Some("<8f>"),
675        '\u{90}' => Some("<90>"),
676        '\u{91}' => Some("<91>"),
677        '\u{92}' => Some("<92>"),
678        '\u{93}' => Some("<93>"),
679        '\u{94}' => Some("<94>"),
680        '\u{95}' => Some("<95>"),
681        '\u{96}' => Some("<96>"),
682        '\u{97}' => Some("<97>"),
683        '\u{98}' => Some("<98>"),
684        '\u{99}' => Some("<99>"),
685        '\u{9a}' => Some("<9a>"),
686        '\u{9b}' => Some("<9b>"),
687        '\u{9c}' => Some("<9c>"),
688        '\u{9d}' => Some("<9d>"),
689        '\u{9e}' => Some("<9e>"),
690        '\u{9f}' => Some("<9f>"),
691        _ => None,
692    }
693}