1use std::{
2 cell::{
3 Ref,
4 RefCell,
5 },
6 io::Write,
7 rc::Rc,
8 time::Instant,
9};
10
11use freya_core::{
12 notify::ArcNotify,
13 prelude::{
14 Platform,
15 TaskHandle,
16 UseId,
17 UserEvent,
18 },
19};
20use futures_channel::mpsc::UnboundedSender;
21use keyboard_types::Modifiers;
22use vt100::Parser;
23
24use crate::{
25 buffer::{
26 TerminalBuffer,
27 TerminalSelection,
28 },
29 parser::{
30 TerminalMouseButton,
31 encode_mouse_move,
32 encode_mouse_press,
33 encode_mouse_release,
34 encode_wheel_event,
35 },
36 pty::{
37 ScrollCommand,
38 spawn_pty,
39 },
40};
41
42type ResizeSender = Rc<UnboundedSender<(u16, u16)>>;
43type ScrollSender = Rc<UnboundedSender<ScrollCommand>>;
44
45#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
47pub struct TerminalId(pub usize);
48
49impl TerminalId {
50 pub fn new() -> Self {
51 Self(UseId::<TerminalId>::get_in_hook())
52 }
53}
54
55impl Default for TerminalId {
56 fn default() -> Self {
57 Self::new()
58 }
59}
60
61#[derive(Debug, thiserror::Error)]
63pub enum TerminalError {
64 #[error("PTY error: {0}")]
65 PtyError(String),
66
67 #[error("Write error: {0}")]
68 WriteError(String),
69
70 #[error("Terminal not initialized")]
71 NotInitialized,
72}
73
74pub(crate) struct TerminalCleaner {
76 pub(crate) writer: Rc<RefCell<Option<Box<dyn Write + Send>>>>,
78 pub(crate) reader_task: TaskHandle,
80 pub(crate) pty_task: TaskHandle,
81 pub(crate) closer_notifier: ArcNotify,
83}
84
85impl Drop for TerminalCleaner {
86 fn drop(&mut self) {
87 *self.writer.borrow_mut() = None;
88 self.reader_task.try_cancel();
89 self.pty_task.try_cancel();
90 self.closer_notifier.notify();
91 }
92}
93
94#[derive(Clone)]
101#[allow(dead_code)]
102pub struct TerminalHandle {
103 pub(crate) id: TerminalId,
105 pub(crate) buffer: Rc<RefCell<TerminalBuffer>>,
107 pub(crate) parser: Rc<RefCell<Parser>>,
109 pub(crate) writer: Rc<RefCell<Option<Box<dyn Write + Send>>>>,
111 pub(crate) resize_sender: ResizeSender,
113 pub(crate) scroll_sender: ScrollSender,
115 pub(crate) closer_notifier: ArcNotify,
117 pub(crate) cleaner: Rc<TerminalCleaner>,
119 pub(crate) output_notifier: ArcNotify,
121 pub(crate) last_write_time: Rc<RefCell<Instant>>,
123 pub(crate) pressed_button: Rc<RefCell<Option<TerminalMouseButton>>>,
125 pub(crate) modifiers: Rc<RefCell<Modifiers>>,
127}
128
129impl PartialEq for TerminalHandle {
130 fn eq(&self, other: &Self) -> bool {
131 self.id == other.id
132 }
133}
134
135impl TerminalHandle {
136 pub fn new(
150 id: TerminalId,
151 command: portable_pty::CommandBuilder,
152 scrollback_length: Option<usize>,
153 ) -> Result<Self, TerminalError> {
154 spawn_pty(id, command, scrollback_length.unwrap_or(1000))
155 }
156
157 pub fn write(&self, data: &[u8]) -> Result<(), TerminalError> {
167 self.write_raw(data)?;
168 let mut buffer = self.buffer.borrow_mut();
169 buffer.selection = None;
170 buffer.scroll_offset = 0;
171 *self.last_write_time.borrow_mut() = Instant::now();
172 let _ = self.scroll_sender.unbounded_send(ScrollCommand::ToBottom);
173 Ok(())
174 }
175
176 fn write_raw(&self, data: &[u8]) -> Result<(), TerminalError> {
178 match &mut *self.writer.borrow_mut() {
179 Some(w) => {
180 w.write_all(data)
181 .map_err(|e| TerminalError::WriteError(e.to_string()))?;
182 w.flush()
183 .map_err(|e| TerminalError::WriteError(e.to_string()))?;
184 Ok(())
185 }
186 None => Err(TerminalError::NotInitialized),
187 }
188 }
189
190 pub fn resize(&self, rows: u16, cols: u16) {
200 let _ = self.resize_sender.unbounded_send((rows, cols));
201 }
202
203 pub fn scroll(&self, delta: i32) {
214 let mut buffer = self.buffer.borrow_mut();
215 let new_offset = (buffer.scroll_offset as i64 + delta as i64).max(0) as usize;
216 buffer.scroll_offset = new_offset.min(buffer.total_scrollback);
217 let _ = self
218 .scroll_sender
219 .unbounded_send(ScrollCommand::Delta(delta));
220 }
221
222 pub fn scroll_to_bottom(&self) {
232 self.buffer.borrow_mut().scroll_offset = 0;
233 let _ = self.scroll_sender.unbounded_send(ScrollCommand::ToBottom);
234 }
235
236 pub fn scrollback_position(&self) -> usize {
246 self.buffer.borrow().scroll_offset
247 }
248
249 pub fn send_wheel_to_pty(&self, row: usize, col: usize, delta_y: f64) {
255 let encoding = self.parser.borrow().screen().mouse_protocol_encoding();
256 let seq = encode_wheel_event(row, col, delta_y, encoding);
257 let _ = self.write_raw(seq.as_bytes());
258 }
259
260 pub fn mouse_move(&self, row: usize, col: usize) {
271 let is_dragging = self.pressed_button.borrow().is_some();
272
273 if self.modifiers.borrow().contains(Modifiers::SHIFT) && is_dragging {
274 self.update_selection(row, col);
276 return;
277 }
278
279 let parser = self.parser.borrow();
280 let mouse_mode = parser.screen().mouse_protocol_mode();
281 let encoding = parser.screen().mouse_protocol_encoding();
282
283 let held = *self.pressed_button.borrow();
284
285 match mouse_mode {
286 vt100::MouseProtocolMode::AnyMotion => {
287 let seq = encode_mouse_move(row, col, held, encoding);
288 let _ = self.write_raw(seq.as_bytes());
289 }
290 vt100::MouseProtocolMode::ButtonMotion => {
291 if let Some(button) = held {
292 let seq = encode_mouse_move(row, col, Some(button), encoding);
293 let _ = self.write_raw(seq.as_bytes());
294 }
295 }
296 vt100::MouseProtocolMode::None => {
297 if is_dragging {
299 self.update_selection(row, col);
300 }
301 }
302 _ => {}
303 }
304 }
305
306 fn is_mouse_tracking_enabled(&self) -> bool {
308 let parser = self.parser.borrow();
309 parser.screen().mouse_protocol_mode() != vt100::MouseProtocolMode::None
310 }
311
312 pub fn mouse_down(&self, row: usize, col: usize, button: TerminalMouseButton) {
321 *self.pressed_button.borrow_mut() = Some(button);
322
323 if self.modifiers.borrow().contains(Modifiers::SHIFT) {
324 self.start_selection(row, col);
326 } else if self.is_mouse_tracking_enabled() {
327 let encoding = self.parser.borrow().screen().mouse_protocol_encoding();
328 let seq = encode_mouse_press(row, col, button, encoding);
329 let _ = self.write_raw(seq.as_bytes());
330 } else {
331 self.start_selection(row, col);
332 }
333 }
334
335 pub fn mouse_up(&self, row: usize, col: usize, button: TerminalMouseButton) {
345 *self.pressed_button.borrow_mut() = None;
346
347 if self.modifiers.borrow().contains(Modifiers::SHIFT) {
348 self.end_selection();
350 return;
351 }
352
353 let parser = self.parser.borrow();
354 let mouse_mode = parser.screen().mouse_protocol_mode();
355 let encoding = parser.screen().mouse_protocol_encoding();
356
357 match mouse_mode {
358 vt100::MouseProtocolMode::PressRelease
359 | vt100::MouseProtocolMode::ButtonMotion
360 | vt100::MouseProtocolMode::AnyMotion => {
361 let seq = encode_mouse_release(row, col, button, encoding);
362 let _ = self.write_raw(seq.as_bytes());
363 }
364 vt100::MouseProtocolMode::Press => {
365 }
367 vt100::MouseProtocolMode::None => {
368 self.end_selection();
369 }
370 }
371 }
372
373 const ALTERNATE_SCROLL_LINES: usize = 3;
375
376 pub fn wheel(&self, delta_y: f64, row: usize, col: usize) {
387 let scroll_delta = if delta_y > 0.0 { 3 } else { -3 };
388 let scroll_offset = self.buffer.borrow().scroll_offset;
389 let (mouse_mode, alt_screen, app_cursor) = {
390 let parser = self.parser.borrow();
391 let screen = parser.screen();
392 (
393 screen.mouse_protocol_mode(),
394 screen.alternate_screen(),
395 screen.application_cursor(),
396 )
397 };
398
399 if scroll_offset > 0 {
400 let delta = scroll_delta;
402 self.scroll(delta);
403 } else if mouse_mode != vt100::MouseProtocolMode::None {
404 self.send_wheel_to_pty(row, col, delta_y);
406 } else if alt_screen {
407 let key = match (delta_y > 0.0, app_cursor) {
410 (true, true) => "\x1bOA",
411 (true, false) => "\x1b[A",
412 (false, true) => "\x1bOB",
413 (false, false) => "\x1b[B",
414 };
415 for _ in 0..Self::ALTERNATE_SCROLL_LINES {
416 let _ = self.write_raw(key.as_bytes());
417 }
418 } else {
419 let delta = scroll_delta;
421 self.scroll(delta);
422 }
423 }
424
425 pub fn read_buffer(&'_ self) -> Ref<'_, TerminalBuffer> {
427 self.buffer.borrow()
428 }
429
430 pub fn output_received(&self) -> impl std::future::Future<Output = ()> + '_ {
434 self.output_notifier.notified()
435 }
436
437 pub fn last_write_elapsed(&self) -> std::time::Duration {
438 self.last_write_time.borrow().elapsed()
439 }
440
441 pub fn closed(&self) -> impl std::future::Future<Output = ()> + '_ {
454 self.closer_notifier.notified()
455 }
456
457 pub fn id(&self) -> TerminalId {
459 self.id
460 }
461
462 pub fn shift_pressed(&self, pressed: bool) {
467 let mut mods = self.modifiers.borrow_mut();
468 if pressed {
469 mods.insert(Modifiers::SHIFT);
470 } else {
471 mods.remove(Modifiers::SHIFT);
472 }
473 }
474
475 pub fn get_selection(&self) -> Option<TerminalSelection> {
477 self.buffer.borrow().selection.clone()
478 }
479
480 pub fn set_selection(&self, selection: Option<TerminalSelection>) {
482 self.buffer.borrow_mut().selection = selection;
483 }
484
485 pub fn start_selection(&self, row: usize, col: usize) {
486 let mut buffer = self.buffer.borrow_mut();
487 let scroll = buffer.scroll_offset;
488 buffer.selection = Some(TerminalSelection {
489 dragging: true,
490 start_row: row,
491 start_col: col,
492 start_scroll: scroll,
493 end_row: row,
494 end_col: col,
495 end_scroll: scroll,
496 });
497 Platform::get().send(UserEvent::RequestRedraw);
498 }
499
500 pub fn update_selection(&self, row: usize, col: usize) {
501 let mut buffer = self.buffer.borrow_mut();
502 let scroll = buffer.scroll_offset;
503 if let Some(selection) = &mut buffer.selection
504 && selection.dragging
505 {
506 selection.end_row = row;
507 selection.end_col = col;
508 selection.end_scroll = scroll;
509 Platform::get().send(UserEvent::RequestRedraw);
510 }
511 }
512
513 pub fn end_selection(&self) {
514 if let Some(selection) = &mut self.buffer.borrow_mut().selection {
515 selection.dragging = false;
516 Platform::get().send(UserEvent::RequestRedraw);
517 }
518 }
519
520 pub fn clear_selection(&self) {
522 self.buffer.borrow_mut().selection = None;
523 Platform::get().send(UserEvent::RequestRedraw);
524 }
525
526 pub fn get_selected_text(&self) -> Option<String> {
527 let buffer = self.buffer.borrow();
528 let selection = buffer.selection.clone()?;
529 if selection.is_empty() {
530 return None;
531 }
532
533 let scroll = buffer.scroll_offset;
534 let (display_start, start_col, display_end, end_col) = selection.display_positions(scroll);
535
536 let mut parser = self.parser.borrow_mut();
537 let saved_scrollback = parser.screen().scrollback();
538 let (_rows, cols) = parser.screen().size();
539
540 let mut lines = Vec::new();
541
542 for d in display_start..=display_end {
543 let cp = d - scroll as i64;
544 let needed_scrollback = (-cp).max(0) as usize;
545 let viewport_row = cp.max(0) as u16;
546
547 parser.screen_mut().set_scrollback(needed_scrollback);
548
549 let row_cells: Vec<_> = (0..cols)
550 .filter_map(|c| parser.screen().cell(viewport_row, c).cloned())
551 .collect();
552
553 let is_single = display_start == display_end;
554 let is_first = d == display_start;
555 let is_last = d == display_end;
556
557 let cells = if is_single {
558 let s = start_col.min(row_cells.len());
559 let e = end_col.min(row_cells.len());
560 &row_cells[s..e]
561 } else if is_first {
562 let s = start_col.min(row_cells.len());
563 &row_cells[s..]
564 } else if is_last {
565 &row_cells[..end_col.min(row_cells.len())]
566 } else {
567 &row_cells
568 };
569
570 let line: String = cells
571 .iter()
572 .map(|cell| {
573 if cell.has_contents() {
574 cell.contents()
575 } else {
576 " "
577 }
578 })
579 .collect::<String>();
580
581 lines.push(line);
582 }
583
584 parser.screen_mut().set_scrollback(saved_scrollback);
585
586 Some(lines.join("\n"))
587 }
588}