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