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