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}