1use crate::grid::{Grid, GridCell, GridCellFlags, GridLineFlags};
2use crate::input::mode;
3use crate::input::{CellState, InputEndType, ScreenWriter, COLOUR_DEFAULT};
4use crate::TerminalPassthrough;
5
6use super::{SavedGrid, Screen, TITLE_STACK_MAX};
7
8fn cursor_backward_tab_steps(n: u32, current: u32) -> u32 {
9 n.max(1).min(current.saturating_add(1))
10}
11
12impl ScreenWriter for Screen {
13 fn collect_add(&mut self, ch: char, cell: &CellState) {
14 self.write_char(ch, cell, false);
15 }
16
17 fn collect_add_with_charset(&mut self, ch: char, cell: &CellState, acs: bool) {
18 self.write_char(ch, cell, acs);
19 }
20
21 fn collect_add_ascii_run(&mut self, bytes: &[u8], cell: &CellState, acs: bool) {
22 if !self.write_plain_ascii_run(bytes, cell, acs) {
23 for &byte in bytes {
24 self.write_char(char::from(byte), cell, acs);
25 }
26 }
27 }
28
29 fn cursor_up(&mut self, n: u32) {
30 self.clear_pending_wrap();
31 self.cursor_y = self.cursor_y.saturating_sub(n);
32 }
33
34 fn cursor_down(&mut self, n: u32) {
35 self.clear_pending_wrap();
36 self.cursor_y = self
37 .cursor_y
38 .saturating_add(n)
39 .min(self.grid.sy().saturating_sub(1));
40 }
41
42 fn cursor_left(&mut self, n: u32) {
43 self.clear_pending_wrap();
44 self.cursor_x = self.cursor_column().saturating_sub(n);
45 }
46
47 fn cursor_right(&mut self, n: u32) {
48 self.clear_pending_wrap();
49 self.cursor_x = self
50 .cursor_column()
51 .saturating_add(n)
52 .min(self.max_cursor_x());
53 }
54
55 fn cursor_move(&mut self, col: i32, row: i32, origin_mode: bool) {
56 self.clear_pending_wrap();
57 let max_x = self.grid.sx().saturating_sub(1);
58 let (min_y, max_y) = if origin_mode && (self.mode & mode::MODE_ORIGIN) != 0 {
59 (self.rupper, self.rlower)
60 } else {
61 (0, self.grid.sy().saturating_sub(1))
62 };
63
64 if col >= 0 {
65 self.cursor_x = (col as u32).min(max_x);
66 }
67 if row >= 0 {
68 self.cursor_y = min_y.saturating_add(row as u32).min(max_y);
69 }
70 }
71
72 fn insert_line(&mut self, n: u32, bg: i32) {
73 self.clear_pending_wrap();
74 if self.cursor_y < self.rupper || self.cursor_y > self.rlower {
75 return;
76 }
77 self.clear_selected_cells();
78
79 let upper = self.cursor_y;
80 let lower = self.rlower;
81 let lines = n.max(1).min(lower.saturating_sub(upper).saturating_add(1));
82 for _ in 0..lines {
83 self.grid.scroll_region_down(upper, lower, bg);
84 }
85 }
86
87 fn delete_line(&mut self, n: u32, bg: i32) {
88 self.clear_pending_wrap();
89 if self.cursor_y < self.rupper || self.cursor_y > self.rlower {
90 return;
91 }
92 self.clear_selected_cells();
93
94 let upper = self.cursor_y;
95 let lower = self.rlower;
96 let lines = n.max(1).min(lower.saturating_sub(upper).saturating_add(1));
97 for _ in 0..lines {
98 self.grid.scroll_region_up(upper, lower, bg, false);
99 }
100 }
101
102 fn scroll_up(&mut self, n: u32, bg: i32) {
103 self.clear_pending_wrap();
104 self.clear_selected_cells();
105 let lines = n
106 .max(1)
107 .min(self.rlower.saturating_sub(self.rupper).saturating_add(1));
108 for _ in 0..lines {
109 self.grid
110 .scroll_region_up(self.rupper, self.rlower, bg, self.rupper == 0);
111 }
112 }
113
114 fn scroll_down(&mut self, n: u32, bg: i32) {
115 self.clear_pending_wrap();
116 self.clear_selected_cells();
117 let lines = n
118 .max(1)
119 .min(self.rlower.saturating_sub(self.rupper).saturating_add(1));
120 for _ in 0..lines {
121 self.grid.scroll_region_down(self.rupper, self.rlower, bg);
122 }
123 }
124
125 fn linefeed(&mut self, wrapped: bool, bg: i32) {
126 self.pending_wrap = false;
127 if wrapped {
128 if let Some(line) = self.current_line_mut() {
129 line.set_wrapped(true);
130 }
131 }
132
133 if self.cursor_y == self.rlower {
134 self.clear_selected_cells();
135 self.grid
136 .scroll_region_up(self.rupper, self.rlower, bg, self.rupper == 0);
137 } else if self.cursor_y < self.grid.sy().saturating_sub(1) {
138 self.cursor_y += 1;
139 }
140 }
141
142 fn reverse_index(&mut self, bg: i32) {
143 self.clear_pending_wrap();
144 if self.cursor_y == self.rupper {
145 self.clear_selected_cells();
146 self.grid.scroll_region_down(self.rupper, self.rlower, bg);
147 } else if self.cursor_y > 0 {
148 self.cursor_y -= 1;
149 }
150 }
151
152 fn carriage_return(&mut self) {
153 self.pending_wrap = false;
154 self.cursor_x = 0;
155 }
156
157 fn backspace(&mut self) {
158 self.clear_pending_wrap();
159 let cx = self.cursor_column();
160 if cx > 0 {
161 self.cursor_x = cx - 1;
162 return;
163 }
164 if self.cursor_y == 0 {
165 return;
166 }
167 if self
168 .grid
169 .visible_line(self.cursor_y - 1)
170 .is_some_and(|line| line.flags().contains(GridLineFlags::WRAPPED))
171 {
172 self.cursor_y -= 1;
173 self.cursor_x = self.max_cursor_x();
174 }
175 }
176
177 fn insert_character(&mut self, n: u32, bg: i32) {
178 self.clear_pending_wrap();
179 self.clear_selected_cells();
180 let x = self.cursor_column();
181 let sx = self.grid.sx();
182 let count = n.max(1).min(sx.saturating_sub(x));
183 let blank = self.blank_cell(bg);
184 if let Some(line) = self.current_line_mut() {
185 line.insert_cells(x, count, &blank);
186 Self::repair_wide_cells_on_line(line, sx, bg);
187 line.touch();
188 }
189 }
190
191 fn delete_character(&mut self, n: u32, bg: i32) {
192 self.clear_pending_wrap();
193 self.clear_selected_cells();
194 let x = self.cursor_column();
195 let sx = self.grid.sx();
196 let count = n.max(1).min(sx.saturating_sub(x));
197 let blank = self.blank_cell(bg);
198 if let Some(line) = self.current_line_mut() {
199 line.delete_cells(x, count, &blank);
200 Self::repair_wide_cells_on_line(line, sx, bg);
201 line.touch();
202 }
203 }
204
205 fn clear_character(&mut self, n: u32, bg: i32) {
206 self.clear_pending_wrap();
207 let x = self.cursor_column();
208 let end = x
209 .saturating_add(n.max(1))
210 .saturating_sub(1)
211 .min(self.grid.sx().saturating_sub(1));
212 self.clear_line_range(self.cursor_y, x, end, bg);
213 }
214
215 fn clear_end_of_screen(&mut self, bg: i32) {
216 if self.cursor_y == 0 && self.cursor_column() == 0 {
217 self.clear_selected_cells();
218 self.grid.clear_visible_to_history(COLOUR_DEFAULT);
219 return;
220 }
221 let x = self.cursor_column();
222 if self.cursor_y < self.grid.sy() {
223 self.clear_line_range(self.cursor_y, x, self.grid.sx().saturating_sub(1), bg);
224 }
225 if self.cursor_y + 1 < self.grid.sy() {
226 self.clear_screen_region(
227 self.cursor_y + 1,
228 self.grid.sy().saturating_sub(1),
229 COLOUR_DEFAULT,
230 );
231 }
232 }
233
234 fn clear_start_of_screen(&mut self, bg: i32) {
235 if self.cursor_y > 0 {
236 self.clear_screen_region(0, self.cursor_y - 1, COLOUR_DEFAULT);
237 }
238 self.clear_line_range(self.cursor_y, 0, self.cursor_column(), bg);
239 }
240
241 fn clear_screen(&mut self, _bg: i32) {
242 self.clear_selected_cells();
243 self.grid.clear_visible_to_history(COLOUR_DEFAULT);
244 }
245
246 fn clear_history(&mut self) {
247 self.clear_selected_cells();
248 self.grid.clear_history();
249 }
250
251 fn clear_end_of_line(&mut self, bg: i32) {
252 self.clear_line_range(
253 self.cursor_y,
254 self.cursor_column(),
255 self.grid.sx().saturating_sub(1),
256 bg,
257 );
258 }
259
260 fn clear_start_of_line(&mut self, bg: i32) {
261 self.clear_line_range(self.cursor_y, 0, self.cursor_column(), bg);
262 }
263
264 fn clear_line(&mut self, bg: i32) {
265 self.clear_line_range(self.cursor_y, 0, self.grid.sx().saturating_sub(1), bg);
266 }
267
268 fn mode_set(&mut self, mode_bits: u32) {
269 self.mode |= mode_bits;
270 }
271
272 fn mode_clear(&mut self, mode_bits: u32) {
273 self.mode &= !mode_bits;
274 if (self.mode & mode::MODE_WRAP) == 0 {
275 self.clear_pending_wrap();
276 }
277 }
278
279 fn set_scroll_region(&mut self, top: u32, bottom: u32) {
280 let max_y = self.grid.sy().saturating_sub(1);
281 let top = top.min(max_y);
282 let bottom = bottom.min(max_y);
283 if top >= bottom {
284 return;
285 }
286 self.pending_wrap = false;
287 self.cursor_x = 0;
288 self.cursor_y = 0;
289 self.rupper = top;
290 self.rlower = bottom;
291 }
292
293 fn alternate_on(&mut self, bg: i32, save_cursor: bool) {
294 if !self.alternate_screen_enabled {
295 return;
296 }
297 if self.is_alternate() {
298 return;
299 }
300 self.clear_selected_cells();
301
302 let mut saved_grid = Grid::new(self.grid.size(), 0);
303 saved_grid.replace_visible(self.grid.visible_lines());
304 self.saved_grid = Some(SavedGrid {
305 grid: saved_grid,
306 history_enabled: self.grid.history_enabled(),
307 });
308 if save_cursor {
309 self.saved_cursor_x = Some(self.cursor_x);
310 self.saved_cursor_y = Some(self.cursor_y);
311 self.saved_cursor_pending_wrap = self.pending_wrap;
312 self.saved_state.cx = self.cursor_column();
313 self.saved_state.cy = self.cursor_y;
314 }
315
316 self.grid.clear_visible(bg);
317 self.grid.set_history_enabled(false);
318 self.pending_wrap = false;
319 if !save_cursor || !self.preserve_alternate_screen_cursor {
320 self.cursor_x = 0;
321 self.cursor_y = 0;
322 }
323 }
324
325 fn alternate_off(&mut self, _bg: i32, restore_cursor: bool) {
326 self.clear_selected_cells();
327 let saved_cursor = if restore_cursor {
328 self.saved_cursor_x
329 .zip(self.saved_cursor_y)
330 .map(|(x, y)| (x, y, self.saved_cursor_pending_wrap))
331 } else {
332 None
333 };
334
335 let Some(saved) = self.saved_grid.take() else {
336 if let Some((x, y, pending_wrap)) = saved_cursor {
337 self.restore_cursor_position(x, y, pending_wrap);
338 }
339 return;
340 };
341
342 let current_size = self.grid.size();
343 let saved_size = saved.grid.size();
344 if current_size.rows == saved_size.rows {
345 self.grid.replace_visible_resized_width_only(
346 saved_size,
347 saved.grid.visible_lines(),
348 COLOUR_DEFAULT,
349 );
350 } else {
351 self.grid
352 .resize_width(u32::from(saved_size.cols), COLOUR_DEFAULT);
353 self.grid.resize_height(
354 u32::from(saved_size.rows),
355 &mut self.cursor_y,
356 COLOUR_DEFAULT,
357 );
358 self.grid.replace_visible(saved.grid.visible_lines());
359 }
360 self.grid.set_history_enabled(saved.history_enabled);
361 self.resize(current_size);
362 if let Some((x, y, pending_wrap)) = saved_cursor {
363 self.restore_cursor_position(x, y, pending_wrap);
364 } else {
365 self.pending_wrap = false;
366 }
367 }
368
369 fn tab(&mut self) {
370 self.clear_pending_wrap();
371 let start = self.cursor_column();
372 let next = ((start + 1) as usize..self.tabs.len())
373 .find(|index| self.tabs[*index])
374 .map(|index| index as u32)
375 .unwrap_or_else(|| self.grid.sx().saturating_sub(1));
376 self.cursor_x = next;
377 }
378
379 fn cursor_backward_tab(&mut self, n: u32) {
380 self.clear_pending_wrap();
381 let mut current = self.cursor_column();
382 for _ in 0..cursor_backward_tab_steps(n, current) {
383 if current == 0 {
384 break;
385 }
386 let previous = (0..current as usize)
387 .rev()
388 .find(|index| self.tabs[*index])
389 .map(|index| index as u32)
390 .unwrap_or(0);
391 current = previous;
392 }
393 self.cursor_x = current;
394 }
395
396 fn set_tab_stop(&mut self) {
397 let column = self.cursor_column() as usize;
398 if let Some(tab) = self.tabs.get_mut(column) {
399 *tab = true;
400 }
401 }
402
403 fn clear_tab_stop(&mut self) {
404 let column = self.cursor_column() as usize;
405 if let Some(tab) = self.tabs.get_mut(column) {
406 *tab = false;
407 }
408 }
409
410 fn clear_all_tab_stops(&mut self) {
411 self.tabs.fill(false);
412 }
413
414 fn set_title(&mut self, title: &str) {
415 if self.title_rename_enabled {
416 Screen::set_title(self, title);
417 }
418 }
419
420 fn set_window_name(&mut self, name: &str) {
421 if self.title_rename_enabled {
422 self.window_name = name.to_owned();
423 }
424 }
425
426 fn set_path(&mut self, path: &str) {
427 self.path = path.to_owned();
428 }
429
430 fn save_cursor(&mut self) {
431 self.saved_cursor_x = Some(self.cursor_x);
432 self.saved_cursor_y = Some(self.cursor_y);
433 self.saved_cursor_pending_wrap = self.pending_wrap;
434 }
435
436 fn restore_cursor(&mut self) {
437 if let (Some(x), Some(y)) = (self.saved_cursor_x, self.saved_cursor_y) {
438 self.restore_cursor_position(x, y, self.saved_cursor_pending_wrap);
439 }
440 }
441
442 fn alignment_test(&mut self) {
443 self.clear_selected_cells();
444 self.rupper = 0;
445 self.rlower = self.grid.sy().saturating_sub(1);
446 let sx = self.grid.sx();
447 for y in 0..self.grid.sy() {
448 if let Some(line) = self.grid.visible_line_mut(y) {
449 for x in 0..sx {
450 if let Some(cell) = line.cell_mut(x) {
451 *cell = GridCell::from_state(
452 'E',
453 1,
454 &CellState::default(),
455 GridCellFlags::default(),
456 );
457 }
458 }
459 line.set_wrapped(false);
460 line.touch();
461 }
462 }
463 }
464
465 fn full_reset(&mut self) {
466 self.clear_selected_cells();
467 if self.is_alternate() {
468 self.alternate_off(COLOUR_DEFAULT, false);
469 }
470 self.cursor_x = 0;
471 self.cursor_y = 0;
472 self.pending_wrap = false;
473 self.rupper = 0;
474 self.rlower = self.grid.sy().saturating_sub(1);
475 self.mode = mode::MODE_CURSOR | mode::MODE_WRAP | (self.mode & mode::MODE_CRLF);
476 self.grid.clear_visible(COLOUR_DEFAULT);
477 self.reset_tabs();
478 self.title_stack.clear();
479 self.active_hyperlink = 0;
480 self.hyperlinks.reset();
481 }
482
483 fn start_sync(&mut self) {
484 self.mode |= mode::MODE_SYNC;
485 }
486
487 fn stop_sync(&mut self) {
488 self.mode &= !mode::MODE_SYNC;
489 }
490
491 fn set_cursor_style(&mut self, n: u32) {
492 self.cursor_style = n;
493 }
494
495 fn osc_hyperlink(&mut self, data: &str) {
496 let (internal_id, uri) = Self::parse_hyperlink(data);
497 if uri.is_empty() {
498 self.active_hyperlink = 0;
499 return;
500 }
501 self.active_hyperlink = self.hyperlinks.put(&uri, internal_id.as_deref());
502 }
503
504 fn current_hyperlink_id(&self) -> u32 {
505 self.active_hyperlink
506 }
507
508 fn bell(&mut self) {
509 self.bell_count = self.bell_count.saturating_add(1);
510 }
511
512 fn apc_passthrough(&mut self, data: &[u8]) {
513 self.push_terminal_passthrough(TerminalPassthrough::kitty_graphics(
514 self.cursor_x,
515 self.cursor_y,
516 data.to_vec(),
517 ));
518 }
519
520 fn dcs_passthrough(&mut self, data: &[u8]) {
521 self.push_terminal_passthrough(TerminalPassthrough::raw(
522 self.cursor_x,
523 self.cursor_y,
524 data.to_vec(),
525 ));
526 }
527
528 fn sixel_passthrough(&mut self, data: &[u8]) {
529 self.push_terminal_passthrough(TerminalPassthrough::sixel(
530 self.cursor_x,
531 self.cursor_y,
532 data.to_vec(),
533 ));
534 }
535
536 fn screen_size_x(&self) -> u32 {
537 self.grid.sx()
538 }
539
540 fn screen_size_y(&self) -> u32 {
541 self.grid.sy()
542 }
543
544 fn cursor_x(&self) -> u32 {
545 self.cursor_x
546 }
547
548 fn cursor_y(&self) -> u32 {
549 self.cursor_y
550 }
551
552 fn current_mode(&self) -> u32 {
553 self.mode
554 }
555
556 fn push_title(&mut self) {
557 if !self.title_rename_enabled {
558 return;
559 }
560 if self.title_stack.len() >= TITLE_STACK_MAX {
561 let excess = self.title_stack.len() + 1 - TITLE_STACK_MAX;
562 self.title_stack.drain(0..excess);
563 }
564 self.title_stack.push(self.title.clone());
565 }
566
567 fn pop_title(&mut self) {
568 if !self.title_rename_enabled {
569 return;
570 }
571 if let Some(title) = self.title_stack.pop() {
572 self.title = title;
573 }
574 }
575
576 fn osc_palette(&mut self, _data: &str, _end: InputEndType) {}
577 fn osc_notification(&mut self, _data: &str) {}
578 fn osc_fg_colour(&mut self, _data: &str, _end: InputEndType) {}
579 fn osc_bg_colour(&mut self, _data: &str, _end: InputEndType) {}
580 fn osc_cursor_colour(&mut self, _data: &str, _end: InputEndType) {}
581 fn osc_clipboard(&mut self, data: &str, end: InputEndType) {
582 let mut sequence = Vec::with_capacity(data.len() + 7);
583 sequence.extend_from_slice(b"\x1b]52;");
584 sequence.extend_from_slice(data.as_bytes());
585 match end {
586 InputEndType::Bel => sequence.push(b'\x07'),
587 InputEndType::St => sequence.extend_from_slice(b"\x1b\\"),
588 }
589 self.push_terminal_passthrough(TerminalPassthrough::clipboard(sequence));
590 }
591 fn osc_reset_palette(&mut self, _data: &str) {}
592 fn osc_reset_fg(&mut self) {}
593 fn osc_reset_bg(&mut self) {}
594 fn osc_reset_cursor(&mut self) {}
595 fn osc_shell_integration(&mut self, _data: &str) {}
596}
597
598#[cfg(test)]
599mod tests {
600 #[test]
601 fn cursor_backward_tab_steps_are_bounded_by_cursor_column() {
602 assert_eq!(super::cursor_backward_tab_steps(0, 0), 1);
603 assert_eq!(super::cursor_backward_tab_steps(1, 7), 1);
604 assert_eq!(super::cursor_backward_tab_steps(u32::MAX, 7), 8);
605 }
606}