vtcode_ghostty_core/
screen.rs1use crate::cell::Cell;
2use crate::cursor::Cursor;
3use crate::style::Style;
4
5#[derive(Clone, Copy, Debug, Eq, PartialEq)]
7pub enum ScreenKind {
8 Primary,
9 Alternate,
10}
11
12#[derive(Clone, Debug)]
14pub(crate) struct Screen {
15 pub(crate) grid: Vec<Cell>,
16 pub(crate) cursor: Cursor,
17 pub(crate) saved_cursor: Cursor,
18 pub(crate) pending_wrap: bool,
19 pub(crate) scrollback: Vec<Vec<Cell>>,
20}
21
22impl Screen {
23 pub(crate) fn new(cols: usize, rows: usize, style: Style) -> Self {
24 Self {
25 grid: vec![Cell::blank(style); cols * rows],
26 cursor: Cursor::default(),
27 saved_cursor: Cursor::default(),
28 pending_wrap: false,
29 scrollback: Vec::new(),
30 }
31 }
32
33 pub(crate) fn reset(&mut self, cols: usize, rows: usize, style: Style) {
34 self.grid = vec![Cell::blank(style); cols * rows];
35 self.cursor = Cursor::default();
36 self.saved_cursor = Cursor::default();
37 self.pending_wrap = false;
38 self.scrollback.clear();
39 }
40
41 pub(crate) fn resize(
42 &mut self,
43 old_cols: usize,
44 old_rows: usize,
45 new_cols: usize,
46 new_rows: usize,
47 style: Style,
48 ) {
49 let mut resized = vec![Cell::blank(style); new_cols * new_rows];
50 let copy_cols = old_cols.min(new_cols);
51 let copy_rows = old_rows.min(new_rows);
52
53 for row in 0..copy_rows {
54 let old_start = Self::index(old_cols, 0, row);
55 let new_start = Self::index(new_cols, 0, row);
56 resized[new_start..new_start + copy_cols]
57 .copy_from_slice(&self.grid[old_start..old_start + copy_cols]);
58 }
59
60 self.grid = resized;
61 if new_cols > 0 {
62 self.cursor.col = self.cursor.col.min(new_cols - 1);
63 self.saved_cursor.col = self.saved_cursor.col.min(new_cols - 1);
64 }
65 if new_rows > 0 {
66 self.cursor.row = self.cursor.row.min(new_rows - 1);
67 self.saved_cursor.row = self.saved_cursor.row.min(new_rows - 1);
68 }
69 self.pending_wrap = false;
70 }
71
72 pub(crate) fn index(cols: usize, col: usize, row: usize) -> usize {
74 row * cols + col
75 }
76}
77
78pub(crate) fn plain_text_for_screen(screen: &Screen, cols: usize, rows: usize) -> String {
80 let mut last_non_blank_row = None;
81 for row in (0..rows).rev() {
82 if row_end(screen, cols, row) > 0 {
83 last_non_blank_row = Some(row);
84 break;
85 }
86 }
87
88 let Some(last_row) = last_non_blank_row else {
89 return String::new();
90 };
91
92 let mut out = String::new();
93 for row in 0..=last_row {
94 if row > 0 {
95 out.push('\n');
96 }
97 let end = row_end(screen, cols, row);
98 let start = Screen::index(cols, 0, row);
99 push_trimmed_row(&mut out, &screen.grid[start..start + end]);
100 }
101 out
102}
103
104pub(crate) fn push_trimmed_row(out: &mut String, row: &[Cell]) {
106 let start = row
108 .iter()
109 .position(|cell| !cell.is_blank())
110 .unwrap_or(row.len());
111 let end = row
112 .iter()
113 .rposition(|cell| !cell.is_blank())
114 .map_or(0, |idx| idx + 1);
115
116 if start >= end {
117 return;
118 }
119
120 for cell in &row[start..end] {
121 if !cell.wide_continuation {
122 out.push(cell.ch);
123 }
124 }
125}
126
127fn row_end(screen: &Screen, cols: usize, row: usize) -> usize {
128 let start = Screen::index(cols, 0, row);
129 screen.grid[start..start + cols]
130 .iter()
131 .rposition(|cell| !cell.is_blank())
132 .map_or(0, |col| col + 1)
133}
134
135pub(crate) fn default_tab_stops(cols: usize) -> Vec<bool> {
137 let mut stops = vec![false; cols];
138 for col in (8..cols).step_by(8) {
139 stops[col] = true;
140 }
141 stops
142}