1use rmux_proto::TerminalSize;
4use std::collections::VecDeque;
5
6use crate::hyperlinks::Hyperlinks;
7use crate::input::{Colour, COLOUR_DEFAULT};
8use crate::style::Style;
9
10#[path = "grid/cell.rs"]
11mod cell;
12#[path = "grid/history_bytes.rs"]
13mod history_bytes;
14#[path = "grid/render.rs"]
15mod render;
16
17pub(crate) use cell::{GridCell, GridCellFlags, GridLine, GridLineFlags};
18use render::{append_cell_text, append_grid_string_code, append_hyperlink};
19
20const HISTORY_STAMP_REFRESH_LINES: u16 = 256;
21
22#[derive(Debug, Clone, PartialEq, Eq, Default)]
24#[cfg_attr(not(test), allow(dead_code))]
25pub(crate) struct GridCapture {
26 pub lines: Vec<String>,
28}
29
30#[derive(Debug, Clone, Copy, PartialEq, Eq)]
31pub(crate) struct GridLogicalCursor {
32 logical_start_y: usize,
33 offset: usize,
34}
35
36#[derive(Debug, Clone, Copy, PartialEq, Eq)]
37pub(crate) struct GridPhysicalCursor {
38 pub absolute_y: usize,
39 pub x: u32,
40 pub pending_wrap: bool,
41}
42
43#[derive(Debug, Clone, Copy, PartialEq, Eq)]
45pub struct GridRenderOptions {
46 pub join_wrapped: bool,
48 pub with_sequences: bool,
50 pub escape_sequences: bool,
52 pub include_empty_cells: bool,
54 pub use_tmux_cell_capacity: bool,
56 pub trim_spaces: bool,
58}
59
60impl Default for GridRenderOptions {
61 fn default() -> Self {
62 Self {
63 join_wrapped: false,
64 with_sequences: false,
65 escape_sequences: false,
66 include_empty_cells: true,
67 use_tmux_cell_capacity: false,
68 trim_spaces: true,
69 }
70 }
71}
72
73#[derive(Debug, Clone, PartialEq, Eq)]
75pub struct GridStringState {
76 last_cell: GridCell,
77}
78
79impl Default for GridStringState {
80 fn default() -> Self {
81 Self {
82 last_cell: GridCell::blank_with_bg(COLOUR_DEFAULT),
83 }
84 }
85}
86
87impl GridStringState {
88 pub(crate) fn reset_to_default_line_style(
89 &mut self,
90 options: GridRenderOptions,
91 hyperlinks: Option<&Hyperlinks>,
92 output: &mut Vec<u8>,
93 ) {
94 if !options.with_sequences {
95 return;
96 }
97
98 let default_cell = GridCell::blank_with_bg(COLOUR_DEFAULT);
99 let mut rendered = String::new();
100 let mut has_link = false;
101 append_grid_string_code(
102 &self.last_cell,
103 &default_cell,
104 &mut rendered,
105 options.escape_sequences,
106 hyperlinks,
107 &mut has_link,
108 );
109 if has_link {
110 append_hyperlink(&mut rendered, "", "", options.escape_sequences);
111 }
112 output.extend_from_slice(rendered.as_bytes());
113 self.last_cell = default_cell;
114 }
115}
116
117#[derive(Debug, Clone, PartialEq, Eq)]
119pub(crate) struct Grid {
120 sx: u32,
121 sy: u32,
122 hlimit: usize,
123 reflow_history_capacity: usize,
124 hscrolled: usize,
125 history_enabled: bool,
126 history_stamp: i64,
127 history_stamp_remaining: u16,
128 history: VecDeque<GridLine>,
129 visible: VecDeque<GridLine>,
130}
131
132impl Grid {
133 #[must_use]
135 pub fn new(size: TerminalSize, hlimit: usize) -> Self {
136 let sx = u32::from(size.cols.max(1));
137 let sy = u32::from(size.rows.max(1));
138 Self {
139 sx,
140 sy,
141 hlimit,
142 reflow_history_capacity: 0,
143 hscrolled: 0,
144 history_enabled: true,
145 history_stamp: 0,
146 history_stamp_remaining: 0,
147 history: VecDeque::new(),
148 visible: (0..sy).map(|_| GridLine::new(sx)).collect(),
149 }
150 }
151
152 #[must_use]
154 pub fn size(&self) -> TerminalSize {
155 TerminalSize {
156 cols: u16::try_from(self.sx).unwrap_or(u16::MAX),
157 rows: u16::try_from(self.sy).unwrap_or(u16::MAX),
158 }
159 }
160
161 #[must_use]
163 pub const fn sx(&self) -> u32 {
164 self.sx
165 }
166
167 #[must_use]
169 pub const fn sy(&self) -> u32 {
170 self.sy
171 }
172
173 #[must_use]
175 pub fn hsize(&self) -> usize {
176 self.history.len()
177 }
178
179 #[must_use]
181 pub const fn hlimit(&self) -> usize {
182 self.hlimit
183 }
184
185 #[must_use]
187 pub const fn history_enabled(&self) -> bool {
188 self.history_enabled
189 }
190
191 pub fn set_hlimit(&mut self, hlimit: usize) {
193 self.hlimit = hlimit;
194 self.reflow_history_capacity = 0;
195 while self.history.len() > self.hlimit {
196 let _ = self.history.pop_front();
197 }
198 self.hscrolled = self.hscrolled.min(self.history.len());
199 }
200
201 pub fn set_history_enabled(&mut self, enabled: bool) {
203 self.history_enabled = enabled;
204 }
205
206 #[allow(dead_code)]
208 #[must_use]
209 pub const fn hscrolled(&self) -> usize {
210 self.hscrolled
211 }
212
213 #[must_use]
215 pub fn visible_line(&self, y: u32) -> Option<&GridLine> {
216 self.visible.get(y as usize)
217 }
218
219 pub(crate) fn visible_line_mut(&mut self, y: u32) -> Option<&mut GridLine> {
220 self.visible.get_mut(y as usize)
221 }
222
223 #[allow(dead_code)]
226 #[must_use]
227 pub fn absolute_line(&self, absolute_y: usize) -> Option<&GridLine> {
228 if absolute_y < self.history.len() {
229 self.history.get(absolute_y)
230 } else {
231 self.visible.get(absolute_y - self.history.len())
232 }
233 }
234
235 pub fn remove_absolute_line(&mut self, absolute_y: usize) -> bool {
240 if absolute_y < self.history.len() {
241 let _ = self.history.remove(absolute_y);
242 self.reflow_history_capacity = self.reflow_history_capacity.saturating_sub(1);
243 self.hscrolled = self.hscrolled.min(self.history.len());
244 return true;
245 }
246
247 let visible_index = absolute_y.saturating_sub(self.history.len());
248 if visible_index >= self.visible.len() {
249 return false;
250 }
251
252 let _ = self.visible.remove(visible_index);
253 self.visible.push_back(GridLine::new(self.sx));
254 true
255 }
256
257 pub(crate) fn truncate_after_absolute_line(&mut self, absolute_y: usize) -> bool {
259 let total = self.history.len() + self.visible.len();
260 if absolute_y >= total {
261 return false;
262 }
263
264 let keep = absolute_y.saturating_add(1);
265 let mut lines = self
266 .history
267 .iter()
268 .chain(self.visible.iter())
269 .take(keep)
270 .cloned()
271 .collect::<Vec<_>>();
272 let visible_rows = self.sy as usize;
273 while lines.len() < visible_rows {
274 lines.push(GridLine::new(self.sx));
275 }
276
277 let visible_start = lines.len().saturating_sub(visible_rows);
278 let mut visible = lines.split_off(visible_start);
279 for line in &mut visible {
280 line.resize_width_preserving_wrap(self.sx, COLOUR_DEFAULT);
281 }
282 self.history = compacted_history(lines);
283 while self.history.len() > self.effective_history_capacity() {
284 let _ = self.history.pop_front();
285 }
286 self.visible = visible.into();
287 self.hscrolled = self.history.len();
288 true
289 }
290
291 #[must_use]
293 pub fn absolute_line_wrapped(&self, absolute_y: usize) -> Option<bool> {
294 self.absolute_line(absolute_y)
295 .map(|line| line.flags.contains(GridLineFlags::WRAPPED))
296 }
297
298 pub fn clear_history(&mut self) {
300 self.history.clear();
301 self.reflow_history_capacity = 0;
302 self.hscrolled = 0;
303 }
304
305 pub fn clear_visible(&mut self, bg: Colour) {
307 for line in &mut self.visible {
308 line.clear(bg);
309 }
310 }
311
312 pub fn clear_visible_to_history(&mut self, bg: Colour) {
314 if self.history_enabled {
315 let last_used = self.visible.iter().rposition(|line| line.used_end() > 0);
316 if let Some(last_used) = last_used {
317 for index in 0..=last_used {
318 let line = self.visible[index].clone();
319 self.push_history(line);
320 }
321 }
322 }
323 self.clear_visible(bg);
324 }
325
326 pub fn replace_visible(&mut self, lines: Vec<GridLine>) {
328 self.sy = lines.len() as u32;
329 self.visible = lines.into();
330 for line in &mut self.visible {
331 line.resize_width_preserving_wrap(self.sx, COLOUR_DEFAULT);
332 }
333 }
334
335 pub(crate) fn resize_visible_width_preserving_cursor(
336 &mut self,
337 sx: u32,
338 bg: Colour,
339 visible_y: u32,
340 cursor_x: u32,
341 pending_wrap: bool,
342 ) -> GridPhysicalCursor {
343 let source_width = self.sx.max(1);
344 let target_width = sx.max(1);
345 let visible = reflow_alternate_visible_lines(
346 self.visible_lines(),
347 target_width,
348 bg,
349 self.sy as usize,
350 );
351 let current_history = self.history.len();
352 self.sx = target_width;
353 self.visible = visible.into();
354
355 let physical_x = if pending_wrap { source_width } else { cursor_x };
360 let (x, pending_wrap) = if physical_x >= target_width {
361 (target_width.saturating_sub(1), true)
362 } else {
363 (physical_x, false)
364 };
365 GridPhysicalCursor {
366 absolute_y: current_history.saturating_add(
367 usize::try_from(visible_y.min(self.sy.saturating_sub(1))).unwrap_or(usize::MAX),
368 ),
369 x,
370 pending_wrap,
371 }
372 }
373
374 pub(crate) fn restore_visible_at_size(
375 &mut self,
376 source_size: TerminalSize,
377 lines: Vec<GridLine>,
378 bg: Colour,
379 ) {
380 self.sx = u32::from(source_size.cols.max(1));
381 self.sy = u32::from(source_size.rows.max(1));
382 self.visible = lines.into();
383 while self.visible.len() > self.sy as usize {
384 let _ = self.visible.pop_back();
385 }
386 while self.visible.len() < self.sy as usize {
387 self.visible.push_back(GridLine::blank_with_bg(self.sx, bg));
388 }
389 for line in &mut self.visible {
390 line.resize_width_preserving_wrap(self.sx, bg);
391 }
392 }
393
394 #[cfg_attr(not(test), allow(dead_code))]
396 #[must_use]
397 pub fn capture(&self, join_wrapped: bool) -> GridCapture {
398 let mut lines = Vec::new();
399 let mut pending = String::new();
400
401 for line in self.history.iter().chain(self.visible.iter()) {
402 let rendered = line.render_text();
403 if join_wrapped {
404 pending.push_str(&rendered);
405 if !line.flags.contains(GridLineFlags::WRAPPED) {
406 lines.push(std::mem::take(&mut pending));
407 }
408 continue;
409 }
410
411 lines.push(rendered);
412 }
413
414 if join_wrapped && !pending.is_empty() {
415 lines.push(pending);
416 }
417
418 GridCapture { lines }
419 }
420
421 #[must_use]
423 pub fn render_absolute_line(
424 &self,
425 absolute_y: usize,
426 options: GridRenderOptions,
427 state: &mut GridStringState,
428 hyperlinks: Option<&Hyperlinks>,
429 ) -> Option<String> {
430 self.absolute_line(absolute_y)
431 .map(|line| line.render_with_options(self.sx as usize, options, state, hyperlinks))
432 }
433
434 pub fn append_rendered_absolute_line(
435 &self,
436 absolute_y: usize,
437 options: GridRenderOptions,
438 state: &mut GridStringState,
439 hyperlinks: Option<&Hyperlinks>,
440 output: &mut Vec<u8>,
441 ) -> Option<()> {
442 let line = self.absolute_line(absolute_y)?;
443 if line.render_bytes_with_options(self.sx as usize, options, output) {
444 return Some(());
445 }
446 let rendered = line.render_with_options(self.sx as usize, options, state, hyperlinks);
447 output.extend_from_slice(rendered.as_bytes());
448 Some(())
449 }
450
451 #[must_use]
455 pub fn render_visible_line_with_default_style(
456 &self,
457 row: usize,
458 options: GridRenderOptions,
459 state: &mut GridStringState,
460 hyperlinks: Option<&Hyperlinks>,
461 style: &Style,
462 ) -> Option<String> {
463 self.visible_line(u32::try_from(row).ok()?).map(|line| {
464 line.render_with_default_style(self.sx as usize, options, state, hyperlinks, style)
465 })
466 }
467
468 #[must_use]
470 pub fn history_byte_size(&self) -> usize {
471 self.history
472 .iter()
473 .map(|line| line.render_text().len() + 1)
474 .sum()
475 }
476
477 #[must_use]
479 pub fn visible_lines(&self) -> Vec<GridLine> {
480 self.visible.iter().cloned().collect()
481 }
482
483 pub(crate) fn scroll_region_up(
484 &mut self,
485 upper: u32,
486 lower: u32,
487 bg: Colour,
488 to_history: bool,
489 ) {
490 if !self.valid_region(upper, lower) {
491 return;
492 }
493
494 let upper = upper as usize;
495 let lower = lower as usize;
496 if upper == 0 && lower + 1 == self.visible.len() {
497 let Some(mut removed) = self.visible.pop_front() else {
498 return;
499 };
500 if to_history && self.history_enabled {
501 self.push_history(removed);
502 self.visible.push_back(GridLine::blank_with_bg(self.sx, bg));
503 } else {
504 removed.clear(bg);
505 self.visible.push_back(removed);
506 }
507 return;
508 }
509
510 let removed_for_history = if to_history && self.history_enabled {
511 let blank = GridLine::blank_with_bg(self.sx, bg);
512 let visible = self.visible.make_contiguous();
513 let removed = std::mem::replace(&mut visible[upper], blank);
514 Some(removed)
515 } else {
516 None
517 };
518 if let Some(removed) = removed_for_history {
519 self.push_history(removed);
520 }
521 let visible = self.visible.make_contiguous();
522 visible[upper..=lower].rotate_left(1);
523 let removed = &mut visible[lower];
524 removed.clear(bg);
525 }
526
527 pub(crate) fn scroll_region_down(&mut self, upper: u32, lower: u32, bg: Colour) {
528 if !self.valid_region(upper, lower) {
529 return;
530 }
531
532 let upper = upper as usize;
533 let lower = lower as usize;
534 if upper == 0 && lower + 1 == self.visible.len() {
535 let Some(mut removed) = self.visible.pop_back() else {
536 return;
537 };
538 removed.clear(bg);
539 self.visible.push_front(removed);
540 return;
541 }
542
543 let visible = self.visible.make_contiguous();
544 visible[upper..=lower].rotate_right(1);
545 visible[upper].clear(bg);
546 }
547
548 pub(crate) fn logical_cursor(
549 &self,
550 visible_y: u32,
551 cursor_x: u32,
552 pending_wrap: bool,
553 ) -> GridLogicalCursor {
554 let total_lines = self.total_line_count();
555 if total_lines == 0 {
556 return GridLogicalCursor {
557 logical_start_y: 0,
558 offset: 0,
559 };
560 }
561
562 let absolute_y = self
563 .history
564 .len()
565 .saturating_add(visible_y as usize)
566 .min(total_lines.saturating_sub(1));
567 let logical_start_y = self.logical_start_y(absolute_y);
568 let mut offset = 0_usize;
569 for line_y in logical_start_y..absolute_y {
570 if let Some(line) = self.absolute_line(line_y) {
571 offset = offset.saturating_add(line.reflow_logical_width());
572 }
573 }
574 let physical_cursor_column = if pending_wrap {
575 self.sx as usize
576 } else {
577 cursor_x.min(self.sx.saturating_sub(1)) as usize
578 };
579 let cursor_column = self
580 .absolute_line(absolute_y)
581 .map_or(physical_cursor_column, |line| {
582 line.reflow_logical_column(physical_cursor_column)
583 });
584 offset = offset.saturating_add(cursor_column);
585
586 GridLogicalCursor {
587 logical_start_y,
588 offset,
589 }
590 }
591
592 pub(crate) fn resize_width_remapping_cursor(
593 &mut self,
594 sx: u32,
595 bg: Colour,
596 cursor: GridLogicalCursor,
597 ) -> GridPhysicalCursor {
598 let sx = sx.max(1);
599 if sx == self.sx {
600 return self.locate_cursor_from_logical(cursor);
601 }
602
603 if self.can_resize_width_without_reflow(sx) {
604 for line in &mut self.history {
605 line.resize_width_preserving_wrap(sx, bg);
606 }
607 for line in &mut self.visible {
608 line.resize_width_preserving_wrap(sx, bg);
609 }
610 self.sx = sx;
611 return self.locate_cursor_from_logical(cursor);
612 }
613
614 let visible_rows = self.sy as usize;
615 let lines = self
616 .history
617 .iter()
618 .chain(self.visible.iter())
619 .cloned()
620 .collect::<Vec<_>>();
621 let (mut reflowed, reflow_cursor) =
622 reflow_wrapped_lines_remapping_cursor(lines, sx, bg, cursor);
623 while reflowed.len() < visible_rows {
624 reflowed.push(GridLine::blank_with_bg(sx, bg));
625 }
626
627 let default_history_rows = reflowed.len().saturating_sub(visible_rows);
628 let mut mapped_cursor = reflow_cursor.unwrap_or(GridPhysicalCursor {
629 absolute_y: reflowed.len().saturating_sub(1),
630 x: sx.saturating_sub(1),
631 pending_wrap: false,
632 });
633 let trailing_empty_rows = reflowed
634 .iter()
635 .rev()
636 .take_while(|line| line.used_end() == 0 && !line.flags.contains(GridLineFlags::WRAPPED))
637 .count();
638 let cursor_shift = default_history_rows.saturating_sub(mapped_cursor.absolute_y);
639 let history_rows =
640 default_history_rows.saturating_sub(cursor_shift.min(trailing_empty_rows));
641 if mapped_cursor.absolute_y < history_rows {
642 mapped_cursor.absolute_y = history_rows;
643 mapped_cursor.x = 0;
644 mapped_cursor.pending_wrap = false;
645 }
646 let mut remaining = reflowed.split_off(history_rows);
647 remaining.truncate(visible_rows);
648 while remaining.len() < visible_rows {
649 remaining.push(GridLine::blank_with_bg(sx, bg));
650 }
651 let mut visible = remaining;
652 for line in &mut visible {
653 line.resize_width_preserving_wrap(sx, bg);
654 }
655 self.history = compacted_history(reflowed);
656 self.reflow_history_capacity = if self.history.len() > self.hlimit {
657 self.history.len()
658 } else {
659 0
660 };
661 self.visible = visible.into();
662 self.hscrolled = if self.hlimit == 0 {
663 0
664 } else {
665 self.history.len()
666 };
667 self.sx = sx;
668 mapped_cursor
669 }
670
671 fn can_resize_width_without_reflow(&self, sx: u32) -> bool {
672 self.history.iter().chain(self.visible.iter()).all(|line| {
673 !line.flags.contains(GridLineFlags::WRAPPED) && line.used_end() <= sx as usize
674 })
675 }
676
677 fn effective_history_capacity(&self) -> usize {
678 self.hlimit.max(self.reflow_history_capacity)
679 }
680
681 pub(crate) fn resize_height(&mut self, sy: u32, cursor_y: &mut u32, bg: Colour) {
682 let sy = sy.max(1);
683 let oldy = self.sy;
684
685 if sy < oldy {
686 let mut needed = oldy - sy;
687
688 let available_bottom = oldy.saturating_sub(1).saturating_sub(*cursor_y);
689 let remove_bottom = available_bottom.min(needed);
690 for _ in 0..remove_bottom {
691 let _ = self.visible.pop_back();
692 }
693 needed -= remove_bottom;
694
695 if self.history_enabled {
696 for _ in 0..needed {
697 let Some(line) = self.visible.pop_front() else {
698 break;
699 };
700 self.push_history_preserving_reflow_capacity(line);
701 }
702 } else {
703 let remove_top = (*cursor_y).min(needed);
704 for _ in 0..remove_top {
705 let _ = self.visible.pop_front();
706 }
707 *cursor_y = cursor_y.saturating_sub(remove_top);
708 }
709 } else if sy > oldy {
710 let mut needed = sy - oldy;
711 let pull = self.hscrolled.min(needed as usize).min(self.history.len()) as u32;
712 if self.history_enabled && pull > 0 {
713 let mut restored = Vec::with_capacity(pull as usize);
714 for _ in 0..pull {
715 if let Some(line) = self.history.pop_back() {
716 restored.push(line);
717 }
718 }
719 restored.reverse();
720 for mut line in restored.into_iter().rev() {
721 line.resize_width_preserving_wrap(self.sx, bg);
722 self.visible.push_front(line);
723 }
724 *cursor_y = cursor_y.saturating_add(pull).min(sy.saturating_sub(1));
725 self.hscrolled -= pull as usize;
726 needed -= pull;
727 }
728
729 for _ in 0..needed {
730 self.visible.push_back(GridLine::blank_with_bg(self.sx, bg));
731 }
732 }
733
734 self.sy = sy;
735 while self.visible.len() > self.sy as usize {
736 let _ = self.visible.pop_back();
737 }
738 while self.visible.len() < self.sy as usize {
739 self.visible.push_back(GridLine::blank_with_bg(self.sx, bg));
740 }
741 for line in &mut self.visible {
742 line.resize_width_preserving_wrap(self.sx, bg);
743 }
744 *cursor_y = (*cursor_y).min(self.sy.saturating_sub(1));
745 }
746
747 fn valid_region(&self, upper: u32, lower: u32) -> bool {
748 upper < self.sy && lower < self.sy && upper <= lower
749 }
750
751 fn total_line_count(&self) -> usize {
752 self.history.len().saturating_add(self.visible.len())
753 }
754
755 fn logical_start_y(&self, absolute_y: usize) -> usize {
756 let mut start = absolute_y.min(self.total_line_count().saturating_sub(1));
757 while start > 0
758 && self
759 .absolute_line(start - 1)
760 .is_some_and(|line| line.flags.contains(GridLineFlags::WRAPPED))
761 {
762 start -= 1;
763 }
764 start
765 }
766
767 fn locate_cursor_from_logical(&self, cursor: GridLogicalCursor) -> GridPhysicalCursor {
768 let total_lines = self.total_line_count();
769 if total_lines == 0 {
770 return GridPhysicalCursor {
771 absolute_y: 0,
772 x: 0,
773 pending_wrap: false,
774 };
775 }
776
777 let start = cursor.logical_start_y.min(total_lines.saturating_sub(1));
778 let mut lines = Vec::new();
779 for absolute_y in start..total_lines {
780 let Some(line) = self.absolute_line(absolute_y) else {
781 break;
782 };
783 lines.push(line.clone());
784 if !line.flags.contains(GridLineFlags::WRAPPED) {
785 break;
786 }
787 }
788
789 if lines.len() == 1 {
790 let used_end = lines[0].used_end();
791 let at_new_edge =
792 used_end > 0 && cursor.offset == used_end && used_end == self.sx as usize;
793 return GridPhysicalCursor {
794 absolute_y: start,
795 x: if at_new_edge {
796 self.sx.saturating_sub(1)
797 } else {
798 u32::try_from(cursor.offset)
799 .unwrap_or(u32::MAX)
800 .min(self.sx.saturating_sub(1))
801 },
802 pending_wrap: at_new_edge,
803 };
804 }
805
806 let (_, relative) = reflow_wrapped_lines_remapping_cursor(
807 lines,
808 self.sx,
809 COLOUR_DEFAULT,
810 GridLogicalCursor {
811 logical_start_y: 0,
812 offset: cursor.offset,
813 },
814 );
815 let relative = relative.unwrap_or(GridPhysicalCursor {
816 absolute_y: 0,
817 x: 0,
818 pending_wrap: false,
819 });
820 GridPhysicalCursor {
821 absolute_y: start.saturating_add(relative.absolute_y),
822 x: relative.x,
823 pending_wrap: relative.pending_wrap,
824 }
825 }
826
827 fn push_history(&mut self, line: GridLine) {
828 if self.hlimit == 0 {
829 return;
830 }
831 if self.reflow_history_capacity > 0 {
835 self.reflow_history_capacity = if self.history.len() > self.hlimit {
836 self.history.len()
837 } else {
838 0
839 };
840 }
841 self.push_history_with_effective_capacity(line);
842 }
843
844 fn push_history_preserving_reflow_capacity(&mut self, line: GridLine) {
845 if self.hlimit == 0 {
846 return;
847 }
848 self.push_history_with_effective_capacity(line);
851 }
852
853 fn push_history_with_effective_capacity(&mut self, mut line: GridLine) {
854 let history_capacity = self.effective_history_capacity();
855 if history_capacity == 0 {
856 return;
857 }
858
859 line.stamp_for_history_at(self.next_history_stamp());
860 line.compact_for_history();
861 if self.history.len() >= history_capacity {
862 let _ = self.history.pop_front();
863 }
864 self.history.push_back(line);
865 self.hscrolled = (self.hscrolled + 1).min(self.history.len());
866 }
867
868 fn next_history_stamp(&mut self) -> i64 {
869 if self.history_stamp_remaining == 0 {
870 self.history_stamp = cell::current_unix_timestamp();
871 self.history_stamp_remaining = HISTORY_STAMP_REFRESH_LINES;
872 }
873 self.history_stamp_remaining = self.history_stamp_remaining.saturating_sub(1);
874 self.history_stamp
875 }
876}
877
878fn compacted_history(lines: Vec<GridLine>) -> VecDeque<GridLine> {
879 lines
880 .into_iter()
881 .map(|mut line| {
882 line.compact_for_history();
883 line
884 })
885 .collect()
886}
887
888struct AlternateReflowGroup {
889 source_rows: usize,
890 rows: Vec<GridLine>,
891}
892
893fn reflow_alternate_visible_lines(
894 lines: Vec<GridLine>,
895 width: u32,
896 bg: Colour,
897 visible_rows: usize,
898) -> Vec<GridLine> {
899 let mut source_groups = Vec::new();
900 let mut current_group = Vec::new();
901 for line in lines {
902 let wrapped = line.flags.contains(GridLineFlags::WRAPPED);
903 current_group.push(line);
904 if !wrapped {
905 source_groups.push(std::mem::take(&mut current_group));
906 }
907 }
908 if !current_group.is_empty() {
909 source_groups.push(current_group);
910 }
911
912 let trailing_blank_groups = source_groups
913 .iter()
914 .rev()
915 .take_while(|group| {
916 group
917 .iter()
918 .all(|line| line.used_end() == 0 && line.flags == GridLineFlags::default())
919 })
920 .count();
921 source_groups.truncate(source_groups.len().saturating_sub(trailing_blank_groups));
922
923 let mut groups = source_groups
924 .into_iter()
925 .map(|group| {
926 let source_rows = group.len();
927 let (rows, _) = reflow_wrapped_lines_remapping_cursor(
928 group,
929 width,
930 bg,
931 GridLogicalCursor {
932 logical_start_y: usize::MAX,
933 offset: 0,
934 },
935 );
936 AlternateReflowGroup { source_rows, rows }
937 })
938 .collect::<Vec<_>>();
939
940 let mut allocations = groups
941 .iter()
942 .map(|group| group.source_rows.min(group.rows.len()))
943 .collect::<Vec<_>>();
944 let allocated = allocations.iter().copied().sum::<usize>();
945 let mut remaining = visible_rows.saturating_sub(allocated);
946 for (group, allocation) in groups.iter().zip(&mut allocations) {
947 let extra = group.rows.len().saturating_sub(*allocation).min(remaining);
948 *allocation = allocation.saturating_add(extra);
949 remaining -= extra;
950 }
951
952 let mut visible = Vec::with_capacity(visible_rows);
953 for (mut group, allocation) in groups.drain(..).zip(allocations) {
954 let truncated = allocation < group.rows.len();
955 group.rows.truncate(allocation);
956 if truncated {
957 if let Some(last) = group.rows.last_mut() {
958 last.set_wrapped(false);
959 }
960 }
961 visible.extend(group.rows);
962 }
963 visible.truncate(visible_rows);
964 while visible.len() < visible_rows {
965 visible.push(GridLine::blank_with_bg(width, bg));
966 }
967 visible
968}
969
970fn reflow_wrapped_lines_remapping_cursor(
971 lines: Vec<GridLine>,
972 width: u32,
973 bg: Colour,
974 cursor: GridLogicalCursor,
975) -> (Vec<GridLine>, Option<GridPhysicalCursor>) {
976 let mut output = Vec::new();
977 let mut logical_cells = Vec::new();
978 let mut logical_plain_text: Option<String> = None;
979 let mut logical_flags = None;
980 let mut logical_start_y = 0_usize;
981 let mut mapped_cursor = None;
982
983 for (absolute_y, line) in lines.into_iter().enumerate() {
984 let wrapped = line.flags.contains(GridLineFlags::WRAPPED);
985 if logical_flags.is_none() {
986 logical_start_y = absolute_y;
987 let mut flags = line.flags;
988 flags.remove(GridLineFlags::WRAPPED);
989 logical_flags = Some(flags);
990 logical_plain_text = (bg == COLOUR_DEFAULT).then(String::new);
991 }
992
993 let end = if wrapped {
994 self::line_width(&line)
995 } else {
996 line.used_end()
997 };
998 if let (Some(logical_text), Some(text)) = (logical_plain_text.as_mut(), line.plain_text()) {
999 logical_text.extend(
1000 text.bytes()
1001 .chain(std::iter::repeat(b' '))
1002 .take(end)
1003 .map(char::from),
1004 );
1005 } else {
1006 if let Some(text) = logical_plain_text.take() {
1007 extend_plain_ascii_cells(&mut logical_cells, text.bytes());
1008 }
1009 if let Some(text) = line.plain_text() {
1010 extend_plain_ascii_cells(
1011 &mut logical_cells,
1012 text.bytes().chain(std::iter::repeat(b' ')).take(end),
1013 );
1014 } else {
1015 logical_cells.extend(
1016 line.cells
1017 .iter()
1018 .take(end)
1019 .filter(|cell| !cell.is_padding() && !cell.is_reflow_gap())
1020 .cloned(),
1021 );
1022 }
1023 }
1024
1025 if !wrapped {
1026 let flags = logical_flags.take().unwrap_or_default();
1027 let cursor_offset =
1028 (logical_start_y == cursor.logical_start_y).then_some(cursor.offset);
1029 let (reflowed, relative_cursor) = if let Some(text) = logical_plain_text.take() {
1030 reflow_plain_ascii_line_remapping_cursor(&text, flags, width, bg, cursor_offset)
1031 } else {
1032 reflow_logical_line_remapping_cursor(
1033 &logical_cells,
1034 flags,
1035 width,
1036 bg,
1037 cursor_offset,
1038 )
1039 };
1040 if let Some(mut physical) = relative_cursor {
1041 physical.absolute_y = physical.absolute_y.saturating_add(output.len());
1042 mapped_cursor = Some(physical);
1043 }
1044 output.extend(reflowed);
1045 logical_cells.clear();
1046 }
1047 }
1048
1049 if logical_flags.is_some() || !logical_cells.is_empty() || logical_plain_text.is_some() {
1050 let flags = logical_flags.unwrap_or_default();
1051 let cursor_offset = (logical_start_y == cursor.logical_start_y).then_some(cursor.offset);
1052 let (reflowed, relative_cursor) = if let Some(text) = logical_plain_text {
1053 reflow_plain_ascii_line_remapping_cursor(&text, flags, width, bg, cursor_offset)
1054 } else {
1055 reflow_logical_line_remapping_cursor(&logical_cells, flags, width, bg, cursor_offset)
1056 };
1057 if let Some(mut physical) = relative_cursor {
1058 physical.absolute_y = physical.absolute_y.saturating_add(output.len());
1059 mapped_cursor = Some(physical);
1060 }
1061 output.extend(reflowed);
1062 }
1063
1064 (output, mapped_cursor)
1065}
1066
1067fn extend_plain_ascii_cells(cells: &mut Vec<GridCell>, bytes: impl IntoIterator<Item = u8>) {
1068 cells.extend(bytes.into_iter().map(GridCell::from_plain_ascii));
1069}
1070
1071fn reflow_plain_ascii_line_remapping_cursor(
1072 text: &str,
1073 first_flags: GridLineFlags,
1074 width: u32,
1075 bg: Colour,
1076 cursor_offset: Option<usize>,
1077) -> (Vec<GridLine>, Option<GridPhysicalCursor>) {
1078 let width = width.max(1);
1079 if text.is_empty() || bg != COLOUR_DEFAULT {
1080 let mut line = GridLine::blank_with_bg(width, bg);
1081 line.flags = first_flags;
1082 let cursor = cursor_offset.map(|offset| GridPhysicalCursor {
1083 absolute_y: 0,
1084 x: u32::try_from(offset)
1085 .unwrap_or(u32::MAX)
1086 .min(width.saturating_sub(1)),
1087 pending_wrap: false,
1088 });
1089 return (vec![line], cursor);
1090 }
1091
1092 let width_usize = width as usize;
1093 let mut output = Vec::with_capacity(text.len().div_ceil(width_usize));
1094 let mut start = 0;
1095 let mut flags = first_flags;
1096 while start < text.len() {
1097 let end = (start + width_usize).min(text.len());
1098 let mut line = GridLine::from_plain_ascii_text(width, flags, text[start..end].to_owned());
1099 if end < text.len() {
1100 line.set_wrapped(true);
1101 }
1102 output.push(line);
1103 flags = GridLineFlags::default();
1104 start = end;
1105 }
1106
1107 let cursor = cursor_offset.map(|offset| {
1108 let content_len = text.len();
1109 if offset == content_len && content_len > 0 && content_len.is_multiple_of(width_usize) {
1110 return GridPhysicalCursor {
1111 absolute_y: content_len.div_ceil(width_usize).saturating_sub(1),
1112 x: width.saturating_sub(1),
1113 pending_wrap: true,
1114 };
1115 }
1116 if offset <= content_len {
1117 return GridPhysicalCursor {
1118 absolute_y: offset / width_usize,
1119 x: u32::try_from(offset % width_usize).unwrap_or(u32::MAX),
1120 pending_wrap: false,
1121 };
1122 }
1123 GridPhysicalCursor {
1124 absolute_y: output.len().saturating_sub(1),
1125 x: u32::try_from(offset)
1126 .unwrap_or(u32::MAX)
1127 .min(width.saturating_sub(1)),
1128 pending_wrap: false,
1129 }
1130 });
1131 (output, cursor)
1132}
1133
1134fn reflow_logical_line_remapping_cursor(
1135 cells: &[GridCell],
1136 first_flags: GridLineFlags,
1137 width: u32,
1138 bg: Colour,
1139 cursor_offset: Option<usize>,
1140) -> (Vec<GridLine>, Option<GridPhysicalCursor>) {
1141 let width = width.max(1);
1142 if cells.is_empty() {
1143 let mut line = GridLine::blank_with_bg(width, bg);
1144 line.flags = first_flags;
1145 let cursor = cursor_offset.map(|offset| GridPhysicalCursor {
1146 absolute_y: 0,
1147 x: u32::try_from(offset)
1148 .unwrap_or(u32::MAX)
1149 .min(width.saturating_sub(1)),
1150 pending_wrap: false,
1151 });
1152 return (vec![line], cursor);
1153 }
1154
1155 let mut output = Vec::new();
1156 let mut current = GridLine::blank_with_bg(width, bg);
1157 current.flags = first_flags;
1158 let mut x: u32 = 0;
1159 let mut logical_offset = 0_usize;
1160 let mut mapped_cursor = None;
1161
1162 for cell in cells {
1163 let mut cell = cell.clone();
1164 let source_cell_width = u32::from(cell.width().max(1));
1165 let mut cell_width = source_cell_width;
1166 if cell_width > width {
1167 cell_width = 1;
1168 cell.set_width(1);
1169 }
1170 if x > 0 && x.saturating_add(cell_width) > width {
1171 current.mark_reflow_gap(x);
1172 current.set_wrapped(true);
1173 output.push(current);
1174 current = GridLine::blank_with_bg(width, bg);
1175 x = 0;
1176 }
1177
1178 if mapped_cursor.is_none() {
1179 if let Some(cursor_offset) = cursor_offset {
1180 let cell_end = logical_offset.saturating_add(source_cell_width as usize);
1181 if cursor_offset >= logical_offset && cursor_offset < cell_end {
1182 let relative = cursor_offset.saturating_sub(logical_offset);
1183 let physical_offset =
1184 u32::try_from(relative).unwrap_or(u32::MAX).min(cell_width);
1185 if physical_offset == cell_width && x.saturating_add(cell_width) == width {
1186 mapped_cursor = Some(GridPhysicalCursor {
1187 absolute_y: output.len(),
1188 x: width.saturating_sub(1),
1189 pending_wrap: true,
1190 });
1191 } else {
1192 mapped_cursor = Some(GridPhysicalCursor {
1193 absolute_y: output.len(),
1194 x: x.saturating_add(physical_offset)
1195 .min(width.saturating_sub(1)),
1196 pending_wrap: false,
1197 });
1198 }
1199 }
1200 }
1201 }
1202
1203 if let Some(target) = current.cell_mut(x) {
1204 *target = cell.clone();
1205 }
1206 for offset in 1..cell_width {
1207 if let Some(padding_cell) = current.cell_mut(x + offset) {
1208 let mut padding = cell.clone();
1209 padding.set_text(" ".to_owned());
1210 padding.set_width(0);
1211 padding.set_flags(GridCellFlags::PADDING);
1212 *padding_cell = padding;
1213 }
1214 }
1215 current.touch();
1216 x += cell_width;
1217 logical_offset = logical_offset.saturating_add(source_cell_width as usize);
1218 }
1219
1220 if mapped_cursor.is_none() {
1221 if let Some(cursor_offset) = cursor_offset {
1222 if cursor_offset == logical_offset {
1223 mapped_cursor = Some(if x == width {
1224 GridPhysicalCursor {
1225 absolute_y: output.len(),
1226 x: width.saturating_sub(1),
1227 pending_wrap: true,
1228 }
1229 } else {
1230 GridPhysicalCursor {
1231 absolute_y: output.len(),
1232 x,
1233 pending_wrap: false,
1234 }
1235 });
1236 } else if cursor_offset > logical_offset {
1237 mapped_cursor = Some(GridPhysicalCursor {
1238 absolute_y: output.len(),
1239 x: u32::try_from(cursor_offset)
1240 .unwrap_or(u32::MAX)
1241 .min(width.saturating_sub(1)),
1242 pending_wrap: false,
1243 });
1244 }
1245 }
1246 }
1247 output.push(current);
1248 (output, mapped_cursor)
1249}
1250
1251fn line_width(line: &GridLine) -> usize {
1252 line.width() as usize
1253}
1254
1255#[cfg(test)]
1256#[path = "grid/tests.rs"]
1257mod tests;