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