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}