diff options
| author | Ogulcan Celik <ogulcancelik@gmail.com> | 2026-03-30 13:28:58 +0300 |
|---|---|---|
| committer | Ogulcan Celik <ogulcancelik@gmail.com> | 2026-03-30 13:28:58 +0300 |
| commit | 78166728a1eaf55c41f2a546da035e9edefadf1a (patch) | |
| tree | 0fea36962c618ad9f1c58d9092f4f5d17ee86325 /src/pane.rs | |
| parent | 741fbbe8b30774247f8a68831d8df469672e1947 (diff) | |
feat: add interactive pane scrollbars
Diffstat (limited to 'src/pane.rs')
| -rw-r--r-- | src/pane.rs | 52 |
1 files changed, 50 insertions, 2 deletions
diff --git a/src/pane.rs b/src/pane.rs index d1d5bae0..6c346f20 100644 --- a/src/pane.rs +++ b/src/pane.rs @@ -93,6 +93,13 @@ pub struct PaneRuntime { detect_handle: tokio::task::AbortHandle, } +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct ScrollMetrics { + pub offset_from_bottom: usize, + pub max_offset_from_bottom: usize, + pub viewport_rows: usize, +} + impl Drop for PaneRuntime { fn drop(&mut self) { // Abort detection task immediately. @@ -108,12 +115,19 @@ fn trim_trailing_blank_rows(rows: &mut Vec<String>) { } } -fn recent_text_from_parser(parser: &mut vt100::Parser<PtyResponses>, lines: usize) -> String { +fn max_scrollback(parser: &mut vt100::Parser<PtyResponses>) -> usize { let screen = parser.screen_mut(); let original_scrollback = screen.scrollback(); - screen.set_scrollback(usize::MAX); let max_scrollback = screen.scrollback(); + screen.set_scrollback(original_scrollback); + max_scrollback +} + +fn recent_text_from_parser(parser: &mut vt100::Parser<PtyResponses>, lines: usize) -> String { + let max_scrollback = max_scrollback(parser); + let screen = parser.screen_mut(); + let original_scrollback = screen.scrollback(); let (_, cols) = screen.size(); screen.set_scrollback(0); @@ -462,6 +476,27 @@ impl PaneRuntime { } } + /// Set scrollback offset measured from the live bottom of the terminal. + pub fn set_scroll_offset_from_bottom(&self, lines: usize) { + if let Ok(mut p) = self.parser.write() { + p.screen_mut().set_scrollback(lines); + } + } + + pub fn scroll_metrics(&self) -> Option<ScrollMetrics> { + let Ok(mut parser) = self.parser.write() else { + return None; + }; + let max_offset_from_bottom = max_scrollback(&mut parser); + let screen = parser.screen(); + let (viewport_rows, _) = screen.size(); + Some(ScrollMetrics { + offset_from_bottom: screen.scrollback(), + max_offset_from_bottom, + viewport_rows: viewport_rows as usize, + }) + } + pub fn visible_text(&self) -> String { let Ok(content) = self.screen_content.read() else { return String::new(); @@ -508,6 +543,19 @@ mod tests { } #[test] + fn max_scrollback_reports_clamped_history_without_changing_position() { + let responses = PtyResponses::new(); + let mut parser = vt100::Parser::new_with_callbacks(3, 10, 100, responses); + parser.process(b"a\r\nb\r\nc\r\nd\r\ne"); + parser.screen_mut().set_scrollback(1); + + let max = max_scrollback(&mut parser); + + assert_eq!(max, 2); + assert_eq!(parser.screen().scrollback(), 1); + } + + #[test] fn trim_trailing_blank_rows_drops_empty_viewport_tail() { let mut rows = vec!["hello".to_string(), "".to_string(), " ".to_string()]; trim_trailing_blank_rows(&mut rows); |
