duat_term/area/
mod.rs

1mod iter;
2mod print_info;
3
4use std::{io::Write, sync::Arc};
5
6use crossterm::{
7    cursor,
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, PushSpecs, SpawnSpecs,
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, queue};
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);
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: CoreAccess<Self>,
335        specs: PushSpecs,
336        on_files: bool,
337        cache: PrintInfo,
338    ) -> Option<(Area, Option<Area>)> {
339        let (child, parent) = self.layouts.push(self.id, specs, on_files, cache)?;
340
341        Some((
342            Self::new(child, self.layouts.clone()),
343            parent.map(|parent| Self::new(parent, self.layouts.clone())),
344        ))
345    }
346
347    fn delete(self: CoreAccess<Self>) -> (bool, Vec<Self>) {
348        let (do_rm_window, rm_areas) = self.layouts.delete(self.id);
349        (
350            do_rm_window,
351            rm_areas
352                .into_iter()
353                .map(|id| Self::new(id, self.layouts.clone()))
354                .collect(),
355        )
356    }
357
358    fn swap(self: CoreAccess<Self>, rhs: &Self) -> bool {
359        self.layouts.swap(self.id, rhs.id)
360    }
361
362    fn spawn(
363        self: CoreAccess<Self>,
364        spawn_id: SpawnId,
365        specs: SpawnSpecs,
366        cache: Self::Cache,
367    ) -> Option<Self> {
368        Some(Self::new(
369            self.layouts
370                .spawn_on_widget(self.id, spawn_id, specs, cache)?,
371            self.layouts.clone(),
372        ))
373    }
374
375    fn set_width(self: CoreAccess<Self>, width: f32) -> Result<(), Text> {
376        if self
377            .layouts
378            .set_constraints(self.id, Some(width), None, None)
379        {
380            Ok(())
381        } else {
382            Err(txt!("Couldn't set the width to [a]{width}"))
383        }
384    }
385
386    fn set_height(self: CoreAccess<Self>, height: f32) -> Result<(), Text> {
387        if self
388            .layouts
389            .set_constraints(self.id, None, Some(height), None)
390        {
391            Ok(())
392        } else {
393            Err(txt!("Couldn't set the height to [a]{height}"))
394        }
395    }
396
397    fn hide(self: CoreAccess<Self>) -> Result<(), Text> {
398        if self
399            .layouts
400            .set_constraints(self.id, None, None, Some(true))
401        {
402            Ok(())
403        } else {
404            Err(txt!("Couldn't hide the Area"))
405        }
406    }
407
408    fn reveal(self: CoreAccess<Self>) -> Result<(), Text> {
409        if self
410            .layouts
411            .set_constraints(self.id, None, None, Some(false))
412        {
413            Ok(())
414        } else {
415            Err(txt!("Couldn't reveal the Area"))
416        }
417    }
418
419    fn width_of_text(self: CoreAccess<Self>, opts: PrintOpts, text: &Text) -> Result<f32, Text> {
420        let max = self
421            .layouts
422            .inspect(self.id, |_, layout| layout.max_value())
423            .ok_or_else(|| txt!("This Area was already deleted"))?;
424
425        let iter = iter::print_iter(text, TwoPoints::default(), max.x, opts);
426
427        // It can be None if there is total concalment.
428        Ok(iter.map(|(c, _)| c.x + c.len).max().unwrap_or(0) as f32)
429    }
430
431    fn scroll_ver(self: CoreAccess<Self>, text: &Text, by: i32, opts: PrintOpts) {
432        if by == 0 {
433            return;
434        }
435
436        let Some(coords) = self.layouts.coords_of(self.id, false) else {
437            context::warn!("This Area was already deleted");
438            return;
439        };
440
441        if coords.width() == 0 || coords.height() == 0 {
442            return;
443        }
444
445        let mut info = self.layouts.get_info_of(self.id).unwrap();
446        info.scroll_ver(by, coords, text, opts);
447        self.layouts.set_info_of(self.id, info);
448    }
449
450    ////////// Printing
451
452    fn scroll_around_points(
453        self: CoreAccess<Self>,
454        text: &Text,
455        points: TwoPoints,
456        opts: PrintOpts,
457    ) {
458        let Some(coords) = self.layouts.coords_of(self.id, false) else {
459            context::warn!("This Area was already deleted");
460            return;
461        };
462
463        if coords.width() == 0 || coords.height() == 0 {
464            return;
465        }
466
467        let mut info = self.layouts.get_info_of(self.id).unwrap();
468        info.scroll_around(points.real, coords, text, opts);
469        self.layouts.set_info_of(self.id, info);
470    }
471
472    fn scroll_to_points(self: CoreAccess<Self>, text: &Text, points: TwoPoints, opts: PrintOpts) {
473        let Some(coords) = self.layouts.coords_of(self.id, false) else {
474            context::warn!("This Area was already deleted");
475            return;
476        };
477
478        if coords.width() == 0 || coords.height() == 0 {
479            return;
480        }
481
482        let mut info = self.layouts.get_info_of(self.id).unwrap();
483        info.scroll_to_points(points, coords, text, opts);
484        self.layouts.set_info_of(self.id, info);
485    }
486
487    fn set_as_active(self: CoreAccess<Self>) {
488        self.layouts.set_active_id(self.id);
489    }
490
491    fn print(self: CoreAccess<Self>, text: &Text, opts: PrintOpts, painter: Painter) {
492        Area::print(&self, text, opts, painter, |_, _| {})
493    }
494
495    fn print_with<'a>(
496        self: CoreAccess<Self>,
497        text: &Text,
498        opts: PrintOpts,
499        painter: Painter,
500        f: impl FnMut(&Caret, &Item) + 'a,
501    ) {
502        Area::print(&self, text, opts, painter, f)
503    }
504
505    ////////// Queries
506
507    fn set_print_info(self: CoreAccess<Self>, info: Self::PrintInfo) {
508        self.layouts.set_info_of(self.id, info);
509    }
510
511    fn print_iter<'a>(
512        self: CoreAccess<Self>,
513        text: &'a Text,
514        points: TwoPoints,
515        opts: PrintOpts,
516    ) -> impl Iterator<Item = (Caret, Item)> + 'a {
517        let width = self.width() as u32;
518        print_iter(text, points, width, opts)
519    }
520
521    fn rev_print_iter<'a>(
522        self: CoreAccess<Self>,
523        text: &'a Text,
524        points: TwoPoints,
525        opts: PrintOpts,
526    ) -> impl Iterator<Item = (Caret, Item)> + 'a {
527        let width = self.width() as u32;
528        rev_print_iter(text, points, opts.wrap_width(width).unwrap_or(width), opts)
529    }
530
531    fn has_changed(self: CoreAccess<Self>) -> 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<Self>, 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<Self>) -> 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<Self>) -> Option<Self::Cache> {
569        let info = self.layouts.get_info_of(self.id)?.for_caching();
570        Some(info)
571    }
572
573    fn width(self: CoreAccess<Self>) -> f32 {
574        self.layouts
575            .coords_of(self.id, false)
576            .map(|coords| coords.width() as f32)
577            .unwrap_or(0.0)
578    }
579
580    fn height(self: CoreAccess<Self>) -> f32 {
581        self.layouts
582            .coords_of(self.id, false)
583            .map(|coords| coords.height() as f32)
584            .unwrap_or(0.0)
585    }
586
587    fn start_points(self: CoreAccess<Self>, text: &Text, opts: PrintOpts) -> TwoPoints {
588        let Some(coords) = self.layouts.coords_of(self.id, false) else {
589            context::warn!("This Area was already deleted");
590            return Default::default();
591        };
592
593        let mut info = self.layouts.get_info_of(self.id).unwrap();
594        let start_points = info.start_points(coords, text, opts);
595        self.layouts.set_info_of(self.id, info);
596
597        start_points
598    }
599
600    fn end_points(self: CoreAccess<Self>, text: &Text, opts: PrintOpts) -> TwoPoints {
601        let Some(coords) = self.layouts.coords_of(self.id, false) else {
602            context::warn!("This Area was already deleted");
603            return Default::default();
604        };
605
606        let mut info = self.layouts.get_info_of(self.id).unwrap();
607        let end_points = info.end_points(coords, text, opts);
608        self.layouts.set_info_of(self.id, info);
609
610        end_points
611    }
612
613    fn get_print_info(self: CoreAccess<Self>) -> Self::PrintInfo {
614        self.layouts.get_info_of(self.id).unwrap_or_default()
615    }
616
617    fn is_active(self: CoreAccess<Self>) -> bool {
618        self.layouts.get_active_id() == self.id
619    }
620}
621
622const fn get_control_str(char: char) -> Option<&'static str> {
623    match char {
624        '\0' => Some("^@"),
625        '\u{01}' => Some("^A"),
626        '\u{02}' => Some("^B"),
627        '\u{03}' => Some("^C"),
628        '\u{04}' => Some("^D"),
629        '\u{05}' => Some("^E"),
630        '\u{06}' => Some("^F"),
631        '\u{07}' => Some("^G"),
632        '\u{08}' => Some("^H"),
633        '\u{0b}' => Some("^K"),
634        '\u{0c}' => Some("^L"),
635        '\u{0e}' => Some("^N"),
636        '\u{0f}' => Some("^O"),
637        '\u{10}' => Some("^P"),
638        '\u{11}' => Some("^Q"),
639        '\u{12}' => Some("^R"),
640        '\u{13}' => Some("^S"),
641        '\u{14}' => Some("^T"),
642        '\u{15}' => Some("^U"),
643        '\u{16}' => Some("^V"),
644        '\u{17}' => Some("^W"),
645        '\u{18}' => Some("^X"),
646        '\u{19}' => Some("^Y"),
647        '\u{1a}' => Some("^Z"),
648        '\u{1b}' => Some("^["),
649        '\u{1c}' => Some("^\\"),
650        '\u{1d}' => Some("^]"),
651        '\u{1e}' => Some("^^"),
652        '\u{1f}' => Some("^_"),
653        '\u{80}' => Some("<80>"),
654        '\u{81}' => Some("<81>"),
655        '\u{82}' => Some("<82>"),
656        '\u{83}' => Some("<83>"),
657        '\u{84}' => Some("<84>"),
658        '\u{85}' => Some("<85>"),
659        '\u{86}' => Some("<86>"),
660        '\u{87}' => Some("<87>"),
661        '\u{88}' => Some("<88>"),
662        '\u{89}' => Some("<89>"),
663        '\u{8a}' => Some("<8a>"),
664        '\u{8b}' => Some("<8b>"),
665        '\u{8c}' => Some("<8c>"),
666        '\u{8d}' => Some("<8d>"),
667        '\u{8e}' => Some("<8e>"),
668        '\u{8f}' => Some("<8f>"),
669        '\u{90}' => Some("<90>"),
670        '\u{91}' => Some("<91>"),
671        '\u{92}' => Some("<92>"),
672        '\u{93}' => Some("<93>"),
673        '\u{94}' => Some("<94>"),
674        '\u{95}' => Some("<95>"),
675        '\u{96}' => Some("<96>"),
676        '\u{97}' => Some("<97>"),
677        '\u{98}' => Some("<98>"),
678        '\u{99}' => Some("<99>"),
679        '\u{9a}' => Some("<9a>"),
680        '\u{9b}' => Some("<9b>"),
681        '\u{9c}' => Some("<9c>"),
682        '\u{9d}' => Some("<9d>"),
683        '\u{9e}' => Some("<9e>"),
684        '\u{9f}' => Some("<9f>"),
685        _ => None,
686    }
687}