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