Skip to main content

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