os_terminal/
terminal.rs

1use alloc::boxed::Box;
2use alloc::string::String;
3use core::mem::swap;
4use core::ops::Range;
5use core::time::Duration;
6use core::{cmp::min, fmt};
7
8use base64ct::{Base64, Encoding};
9use pc_keyboard::{DecodedKey, KeyCode};
10use vte::ansi::{Attr, NamedMode, Rgb};
11use vte::ansi::{CharsetIndex, StandardCharset, TabulationClearMode};
12use vte::ansi::{ClearMode, CursorShape, Processor, Timeout};
13use vte::ansi::{CursorStyle, Hyperlink, KeyboardModes};
14use vte::ansi::{Handler, LineClearMode, Mode, NamedPrivateMode, PrivateMode};
15
16use crate::buffer::TerminalBuffer;
17use crate::cell::{Cell, Flags};
18use crate::color::ColorScheme;
19use crate::font::FontManager;
20use crate::graphic::{DrawTarget, Graphic};
21use crate::keyboard::{KeyboardEvent, KeyboardManager};
22use crate::mouse::{MouseEvent, MouseInput, MouseManager};
23use crate::palette::Palette;
24
25pub trait ClipboardHandler {
26    fn get_text(&mut self) -> Option<String>;
27    fn set_text(&mut self, text: String);
28}
29
30pub type PtyWriter = Box<dyn Fn(String) + Send>;
31pub type Clipboard = Box<dyn ClipboardHandler + Send>;
32
33#[derive(Default)]
34pub struct DummySyncHandler;
35
36#[rustfmt::skip]
37impl Timeout for DummySyncHandler {
38    fn set_timeout(&mut self, _: Duration) {}
39    fn clear_timeout(&mut self) {}
40    fn pending_timeout(&self) -> bool { false }
41}
42
43bitflags::bitflags! {
44    pub struct TerminalMode: u32 {
45        const SHOW_CURSOR = 1 << 0;
46        const APP_CURSOR = 1 << 1;
47        const APP_KEYPAD = 1 << 2;
48        const MOUSE_REPORT_CLICK = 1 << 3;
49        const BRACKETED_PASTE = 1 << 4;
50        const SGR_MOUSE = 1 << 5;
51        const MOUSE_MOTION = 1 << 6;
52        const LINE_WRAP = 1 << 7;
53        const LINE_FEED_NEW_LINE = 1 << 8;
54        const ORIGIN = 1 << 9;
55        const INSERT = 1 << 10;
56        const FOCUS_IN_OUT = 1 << 11;
57        const ALT_SCREEN = 1 << 12;
58        const MOUSE_DRAG = 1 << 13;
59        const MOUSE_MODE = 1 << 14;
60        const UTF8_MOUSE = 1 << 15;
61        const ALTERNATE_SCROLL = 1 << 16;
62        const VI = 1 << 17;
63        const URGENCY_HINTS = 1 << 18;
64        const ANY = u32::MAX;
65    }
66}
67
68impl Default for TerminalMode {
69    fn default() -> TerminalMode {
70        TerminalMode::SHOW_CURSOR | TerminalMode::LINE_WRAP
71    }
72}
73
74#[derive(Debug, Default, Clone, Copy)]
75struct Cursor {
76    row: usize,
77    column: usize,
78    shape: CursorShape,
79}
80
81pub struct Terminal<D: DrawTarget> {
82    performer: Processor<DummySyncHandler>,
83    inner: TerminalInner<D>,
84}
85
86pub struct TerminalInner<D: DrawTarget> {
87    graphic: Graphic<D>,
88    cursor: Cursor,
89    saved_cursor: Cursor,
90    alt_cursor: Cursor,
91    mode: TerminalMode,
92    attribute_template: Cell,
93    buffer: TerminalBuffer,
94    keyboard: KeyboardManager,
95    mouse: MouseManager,
96    auto_flush: bool,
97    logger: Option<fn(fmt::Arguments)>,
98    pty_writer: Option<PtyWriter>,
99    bell_handler: Option<fn()>,
100    clipboard: Option<Clipboard>,
101    scroll_region: Range<usize>,
102    charsets: [StandardCharset; 4],
103    active_charset: CharsetIndex,
104}
105
106impl<D: DrawTarget> Terminal<D> {
107    pub fn new(display: D) -> Self {
108        let mut graphic = Graphic::new(display);
109        graphic.clear(Cell::default());
110
111        Self {
112            performer: Processor::new(),
113            inner: TerminalInner {
114                graphic,
115                cursor: Cursor::default(),
116                saved_cursor: Cursor::default(),
117                alt_cursor: Cursor::default(),
118                mode: TerminalMode::default(),
119                attribute_template: Cell::default(),
120                buffer: TerminalBuffer::default(),
121                keyboard: KeyboardManager::default(),
122                mouse: MouseManager::default(),
123                auto_flush: true,
124                pty_writer: Default::default(),
125                logger: None,
126                bell_handler: None,
127                clipboard: None,
128                scroll_region: Default::default(),
129                charsets: Default::default(),
130                active_charset: Default::default(),
131            },
132        }
133    }
134
135    pub fn rows(&self) -> usize {
136        self.inner.buffer.height()
137    }
138
139    pub fn columns(&self) -> usize {
140        self.inner.buffer.width()
141    }
142
143    pub fn flush(&mut self) {
144        self.inner.buffer.flush(&mut self.inner.graphic);
145    }
146
147    pub fn process(&mut self, bstr: &[u8]) {
148        self.inner.cursor_handler(false);
149        self.performer.advance(&mut self.inner, bstr);
150        if self.inner.mode.contains(TerminalMode::SHOW_CURSOR) {
151            self.inner.cursor_handler(true);
152        }
153        self.inner.auto_flush.then(|| self.flush());
154    }
155}
156
157impl<D: DrawTarget> Terminal<D> {
158    pub fn handle_keyboard(&mut self, scancode: u8) {
159        match self.inner.keyboard.handle_keyboard(scancode) {
160            KeyboardEvent::SetColorScheme(index) => {
161                self.set_color_scheme(index);
162            }
163            KeyboardEvent::Scroll { up, page } => {
164                let lines = if page { self.rows() } else { 1 } as isize;
165                self.inner.scroll_history(if up { -lines } else { lines });
166            }
167            KeyboardEvent::AnsiString(s) => {
168                self.inner.buffer.ensure_latest();
169                self.inner.pty_write(s);
170            }
171            KeyboardEvent::Paste => {
172                let Some(clipboard) = self.inner.clipboard.as_mut() else {
173                    return;
174                };
175
176                let Some(text) = clipboard.get_text() else {
177                    return;
178                };
179
180                if self.inner.mode.contains(TerminalMode::BRACKETED_PASTE) {
181                    self.inner.pty_write(format!("\x1b[200~{text}\x1b[201~"));
182                } else {
183                    self.inner.pty_write(text);
184                }
185            }
186            _ => {}
187        }
188    }
189
190    pub fn handle_mouse(&mut self, input: MouseInput) {
191        match self.inner.mouse.handle_mouse(input) {
192            MouseEvent::Scroll(lines) => {
193                if !self.inner.mode.contains(TerminalMode::ALT_SCREEN) {
194                    self.inner.scroll_history(lines);
195                    return;
196                }
197
198                let key = DecodedKey::RawKey(if lines > 0 {
199                    KeyCode::ArrowUp
200                } else {
201                    KeyCode::ArrowDown
202                });
203
204                let e = self.inner.keyboard.key_to_event(key);
205                if let KeyboardEvent::AnsiString(s) = e {
206                    for _ in 0..lines.unsigned_abs() {
207                        self.inner.pty_write(s.clone());
208                    }
209                }
210            }
211            MouseEvent::None => {}
212        }
213    }
214}
215
216impl<D: DrawTarget> Terminal<D> {
217    pub fn set_auto_flush(&mut self, auto_flush: bool) {
218        self.inner.auto_flush = auto_flush;
219    }
220
221    pub fn set_logger(&mut self, logger: fn(fmt::Arguments)) {
222        self.inner.logger = Some(logger);
223    }
224
225    pub fn set_bell_handler(&mut self, handler: fn()) {
226        self.inner.bell_handler = Some(handler);
227    }
228
229    pub fn set_clipboard(&mut self, clipboard: Clipboard) {
230        self.inner.clipboard = Some(clipboard);
231    }
232
233    pub fn set_pty_writer(&mut self, writer: PtyWriter) {
234        self.inner.pty_writer = Some(writer);
235    }
236
237    pub fn set_history_size(&mut self, size: usize) {
238        self.inner.buffer.resize_history(size);
239    }
240
241    pub fn set_scroll_speed(&mut self, speed: usize) {
242        self.inner.mouse.set_scroll_speed(speed);
243    }
244
245    pub fn set_crnl_mapping(&mut self, mapping: bool) {
246        self.inner.keyboard.crnl_mapping = mapping;
247    }
248
249    pub fn set_font_manager(&mut self, font_manager: Box<dyn FontManager>) {
250        self.inner
251            .buffer
252            .update_size(font_manager.size(), self.inner.graphic.size());
253        self.inner.scroll_region = 0..self.inner.buffer.height() - 1;
254        self.inner.reset_state();
255        self.inner.graphic.font_manager = Some(font_manager);
256    }
257
258    pub fn set_color_scheme(&mut self, palette_index: usize) {
259        self.inner.graphic.color_scheme = ColorScheme::new(palette_index);
260        self.inner.attribute_template = Cell::default();
261        self.inner.buffer.full_flush(&mut self.inner.graphic);
262    }
263
264    pub fn set_custom_color_scheme(&mut self, palette: &Palette) {
265        self.inner.graphic.color_scheme = ColorScheme::from(palette);
266        self.inner.attribute_template = Cell::default();
267        self.inner.buffer.full_flush(&mut self.inner.graphic);
268    }
269}
270
271impl<D: DrawTarget> fmt::Write for Terminal<D> {
272    fn write_str(&mut self, s: &str) -> fmt::Result {
273        self.process(s.as_bytes());
274        Ok(())
275    }
276}
277
278impl<D: DrawTarget> TerminalInner<D> {
279    fn cursor_handler(&mut self, enable: bool) {
280        let row = self.cursor.row % self.buffer.height();
281        let column = self.cursor.column % self.buffer.width();
282
283        let mut origin_cell = self.buffer.read(row, column);
284
285        let flag = match self.cursor.shape {
286            CursorShape::Block => Flags::CURSOR_BLOCK,
287            CursorShape::Underline => Flags::CURSOR_UNDERLINE,
288            CursorShape::Beam => Flags::CURSOR_BEAM,
289            CursorShape::HollowBlock => Flags::CURSOR_BLOCK,
290            CursorShape::Hidden => Flags::HIDDEN,
291        };
292
293        if enable {
294            origin_cell.flags.insert(flag);
295        } else {
296            origin_cell.flags.remove(flag);
297        }
298
299        self.buffer.write(row, column, origin_cell);
300    }
301
302    fn log_message(&self, args: fmt::Arguments) {
303        self.logger.map(|logger| logger(args));
304    }
305
306    fn pty_write(&self, data: String) {
307        self.pty_writer.as_ref().map(|writer| writer(data));
308    }
309
310    fn scroll_history(&mut self, count: isize) {
311        self.buffer.scroll_history(count);
312        self.auto_flush
313            .then(|| self.buffer.flush(&mut self.graphic));
314    }
315
316    fn swap_alt_screen(&mut self) {
317        self.mode ^= TerminalMode::ALT_SCREEN;
318        swap(&mut self.cursor, &mut self.alt_cursor);
319        self.buffer.swap_alt_screen(self.attribute_template);
320
321        if !self.mode.contains(TerminalMode::ALT_SCREEN) {
322            self.saved_cursor = self.cursor;
323            self.attribute_template = Cell::default();
324        }
325    }
326}
327
328macro_rules! log {
329    ($self:ident, $($arg:tt)*) => {
330        $self.log_message(format_args!($($arg)*))
331    }
332}
333
334impl<D: DrawTarget> Handler for TerminalInner<D> {
335    fn set_title(&mut self, title: Option<String>) {
336        log!(self, "Unhandled set_title: {:?}", title);
337    }
338
339    fn set_cursor_style(&mut self, style: Option<CursorStyle>) {
340        log!(self, "Set cursor style: {:?}", style);
341        if let Some(style) = style {
342            self.set_cursor_shape(style.shape);
343        }
344    }
345
346    fn set_cursor_shape(&mut self, shape: CursorShape) {
347        log!(self, "Set cursor shape: {:?}", shape);
348        self.cursor.shape = shape;
349    }
350
351    fn input(&mut self, content: char) {
352        let index = self.active_charset as usize;
353        let template = self
354            .attribute_template
355            .set_content(self.charsets[index].map(content));
356
357        let width = if template.wide { 2 } else { 1 };
358        if self.cursor.column + width > self.buffer.width() {
359            if !self.mode.contains(TerminalMode::LINE_WRAP) {
360                return;
361            }
362            self.linefeed();
363            self.carriage_return();
364        }
365
366        self.buffer
367            .write(self.cursor.row, self.cursor.column, template);
368        self.cursor.column += 1;
369
370        if template.wide {
371            self.buffer.write(
372                self.cursor.row,
373                self.cursor.column,
374                template.set_placeholder(),
375            );
376            self.cursor.column += 1;
377        }
378    }
379
380    fn goto(&mut self, row: i32, col: usize) {
381        self.cursor.row = min(row as usize, self.buffer.height() - 1);
382        self.cursor.column = min(col, self.buffer.width() - 1);
383    }
384
385    fn goto_line(&mut self, row: i32) {
386        log!(self, "Goto line: {}", row);
387        self.goto(row, self.cursor.column);
388    }
389
390    fn goto_col(&mut self, col: usize) {
391        log!(self, "Goto column: {}", col);
392        self.goto(self.cursor.row as i32, col);
393    }
394
395    fn insert_blank(&mut self, count: usize) {
396        log!(self, "Insert blank: {}", count);
397        let (row, columns) = (self.cursor.row, self.buffer.width());
398        let count = min(count, columns - self.cursor.column);
399
400        let template = self.attribute_template.clear();
401        for column in (self.cursor.column..columns - count).rev() {
402            self.buffer
403                .write(row, column + count, self.buffer.read(row, column));
404            self.buffer.write(row, column, template);
405        }
406    }
407
408    fn move_up(&mut self, rows: usize) {
409        log!(self, "Move up: {}", rows);
410        self.goto(
411            self.cursor.row.saturating_sub(rows) as i32,
412            self.cursor.column,
413        );
414    }
415
416    fn move_down(&mut self, rows: usize) {
417        log!(self, "Move down: {}", rows);
418        let goto_line = min(self.cursor.row + rows, self.buffer.height() - 1) as i32;
419        self.goto(goto_line, self.cursor.column);
420    }
421
422    fn identify_terminal(&mut self, intermediate: Option<char>) {
423        log!(self, "Identify terminal: {:?}", intermediate);
424
425        let version_number = |version: &str| -> usize {
426            let mut result = 0;
427            let semver_versions = version.split('.');
428            for (i, part) in semver_versions.rev().enumerate() {
429                let semver_number = part.parse::<usize>().unwrap_or(0);
430                result += usize::pow(100, i as u32) * semver_number;
431            }
432            result
433        };
434
435        match intermediate {
436            None => self.pty_write(String::from("\x1b[?6c")),
437            Some('>') => {
438                let version = version_number(env!("CARGO_PKG_VERSION"));
439                self.pty_write(format!("\x1b[>0;{version};1c"));
440            }
441            _ => log!(self, "Unsupported device attributes intermediate"),
442        }
443    }
444
445    fn device_status(&mut self, arg: usize) {
446        match arg {
447            5 => self.pty_write(String::from("\x1b[0n")),
448            6 => {
449                let (row, column) = (self.cursor.row, self.cursor.column);
450                self.pty_write(format!("\x1b[{};{}R", row + 1, column + 1));
451            }
452            _ => log!(self, "Unknown device status query: {}", arg),
453        };
454    }
455
456    fn move_forward(&mut self, cols: usize) {
457        log!(self, "Move forward: {}", cols);
458        self.cursor.column = min(self.cursor.column + cols, self.buffer.width() - 1);
459    }
460
461    fn move_backward(&mut self, cols: usize) {
462        log!(self, "Move backward: {}", cols);
463        self.cursor.column = self.cursor.column.saturating_sub(cols);
464    }
465
466    fn move_up_and_cr(&mut self, rows: usize) {
467        log!(self, "Move up and cr: {}", rows);
468        self.goto(self.cursor.row.saturating_sub(rows) as i32, 0);
469    }
470
471    fn move_down_and_cr(&mut self, rows: usize) {
472        log!(self, "Move down and cr: {}", rows);
473        let goto_line = min(self.cursor.row + rows, self.buffer.height() - 1);
474        self.goto(goto_line as i32, 0);
475    }
476
477    fn put_tab(&mut self, count: u16) {
478        log!(self, "Put tab: {}", count);
479        for _ in 0..count {
480            let tab_stop = self.cursor.column.div_ceil(8) * 8;
481            let end_column = tab_stop.min(self.buffer.width());
482            let template = self.attribute_template.clear();
483
484            while self.cursor.column < end_column {
485                self.buffer
486                    .write(self.cursor.row, self.cursor.column, template);
487                self.cursor.column += 1;
488            }
489        }
490    }
491
492    fn backspace(&mut self) {
493        self.cursor.column = self.cursor.column.saturating_sub(1);
494    }
495
496    fn carriage_return(&mut self) {
497        self.cursor.column = 0;
498    }
499
500    fn linefeed(&mut self) {
501        if self.keyboard.crnl_mapping {
502            self.carriage_return();
503        }
504
505        if self.cursor.row == self.scroll_region.end {
506            self.scroll_up(1);
507        } else if self.cursor.row < self.buffer.height() - 1 {
508            self.cursor.row += 1;
509        }
510    }
511
512    fn bell(&mut self) {
513        log!(self, "Bell triggered!");
514        self.bell_handler.map(|handler| handler());
515    }
516
517    fn substitute(&mut self) {
518        log!(self, "Unhandled substitute!");
519    }
520
521    fn newline(&mut self) {
522        self.linefeed();
523
524        if self.mode.contains(TerminalMode::LINE_FEED_NEW_LINE) {
525            self.carriage_return();
526        }
527    }
528
529    fn set_horizontal_tabstop(&mut self) {
530        log!(self, "Unhandled set horizontal tabstop!");
531    }
532
533    fn scroll_up(&mut self, count: usize) {
534        self.buffer.scroll_region(
535            -(count as isize),
536            self.attribute_template,
537            self.scroll_region.clone(),
538        );
539    }
540
541    fn scroll_down(&mut self, count: usize) {
542        self.buffer.scroll_region(
543            count as isize,
544            self.attribute_template,
545            self.scroll_region.clone(),
546        );
547    }
548
549    fn insert_blank_lines(&mut self, count: usize) {
550        log!(self, "Insert blank lines: {}", count);
551        self.scroll_down(count);
552    }
553
554    fn delete_lines(&mut self, count: usize) {
555        log!(self, "Delete lines: {}", count);
556        self.scroll_up(count);
557    }
558
559    fn erase_chars(&mut self, count: usize) {
560        log!(self, "Erase chars: {}", count);
561        let start = self.cursor.column;
562        let end = min(start + count, self.buffer.width());
563
564        let template = self.attribute_template.clear();
565        for column in start..end {
566            self.buffer.write(self.cursor.row, column, template);
567        }
568    }
569
570    fn delete_chars(&mut self, count: usize) {
571        log!(self, "Delete chars: {}", count);
572        let (row, width) = (self.cursor.row, self.buffer.width());
573        let count = min(count, width - self.cursor.column - 1);
574
575        for i in self.cursor.column..width - count {
576            self.buffer.write(row, i, self.buffer.read(row, i + count));
577        }
578
579        for i in width - count..width {
580            self.buffer.write(row, i, self.attribute_template.clear());
581        }
582    }
583
584    fn move_backward_tabs(&mut self, count: u16) {
585        log!(self, "Unhandled move backward tabs: {}", count);
586    }
587
588    fn move_forward_tabs(&mut self, count: u16) {
589        log!(self, "Unhandled move forward tabs: {}", count);
590    }
591
592    fn save_cursor_position(&mut self) {
593        log!(self, "Save cursor position");
594        self.saved_cursor = self.cursor;
595    }
596
597    fn restore_cursor_position(&mut self) {
598        log!(self, "Restore cursor position");
599        self.cursor = self.saved_cursor;
600    }
601
602    fn clear_line(&mut self, mode: LineClearMode) {
603        log!(self, "Clear line: {:?}", mode);
604        let template = self.attribute_template.clear();
605        match mode {
606            LineClearMode::Right => {
607                for column in self.cursor.column..self.buffer.width() {
608                    self.buffer.write(self.cursor.row, column, template);
609                }
610            }
611            LineClearMode::Left => {
612                for column in 0..=self.cursor.column {
613                    self.buffer.write(self.cursor.row, column, template);
614                }
615            }
616            LineClearMode::All => {
617                for column in 0..self.buffer.width() {
618                    self.buffer.write(self.cursor.row, column, template);
619                }
620            }
621        }
622    }
623
624    fn clear_screen(&mut self, mode: ClearMode) {
625        log!(self, "Clear screen: {:?}", mode);
626        let template = self.attribute_template.clear();
627
628        match mode {
629            ClearMode::All | ClearMode::Saved => {
630                self.buffer.clear(template);
631                self.cursor = Cursor::default();
632                if matches!(mode, ClearMode::Saved) {
633                    self.buffer.clear_history();
634                }
635            }
636            ClearMode::Above => {
637                for row in 0..self.cursor.row {
638                    for column in 0..self.buffer.width() {
639                        self.buffer.write(row, column, template);
640                    }
641                }
642                for column in 0..=self.cursor.column {
643                    self.buffer.write(self.cursor.row, column, template);
644                }
645            }
646            ClearMode::Below => {
647                for column in self.cursor.column..self.buffer.width() {
648                    self.buffer.write(self.cursor.row, column, template);
649                }
650                for row in self.cursor.row + 1..self.buffer.height() {
651                    for column in 0..self.buffer.width() {
652                        self.buffer.write(row, column, template);
653                    }
654                }
655            }
656        }
657    }
658
659    fn clear_tabs(&mut self, mode: TabulationClearMode) {
660        log!(self, "Unhandled clear tabs: {:?}", mode);
661    }
662
663    fn reset_state(&mut self) {
664        log!(self, "Reset state");
665        if self.mode.contains(TerminalMode::ALT_SCREEN) {
666            self.swap_alt_screen();
667        }
668        self.buffer.clear(Cell::default());
669        self.cursor = Cursor::default();
670        self.saved_cursor = self.cursor;
671        self.buffer.clear_history();
672        self.mode = TerminalMode::default();
673        self.attribute_template = Cell::default();
674    }
675
676    fn reverse_index(&mut self) {
677        log!(self, "Reverse index");
678        if self.cursor.row == self.scroll_region.start {
679            self.scroll_down(1);
680        } else {
681            self.cursor.row -= 1;
682        }
683    }
684
685    fn terminal_attribute(&mut self, attr: Attr) {
686        match attr {
687            Attr::Foreground(color) => self.attribute_template.foreground = color,
688            Attr::Background(color) => self.attribute_template.background = color,
689            Attr::Reset => self.attribute_template = Cell::default(),
690            Attr::Reverse => self.attribute_template.flags |= Flags::INVERSE,
691            Attr::CancelReverse => self.attribute_template.flags.remove(Flags::INVERSE),
692            Attr::Bold => self.attribute_template.flags.insert(Flags::BOLD),
693            Attr::CancelBold => self.attribute_template.flags.remove(Flags::BOLD),
694            Attr::CancelBoldDim => self.attribute_template.flags.remove(Flags::BOLD),
695            Attr::Italic => self.attribute_template.flags.insert(Flags::ITALIC),
696            Attr::CancelItalic => self.attribute_template.flags.remove(Flags::ITALIC),
697            Attr::Underline => self.attribute_template.flags.insert(Flags::UNDERLINE),
698            Attr::CancelUnderline => self.attribute_template.flags.remove(Flags::UNDERLINE),
699            Attr::Hidden => self.attribute_template.flags.insert(Flags::HIDDEN),
700            Attr::CancelHidden => self.attribute_template.flags.remove(Flags::HIDDEN),
701            _ => log!(self, "Unhandled terminal attribute: {:?}", attr),
702        }
703    }
704
705    fn set_mode(&mut self, mode: Mode) {
706        let mode = match mode {
707            Mode::Named(mode) => mode,
708            Mode::Unknown(mode) => {
709                log!(self, "Ignoring unknown mode {} in set_mode", mode);
710                return;
711            }
712        };
713
714        match mode {
715            NamedMode::Insert => self.mode.insert(TerminalMode::INSERT),
716            NamedMode::LineFeedNewLine => self.mode.insert(TerminalMode::LINE_FEED_NEW_LINE),
717        }
718    }
719
720    fn unset_mode(&mut self, mode: Mode) {
721        let mode = match mode {
722            Mode::Named(mode) => mode,
723            Mode::Unknown(mode) => {
724                log!(self, "Ignoring unknown mode {} in unset_mode", mode);
725                return;
726            }
727        };
728
729        match mode {
730            NamedMode::Insert => self.mode.remove(TerminalMode::INSERT),
731            NamedMode::LineFeedNewLine => self.mode.remove(TerminalMode::LINE_FEED_NEW_LINE),
732        }
733    }
734
735    fn report_mode(&mut self, mode: Mode) {
736        log!(self, "Unhandled report mode: {:?}", mode);
737    }
738
739    fn set_private_mode(&mut self, mode: PrivateMode) {
740        let mode = match mode {
741            PrivateMode::Named(mode) => mode,
742            PrivateMode::Unknown(mode) => {
743                log!(self, "Ignoring unknown mode {} in set_private_mode", mode);
744                return;
745            }
746        };
747
748        match mode {
749            NamedPrivateMode::SwapScreenAndSetRestoreCursor => {
750                if !self.mode.contains(TerminalMode::ALT_SCREEN) {
751                    self.swap_alt_screen();
752                }
753            }
754            NamedPrivateMode::ShowCursor => self.mode.insert(TerminalMode::SHOW_CURSOR),
755            NamedPrivateMode::CursorKeys => {
756                self.mode.insert(TerminalMode::APP_CURSOR);
757                self.keyboard.app_cursor_mode = true;
758            }
759            NamedPrivateMode::LineWrap => self.mode.insert(TerminalMode::LINE_WRAP),
760            NamedPrivateMode::BracketedPaste => self.mode.insert(TerminalMode::BRACKETED_PASTE),
761            _ => log!(self, "Unhandled set mode: {:?}", mode),
762        }
763    }
764
765    fn unset_private_mode(&mut self, mode: PrivateMode) {
766        let mode = match mode {
767            PrivateMode::Named(mode) => mode,
768            PrivateMode::Unknown(mode) => {
769                log!(self, "Ignoring unknown mode {} in unset_private_mode", mode);
770                return;
771            }
772        };
773
774        match mode {
775            NamedPrivateMode::SwapScreenAndSetRestoreCursor => {
776                if self.mode.contains(TerminalMode::ALT_SCREEN) {
777                    self.swap_alt_screen();
778                }
779            }
780            NamedPrivateMode::ShowCursor => self.mode.remove(TerminalMode::SHOW_CURSOR),
781            NamedPrivateMode::CursorKeys => {
782                self.mode.remove(TerminalMode::APP_CURSOR);
783                self.keyboard.app_cursor_mode = false;
784            }
785            NamedPrivateMode::LineWrap => self.mode.remove(TerminalMode::LINE_WRAP),
786            NamedPrivateMode::BracketedPaste => self.mode.remove(TerminalMode::BRACKETED_PASTE),
787            _ => log!(self, "Unhandled unset mode: {:?}", mode),
788        }
789    }
790
791    fn report_private_mode(&mut self, mode: PrivateMode) {
792        log!(self, "Unhandled report private mode: {:?}", mode);
793    }
794
795    fn set_scrolling_region(&mut self, top: usize, bottom: Option<usize>) {
796        log!(
797            self,
798            "Set scrolling region: top={}, bottom={:?}",
799            top,
800            bottom
801        );
802        let bottom = bottom.unwrap_or(self.buffer.height());
803
804        if top >= bottom {
805            log!(self, "Invalid scrolling region: ({};{})", top, bottom);
806            return;
807        }
808
809        self.scroll_region.start = min(top, self.buffer.height()) - 1;
810        self.scroll_region.end = min(bottom, self.buffer.height()) - 1;
811        self.goto(0, 0);
812    }
813
814    fn set_keypad_application_mode(&mut self) {
815        log!(self, "Set keypad application mode");
816        self.mode.insert(TerminalMode::APP_KEYPAD);
817    }
818
819    fn unset_keypad_application_mode(&mut self) {
820        log!(self, "Unset keypad application mode");
821        self.mode.remove(TerminalMode::APP_KEYPAD);
822    }
823
824    fn set_active_charset(&mut self, index: CharsetIndex) {
825        log!(self, "Set active charset: {:?}", index);
826        self.active_charset = index;
827    }
828
829    fn configure_charset(&mut self, index: CharsetIndex, charset: StandardCharset) {
830        log!(self, "Configure charset: {:?}, {:?}", index, charset);
831        self.charsets[index as usize] = charset;
832    }
833
834    fn set_color(&mut self, index: usize, color: Rgb) {
835        log!(self, "Unhandled set color: {}, {:?}", index, color);
836    }
837
838    fn dynamic_color_sequence(&mut self, prefix: String, index: usize, terminator: &str) {
839        log!(
840            self,
841            "Unhandled dynamic color sequence: {}, {}, {}",
842            prefix,
843            index,
844            terminator
845        );
846    }
847
848    fn reset_color(&mut self, index: usize) {
849        log!(self, "Unhandled reset color: {}", index);
850    }
851
852    fn clipboard_store(&mut self, clipboard: u8, base64: &[u8]) {
853        log!(self, "Clipboard store: {}, {:?}", clipboard, base64);
854
855        let text = core::str::from_utf8(base64)
856            .ok()
857            .and_then(|b64| Base64::decode_vec(b64).ok())
858            .and_then(|bytes| String::from_utf8(bytes).ok());
859
860        if let Some(text) = text {
861            self.clipboard.as_mut().map(|c| c.set_text(text));
862        }
863    }
864
865    fn clipboard_load(&mut self, clipboard: u8, terminator: &str) {
866        log!(self, "Clipboard load: {}, {}", clipboard, terminator);
867
868        if let Some(handler) = self.clipboard.as_mut() {
869            let Some(text) = handler.get_text() else {
870                return;
871            };
872
873            let base64 = Base64::encode_string(text.as_bytes());
874            let result = format!("\x1b]52;{};{base64}{terminator}", clipboard as char);
875            self.pty_write(result);
876        };
877    }
878
879    fn decaln(&mut self) {
880        log!(self, "Unhandled decaln!");
881    }
882
883    fn push_title(&mut self) {
884        log!(self, "Unhandled push title!");
885    }
886
887    fn pop_title(&mut self) {
888        log!(self, "Unhandled pop title!");
889    }
890
891    fn text_area_size_pixels(&mut self) {
892        log!(self, "Unhandled text area size pixels!");
893    }
894
895    fn text_area_size_chars(&mut self) {
896        log!(self, "Unhandled text area size chars!");
897    }
898
899    fn set_hyperlink(&mut self, hyperlink: Option<Hyperlink>) {
900        log!(self, "Unhandled set hyperlink: {:?}", hyperlink);
901    }
902
903    fn report_keyboard_mode(&mut self) {
904        log!(self, "Report keyboard mode!");
905        let current_mode = KeyboardModes::NO_MODE.bits();
906        self.pty_write(format!("\x1b[?{current_mode}u"));
907    }
908
909    fn push_keyboard_mode(&mut self, mode: KeyboardModes) {
910        log!(self, "Unhandled push keyboard mode: {:?}", mode);
911    }
912
913    fn pop_keyboard_modes(&mut self, to_pop: u16) {
914        log!(self, "Unhandled pop keyboard modes: {}", to_pop);
915    }
916}