Skip to main content

duat_term/area/
mod.rs

1mod iter;
2mod print_info;
3
4use std::{io::Write, iter::Peekable, sync::Arc};
5
6use crossterm::{
7    cursor, queue,
8    style::{Attribute, Attributes},
9};
10use duat_core::{
11    context::{
12        self,
13        cache::{Decode, Encode},
14    },
15    form::{CONTROL_CHAR_ID, Painter},
16    mode::{TwoPointsPlace, VPoint},
17    opts::PrintOpts,
18    text::{Point, Text, TextPart, TextPlace, TwoPoints, txt},
19    ui::{
20        self, Columns, DynSpawnSpecs, PrintedLine, PushSpecs, SpawnId,
21        traits::{RawArea, UiPass},
22    },
23};
24use iter::{PrintedPlace, print_iter, rev_print_iter};
25
26pub use self::print_info::PrintInfo;
27use crate::{
28    AreaId, Mutex,
29    layout::{Frame, Layouts},
30    print_style,
31    printer::Lines,
32};
33
34#[derive(Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Encode, Decode)]
35#[bincode(crate = "duat_core::context::cache::bincode")]
36pub struct Coord {
37    pub x: u32,
38    pub y: u32,
39}
40
41impl std::fmt::Debug for Coord {
42    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
43        f.write_fmt(format_args!("x: {}, y: {}", self.x, self.y))
44    }
45}
46
47impl Coord {
48    pub fn new(x: u32, y: u32) -> Coord {
49        Coord { x, y }
50    }
51}
52
53#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Encode, Decode)]
54#[bincode(crate = "duat_core::context::cache::bincode")]
55pub struct Coords {
56    pub tl: Coord,
57    pub br: Coord,
58}
59
60impl Coords {
61    pub fn new(tl: Coord, br: Coord) -> Self {
62        Coords { tl, br }
63    }
64
65    pub fn width(&self) -> u32 {
66        self.br.x - self.tl.x
67    }
68
69    pub fn height(&self) -> u32 {
70        self.br.y - self.tl.y
71    }
72
73    pub fn area(&self) -> u32 {
74        self.width() * self.height()
75    }
76
77    pub fn intersects(&self, other: Self) -> bool {
78        self.tl.x < other.br.x
79            && self.br.x > other.tl.x
80            && self.tl.y < other.br.y
81            && self.br.y > other.tl.y
82    }
83
84    pub fn x_range(&self) -> std::ops::Range<u32> {
85        self.tl.x..self.br.x
86    }
87
88    pub fn y_range(&self) -> std::ops::Range<u32> {
89        self.tl.y..self.br.y
90    }
91}
92
93#[derive(Clone)]
94pub struct Area {
95    prev_print_info: Arc<Mutex<PrintInfo>>,
96    layouts: Layouts,
97    id: AreaId,
98}
99
100impl PartialEq for Area {
101    fn eq(&self, other: &Self) -> bool {
102        self.id == other.id
103    }
104}
105
106impl Area {
107    /// Returns a new `Area` from raw parts
108    pub(crate) fn new(id: AreaId, layouts: Layouts) -> Self {
109        Self {
110            prev_print_info: Arc::new(Mutex::default()),
111            layouts,
112            id,
113        }
114    }
115
116    /// Adds a frame to this [`Area`]
117    ///
118    /// This function will fail if the `Area` was either deleted or is
119    /// not a spawned `Area`.
120    #[track_caller]
121    pub fn set_frame(&mut self, frame: Frame) {
122        if !self.layouts.set_frame(self.id, frame) {
123            context::warn!("This Area was already deleted");
124        }
125    }
126
127    /// Prints the `Text`
128    fn print(&self, text: &Text, opts: PrintOpts, mut painter: Painter) {
129        let Some(coords) = self.layouts.coords_of(self.id, true) else {
130            context::warn!("This Area was already deleted");
131            return;
132        };
133
134        let (has_edge_ahead, max) = self
135            .layouts
136            .inspect(self.id, |_, layout| {
137                let master_id = layout.get_cluster_master(self.id).unwrap();
138                let master_coords = self.layouts.coords_of(master_id, true).unwrap();
139                let master_has_edge = layout.get(master_id).unwrap().edge().is_some();
140
141                (
142                    master_has_edge && master_coords.br.x == coords.br.x,
143                    layout.max_value(),
144                )
145            })
146            .unwrap();
147
148        if coords.width() == 0 || coords.height() == 0 {
149            return;
150        }
151
152        let (s_points, x_shift) = {
153            let mut info = self.layouts.get_info_of(self.id).unwrap();
154            let s_points = info.start_points(coords, text, opts);
155            *self.prev_print_info.lock().unwrap() = info;
156            self.layouts.set_info_of(self.id, info);
157            (s_points, info.x_shift())
158        };
159
160        let is_active = self.id == self.layouts.get_active_id();
161
162        let Some((lines, observed_spawns)) = print_text(
163            (text, opts, &mut painter),
164            (coords, max, has_edge_ahead),
165            (is_active, s_points, x_shift),
166            |lines, len| lines.write_all(&SPACES[..len as usize]).unwrap(),
167            |lines, len, max_x| {
168                if lines.coords().br.x == max_x {
169                    lines.write_all(b"\x1b[0K").unwrap();
170                } else {
171                    lines
172                        .write_all(&SPACES[..(lines.coords().width() - len) as usize])
173                        .unwrap();
174                }
175            },
176            |lines, spacer_len| lines.write_all(&SPACES[..spacer_len as usize]).unwrap(),
177        ) else {
178            return;
179        };
180
181        let spawns = text.get_spawned_ids();
182
183        // To prevent deadlocks while sending spawned lines.
184        drop(painter);
185
186        self.layouts
187            .send_lines(self.id, lines, spawns, &observed_spawns);
188    }
189}
190
191impl RawArea for Area {
192    type Cache = PrintInfo;
193    type PrintInfo = PrintInfo;
194
195    /////////// Modification
196
197    fn push(
198        &self,
199        _: UiPass,
200        specs: PushSpecs,
201        on_buffers: bool,
202        cache: PrintInfo,
203    ) -> Option<(Area, Option<Area>)> {
204        let (child, parent) = self.layouts.push(self.id, specs, on_buffers, cache)?;
205
206        Some((
207            Self::new(child, self.layouts.clone()),
208            parent.map(|parent| Self::new(parent, self.layouts.clone())),
209        ))
210    }
211
212    fn delete(&self, _: UiPass) -> (bool, Vec<Self>) {
213        let (do_rm_window, rm_areas) = self.layouts.delete(self.id);
214        (
215            do_rm_window,
216            rm_areas
217                .into_iter()
218                .map(|id| Self::new(id, self.layouts.clone()))
219                .collect(),
220        )
221    }
222
223    fn swap(&self, _: UiPass, rhs: &Self) -> bool {
224        self.layouts.swap(self.id, rhs.id)
225    }
226
227    fn spawn(
228        &self,
229        _: UiPass,
230        spawn_id: SpawnId,
231        specs: DynSpawnSpecs,
232        cache: Self::Cache,
233    ) -> Option<Self> {
234        Some(Self::new(
235            self.layouts
236                .spawn_on_widget(self.id, spawn_id, specs, cache)?,
237            self.layouts.clone(),
238        ))
239    }
240
241    fn set_width(&self, _: UiPass, width: f32) -> Result<(), Text> {
242        if self
243            .layouts
244            .set_constraints(self.id, Some(width), None, None)
245        {
246            Ok(())
247        } else {
248            Err(txt!("This Area was already deleted"))
249        }
250    }
251
252    fn set_height(&self, _: UiPass, height: f32) -> Result<(), Text> {
253        if self
254            .layouts
255            .set_constraints(self.id, None, Some(height), None)
256        {
257            Ok(())
258        } else {
259            Err(txt!("This Area was already deleted"))
260        }
261    }
262
263    fn hide(&self, _: UiPass) -> Result<(), Text> {
264        if self
265            .layouts
266            .set_constraints(self.id, None, None, Some(true))
267        {
268            Ok(())
269        } else {
270            Err(txt!("This Area was already deleted"))
271        }
272    }
273
274    fn reveal(&self, _: UiPass) -> Result<(), Text> {
275        if self
276            .layouts
277            .set_constraints(self.id, None, None, Some(false))
278        {
279            Ok(())
280        } else {
281            Err(txt!("This Area was already deleted"))
282        }
283    }
284
285    fn size_of_text(&self, _: UiPass, opts: PrintOpts, text: &Text) -> Result<ui::Coord, Text> {
286        let max = self
287            .layouts
288            .inspect(self.id, |_, layout| layout.max_value())
289            .ok_or_else(|| txt!("This Area was already deleted"))?;
290
291        let iter = iter::print_iter(text, TwoPoints::default(), max.x, opts);
292
293        let mut max_x = 0;
294        let mut max_y = 0;
295        let mut width = 0;
296        let mut is_first = true;
297
298        for (place, item) in iter {
299            if place.wrap && !is_first {
300                if max_y == max.y {
301                    break;
302                }
303                max_x = width.max(max_x);
304                max_y += 1;
305                width = 0;
306            } else {
307                is_first = false;
308            }
309            if item.part.is_char() {
310                width += place.len;
311            }
312        }
313
314        Ok(ui::Coord::new(max_x.max(width) as f32, max_y as f32))
315    }
316
317    fn scroll_ver(&self, _: UiPass, text: &Text, by: i32, opts: PrintOpts) {
318        if by == 0 {
319            return;
320        }
321
322        let Some(coords) = self.layouts.coords_of(self.id, false) else {
323            context::warn!("This Area was already deleted");
324            return;
325        };
326
327        if coords.width() == 0 || coords.height() == 0 {
328            return;
329        }
330
331        let mut info = self.layouts.get_info_of(self.id).unwrap();
332        info.scroll_ver(by, coords, text, opts);
333        self.layouts.set_info_of(self.id, info);
334    }
335
336    ////////// Printing
337
338    fn scroll_around_points(&self, _: UiPass, text: &Text, points: TwoPoints, opts: PrintOpts) {
339        let Some(coords) = self.layouts.coords_of(self.id, false) else {
340            context::warn!("This Area was already deleted");
341            return;
342        };
343
344        if coords.width() == 0 || coords.height() == 0 {
345            return;
346        }
347
348        let mut info = self.layouts.get_info_of(self.id).unwrap();
349        info.scroll_around(points.real, coords, text, opts);
350        self.layouts.set_info_of(self.id, info);
351    }
352
353    fn scroll_to_points(&self, _: UiPass, text: &Text, points: TwoPoints, opts: PrintOpts) {
354        let Some(coords) = self.layouts.coords_of(self.id, false) else {
355            context::warn!("This Area was already deleted");
356            return;
357        };
358
359        if coords.width() == 0 || coords.height() == 0 {
360            return;
361        }
362
363        let mut info = self.layouts.get_info_of(self.id).unwrap();
364        info.scroll_to_points(points, coords, text, opts);
365        self.layouts.set_info_of(self.id, info);
366    }
367
368    fn set_as_active(&self, _: UiPass) {
369        self.layouts.set_active_id(self.id);
370    }
371
372    fn print(&self, _: UiPass, text: &Text, opts: PrintOpts, painter: Painter) {
373        self.print(text, opts, painter)
374    }
375
376    ////////// Queries
377
378    fn get_print_info(&self, _: UiPass) -> Self::PrintInfo {
379        self.layouts.get_info_of(self.id).unwrap_or_default()
380    }
381
382    fn set_print_info(&self, _: UiPass, info: Self::PrintInfo) {
383        self.layouts.set_info_of(self.id, info);
384    }
385
386    fn get_printed_lines(
387        &self,
388        pa: UiPass,
389        text: &Text,
390        opts: PrintOpts,
391    ) -> Option<Vec<ui::PrintedLine>> {
392        let coords = self.layouts.coords_of(self.id, true)?;
393        let points = self.start_points(pa, text, opts);
394
395        if coords.height() == 0 || coords.width() == 0 {
396            return Some(Vec::new());
397        }
398
399        let mut prev_point = rev_print_iter(text, points, coords.width(), opts)
400            .find_map(|(place, item)| place.wrap.then_some(item.points()))
401            .map(|points| points.real.line());
402
403        let mut printed_lines = Vec::<PrintedLine>::new();
404        let mut y = coords.tl.y;
405        let mut is_ghost = true;
406
407        for (place, item) in print_iter(text, points, coords.width(), opts) {
408            if y == coords.br.y || item.line() == text.end_point().line() {
409                break;
410            }
411            y += place.wrap as u32;
412
413            if place.wrap {
414                if let Some(last) = printed_lines.last_mut() {
415                    last.is_ghost = is_ghost;
416                }
417                is_ghost = true;
418
419                let number = item.line();
420                let is_wrapped = prev_point.is_some_and(|ll| ll == number);
421                prev_point = Some(number);
422                printed_lines.push(PrintedLine { number, is_wrapped, is_ghost: true });
423            }
424            
425            if item.as_real_char().is_some() {
426                is_ghost = false;
427            }
428        }
429
430        if let Some(last) = printed_lines.last_mut() {
431            last.is_ghost = is_ghost;
432        }
433
434        Some(printed_lines)
435    }
436
437    fn move_ver(
438        &self,
439        _: UiPass,
440        by: i32,
441        text: &Text,
442        point: Point,
443        desired_col: Option<usize>,
444        opts: PrintOpts,
445    ) -> VPoint {
446        let Some(coords) = self.layouts.coords_of(self.id, true) else {
447            panic!("Tried to move vertically on a deleted area");
448        };
449
450        let cap = coords.width();
451
452        if by == 0 {
453            return calculate_vpoint(text, point, cap, opts);
454        }
455
456        let desired_col = match desired_col {
457            Some(desired_col) => desired_col as u16,
458            None => calculate_vpoint(text, point, cap, opts).desired_visual_col() as u16,
459        };
460
461        let line_start = {
462            let target = point.line().saturating_add_signed(by as isize);
463            text.point_at_coords(target.min(text.last_point().line()), 0)
464        };
465
466        let mut wraps = 0;
467        let mut vcol = 0;
468
469        let points = line_start.to_two_points_before();
470        let (wcol, point) = print_iter(text, points, cap, opts)
471            .find_map(|(PrintedPlace { len, x, wrap }, item)| {
472                wraps += wrap as usize;
473
474                if let Some((p, char)) = item.as_real_char()
475                    && (vcol + len as u16 > desired_col || char == '\n')
476                {
477                    return Some((x as u16, p));
478                }
479
480                vcol += len as u16;
481                None
482            })
483            .unwrap_or((0, text.last_point()));
484
485        let ccol = (point.char() - line_start.char()) as u16;
486
487        VPoint::new(point, ccol, vcol, desired_col, wcol, wcol)
488    }
489
490    fn move_ver_wrapped(
491        &self,
492        _: UiPass,
493        by: i32,
494        text: &Text,
495        point: Point,
496        desired_col: Option<usize>,
497        opts: PrintOpts,
498    ) -> VPoint {
499        let mut wraps = 0;
500
501        let Some(coords) = self.layouts.coords_of(self.id, true) else {
502            panic!("Tried to move vertically on a deleted area");
503        };
504
505        let cap = coords.width();
506
507        if by == 0 {
508            return calculate_vpoint(text, point, cap, opts);
509        }
510
511        let desired_col = match desired_col {
512            Some(desired_col) => desired_col as u16,
513            None => calculate_vpoint(text, point, cap, opts).desired_wrapped_col() as u16,
514        };
515
516        if by > 0 {
517            let line_start = text.point_at_coords(point.line(), 0);
518            let points = line_start.to_two_points_after();
519
520            let mut vcol = 0;
521            let mut last = (0, 0, line_start);
522            let mut last_valid = last;
523
524            let (vcol, wcol, point) = print_iter(text, points, cap, opts)
525                .find_map(|(PrintedPlace { x, len, wrap }, item)| {
526                    wraps += (wrap && item.char() > point.char()) as i32;
527                    if let Some((p, char)) = item.as_real_char() {
528                        if (x..x + len).contains(&(desired_col as u32))
529                            || (char == '\n' && x <= desired_col as u32)
530                        {
531                            last_valid = (vcol, x as u16, p);
532                            if wraps == by {
533                                return Some((vcol, x as u16, p));
534                            }
535                        } else if wraps > by {
536                            return Some(last);
537                        }
538                        last = (vcol, x as u16, p);
539                    }
540                    vcol += len as u16;
541                    None
542                })
543                .unwrap_or(last_valid);
544
545            let ccol = (point.char() - line_start.char()) as u16;
546
547            VPoint::new(point, ccol, vcol, vcol, wcol, desired_col)
548        } else {
549            let end_points = text.points_after(point.to_two_points_after()).unwrap();
550            let mut just_wrapped = false;
551            let mut last_valid = None;
552
553            let mut iter = rev_print_iter(text, end_points, cap, opts);
554            let wcol_and_p = iter.find_map(|(PrintedPlace { x, len, wrap }, item)| {
555                if let Some((p, _)) = item.as_real_char() {
556                    // max(1) because it could be a '\n'
557                    if (x..x + len.max(1)).contains(&(desired_col as u32))
558                        || (just_wrapped && x + len < desired_col as u32)
559                    {
560                        last_valid = Some((x as u16, p));
561                        if wraps == by {
562                            return Some((x as u16, p));
563                        }
564                    }
565                    just_wrapped = false;
566                }
567                wraps -= wrap as i32;
568                just_wrapped |= wrap;
569                None
570            });
571
572            if let Some((wcol, point)) = wcol_and_p {
573                let (ccol, vcol) = iter
574                    .take_while(|(_, item)| item.as_real_char().is_none_or(|(_, c)| c != '\n'))
575                    .fold((0, 0), |(ccol, vcol), (place, item)| {
576                        (ccol + item.is_real() as u16, vcol + place.len as u16)
577                    });
578
579                VPoint::new(point, ccol, vcol, vcol, wcol, desired_col)
580            } else if let Some((wcol, point)) = last_valid {
581                let points = point.to_two_points_before();
582                let (ccol, vcol) = rev_print_iter(text, points, cap, opts)
583                    .take_while(|(_, item)| item.as_real_char().is_none_or(|(_, c)| c != '\n'))
584                    .fold((0, 0), |(ccol, vcol), (place, item)| {
585                        (ccol + item.is_real() as u16, vcol + place.len as u16)
586                    });
587
588                VPoint::new(point, ccol, vcol, vcol, wcol, desired_col)
589            } else {
590                VPoint::default()
591            }
592        }
593    }
594
595    fn has_changed(&self, _: UiPass) -> bool {
596        self.layouts
597            .inspect(self.id, |rect, layout| {
598                rect.has_changed(layout)
599                    || rect
600                        .print_info()
601                        .is_some_and(|info| *info != *self.prev_print_info.lock().unwrap())
602            })
603            .unwrap_or(false)
604    }
605
606    fn is_master_of(&self, _: UiPass, other: &Self) -> bool {
607        if other.id == self.id {
608            return true;
609        }
610
611        let mut parent_id = other.id;
612
613        self.layouts.inspect(self.id, |_, layout| {
614            while let Some((_, parent)) = layout.get_parent(parent_id) {
615                parent_id = parent.id();
616                if parent.id() == self.id {
617                    break;
618                }
619            }
620        });
621
622        parent_id == self.id
623    }
624
625    fn get_cluster_master(&self, _: UiPass) -> Option<Self> {
626        let id = self
627            .layouts
628            .inspect(self.id, |_, layout| layout.get_cluster_master(self.id))??;
629
630        Some(Self {
631            prev_print_info: Arc::default(),
632            layouts: self.layouts.clone(),
633            id,
634        })
635    }
636
637    fn cache(&self, _: UiPass) -> Option<Self::Cache> {
638        let info = self.layouts.get_info_of(self.id)?.for_caching();
639        Some(info)
640    }
641
642    fn top_left(&self, _: UiPass) -> ui::Coord {
643        self.layouts.update(self.id);
644        self.layouts
645            .coords_of(self.id, false)
646            .map(|coords| ui::Coord {
647                x: coords.tl.x as f32,
648                y: coords.tl.y as f32,
649            })
650            .unwrap_or_default()
651    }
652
653    fn bottom_right(&self, _: UiPass) -> ui::Coord {
654        self.layouts.update(self.id);
655        self.layouts
656            .coords_of(self.id, false)
657            .map(|coords| ui::Coord {
658                x: coords.br.x as f32,
659                y: coords.br.y as f32,
660            })
661            .unwrap_or_default()
662    }
663
664    #[track_caller]
665    fn points_at_coord(
666        &self,
667        _: UiPass,
668        text: &Text,
669        coord: ui::Coord,
670        opts: PrintOpts,
671    ) -> Option<TwoPointsPlace> {
672        self.layouts.update(self.id);
673        let Some(coords) = self.layouts.coords_of(self.id, false) else {
674            context::warn!("This Area was already deleted");
675            return None;
676        };
677
678        if coords.width() == 0 || coords.height() == 0 {
679            return None;
680        } else if !(coords.tl.x..coords.br.x).contains(&(coord.x as u32))
681            || !(coords.tl.y..coords.br.y).contains(&(coord.y as u32))
682        {
683            context::warn!("Coordinate not contained in area");
684            return None;
685        }
686
687        let (s_points, x_shift) = {
688            let mut info = self.layouts.get_info_of(self.id).unwrap();
689            let s_points = info.start_points(coords, text, opts);
690            self.layouts.set_info_of(self.id, info);
691            (s_points, info.x_shift())
692        };
693
694        let mut is_first = true;
695        let mut row = coords.tl.y;
696        let mut backup = None;
697        for (place, item) in print_iter(text, s_points, coords.width(), opts) {
698            if is_first {
699                is_first = false;
700            } else {
701                row += place.wrap as u32;
702            }
703
704            if row > coord.y as u32 {
705                return backup;
706            } else if row == coord.y as u32
707                && place.len > 0
708                && let Some(col) = place.x.checked_sub(x_shift)
709            {
710                if (coords.tl.x + col..coords.tl.x + col + place.len).contains(&(coord.x as u32)) {
711                    return Some(TwoPointsPlace::Within(item.points()));
712                } else if coords.tl.x + col >= coord.x as u32 {
713                    break;
714                }
715            }
716
717            backup = Some(TwoPointsPlace::AheadOf(item.points()));
718        }
719
720        None
721    }
722
723    fn coord_at_points(
724        &self,
725        _: UiPass,
726        text: &Text,
727        points: TwoPoints,
728        opts: PrintOpts,
729    ) -> Option<ui::Coord> {
730        self.layouts.update(self.id);
731        let Some(coords) = self.layouts.coords_of(self.id, false) else {
732            context::warn!("This Area was already deleted");
733            return None;
734        };
735
736        if coords.width() == 0 || coords.height() == 0 {
737            return None;
738        }
739
740        let (s_points, x_shift) = {
741            let mut info = self.layouts.get_info_of(self.id).unwrap();
742            let s_points = info.start_points(coords, text, opts);
743            self.layouts.set_info_of(self.id, info);
744            (s_points, info.x_shift())
745        };
746
747        let mut row = coords.tl.y;
748        for (place, item) in print_iter(text, s_points, coords.width(), opts) {
749            row += place.wrap as u32;
750
751            if row > coords.br.y {
752                break;
753            }
754
755            if item.points() == points && item.part.is_char() {
756                if place.x >= x_shift && place.x <= x_shift + coords.width() {
757                    return Some(ui::Coord {
758                        x: (coords.tl.x + place.x - x_shift) as f32,
759                        y: (row - 1) as f32,
760                    });
761                } else {
762                    break;
763                }
764            }
765        }
766
767        None
768    }
769
770    fn columns_at(
771        &self,
772        _: UiPass,
773        text: &Text,
774        points: TwoPoints,
775        opts: PrintOpts,
776    ) -> Option<Columns> {
777        self.layouts.update(self.id);
778        let Some(coords) = self.layouts.coords_of(self.id, false) else {
779            context::warn!("This Area was already deleted");
780            return None;
781        };
782
783        if coords.width() == 0 {
784            return None;
785        }
786
787        let mut line = 0;
788
789        for (place, item) in print_iter(text, points, coords.width(), opts) {
790            if item.points() == points && item.part.is_char() {
791                return Some(Columns {
792                    line,
793                    wrapped: place.x as usize,
794                    len: place.len as usize,
795                });
796            } else if let Some('\n') = item.part.as_char() {
797                break;
798            }
799
800            line += place.len as usize;
801        }
802
803        None
804    }
805
806    fn start_points(&self, _: UiPass, text: &Text, opts: PrintOpts) -> TwoPoints {
807        self.layouts.update(self.id);
808        let Some(coords) = self.layouts.coords_of(self.id, false) else {
809            context::warn!("This Area was already deleted");
810            return Default::default();
811        };
812
813        let mut info = self.layouts.get_info_of(self.id).unwrap();
814        let start_points = info.start_points(coords, text, opts);
815        self.layouts.set_info_of(self.id, info);
816
817        start_points
818    }
819
820    fn end_points(&self, _: UiPass, text: &Text, opts: PrintOpts) -> TwoPoints {
821        self.layouts.update(self.id);
822        let Some(coords) = self.layouts.coords_of(self.id, false) else {
823            context::warn!("This Area was already deleted");
824            return Default::default();
825        };
826
827        let mut info = self.layouts.get_info_of(self.id).unwrap();
828        let end_points = info.end_points(coords, text, opts);
829        self.layouts.set_info_of(self.id, info);
830
831        end_points
832    }
833
834    fn is_active(&self, _: UiPass) -> bool {
835        self.layouts.get_active_id() == self.id
836    }
837}
838
839/// The [`Text`] printing function
840#[allow(clippy::type_complexity)]
841pub fn print_text(
842    (text, opts, painter): (&Text, PrintOpts, &mut Painter),
843    (coords, max, has_edge_ahead): (Coords, Coord, bool),
844    (is_active, s_points, x_shift): (bool, TwoPoints, u32),
845    start_line: impl Fn(&mut Lines, u32),
846    end_line: impl Fn(&mut Lines, u32, u32),
847    print_spacer: impl Fn(&mut Lines, u32),
848) -> Option<(Lines, Vec<(SpawnId, Coord, u32)>)> {
849    if coords.width() == 0 || coords.height() == 0 {
850        return None;
851    }
852
853    let (mut lines, iter, mut next_cursor_end) = {
854        let lines = Lines::new(coords, has_edge_ahead);
855        let width = opts.wrap_width(coords.width()).unwrap_or(coords.width());
856        let iter = print_iter(text, s_points, width, opts);
857        let mut selections = iter_selections(text, s_points.real).peekable();
858
859        let next_c = move |lines: &mut Lines, painter: &mut Painter, real, ghost: Option<_>| {
860            let mut last_found = None;
861            while let Some(sel) = selections
862                .next_if(|parts| (parts.point == real && ghost.is_none()) || parts.point < real)
863            {
864                last_found = Some(sel)
865            }
866
867            match last_found {
868                Some(CursorParts { is_main: true, is_caret, is_start, .. }) => {
869                    if let Some(shape) = painter.main_cursor()
870                        && is_active
871                    {
872                        lines.show_real_cursor();
873                        queue!(lines, shape, cursor::SavePosition).unwrap();
874                    }
875
876                    lines.hide_real_cursor();
877                    painter.apply_main_selection(is_caret, is_start == Some(true));
878                    Some(Cursor::Main(is_caret, is_start == Some(false)))
879                }
880                Some(CursorParts { is_main: false, is_caret, is_start, .. }) => {
881                    painter.apply_extra_selection(is_caret, is_start == Some(true));
882                    Some(Cursor::Extra(is_caret, is_start == Some(false)))
883                }
884                None => None,
885            }
886        };
887
888        (lines, iter, next_c)
889    };
890
891    // The y here represents the bottom of the current row of cells.
892    let tl_y = lines.coords().tl.y;
893    let mut y = tl_y;
894    let mut last_x = 0;
895
896    // For spawns.
897    let mut spawns_for_next: Vec<SpawnId> = Vec::new();
898    let mut spawns = Vec::new();
899    // For Forms and Masks.
900    let mut style_was_set = false;
901    // For Overlays.
902    let mut overlays = Vec::new();
903    let mut minimum_x;
904
905    let endl = |lines: &mut _, painter: &mut Painter, x, overlays: &mut Vec<_>, spawns: &mut _| {
906        let mut default = painter.get_default();
907        default.style.foreground_color = None;
908        default.style.underline_color = None;
909        default.style.attributes = Attributes::from(Attribute::Reset);
910        print_style(lines, default.style);
911        end_line(lines, x, max.x);
912
913        if !overlays.is_empty() {
914            let coords = lines.coords();
915            painter.reset_prev_style();
916            continue_overlays(
917                (lines, overlays),
918                (coords.br.x, x_shift, u32::MAX, &mut true),
919                (painter, spawns),
920            );
921            write!(lines, "\x1b[{}G", lines.coords().br.x + 1).unwrap();
922            overlays.clear();
923        }
924
925        lines.flush().unwrap();
926    };
927
928    for (place, item) in iter {
929        let lines = &mut lines;
930
931        let PrintedPlace { x, len, wrap } = place;
932        let TextPlace { part, real, ghost } = item;
933
934        if wrap {
935            if y == lines.coords().br.y {
936                break;
937            }
938            if y > lines.coords().tl.y {
939                endl(lines, painter, last_x, &mut overlays, &mut spawns);
940                overlays.clear();
941            }
942
943            let initial_space = x.saturating_sub(x_shift).min(lines.coords().width());
944            if initial_space > 0 {
945                let mut default_style = painter.get_default().style;
946                default_style.attributes.set(Attribute::Reset);
947                print_style(lines, default_style);
948                start_line(lines, initial_space);
949            }
950            y += 1;
951
952            // Resetting painter to prevent erroneous printing.
953            painter.reset_prev_style();
954            style_was_set = true;
955            last_x = initial_space;
956        }
957
958        let is_contained = x + len > x_shift && x < x_shift + lines.coords().width();
959
960        match part {
961            TextPart::Char(char) if is_contained => {
962                let cursor_style = next_cursor_end(lines, painter, real, ghost);
963                style_was_set |= cursor_style.is_some();
964
965                minimum_x = continue_overlays(
966                    (lines, &mut overlays),
967                    (x, x_shift, x + len, &mut style_was_set),
968                    (painter, &mut spawns),
969                );
970
971                if let Some(str) = get_control_str(char) {
972                    painter.apply(CONTROL_CHAR_ID, 100);
973                    if let Some(style) = painter.relative_style() {
974                        print_style(lines, style);
975                    }
976                    lines.write_all(str.as_bytes()).unwrap();
977                    painter.remove(CONTROL_CHAR_ID)
978                } else {
979                    if style_was_set && let Some(style) = painter.relative_style() {
980                        print_style(lines, style);
981                    }
982
983                    let mut bytes = [0; 4];
984                    match char {
985                        '\t' => {
986                            let truncated_start = x_shift.saturating_sub(x);
987                            let truncated_end =
988                                (x + len).saturating_sub(lines.coords().width() + x_shift);
989                            let tab_len = (len - (truncated_start + truncated_end))
990                                .saturating_sub(minimum_x.saturating_sub(x));
991                            lines.write_all(&SPACES[..tab_len as usize]).unwrap()
992                        }
993                        _ if minimum_x >= x + len => {}
994                        _ if minimum_x > x => {
995                            let range = minimum_x as usize..(x + len) as usize;
996                            lines.write_all(&SPACES[range]).unwrap()
997                        }
998                        '\n' if len == 1 => lines.write_all(b" ").unwrap(),
999                        '\n' | '\r' => {}
1000                        char => {
1001                            char.encode_utf8(&mut bytes);
1002                            lines.write_all(&bytes[..char.len_utf8()]).unwrap();
1003                        }
1004                    }
1005                }
1006
1007                if let Some(cursor) = cursor_style {
1008                    match cursor {
1009                        Cursor::Main(is_caret, end_range) => {
1010                            painter.remove_main_selection(is_caret, end_range)
1011                        }
1012                        Cursor::Extra(is_caret, end_range) => {
1013                            painter.remove_extra_selection(is_caret, end_range)
1014                        }
1015                    }
1016                    if let Some(style) = painter.relative_style() {
1017                        print_style(lines, style)
1018                    }
1019                }
1020
1021                for id in spawns_for_next.drain(..) {
1022                    spawns.push((
1023                        id,
1024                        Coord::new(lines.coords().tl.x + x - x_shift, y - 1),
1025                        len,
1026                    ));
1027                }
1028
1029                last_x = x + len - x_shift;
1030                style_was_set = false;
1031            }
1032            TextPart::Char(_) => {
1033                let cursor_style = next_cursor_end(lines, painter, real, ghost);
1034                style_was_set |= cursor_style.is_some();
1035
1036                match cursor_style {
1037                    Some(Cursor::Main(is_caret, end_range)) => {
1038                        painter.remove_main_selection(is_caret, end_range)
1039                    }
1040                    Some(Cursor::Extra(is_caret, end_range)) => {
1041                        painter.remove_extra_selection(is_caret, end_range)
1042                    }
1043                    None => {}
1044                }
1045                spawns_for_next.clear();
1046            }
1047            TextPart::PushForm(id, prio) => {
1048                painter.apply(id, prio);
1049                style_was_set = true;
1050            }
1051            TextPart::PopForm(id) => {
1052                painter.remove(id);
1053                style_was_set = true;
1054            }
1055            TextPart::Spacer => {
1056                if style_was_set && let Some(style) = painter.relative_style() {
1057                    print_style(lines, style);
1058                }
1059                style_was_set = false;
1060                let truncated_start = x_shift.saturating_sub(x).min(len);
1061                let truncated_end = (x + len)
1062                    .saturating_sub(lines.coords().width().saturating_sub(x_shift))
1063                    .min(len);
1064                let spacer_len = len - (truncated_start + truncated_end);
1065                print_spacer(lines, spacer_len);
1066                last_x = (x + len)
1067                    .saturating_sub(x_shift)
1068                    .min(lines.coords().width());
1069            }
1070            TextPart::ResetState => print_style(lines, painter.reset()),
1071            TextPart::SpawnedWidget(id) => spawns_for_next.push(id),
1072            TextPart::PushMask(id) => {
1073                painter.apply_mask(id);
1074                style_was_set = true;
1075            }
1076            TextPart::PopMask(id) => {
1077                painter.remove_mask(id);
1078                style_was_set = true;
1079            }
1080            TextPart::Overlay(overlay) => {
1081                let opts = PrintOpts { print_new_line: false, ..opts };
1082                let overlay_coords =
1083                    Coords::new(Coord::new(coords.tl.x + x, 0), Coord::new(coords.br.x, 1));
1084
1085                overlays.push(
1086                    print_iter(overlay, TwoPoints::default(), overlay_coords.width(), opts)
1087                        .map(move |(mut place, item)| {
1088                            place.x += x;
1089                            (place, item)
1090                        })
1091                        .peekable(),
1092                );
1093            }
1094        }
1095    }
1096
1097    endl(&mut lines, painter, last_x, &mut overlays, &mut spawns);
1098
1099    for _ in 0..lines.coords().br.y - y {
1100        endl(&mut lines, painter, 0, &mut Vec::new(), &mut spawns);
1101    }
1102
1103    Some((lines, spawns))
1104}
1105
1106#[inline(always)]
1107fn continue_overlays<'t, Iter: Iterator<Item = (PrintedPlace, TextPlace<'t>)>>(
1108    (lines, overlays): (&mut Lines, &mut Vec<Peekable<Iter>>),
1109    (original_x, x_shift, until_x, style_was_set): (u32, u32, u32, &mut bool),
1110    (painter, spawns): (&mut Painter, &mut Vec<(SpawnId, Coord, u32)>),
1111) -> u32 {
1112    if overlays.is_empty() {
1113        return original_x;
1114    }
1115
1116    // For specific Tags
1117    let mut spawns_for_next: Vec<SpawnId> = Vec::new();
1118    let mut current_x = original_x;
1119
1120    while let Some((place, item)) = {
1121        let mut min = None;
1122        for (i, overlay) in overlays.iter_mut().enumerate() {
1123            if let Some((place, _)) = overlay.peek()
1124                && min.is_none_or(|(x, _)| place.x < x)
1125            {
1126                min = Some((place.x, i));
1127            }
1128        }
1129
1130        min.and_then(|(_, i)| overlays[i].next_if(|(place, _)| place.x < until_x))
1131    } {
1132        let PrintedPlace { x, len, wrap } = place;
1133        let TextPlace { part, real, .. } = item;
1134
1135        if wrap && real != Point::default() {
1136            break;
1137        }
1138
1139        let is_contained = x + len > x_shift && x < x_shift + lines.coords().width();
1140
1141        match part {
1142            TextPart::Char(char) if is_contained => {
1143                if x != original_x {
1144                    write!(lines, "\x1b[{}G", lines.coords().tl.x + x + 1).unwrap();
1145                }
1146
1147                if let Some(str) = get_control_str(char) {
1148                    painter.apply(CONTROL_CHAR_ID, 100);
1149                    if let Some(style) = painter.relative_style() {
1150                        print_style(lines, style);
1151                    }
1152                    lines.write_all(str.as_bytes()).unwrap();
1153                    painter.remove(CONTROL_CHAR_ID)
1154                } else {
1155                    if *style_was_set && let Some(style) = painter.relative_style() {
1156                        print_style(lines, style);
1157                    }
1158                    let mut bytes = [0; 4];
1159
1160                    match char {
1161                        '\t' => {
1162                            let truncated_start = x_shift.saturating_sub(x);
1163                            let truncated_end =
1164                                (x + len).saturating_sub(lines.coords().width() + x_shift);
1165                            let tab_len = len - (truncated_start + truncated_end);
1166                            lines.write_all(&SPACES[..tab_len as usize]).unwrap()
1167                        }
1168                        '\n' if len == 1 => lines.write_all(b" ").unwrap(),
1169                        '\n' | '\r' => {}
1170                        char => {
1171                            char.encode_utf8(&mut bytes);
1172                            lines.write_all(&bytes[..char.len_utf8()]).unwrap();
1173                        }
1174                    }
1175                }
1176
1177                for id in spawns_for_next.drain(..) {
1178                    spawns.push((
1179                        id,
1180                        Coord::new(lines.coords().tl.x + x - x_shift, lines.coords().tl.y),
1181                        len,
1182                    ));
1183                }
1184
1185                current_x = x + len;
1186                *style_was_set = false;
1187            }
1188            TextPart::Char(_) => spawns_for_next.clear(),
1189            TextPart::PushForm(id, prio) => {
1190                painter.apply(id, prio);
1191                *style_was_set = true;
1192            }
1193            TextPart::PopForm(id) => {
1194                painter.remove(id);
1195                *style_was_set = true;
1196            }
1197            TextPart::Spacer => {}
1198            TextPart::ResetState => print_style(lines, painter.reset()),
1199            TextPart::SpawnedWidget(id) => spawns_for_next.push(id),
1200            TextPart::PushMask(id) => {
1201                painter.apply_mask(id);
1202                *style_was_set = true;
1203            }
1204            TextPart::PopMask(id) => {
1205                painter.remove_mask(id);
1206                *style_was_set = true;
1207            }
1208            TextPart::Overlay(_) => unreachable!(),
1209        }
1210    }
1211
1212    if current_x < original_x
1213        && let Some(column) = (lines.coords().tl.x + original_x).checked_sub(x_shift)
1214        && column < lines.coords().br.x
1215    {
1216        write!(lines, "\x1b[{}G", column + 1).unwrap();
1217    }
1218    overlays.retain_mut(|iter| iter.peek().is_some());
1219
1220    current_x
1221}
1222
1223fn calculate_vpoint(text: &Text, point: Point, cap: u32, opts: PrintOpts) -> VPoint {
1224    let start = text.point_at_coords(point.line(), 0);
1225
1226    let mut vcol = 0;
1227
1228    let points = start.to_two_points_before();
1229    let wcol = print_iter(text, points, cap, opts)
1230        .find_map(|(place, item)| {
1231            if let Some((lhs, _)) = item.as_real_char()
1232                && lhs == point
1233            {
1234                return Some(place.x as u16);
1235            }
1236            vcol += place.len as u16;
1237            None
1238        })
1239        .unwrap_or(0);
1240
1241    VPoint::new(
1242        point,
1243        (point.char() - start.char()) as u16,
1244        vcol,
1245        vcol,
1246        wcol,
1247        wcol,
1248    )
1249}
1250
1251const fn get_control_str(char: char) -> Option<&'static str> {
1252    match char {
1253        '\0' => Some("^@"),
1254        '\u{01}' => Some("^A"),
1255        '\u{02}' => Some("^B"),
1256        '\u{03}' => Some("^C"),
1257        '\u{04}' => Some("^D"),
1258        '\u{05}' => Some("^E"),
1259        '\u{06}' => Some("^F"),
1260        '\u{07}' => Some("^G"),
1261        '\u{08}' => Some("^H"),
1262        '\u{0b}' => Some("^K"),
1263        '\u{0c}' => Some("^L"),
1264        '\u{0e}' => Some("^N"),
1265        '\u{0f}' => Some("^O"),
1266        '\u{10}' => Some("^P"),
1267        '\u{11}' => Some("^Q"),
1268        '\u{12}' => Some("^R"),
1269        '\u{13}' => Some("^S"),
1270        '\u{14}' => Some("^T"),
1271        '\u{15}' => Some("^U"),
1272        '\u{16}' => Some("^V"),
1273        '\u{17}' => Some("^W"),
1274        '\u{18}' => Some("^X"),
1275        '\u{19}' => Some("^Y"),
1276        '\u{1a}' => Some("^Z"),
1277        '\u{1b}' => Some("^["),
1278        '\u{1c}' => Some("^\\"),
1279        '\u{1d}' => Some("^]"),
1280        '\u{1e}' => Some("^^"),
1281        '\u{1f}' => Some("^_"),
1282        '\u{80}' => Some("<80>"),
1283        '\u{81}' => Some("<81>"),
1284        '\u{82}' => Some("<82>"),
1285        '\u{83}' => Some("<83>"),
1286        '\u{84}' => Some("<84>"),
1287        '\u{85}' => Some("<85>"),
1288        '\u{86}' => Some("<86>"),
1289        '\u{87}' => Some("<87>"),
1290        '\u{88}' => Some("<88>"),
1291        '\u{89}' => Some("<89>"),
1292        '\u{8a}' => Some("<8a>"),
1293        '\u{8b}' => Some("<8b>"),
1294        '\u{8c}' => Some("<8c>"),
1295        '\u{8d}' => Some("<8d>"),
1296        '\u{8e}' => Some("<8e>"),
1297        '\u{8f}' => Some("<8f>"),
1298        '\u{90}' => Some("<90>"),
1299        '\u{91}' => Some("<91>"),
1300        '\u{92}' => Some("<92>"),
1301        '\u{93}' => Some("<93>"),
1302        '\u{94}' => Some("<94>"),
1303        '\u{95}' => Some("<95>"),
1304        '\u{96}' => Some("<96>"),
1305        '\u{97}' => Some("<97>"),
1306        '\u{98}' => Some("<98>"),
1307        '\u{99}' => Some("<99>"),
1308        '\u{9a}' => Some("<9a>"),
1309        '\u{9b}' => Some("<9b>"),
1310        '\u{9c}' => Some("<9c>"),
1311        '\u{9d}' => Some("<9d>"),
1312        '\u{9e}' => Some("<9e>"),
1313        '\u{9f}' => Some("<9f>"),
1314        _ => None,
1315    }
1316}
1317
1318const SPACES: &[u8] = &[b' '; 3000];
1319
1320fn iter_selections(text: &Text, from: Point) -> impl Iterator<Item = CursorParts> {
1321    text.selections()
1322        .iter_within(from..)
1323        .flat_map(|(_, sel, is_main)| {
1324            if let Some(anchor) = sel.anchor()
1325                && anchor != sel.caret()
1326            {
1327                let caret_is_start = sel.caret() < anchor;
1328                let start = sel.caret().min(anchor);
1329                let end = sel.caret().max(anchor);
1330                [
1331                    Some(CursorParts::new(start, is_main, caret_is_start, Some(true))),
1332                    Some(CursorParts::new(end, is_main, !caret_is_start, Some(false))),
1333                ]
1334            } else {
1335                [
1336                    Some(CursorParts::new(sel.caret(), is_main, true, None)),
1337                    None,
1338                ]
1339            }
1340        })
1341        .flatten()
1342}
1343
1344pub struct CursorParts {
1345    point: Point,
1346    is_main: bool,
1347    is_caret: bool,
1348    is_start: Option<bool>,
1349}
1350
1351impl CursorParts {
1352    fn new(point: Point, is_main: bool, is_caret: bool, is_start: Option<bool>) -> Self {
1353        Self { point, is_main, is_caret, is_start }
1354    }
1355}
1356
1357#[derive(Clone, Copy)]
1358enum Cursor {
1359    Main(bool, bool),
1360    Extra(bool, bool),
1361}