1use std::{
2 cell::{
3 Ref,
4 RefCell,
5 },
6 io::Write,
7 path::PathBuf,
8 rc::Rc,
9 time::Instant,
10};
11
12use freya_core::{
13 notify::ArcNotify,
14 prelude::{
15 Platform,
16 TaskHandle,
17 UseId,
18 UserEvent,
19 },
20};
21use keyboard_types::Modifiers;
22use portable_pty::{
23 MasterPty,
24 PtySize,
25};
26use vt100::Parser;
27
28use crate::{
29 buffer::{
30 TerminalBuffer,
31 TerminalSelection,
32 },
33 parser::{
34 TerminalMouseButton,
35 encode_mouse_move,
36 encode_mouse_press,
37 encode_mouse_release,
38 encode_wheel_event,
39 },
40 pty::{
41 extract_buffer,
42 query_max_scrollback,
43 spawn_pty,
44 },
45};
46
47#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
49pub struct TerminalId(pub usize);
50
51impl TerminalId {
52 pub fn new() -> Self {
53 Self(UseId::<TerminalId>::get_in_hook())
54 }
55}
56
57impl Default for TerminalId {
58 fn default() -> Self {
59 Self::new()
60 }
61}
62
63#[derive(Debug, thiserror::Error)]
65pub enum TerminalError {
66 #[error("PTY error: {0}")]
67 PtyError(String),
68
69 #[error("Write error: {0}")]
70 WriteError(String),
71
72 #[error("Terminal not initialized")]
73 NotInitialized,
74}
75
76pub(crate) struct TerminalCleaner {
78 pub(crate) writer: Rc<RefCell<Option<Box<dyn Write + Send>>>>,
80 pub(crate) reader_task: TaskHandle,
82 pub(crate) pty_task: TaskHandle,
83 pub(crate) closer_notifier: ArcNotify,
85}
86
87impl Drop for TerminalCleaner {
88 fn drop(&mut self) {
89 *self.writer.borrow_mut() = None;
90 self.reader_task.try_cancel();
91 self.pty_task.try_cancel();
92 self.closer_notifier.notify();
93 }
94}
95
96#[derive(Clone)]
103#[allow(dead_code)]
104pub struct TerminalHandle {
105 pub(crate) id: TerminalId,
107 pub(crate) buffer: Rc<RefCell<TerminalBuffer>>,
109 pub(crate) parser: Rc<RefCell<Parser>>,
111 pub(crate) writer: Rc<RefCell<Option<Box<dyn Write + Send>>>>,
113 pub(crate) master: Rc<RefCell<Box<dyn MasterPty + Send>>>,
115 pub(crate) cwd: Rc<RefCell<Option<PathBuf>>>,
117 pub(crate) title: Rc<RefCell<Option<String>>>,
119 pub(crate) closer_notifier: ArcNotify,
121 pub(crate) cleaner: Rc<TerminalCleaner>,
123 pub(crate) output_notifier: ArcNotify,
125 pub(crate) title_notifier: ArcNotify,
127 pub(crate) last_write_time: Rc<RefCell<Instant>>,
129 pub(crate) pressed_button: Rc<RefCell<Option<TerminalMouseButton>>>,
131 pub(crate) modifiers: Rc<RefCell<Modifiers>>,
133}
134
135impl PartialEq for TerminalHandle {
136 fn eq(&self, other: &Self) -> bool {
137 self.id == other.id
138 }
139}
140
141impl TerminalHandle {
142 pub fn new(
156 id: TerminalId,
157 command: portable_pty::CommandBuilder,
158 scrollback_length: Option<usize>,
159 ) -> Result<Self, TerminalError> {
160 spawn_pty(id, command, scrollback_length.unwrap_or(1000))
161 }
162
163 fn refresh_buffer(&self) {
165 let mut parser = self.parser.borrow_mut();
166 let total_scrollback = query_max_scrollback(&mut parser);
167
168 let mut buffer = self.buffer.borrow_mut();
169 buffer.scroll_offset = buffer.scroll_offset.min(total_scrollback);
170
171 parser.screen_mut().set_scrollback(buffer.scroll_offset);
172 let mut new_buffer = extract_buffer(&parser, buffer.scroll_offset, total_scrollback);
173 parser.screen_mut().set_scrollback(0);
174
175 new_buffer.selection = buffer.selection.take();
176 *buffer = new_buffer;
177 }
178
179 pub fn write(&self, data: &[u8]) -> Result<(), TerminalError> {
189 self.write_raw(data)?;
190 let mut buffer = self.buffer.borrow_mut();
191 buffer.selection = None;
192 buffer.scroll_offset = 0;
193 drop(buffer);
194 *self.last_write_time.borrow_mut() = Instant::now();
195 self.scroll_to_bottom();
196 Ok(())
197 }
198
199 pub fn paste(&self, text: &str) -> Result<(), TerminalError> {
203 let bracketed = self.parser.borrow().screen().bracketed_paste();
204
205 let mut data = Vec::with_capacity(text.len() + 12);
206 if bracketed {
207 data.extend_from_slice(b"\x1b[200~");
208 }
209 data.extend_from_slice(text.as_bytes());
210 if bracketed {
211 data.extend_from_slice(b"\x1b[201~");
212 }
213
214 self.write(&data)
215 }
216
217 fn write_raw(&self, data: &[u8]) -> Result<(), TerminalError> {
219 match &mut *self.writer.borrow_mut() {
220 Some(w) => {
221 w.write_all(data)
222 .map_err(|e| TerminalError::WriteError(e.to_string()))?;
223 w.flush()
224 .map_err(|e| TerminalError::WriteError(e.to_string()))?;
225 Ok(())
226 }
227 None => Err(TerminalError::NotInitialized),
228 }
229 }
230
231 pub fn resize(&self, rows: u16, cols: u16) {
241 self.parser.borrow_mut().screen_mut().set_size(rows, cols);
242 self.refresh_buffer();
243 let _ = self.master.borrow().resize(PtySize {
244 rows,
245 cols,
246 pixel_width: 0,
247 pixel_height: 0,
248 });
249 }
250
251 pub fn scroll(&self, delta: i32) {
262 if self.parser.borrow().screen().alternate_screen() {
263 return;
264 }
265
266 {
267 let mut buffer = self.buffer.borrow_mut();
268 let new_offset = (buffer.scroll_offset as i64 + delta as i64).max(0) as usize;
269 buffer.scroll_offset = new_offset.min(buffer.total_scrollback);
270 }
271
272 self.refresh_buffer();
273 Platform::get().send(UserEvent::RequestRedraw);
274 }
275
276 pub fn scroll_to_bottom(&self) {
286 if self.parser.borrow().screen().alternate_screen() {
287 return;
288 }
289
290 self.buffer.borrow_mut().scroll_offset = 0;
291 self.refresh_buffer();
292 Platform::get().send(UserEvent::RequestRedraw);
293 }
294
295 pub fn scrollback_position(&self) -> usize {
305 self.buffer.borrow().scroll_offset
306 }
307
308 pub fn cwd(&self) -> Option<PathBuf> {
312 self.cwd.borrow().clone()
313 }
314
315 pub fn title(&self) -> Option<String> {
319 self.title.borrow().clone()
320 }
321
322 pub fn send_wheel_to_pty(&self, row: usize, col: usize, delta_y: f64) {
328 let encoding = self.parser.borrow().screen().mouse_protocol_encoding();
329 let seq = encode_wheel_event(row, col, delta_y, encoding);
330 let _ = self.write_raw(seq.as_bytes());
331 }
332
333 pub fn mouse_move(&self, row: usize, col: usize) {
344 let is_dragging = self.pressed_button.borrow().is_some();
345
346 if self.modifiers.borrow().contains(Modifiers::SHIFT) && is_dragging {
347 self.update_selection(row, col);
349 return;
350 }
351
352 let parser = self.parser.borrow();
353 let mouse_mode = parser.screen().mouse_protocol_mode();
354 let encoding = parser.screen().mouse_protocol_encoding();
355
356 let held = *self.pressed_button.borrow();
357
358 match mouse_mode {
359 vt100::MouseProtocolMode::AnyMotion => {
360 let seq = encode_mouse_move(row, col, held, encoding);
361 let _ = self.write_raw(seq.as_bytes());
362 }
363 vt100::MouseProtocolMode::ButtonMotion => {
364 if let Some(button) = held {
365 let seq = encode_mouse_move(row, col, Some(button), encoding);
366 let _ = self.write_raw(seq.as_bytes());
367 }
368 }
369 vt100::MouseProtocolMode::None => {
370 if is_dragging {
372 self.update_selection(row, col);
373 }
374 }
375 _ => {}
376 }
377 }
378
379 fn is_mouse_tracking_enabled(&self) -> bool {
381 let parser = self.parser.borrow();
382 parser.screen().mouse_protocol_mode() != vt100::MouseProtocolMode::None
383 }
384
385 pub fn mouse_down(&self, row: usize, col: usize, button: TerminalMouseButton) {
394 *self.pressed_button.borrow_mut() = Some(button);
395
396 if self.modifiers.borrow().contains(Modifiers::SHIFT) {
397 self.start_selection(row, col);
399 } else if self.is_mouse_tracking_enabled() {
400 let encoding = self.parser.borrow().screen().mouse_protocol_encoding();
401 let seq = encode_mouse_press(row, col, button, encoding);
402 let _ = self.write_raw(seq.as_bytes());
403 } else {
404 self.start_selection(row, col);
405 }
406 }
407
408 pub fn mouse_up(&self, row: usize, col: usize, button: TerminalMouseButton) {
418 *self.pressed_button.borrow_mut() = None;
419
420 if self.modifiers.borrow().contains(Modifiers::SHIFT) {
421 self.end_selection();
423 return;
424 }
425
426 let parser = self.parser.borrow();
427 let mouse_mode = parser.screen().mouse_protocol_mode();
428 let encoding = parser.screen().mouse_protocol_encoding();
429
430 match mouse_mode {
431 vt100::MouseProtocolMode::PressRelease
432 | vt100::MouseProtocolMode::ButtonMotion
433 | vt100::MouseProtocolMode::AnyMotion => {
434 let seq = encode_mouse_release(row, col, button, encoding);
435 let _ = self.write_raw(seq.as_bytes());
436 }
437 vt100::MouseProtocolMode::Press => {
438 }
440 vt100::MouseProtocolMode::None => {
441 self.end_selection();
442 }
443 }
444 }
445
446 const ALTERNATE_SCROLL_LINES: usize = 3;
448
449 pub fn release(&self) {
454 *self.pressed_button.borrow_mut() = None;
455 self.end_selection();
456 }
457
458 pub fn wheel(&self, delta_y: f64, row: usize, col: usize) {
469 let scroll_delta = if delta_y > 0.0 { 3 } else { -3 };
470 let scroll_offset = self.buffer.borrow().scroll_offset;
471 let (mouse_mode, alt_screen, app_cursor) = {
472 let parser = self.parser.borrow();
473 let screen = parser.screen();
474 (
475 screen.mouse_protocol_mode(),
476 screen.alternate_screen(),
477 screen.application_cursor(),
478 )
479 };
480
481 if scroll_offset > 0 {
482 let delta = scroll_delta;
484 self.scroll(delta);
485 } else if mouse_mode != vt100::MouseProtocolMode::None {
486 self.send_wheel_to_pty(row, col, delta_y);
488 } else if alt_screen {
489 let key = match (delta_y > 0.0, app_cursor) {
492 (true, true) => "\x1bOA",
493 (true, false) => "\x1b[A",
494 (false, true) => "\x1bOB",
495 (false, false) => "\x1b[B",
496 };
497 for _ in 0..Self::ALTERNATE_SCROLL_LINES {
498 let _ = self.write_raw(key.as_bytes());
499 }
500 } else {
501 let delta = scroll_delta;
503 self.scroll(delta);
504 }
505 }
506
507 pub fn read_buffer(&'_ self) -> Ref<'_, TerminalBuffer> {
509 self.buffer.borrow()
510 }
511
512 pub fn output_received(&self) -> impl std::future::Future<Output = ()> + '_ {
516 self.output_notifier.notified()
517 }
518
519 pub fn title_changed(&self) -> impl std::future::Future<Output = ()> + '_ {
523 self.title_notifier.notified()
524 }
525
526 pub fn last_write_elapsed(&self) -> std::time::Duration {
527 self.last_write_time.borrow().elapsed()
528 }
529
530 pub fn closed(&self) -> impl std::future::Future<Output = ()> + '_ {
543 self.closer_notifier.notified()
544 }
545
546 pub fn id(&self) -> TerminalId {
548 self.id
549 }
550
551 pub fn shift_pressed(&self, pressed: bool) {
556 let mut mods = self.modifiers.borrow_mut();
557 if pressed {
558 mods.insert(Modifiers::SHIFT);
559 } else {
560 mods.remove(Modifiers::SHIFT);
561 }
562 }
563
564 pub fn get_selection(&self) -> Option<TerminalSelection> {
566 self.buffer.borrow().selection.clone()
567 }
568
569 pub fn set_selection(&self, selection: Option<TerminalSelection>) {
571 self.buffer.borrow_mut().selection = selection;
572 }
573
574 pub fn start_selection(&self, row: usize, col: usize) {
575 let mut buffer = self.buffer.borrow_mut();
576 let scroll = buffer.scroll_offset;
577 buffer.selection = Some(TerminalSelection {
578 dragging: true,
579 start_row: row,
580 start_col: col,
581 start_scroll: scroll,
582 end_row: row,
583 end_col: col,
584 end_scroll: scroll,
585 });
586 Platform::get().send(UserEvent::RequestRedraw);
587 }
588
589 pub fn update_selection(&self, row: usize, col: usize) {
590 let mut buffer = self.buffer.borrow_mut();
591 let scroll = buffer.scroll_offset;
592 if let Some(selection) = &mut buffer.selection
593 && selection.dragging
594 {
595 selection.end_row = row;
596 selection.end_col = col;
597 selection.end_scroll = scroll;
598 Platform::get().send(UserEvent::RequestRedraw);
599 }
600 }
601
602 pub fn end_selection(&self) {
603 if let Some(selection) = &mut self.buffer.borrow_mut().selection {
604 selection.dragging = false;
605 Platform::get().send(UserEvent::RequestRedraw);
606 }
607 }
608
609 pub fn clear_selection(&self) {
611 self.buffer.borrow_mut().selection = None;
612 Platform::get().send(UserEvent::RequestRedraw);
613 }
614
615 pub fn get_selected_text(&self) -> Option<String> {
616 let buffer = self.buffer.borrow();
617 let selection = buffer.selection.clone()?;
618 if selection.is_empty() {
619 return None;
620 }
621
622 let scroll = buffer.scroll_offset;
623 let (display_start, start_col, display_end, end_col) = selection.display_positions(scroll);
624
625 let mut parser = self.parser.borrow_mut();
626 let saved_scrollback = parser.screen().scrollback();
627 let (_rows, cols) = parser.screen().size();
628
629 let mut lines = Vec::new();
630
631 for d in display_start..=display_end {
632 let cp = d - scroll as i64;
633 let needed_scrollback = (-cp).max(0) as usize;
634 let viewport_row = cp.max(0) as u16;
635
636 parser.screen_mut().set_scrollback(needed_scrollback);
637
638 let row_cells: Vec<_> = (0..cols)
639 .filter_map(|c| parser.screen().cell(viewport_row, c).cloned())
640 .collect();
641
642 let is_single = display_start == display_end;
643 let is_first = d == display_start;
644 let is_last = d == display_end;
645
646 let cells = if is_single {
647 let s = start_col.min(row_cells.len());
648 let e = end_col.min(row_cells.len());
649 &row_cells[s..e]
650 } else if is_first {
651 let s = start_col.min(row_cells.len());
652 &row_cells[s..]
653 } else if is_last {
654 &row_cells[..end_col.min(row_cells.len())]
655 } else {
656 &row_cells
657 };
658
659 let line: String = cells
660 .iter()
661 .map(|cell| {
662 if cell.has_contents() {
663 cell.contents()
664 } else {
665 " "
666 }
667 })
668 .collect::<String>();
669
670 lines.push(line);
671 }
672
673 parser.screen_mut().set_scrollback(saved_scrollback);
674
675 Some(lines.join("\n"))
676 }
677}