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