Skip to main content

rmux_core/screen/
writer.rs

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}