ratatui_toolkit/vt100_term/
screen.rs1use super::cell::{Attrs, Cell};
7use super::grid::{Grid, Size};
8use termwiz::escape::Action;
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq)]
12struct Cursor {
13 row: usize,
14 col: usize,
15}
16
17impl Cursor {
18 fn new() -> Self {
19 Self { row: 0, col: 0 }
20 }
21}
22
23#[derive(Debug, Clone)]
25pub struct Screen {
26 grid: Grid,
28
29 cursor: Cursor,
31
32 attrs: Attrs,
34
35 saved_cursor: Option<Cursor>,
37
38 #[allow(dead_code)]
40 cursor_visible: bool,
41}
42
43impl Screen {
44 pub fn new(rows: usize, cols: usize, scrollback_len: usize) -> Self {
46 Self {
47 grid: Grid::new(rows, cols, scrollback_len),
48 cursor: Cursor::new(),
49 attrs: Attrs::default(),
50 saved_cursor: None,
51 cursor_visible: true,
52 }
53 }
54
55 pub fn size(&self) -> Size {
57 self.grid.size()
58 }
59
60 pub fn scrollback(&self) -> usize {
62 self.grid.scrollback()
63 }
64
65 pub fn scrollback_len(&self) -> usize {
67 self.grid.scrollback_len()
68 }
69
70 pub fn set_scrollback(&mut self, offset: usize) {
72 self.grid.set_scrollback(offset);
73 }
74
75 pub fn scroll_screen_up(&mut self, n: usize) {
77 self.grid.scroll_screen_up(n);
78 }
79
80 pub fn scroll_screen_down(&mut self, n: usize) {
82 self.grid.scroll_screen_down(n);
83 }
84
85 pub fn cell(&self, row: usize, col: usize) -> Option<&Cell> {
87 self.grid.cell(row, col)
88 }
89
90 pub fn resize(&mut self, rows: usize, cols: usize) {
92 self.grid.resize(rows, cols);
93 self.cursor.row = self.cursor.row.min(rows.saturating_sub(1));
95 self.cursor.col = self.cursor.col.min(cols.saturating_sub(1));
96 }
97
98 pub fn get_selected_text(&self, low_x: i32, low_y: i32, high_x: i32, high_y: i32) -> String {
100 self.grid.get_selected_text(low_x, low_y, high_x, high_y)
101 }
102
103 pub fn handle_action(&mut self, action: &Action) {
105 match action {
106 Action::Print(c) => self.print_char(*c),
107 Action::PrintString(s) => {
108 for c in s.chars() {
109 self.print_char(c);
110 }
111 }
112 Action::Control(code) => {
113 let byte = match code {
115 termwiz::escape::ControlCode::Null => 0x00,
116 termwiz::escape::ControlCode::Bell => 0x07,
117 termwiz::escape::ControlCode::Backspace => 0x08,
118 termwiz::escape::ControlCode::HorizontalTab => 0x09,
119 termwiz::escape::ControlCode::LineFeed => 0x0A,
120 termwiz::escape::ControlCode::VerticalTab => 0x0B,
121 termwiz::escape::ControlCode::FormFeed => 0x0C,
122 termwiz::escape::ControlCode::CarriageReturn => 0x0D,
123 _ => return, };
125 self.handle_control(byte);
126 }
127 Action::CSI(csi) => self.handle_csi(csi),
128 Action::Esc(esc) => self.handle_esc(esc),
129 Action::OperatingSystemCommand(osc) => self.handle_osc(osc),
130 _ => {
131 }
133 }
134 }
135
136 fn print_char(&mut self, c: char) {
138 let size = self.grid.size();
139
140 if c == '\n' {
142 self.cursor.col = 0;
143 self.cursor.row += 1;
144 if self.cursor.row >= size.rows {
145 self.grid.scroll_up(1);
146 self.cursor.row = size.rows - 1;
147 }
148 return;
149 }
150
151 if c == '\r' {
153 self.cursor.col = 0;
154 return;
155 }
156
157 if c == '\t' {
159 self.cursor.col = ((self.cursor.col + 8) / 8) * 8;
160 if self.cursor.col >= size.cols {
161 self.cursor.col = 0;
162 self.cursor.row += 1;
163 if self.cursor.row >= size.rows {
164 self.grid.scroll_up(1);
165 self.cursor.row = size.rows - 1;
166 }
167 }
168 return;
169 }
170
171 if let Some(cell) = self.grid.cell_mut(self.cursor.row, self.cursor.col) {
173 cell.text = c.to_string();
174 cell.attrs = self.attrs;
175 }
176
177 self.cursor.col += 1;
179 if self.cursor.col >= size.cols {
180 self.cursor.col = 0;
181 self.cursor.row += 1;
182 if self.cursor.row >= size.rows {
183 self.grid.scroll_up(1);
184 self.cursor.row = size.rows - 1;
185 }
186 }
187 }
188
189 fn handle_control(&mut self, code: u8) {
191 match code {
192 0x08 => {
193 if self.cursor.col > 0 {
195 self.cursor.col -= 1;
196 }
197 }
198 0x0A => {
199 self.cursor.row += 1;
201 if self.cursor.row >= self.grid.size().rows {
202 self.grid.scroll_up(1);
203 self.cursor.row = self.grid.size().rows - 1;
204 }
205 }
206 0x0D => {
207 self.cursor.col = 0;
209 }
210 _ => {}
211 }
212 }
213
214 fn handle_csi(&mut self, csi: &termwiz::escape::CSI) {
216 use termwiz::escape::CSI;
217
218 match csi {
219 CSI::Cursor(cursor) => self.handle_cursor(cursor),
220 CSI::Sgr(sgr) => self.handle_sgr(sgr),
221 CSI::Edit(edit) => self.handle_edit(edit),
222 CSI::Mode(mode) => self.handle_mode(mode),
223 _ => {}
224 }
225 }
226
227 fn handle_cursor(&mut self, cursor: &termwiz::escape::csi::Cursor) {
229 use termwiz::escape::csi::Cursor;
230
231 let size = self.grid.size();
232
233 match cursor {
234 Cursor::Position { line, col } => {
235 self.cursor.row = (line.as_zero_based() as usize).min(size.rows - 1);
236 self.cursor.col = (col.as_zero_based() as usize).min(size.cols - 1);
237 }
238 Cursor::Up(n) => {
239 self.cursor.row = self.cursor.row.saturating_sub(*n as usize);
240 }
241 Cursor::Down(n) => {
242 self.cursor.row = (self.cursor.row + *n as usize).min(size.rows - 1);
243 }
244 Cursor::Right(n) => {
245 self.cursor.col = (self.cursor.col + *n as usize).min(size.cols - 1);
246 }
247 Cursor::Left(n) => {
248 self.cursor.col = self.cursor.col.saturating_sub(*n as usize);
249 }
250 Cursor::CharacterAbsolute(col) => {
251 self.cursor.col = (col.as_zero_based() as usize).min(size.cols - 1);
252 }
253 Cursor::LineTabulation(n) => {
254 self.cursor.row = (self.cursor.row + *n as usize).min(size.rows - 1);
255 }
256 Cursor::SaveCursor => {
257 self.saved_cursor = Some(self.cursor);
258 }
259 Cursor::RestoreCursor => {
260 if let Some(saved) = self.saved_cursor {
261 self.cursor = saved;
262 }
263 }
264 _ => {}
265 }
266 }
267
268 fn handle_sgr(&mut self, sgr: &termwiz::escape::csi::Sgr) {
270 use termwiz::escape::csi::Sgr;
271
272 match sgr {
273 Sgr::Reset => {
274 self.attrs = Attrs::default();
275 }
276 Sgr::Intensity(intensity) => {
277 self.attrs.intensity = *intensity;
278 }
279 Sgr::Underline(underline) => {
280 self.attrs.underline = *underline != termwiz::cell::Underline::None;
281 }
282 Sgr::Blink(blink) => {
283 self.attrs.blink = *blink != termwiz::cell::Blink::None;
284 }
285 Sgr::Inverse(inverse) => {
286 self.attrs.reverse = *inverse;
287 }
288 Sgr::Italic(italic) => {
289 self.attrs.italic = *italic;
290 }
291 Sgr::StrikeThrough(strike) => {
292 self.attrs.strikethrough = *strike;
293 }
294 Sgr::Foreground(color) => {
295 self.attrs.fgcolor = match color {
297 termwiz::color::ColorSpec::Default => termwiz::color::ColorAttribute::Default,
298 termwiz::color::ColorSpec::PaletteIndex(idx) => {
299 termwiz::color::ColorAttribute::PaletteIndex(*idx)
300 }
301 termwiz::color::ColorSpec::TrueColor(rgb) => {
302 termwiz::color::ColorAttribute::TrueColorWithDefaultFallback(*rgb)
303 }
304 };
305 }
306 Sgr::Background(color) => {
307 self.attrs.bgcolor = match color {
309 termwiz::color::ColorSpec::Default => termwiz::color::ColorAttribute::Default,
310 termwiz::color::ColorSpec::PaletteIndex(idx) => {
311 termwiz::color::ColorAttribute::PaletteIndex(*idx)
312 }
313 termwiz::color::ColorSpec::TrueColor(rgb) => {
314 termwiz::color::ColorAttribute::TrueColorWithDefaultFallback(*rgb)
315 }
316 };
317 }
318 _ => {}
319 }
320 }
321
322 fn handle_edit(&mut self, edit: &termwiz::escape::csi::Edit) {
324 use termwiz::escape::csi::Edit;
325
326 match edit {
327 Edit::EraseInLine(erase) => {
328 use termwiz::escape::csi::EraseInLine;
329 let size = self.grid.size();
330 let row = self.cursor.row;
331
332 match erase {
333 EraseInLine::EraseToEndOfLine => {
334 for col in self.cursor.col..size.cols {
335 if let Some(cell) = self.grid.cell_mut(row, col) {
336 *cell = Cell::default();
337 }
338 }
339 }
340 EraseInLine::EraseToStartOfLine => {
341 for col in 0..=self.cursor.col {
342 if let Some(cell) = self.grid.cell_mut(row, col) {
343 *cell = Cell::default();
344 }
345 }
346 }
347 EraseInLine::EraseLine => {
348 for col in 0..size.cols {
349 if let Some(cell) = self.grid.cell_mut(row, col) {
350 *cell = Cell::default();
351 }
352 }
353 }
354 }
355 }
356 Edit::EraseInDisplay(erase) => {
357 use termwiz::escape::csi::EraseInDisplay;
358
359 match erase {
360 EraseInDisplay::EraseToEndOfDisplay => {
361 let size = self.grid.size();
363
364 for col in self.cursor.col..size.cols {
366 if let Some(cell) = self.grid.cell_mut(self.cursor.row, col) {
367 *cell = Cell::default();
368 }
369 }
370
371 for row in (self.cursor.row + 1)..size.rows {
373 for col in 0..size.cols {
374 if let Some(cell) = self.grid.cell_mut(row, col) {
375 *cell = Cell::default();
376 }
377 }
378 }
379 }
380 EraseInDisplay::EraseToStartOfDisplay => {
381 let size = self.grid.size();
382
383 for row in 0..self.cursor.row {
385 for col in 0..size.cols {
386 if let Some(cell) = self.grid.cell_mut(row, col) {
387 *cell = Cell::default();
388 }
389 }
390 }
391
392 for col in 0..=self.cursor.col {
394 if let Some(cell) = self.grid.cell_mut(self.cursor.row, col) {
395 *cell = Cell::default();
396 }
397 }
398 }
399 EraseInDisplay::EraseDisplay => {
400 self.grid.clear();
401 }
402 _ => {}
403 }
404 }
405 _ => {}
406 }
407 }
408
409 fn handle_mode(&mut self, _mode: &termwiz::escape::csi::Mode) {
411 }
415
416 fn handle_esc(&mut self, _esc: &termwiz::escape::Esc) {
418 }
420
421 fn handle_osc(&mut self, _osc: &termwiz::escape::OperatingSystemCommand) {
423 }
426}