duat_term/area/
mod.rs

1mod iter;
2mod print_info;
3
4use std::{io::Write, sync::Arc};
5
6use crossterm::{
7    cursor, queue,
8    style::{Attribute, Attributes},
9};
10use duat_core::{
11    context::{self, cache::{Decode, Encode}},
12    form::{CONTROL_CHAR_ID, Painter},
13    opts::PrintOpts,
14    session::TwoPointsPlace,
15    text::{Item, Part, Text, TwoPoints, txt},
16    ui::{
17        self, Caret, DynSpawnSpecs, PrintedLine, PushSpecs, SpawnId,
18        traits::{RawArea, UiPass},
19    },
20};
21use iter::{print_iter, rev_print_iter};
22
23pub use self::print_info::PrintInfo;
24use crate::{
25    AreaId, Mutex,
26    layout::{Frame, Layouts},
27    print_style,
28    printer::Lines,
29};
30
31#[derive(Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Encode, Decode)]
32#[bincode(crate = "duat_core::context::cache::bincode")]
33pub struct Coord {
34    pub x: u32,
35    pub y: u32,
36}
37
38impl std::fmt::Debug for Coord {
39    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
40        f.write_fmt(format_args!("x: {}, y: {}", self.x, self.y))
41    }
42}
43
44impl Coord {
45    pub fn new(x: u32, y: u32) -> Coord {
46        Coord { x, y }
47    }
48}
49
50#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Encode, Decode)]
51#[bincode(crate = "duat_core::context::cache::bincode")]
52pub struct Coords {
53    pub tl: Coord,
54    pub br: Coord,
55}
56
57impl Coords {
58    pub fn new(tl: Coord, br: Coord) -> Self {
59        Coords { tl, br }
60    }
61
62    pub fn width(&self) -> u32 {
63        self.br.x - self.tl.x
64    }
65
66    pub fn height(&self) -> u32 {
67        self.br.y - self.tl.y
68    }
69
70    pub fn area(&self) -> u32 {
71        self.width() * self.height()
72    }
73
74    pub fn intersects(&self, other: Self) -> bool {
75        self.tl.x < other.br.x
76            && self.br.x > other.tl.x
77            && self.tl.y < other.br.y
78            && self.br.y > other.tl.y
79    }
80
81    pub fn x_range(&self) -> std::ops::Range<u32> {
82        self.tl.x..self.br.x
83    }
84
85    pub fn y_range(&self) -> std::ops::Range<u32> {
86        self.tl.y..self.br.y
87    }
88}
89
90#[derive(Clone)]
91pub struct Area {
92    prev_print_info: Arc<Mutex<PrintInfo>>,
93    layouts: Layouts,
94    id: AreaId,
95    set_frame: fn(&mut Self, Frame),
96}
97
98impl PartialEq for Area {
99    fn eq(&self, other: &Self) -> bool {
100        self.id == other.id
101    }
102}
103
104impl Area {
105    /// Returns a new `Area` from raw parts
106    pub(crate) fn new(id: AreaId, layouts: Layouts) -> Self {
107        Self {
108            prev_print_info: Arc::new(Mutex::default()),
109            layouts,
110            id,
111            set_frame: |area, frame| {
112                if !area.layouts.set_frame(area.id, frame) {
113                    context::warn!("This Area was already deleted");
114                }
115            },
116        }
117    }
118
119    /// Adds a frame to this [`Area`]
120    ///
121    /// This function will fail if the `Area` was either deleted or is
122    /// not a spawned `Area`.
123    pub fn set_frame(&mut self, frame: Frame) {
124        (self.set_frame)(self, frame)
125    }
126
127    /// Prints the `Text`
128    fn print(&self, text: &Text, opts: PrintOpts, 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 max = self
135            .layouts
136            .inspect(self.id, |_, layout| layout.max_value())
137            .unwrap();
138
139        if coords.width() == 0 || coords.height() == 0 {
140            return;
141        }
142
143        let (s_points, x_shift) = {
144            let mut info = self.layouts.get_info_of(self.id).unwrap();
145            let s_points = info.start_points(coords, text, opts);
146            *self.prev_print_info.lock().unwrap() = info;
147            self.layouts.set_info_of(self.id, info);
148            (s_points, info.x_shift())
149        };
150
151        let is_active = self.id == self.layouts.get_active_id();
152
153        let Some((lines, observed_spawns)) = print_text(
154            (text, opts, painter),
155            (coords, max),
156            (is_active, s_points, x_shift),
157            |lines, len| lines.write_all(&SPACES[..len as usize]).unwrap(),
158            |lines, len, max_x| {
159                if lines.coords().br.x == max_x {
160                    lines.write_all(b"\x1b[0K").unwrap();
161                } else {
162                    lines
163                        .write_all(&SPACES[..(lines.coords().width() - len) as usize])
164                        .unwrap();
165                }
166                lines.flush().unwrap();
167            },
168            |lines, spacer_len| lines.write_all(&SPACES[..spacer_len as usize]).unwrap(),
169        ) else {
170            return;
171        };
172
173        let spawns = text.get_spawned_ids();
174
175        self.layouts
176            .send_lines(self.id, lines, spawns, &observed_spawns);
177    }
178}
179
180impl RawArea for Area {
181    type Cache = PrintInfo;
182    type PrintInfo = PrintInfo;
183
184    /////////// Modification
185
186    fn push(
187        &self,
188        _: UiPass,
189        specs: PushSpecs,
190        on_files: bool,
191        cache: PrintInfo,
192    ) -> Option<(Area, Option<Area>)> {
193        let (child, parent) = self.layouts.push(self.id, specs, on_files, cache)?;
194
195        Some((
196            Self::new(child, self.layouts.clone()),
197            parent.map(|parent| Self::new(parent, self.layouts.clone())),
198        ))
199    }
200
201    fn delete(&self, _: UiPass) -> (bool, Vec<Self>) {
202        let (do_rm_window, rm_areas) = self.layouts.delete(self.id);
203        (
204            do_rm_window,
205            rm_areas
206                .into_iter()
207                .map(|id| Self::new(id, self.layouts.clone()))
208                .collect(),
209        )
210    }
211
212    fn swap(&self, _: UiPass, rhs: &Self) -> bool {
213        self.layouts.swap(self.id, rhs.id)
214    }
215
216    fn spawn(
217        &self,
218        _: UiPass,
219        spawn_id: SpawnId,
220        specs: DynSpawnSpecs,
221        cache: Self::Cache,
222    ) -> Option<Self> {
223        Some(Self::new(
224            self.layouts
225                .spawn_on_widget(self.id, spawn_id, specs, cache)?,
226            self.layouts.clone(),
227        ))
228    }
229
230    fn set_width(&self, _: UiPass, width: f32) -> Result<(), Text> {
231        if self
232            .layouts
233            .set_constraints(self.id, Some(width), None, None)
234        {
235            Ok(())
236        } else {
237            Err(txt!("This Area was already deleted"))
238        }
239    }
240
241    fn set_height(&self, _: UiPass, height: f32) -> Result<(), Text> {
242        if self
243            .layouts
244            .set_constraints(self.id, None, Some(height), None)
245        {
246            Ok(())
247        } else {
248            Err(txt!("This Area was already deleted"))
249        }
250    }
251
252    fn hide(&self, _: UiPass) -> Result<(), Text> {
253        if self
254            .layouts
255            .set_constraints(self.id, None, None, Some(true))
256        {
257            Ok(())
258        } else {
259            Err(txt!("This Area was already deleted"))
260        }
261    }
262
263    fn reveal(&self, _: UiPass) -> Result<(), Text> {
264        if self
265            .layouts
266            .set_constraints(self.id, None, None, Some(false))
267        {
268            Ok(())
269        } else {
270            Err(txt!("This Area was already deleted"))
271        }
272    }
273
274    fn size_of_text(&self, _: UiPass, opts: PrintOpts, text: &Text) -> Result<ui::Coord, Text> {
275        let max = self
276            .layouts
277            .inspect(self.id, |_, layout| layout.max_value())
278            .ok_or_else(|| txt!("This Area was already deleted"))?;
279
280        let iter = iter::print_iter(text, TwoPoints::default(), max.x, opts);
281
282        let mut max_x = 0;
283        let mut max_y = 0;
284        let mut width = 0;
285
286        for (caret, item) in iter {
287            if caret.wrap {
288                if max_y == max.y {
289                    break;
290                }
291                max_x = width.max(max_x);
292                max_y += 1;
293                width = 0;
294            }
295            if item.part.is_char() {
296                width += caret.len;
297            }
298        }
299
300        Ok(ui::Coord::new(max_x.max(width) as f32, max_y as f32))
301    }
302
303    fn scroll_ver(&self, _: UiPass, text: &Text, by: i32, opts: PrintOpts) {
304        if by == 0 {
305            return;
306        }
307
308        let Some(coords) = self.layouts.coords_of(self.id, false) else {
309            context::warn!("This Area was already deleted");
310            return;
311        };
312
313        if coords.width() == 0 || coords.height() == 0 {
314            return;
315        }
316
317        let mut info = self.layouts.get_info_of(self.id).unwrap();
318        info.scroll_ver(by, coords, text, opts);
319        self.layouts.set_info_of(self.id, info);
320    }
321
322    ////////// Printing
323
324    fn scroll_around_points(&self, _: UiPass, text: &Text, points: TwoPoints, opts: PrintOpts) {
325        let Some(coords) = self.layouts.coords_of(self.id, false) else {
326            context::warn!("This Area was already deleted");
327            return;
328        };
329
330        if coords.width() == 0 || coords.height() == 0 {
331            return;
332        }
333
334        let mut info = self.layouts.get_info_of(self.id).unwrap();
335        info.scroll_around(points.real, coords, text, opts);
336        self.layouts.set_info_of(self.id, info);
337    }
338
339    fn scroll_to_points(&self, _: UiPass, text: &Text, points: TwoPoints, opts: PrintOpts) {
340        let Some(coords) = self.layouts.coords_of(self.id, false) else {
341            context::warn!("This Area was already deleted");
342            return;
343        };
344
345        if coords.width() == 0 || coords.height() == 0 {
346            return;
347        }
348
349        let mut info = self.layouts.get_info_of(self.id).unwrap();
350        info.scroll_to_points(points, coords, text, opts);
351        self.layouts.set_info_of(self.id, info);
352    }
353
354    fn set_as_active(&self, _: UiPass) {
355        self.layouts.set_active_id(self.id);
356    }
357
358    fn print(&self, _: UiPass, text: &Text, opts: PrintOpts, painter: Painter) {
359        self.print(text, opts, painter)
360    }
361
362    ////////// Queries
363
364    fn get_print_info(&self, _: UiPass) -> Self::PrintInfo {
365        self.layouts.get_info_of(self.id).unwrap_or_default()
366    }
367
368    fn set_print_info(&self, _: UiPass, info: Self::PrintInfo) {
369        self.layouts.set_info_of(self.id, info);
370    }
371
372    fn get_printed_lines(
373        &self,
374        pa: UiPass,
375        text: &Text,
376        opts: PrintOpts,
377    ) -> Option<Vec<ui::PrintedLine>> {
378        let coords = self.layouts.coords_of(self.id, true)?;
379        let points = self.start_points(pa, text, opts);
380
381        let mut prev_line = self
382            .rev_print_iter(pa, text, points, opts)
383            .find_map(|(caret, item)| caret.wrap.then_some(item.line()));
384
385        let mut printed_lines = Vec::new();
386        let mut has_wrapped = false;
387        let mut y = coords.tl.y;
388
389        for (caret, item) in print_iter(text, points, coords.width(), opts) {
390            if y == coords.br.y {
391                break;
392            }
393            y += caret.wrap as u32;
394
395            has_wrapped |= caret.wrap;
396            if has_wrapped && item.part.is_char() {
397                has_wrapped = false;
398                let number = item.line();
399                let is_wrapped = prev_line.is_some_and(|ll| ll == number);
400                prev_line = Some(number);
401                printed_lines.push(PrintedLine { number, is_wrapped });
402            }
403        }
404
405        Some(printed_lines)
406    }
407
408    fn print_iter<'a>(
409        &self,
410        ca: UiPass,
411        text: &'a Text,
412        points: TwoPoints,
413        opts: PrintOpts,
414    ) -> impl Iterator<Item = (Caret, Item)> + 'a {
415        let width = (self.bottom_right(ca).x - self.top_left(ca).x) as u32;
416        print_iter(text, points, width, opts)
417    }
418
419    fn rev_print_iter<'a>(
420        &self,
421        ca: UiPass,
422        text: &'a Text,
423        points: TwoPoints,
424        opts: PrintOpts,
425    ) -> impl Iterator<Item = (Caret, Item)> + 'a {
426        let width = (self.bottom_right(ca).x - self.top_left(ca).x) as u32;
427        rev_print_iter(text, points, opts.wrap_width(width).unwrap_or(width), opts)
428    }
429
430    fn has_changed(&self, _: UiPass) -> bool {
431        self.layouts
432            .inspect(self.id, |rect, layout| {
433                rect.has_changed(layout)
434                    || rect
435                        .print_info()
436                        .is_some_and(|info| *info != *self.prev_print_info.lock().unwrap())
437            })
438            .unwrap_or(false)
439    }
440
441    fn is_master_of(&self, _: UiPass, other: &Self) -> bool {
442        if other.id == self.id {
443            return true;
444        }
445
446        let mut parent_id = other.id;
447
448        self.layouts.inspect(self.id, |_, layout| {
449            while let Some((_, parent)) = layout.get_parent(parent_id) {
450                parent_id = parent.id();
451                if parent.id() == self.id {
452                    break;
453                }
454            }
455        });
456
457        parent_id == self.id
458    }
459
460    fn get_cluster_master(&self, _: UiPass) -> Option<Self> {
461        let id = self
462            .layouts
463            .inspect(self.id, |_, layout| layout.get_cluster_master(self.id))??;
464
465        Some(Self {
466            prev_print_info: Arc::default(),
467            layouts: self.layouts.clone(),
468            id,
469            set_frame: |area, frame| {
470                if !area.layouts.set_frame(area.id, frame) {
471                    context::warn!("This Area was already deleted");
472                }
473            },
474        })
475    }
476
477    fn cache(&self, _: UiPass) -> Option<Self::Cache> {
478        let info = self.layouts.get_info_of(self.id)?.for_caching();
479        Some(info)
480    }
481
482    fn top_left(&self, _: UiPass) -> ui::Coord {
483        self.layouts.update(self.id);
484        self.layouts
485            .coords_of(self.id, false)
486            .map(|coords| ui::Coord {
487                x: coords.tl.x as f32,
488                y: coords.tl.y as f32,
489            })
490            .unwrap_or_default()
491    }
492
493    fn bottom_right(&self, _: UiPass) -> ui::Coord {
494        self.layouts.update(self.id);
495        self.layouts
496            .coords_of(self.id, false)
497            .map(|coords| ui::Coord {
498                x: coords.br.x as f32,
499                y: coords.br.y as f32,
500            })
501            .unwrap_or_default()
502    }
503
504    fn coord_at_points(
505        &self,
506        _: UiPass,
507        text: &Text,
508        points: TwoPoints,
509        opts: PrintOpts,
510    ) -> Option<ui::Coord> {
511        let Some(coords) = self.layouts.coords_of(self.id, false) else {
512            context::warn!("This Area was already deleted");
513            return None;
514        };
515
516        if coords.width() == 0 || coords.height() == 0 {
517            return None;
518        }
519
520        let (s_points, x_shift) = {
521            let mut info = self.layouts.get_info_of(self.id).unwrap();
522            let s_points = info.start_points(coords, text, opts);
523            self.layouts.set_info_of(self.id, info);
524            (s_points, info.x_shift())
525        };
526
527        let mut row = coords.tl.y;
528        for (caret, item) in print_iter(text, s_points, coords.width(), opts) {
529            row += caret.wrap as u32;
530
531            if row > coords.br.y {
532                break;
533            }
534
535            if item.points() == points && item.part.is_char() {
536                if caret.x >= x_shift && caret.x <= x_shift + coords.width() {
537                    return Some(ui::Coord {
538                        x: (coords.tl.x + caret.x - x_shift) as f32,
539                        y: (row - 1) as f32,
540                    });
541                } else {
542                    break;
543                }
544            }
545        }
546
547        None
548    }
549
550    fn points_at_coord(
551        &self,
552        _: UiPass,
553        text: &Text,
554        coord: ui::Coord,
555        opts: PrintOpts,
556    ) -> Option<TwoPointsPlace> {
557        let Some(coords) = self.layouts.coords_of(self.id, false) else {
558            context::warn!("This Area was already deleted");
559            return None;
560        };
561
562        if coords.width() == 0 || coords.height() == 0 {
563            return None;
564        } else if !(coords.tl.x..coords.br.x).contains(&(coord.x as u32))
565            || !(coords.tl.y..coords.br.y).contains(&(coord.y as u32))
566        {
567            context::warn!("Coordinate not contained in area");
568            return None;
569        }
570
571        let (s_points, x_shift) = {
572            let mut info = self.layouts.get_info_of(self.id).unwrap();
573            let s_points = info.start_points(coords, text, opts);
574            self.layouts.set_info_of(self.id, info);
575            (s_points, info.x_shift())
576        };
577
578        let mut row = coords.tl.y;
579        let mut backup = None;
580        for (caret, item) in print_iter(text, s_points, coords.width(), opts) {
581            row += caret.wrap as u32;
582
583            if row > coord.y as u32 + 1 {
584                return backup;
585            } else if row == coord.y as u32 + 1
586                && let Some(col) = caret.x.checked_sub(x_shift)
587            {
588                if (coords.tl.x + col..coords.tl.x + col + caret.len).contains(&(coord.x as u32)) {
589                    return Some(TwoPointsPlace::Within(item.points()));
590                } else if coords.tl.x + col >= coord.x as u32 {
591                    return backup;
592                }
593            }
594
595            if item.part.is_char() {
596                backup = Some(TwoPointsPlace::AheadOf(item.points()));
597            }
598        }
599
600        None
601    }
602
603    fn start_points(&self, _: UiPass, text: &Text, opts: PrintOpts) -> TwoPoints {
604        if !self.layouts.update(self.id) {
605            context::warn!("This Area was already deleted");
606            return Default::default();
607        }
608        let coords = self.layouts.coords_of(self.id, false).unwrap();
609
610        let mut info = self.layouts.get_info_of(self.id).unwrap();
611        let start_points = info.start_points(coords, text, opts);
612        self.layouts.set_info_of(self.id, info);
613
614        start_points
615    }
616
617    fn end_points(&self, _: UiPass, text: &Text, opts: PrintOpts) -> TwoPoints {
618        let Some(coords) = self.layouts.coords_of(self.id, false) else {
619            context::warn!("This Area was already deleted");
620            return Default::default();
621        };
622
623        let mut info = self.layouts.get_info_of(self.id).unwrap();
624        let end_points = info.end_points(coords, text, opts);
625        self.layouts.set_info_of(self.id, info);
626
627        end_points
628    }
629
630    fn is_active(&self, _: UiPass) -> bool {
631        self.layouts.get_active_id() == self.id
632    }
633}
634
635const fn get_control_str(char: char) -> Option<&'static str> {
636    match char {
637        '\0' => Some("^@"),
638        '\u{01}' => Some("^A"),
639        '\u{02}' => Some("^B"),
640        '\u{03}' => Some("^C"),
641        '\u{04}' => Some("^D"),
642        '\u{05}' => Some("^E"),
643        '\u{06}' => Some("^F"),
644        '\u{07}' => Some("^G"),
645        '\u{08}' => Some("^H"),
646        '\u{0b}' => Some("^K"),
647        '\u{0c}' => Some("^L"),
648        '\u{0e}' => Some("^N"),
649        '\u{0f}' => Some("^O"),
650        '\u{10}' => Some("^P"),
651        '\u{11}' => Some("^Q"),
652        '\u{12}' => Some("^R"),
653        '\u{13}' => Some("^S"),
654        '\u{14}' => Some("^T"),
655        '\u{15}' => Some("^U"),
656        '\u{16}' => Some("^V"),
657        '\u{17}' => Some("^W"),
658        '\u{18}' => Some("^X"),
659        '\u{19}' => Some("^Y"),
660        '\u{1a}' => Some("^Z"),
661        '\u{1b}' => Some("^["),
662        '\u{1c}' => Some("^\\"),
663        '\u{1d}' => Some("^]"),
664        '\u{1e}' => Some("^^"),
665        '\u{1f}' => Some("^_"),
666        '\u{80}' => Some("<80>"),
667        '\u{81}' => Some("<81>"),
668        '\u{82}' => Some("<82>"),
669        '\u{83}' => Some("<83>"),
670        '\u{84}' => Some("<84>"),
671        '\u{85}' => Some("<85>"),
672        '\u{86}' => Some("<86>"),
673        '\u{87}' => Some("<87>"),
674        '\u{88}' => Some("<88>"),
675        '\u{89}' => Some("<89>"),
676        '\u{8a}' => Some("<8a>"),
677        '\u{8b}' => Some("<8b>"),
678        '\u{8c}' => Some("<8c>"),
679        '\u{8d}' => Some("<8d>"),
680        '\u{8e}' => Some("<8e>"),
681        '\u{8f}' => Some("<8f>"),
682        '\u{90}' => Some("<90>"),
683        '\u{91}' => Some("<91>"),
684        '\u{92}' => Some("<92>"),
685        '\u{93}' => Some("<93>"),
686        '\u{94}' => Some("<94>"),
687        '\u{95}' => Some("<95>"),
688        '\u{96}' => Some("<96>"),
689        '\u{97}' => Some("<97>"),
690        '\u{98}' => Some("<98>"),
691        '\u{99}' => Some("<99>"),
692        '\u{9a}' => Some("<9a>"),
693        '\u{9b}' => Some("<9b>"),
694        '\u{9c}' => Some("<9c>"),
695        '\u{9d}' => Some("<9d>"),
696        '\u{9e}' => Some("<9e>"),
697        '\u{9f}' => Some("<9f>"),
698        _ => None,
699    }
700}
701
702/// The [`Text`] printing function
703#[allow(clippy::type_complexity)]
704pub fn print_text(
705    (text, opts, mut painter): (&Text, PrintOpts, Painter),
706    (coords, max): (Coords, Coord),
707    (is_active, s_points, x_shift): (bool, TwoPoints, u32),
708    start_line: fn(&mut Lines, u32),
709    end_line: fn(&mut Lines, u32, u32),
710    print_spacer: fn(&mut Lines, u32),
711) -> Option<(Lines, Vec<(SpawnId, Coord, u32)>)> {
712    fn print_end_style(lines: &mut Lines, painter: &Painter) {
713        let mut default = painter.get_default();
714        default.style.foreground_color = None;
715        default.style.underline_color = None;
716        default.style.attributes = Attributes::from(Attribute::Reset);
717        print_style(lines, default.style);
718    }
719
720    let (mut lines, iter, x_shift, max_x) = {
721        if coords.width() == 0 || coords.height() == 0 {
722            return None;
723        }
724
725        let lines = Lines::new(coords);
726        let width = opts.wrap_width(coords.width()).unwrap_or(coords.width());
727        let iter = print_iter(text, s_points, width, opts);
728
729        (lines, iter, x_shift, max.x)
730    };
731
732    let mut observed_spawns = Vec::new();
733
734    let mut style_was_set = false;
735
736    enum Cursor {
737        Main,
738        Extra,
739    }
740
741    // The y here represents the bottom of the current row of cells.
742    let tl_y = lines.coords().tl.y;
743    let mut y = tl_y;
744    let mut cursor = None;
745    let mut spawns_for_next: Vec<SpawnId> = Vec::new();
746    let mut last_len = 0;
747
748    for (caret, item) in iter {
749        let (painter, lines) = (&mut painter, &mut lines);
750
751        let Caret { x, len, wrap } = caret;
752        let Item { part, .. } = item;
753
754        if wrap {
755            if y == lines.coords().br.y {
756                break;
757            }
758            if y > lines.coords().tl.y {
759                print_end_style(lines, painter);
760                end_line(lines, last_len, max_x);
761            }
762            let initial_space = x.saturating_sub(x_shift).min(lines.coords().width());
763            if initial_space > 0 {
764                let mut default_style = painter.get_default().style;
765                default_style.attributes.set(Attribute::Reset);
766                print_style(lines, default_style);
767                start_line(lines, initial_space);
768            }
769            y += 1;
770
771            // Resetting space to prevent erroneous printing.
772            painter.reset_prev_style();
773            style_was_set = true;
774            last_len = initial_space;
775        }
776
777        let is_contained = x + len > x_shift && x < x_shift + lines.coords().width();
778
779        match part {
780            Part::Char(char) if is_contained => {
781                if let Some(str) = get_control_str(char) {
782                    painter.apply(CONTROL_CHAR_ID, 100);
783                    if style_was_set && let Some(style) = painter.relative_style() {
784                        print_style(lines, style);
785                    }
786                    lines.write_all(str.as_bytes()).unwrap();
787                    painter.remove(CONTROL_CHAR_ID)
788                } else {
789                    if style_was_set && let Some(style) = painter.relative_style() {
790                        print_style(lines, style);
791                    }
792                    match char {
793                        '\t' => {
794                            let truncated_start = x_shift.saturating_sub(x);
795                            let truncated_end =
796                                (x + len).saturating_sub(lines.coords().width() + x_shift);
797                            let tab_len = len - (truncated_start + truncated_end);
798                            lines.write_all(&SPACES[..tab_len as usize]).unwrap()
799                        }
800                        '\n' if len == 1 => lines.write_all(b" ").unwrap(),
801                        '\n' | '\r' => {}
802                        char => {
803                            let mut bytes = [0; 4];
804                            char.encode_utf8(&mut bytes);
805                            lines.write_all(&bytes[..char.len_utf8()]).unwrap();
806                        }
807                    }
808                }
809
810                if let Some(cursor) = cursor.take() {
811                    match cursor {
812                        Cursor::Main => painter.remove_main_caret(),
813                        Cursor::Extra => painter.remove_extra_caret(),
814                    }
815                    if let Some(style) = painter.relative_style() {
816                        print_style(lines, style)
817                    }
818                }
819                for id in spawns_for_next.drain(..) {
820                    observed_spawns.push((
821                        id,
822                        Coord::new(lines.coords().tl.x + x - x_shift, y - 1),
823                        len,
824                    ));
825                }
826
827                last_len = x + len - x_shift;
828                style_was_set = false;
829            }
830            Part::Char(_) => {
831                match cursor.take() {
832                    Some(Cursor::Main) => painter.remove_main_caret(),
833                    Some(Cursor::Extra) => painter.remove_extra_caret(),
834                    None => {}
835                }
836                spawns_for_next.clear();
837            }
838            Part::PushForm(id, prio) => {
839                painter.apply(id, prio);
840                style_was_set = true;
841            }
842            Part::PopForm(id) => {
843                painter.remove(id);
844                style_was_set = true;
845            }
846            Part::MainCaret => {
847                if let Some(shape) = painter.main_cursor()
848                    && is_active
849                {
850                    lines.show_real_cursor();
851                    queue!(lines, shape, cursor::SavePosition).unwrap();
852                } else {
853                    cursor = Some(Cursor::Main);
854                    lines.hide_real_cursor();
855                    painter.apply_main_cursor();
856                    style_was_set = true;
857                }
858            }
859            Part::ExtraCaret => {
860                cursor = Some(Cursor::Extra);
861                painter.apply_extra_cursor();
862                style_was_set = true;
863            }
864            Part::Spacer => {
865                let truncated_start = x_shift.saturating_sub(x).min(len);
866                let truncated_end = (x + len)
867                    .saturating_sub(lines.coords().width().saturating_sub(x_shift))
868                    .min(len);
869                let spacer_len = len - (truncated_start + truncated_end);
870                print_spacer(lines, spacer_len);
871                last_len = (x + len)
872                    .saturating_sub(x_shift)
873                    .min(lines.coords().width());
874            }
875            Part::ResetState => print_style(lines, painter.reset()),
876            Part::SpawnedWidget(id) => spawns_for_next.push(id),
877            Part::ToggleStart(_) | Part::ToggleEnd(_) => {
878                todo!("Toggles have not been implemented yet.")
879            }
880        }
881    }
882
883    print_end_style(&mut lines, &painter);
884    end_line(&mut lines, last_len, max_x);
885
886    for _ in 0..lines.coords().br.y - y {
887        print_end_style(&mut lines, &painter);
888        end_line(&mut lines, 0, max_x);
889    }
890
891    Some((lines, observed_spawns))
892}
893
894const SPACES: &[u8] = &[b' '; 3000];