terminal_scrollbar.rs

 1use std::{
 2    cell::{Cell, RefCell},
 3    rc::Rc,
 4};
 5
 6use gpui::{Bounds, Point, Size, size};
 7use terminal::Terminal;
 8use ui::{Pixels, ScrollableHandle, px};
 9
10#[derive(Debug)]
11struct ScrollHandleState {
12    line_height: Pixels,
13    total_lines: usize,
14    viewport_lines: usize,
15    display_offset: usize,
16}
17
18impl ScrollHandleState {
19    fn new(terminal: &Terminal) -> Self {
20        Self {
21            line_height: terminal.last_content().terminal_bounds.line_height,
22            total_lines: terminal.total_lines(),
23            viewport_lines: terminal.viewport_lines(),
24            display_offset: terminal.last_content().display_offset,
25        }
26    }
27}
28
29#[derive(Debug, Clone)]
30pub struct TerminalScrollHandle {
31    state: Rc<RefCell<ScrollHandleState>>,
32    pub future_display_offset: Rc<Cell<Option<usize>>>,
33}
34
35impl TerminalScrollHandle {
36    pub fn new(terminal: &Terminal) -> Self {
37        Self {
38            state: Rc::new(RefCell::new(ScrollHandleState::new(terminal))),
39            future_display_offset: Rc::new(Cell::new(None)),
40        }
41    }
42
43    pub fn update(&self, terminal: &Terminal) {
44        *self.state.borrow_mut() = ScrollHandleState::new(terminal);
45    }
46}
47
48impl ScrollableHandle for TerminalScrollHandle {
49    fn max_offset(&self) -> Size<Pixels> {
50        let state = self.state.borrow();
51        size(
52            Pixels::ZERO,
53            state
54                .total_lines
55                .checked_sub(state.viewport_lines)
56                .unwrap_or(0) as f32
57                * state.line_height,
58        )
59    }
60
61    fn offset(&self) -> Point<Pixels> {
62        let state = self.state.borrow();
63        let scroll_offset = state.total_lines - state.viewport_lines - state.display_offset;
64        Point::new(
65            px(0.),
66            -px(scroll_offset as f32 * self.state.borrow().line_height.0),
67        )
68    }
69
70    fn set_offset(&self, point: Point<Pixels>) {
71        let state = self.state.borrow();
72        let offset_delta = (point.y.0 / state.line_height.0).round() as i32;
73
74        let max_offset = state.total_lines - state.viewport_lines;
75        let display_offset = (max_offset as i32 + offset_delta).clamp(0, max_offset as i32);
76
77        self.future_display_offset
78            .set(Some(display_offset as usize));
79    }
80
81    fn viewport(&self) -> Bounds<Pixels> {
82        let state = self.state.borrow();
83        Bounds::new(
84            Point::new(px(0.), px(0.)),
85            size(
86                px(0.),
87                px(state.viewport_lines as f32 * state.line_height.0),
88            ),
89        )
90    }
91}