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