diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/app/input/mouse.rs | 55 | ||||
| -rw-r--r-- | src/app/input/selection.rs | 6 | ||||
| -rw-r--r-- | src/app/mod.rs | 2 | ||||
| -rw-r--r-- | src/app/state.rs | 2 | ||||
| -rw-r--r-- | src/config.rs | 1 | ||||
| -rw-r--r-- | src/config/model.rs | 41 | ||||
| -rw-r--r-- | src/main.rs | 3 |
7 files changed, 98 insertions, 12 deletions
diff --git a/src/app/input/mouse.rs b/src/app/input/mouse.rs index 9e54fce1..8d7fc35d 100644 --- a/src/app/input/mouse.rs +++ b/src/app/input/mouse.rs @@ -1133,7 +1133,7 @@ impl AppState { terminal_runtimes: &TerminalRuntimeRegistry, mouse: MouseEvent, ) { - const LINES_PER_NOTCH: usize = 3; + let lines_per_notch = self.mouse_scroll_lines; if let Some(info) = self.pane_at(mouse.column, mouse.row).cloned() { self.focus_pane(info.id); @@ -1142,10 +1142,10 @@ impl AppState { } match mouse.kind { MouseEventKind::ScrollUp => { - self.scroll_pane_up(terminal_runtimes, info.id, LINES_PER_NOTCH) + self.scroll_pane_up(terminal_runtimes, info.id, lines_per_notch) } MouseEventKind::ScrollDown => { - self.scroll_pane_down(terminal_runtimes, info.id, LINES_PER_NOTCH) + self.scroll_pane_down(terminal_runtimes, info.id, lines_per_notch) } _ => {} } @@ -1156,10 +1156,10 @@ impl AppState { self.focus_pane(info.id); match mouse.kind { MouseEventKind::ScrollUp => { - self.scroll_pane_up(terminal_runtimes, info.id, LINES_PER_NOTCH) + self.scroll_pane_up(terminal_runtimes, info.id, lines_per_notch) } MouseEventKind::ScrollDown => { - self.scroll_pane_down(terminal_runtimes, info.id, LINES_PER_NOTCH) + self.scroll_pane_down(terminal_runtimes, info.id, lines_per_notch) } _ => {} } @@ -1169,8 +1169,8 @@ impl AppState { if let Some(ws_idx) = self.active { if let Some(rt) = self.focused_runtime_in_workspace(terminal_runtimes, ws_idx) { match mouse.kind { - MouseEventKind::ScrollUp => rt.scroll_up(LINES_PER_NOTCH), - MouseEventKind::ScrollDown => rt.scroll_down(LINES_PER_NOTCH), + MouseEventKind::ScrollUp => rt.scroll_up(lines_per_notch), + MouseEventKind::ScrollDown => rt.scroll_down(lines_per_notch), _ => {} } } @@ -1385,7 +1385,8 @@ mod tests { use ratatui::layout::{Direction, Rect}; use super::super::{ - app_for_mouse_test, capture_snapshot, handle_context_menu_key, mouse, root_layout_ratio, + app_for_mouse_test, capture_snapshot, handle_context_menu_key, mouse, numbered_lines_bytes, + root_layout_ratio, }; use super::*; use crate::{ @@ -1394,6 +1395,44 @@ mod tests { workspace::Workspace, }; + #[tokio::test] + async fn terminal_wheel_uses_configured_mouse_scroll_lines() { + let mut app = app_for_mouse_test(); + let mut ws = Workspace::test_new("test"); + let pane_id = ws.tabs[0].root_pane; + let pane_infos = ws.tabs[0].layout.panes(Rect::new(26, 2, 80, 18)); + let info = pane_infos[0].clone(); + ws.tabs[0].runtimes.insert( + pane_id, + crate::terminal::TerminalRuntime::test_with_scrollback_bytes( + info.inner_rect.width, + info.inner_rect.height, + 16 * 1024, + &numbered_lines_bytes(64), + ), + ); + + app.state.workspaces = vec![ws]; + app.state.active = Some(0); + app.state.selected = 0; + app.state.mode = Mode::Terminal; + app.state.view.pane_infos = pane_infos; + app.state.mouse_scroll_lines = 7; + + app.handle_mouse(mouse( + MouseEventKind::ScrollUp, + info.inner_rect.x + 1, + info.inner_rect.y + 1, + )); + + let metrics = app + .state + .runtime_for_pane_in_workspace(&app.terminal_runtimes, 0, pane_id) + .and_then(crate::terminal::TerminalRuntime::scroll_metrics) + .expect("scroll metrics after wheel"); + assert_eq!(metrics.offset_from_bottom, 7); + } + #[test] fn hovering_context_menu_updates_highlight() { let mut app = app_for_mouse_test(); diff --git a/src/app/input/selection.rs b/src/app/input/selection.rs index 06de30b5..0ccb5506 100644 --- a/src/app/input/selection.rs +++ b/src/app/input/selection.rs @@ -144,7 +144,7 @@ impl AppState { terminal_runtimes: &TerminalRuntimeRegistry, mouse: MouseEvent, ) -> bool { - const LINES_PER_NOTCH: usize = 3; + let lines_per_notch = self.mouse_scroll_lines; let Some(selection) = self.selection.as_ref() else { return false; @@ -156,10 +156,10 @@ impl AppState { self.focus_pane(pane_id); match mouse.kind { MouseEventKind::ScrollUp => { - self.scroll_pane_up(terminal_runtimes, pane_id, LINES_PER_NOTCH) + self.scroll_pane_up(terminal_runtimes, pane_id, lines_per_notch) } MouseEventKind::ScrollDown => { - self.scroll_pane_down(terminal_runtimes, pane_id, LINES_PER_NOTCH) + self.scroll_pane_down(terminal_runtimes, pane_id, lines_per_notch) } _ => return false, } diff --git a/src/app/mod.rs b/src/app/mod.rs index 6a94f116..107a4393 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -424,6 +424,7 @@ impl App { sidebar_section_split, agent_panel_scope, mouse_capture: config.ui.mouse_capture, + mouse_scroll_lines: config.ui.mouse_scroll_lines(), confirm_close: config.ui.confirm_close, prompt_new_tab_name: config.ui.prompt_new_tab_name, show_agent_labels_on_pane_borders: config.ui.show_agent_labels_on_pane_borders, @@ -903,6 +904,7 @@ impl App { .sidebar_width .clamp(self.state.sidebar_min_width, self.state.sidebar_max_width); self.state.mouse_capture = config.ui.mouse_capture; + self.state.mouse_scroll_lines = config.ui.mouse_scroll_lines(); self.state.confirm_close = config.ui.confirm_close; self.state.prompt_new_tab_name = config.ui.prompt_new_tab_name; self.state.show_agent_labels_on_pane_borders = diff --git a/src/app/state.rs b/src/app/state.rs index 13958d03..b38afdb3 100644 --- a/src/app/state.rs +++ b/src/app/state.rs @@ -960,6 +960,7 @@ pub struct AppState { /// Capture mouse input for Herdr's own mouse UI. When false, Herdr only /// captures mouse while the focused pane app requests mouse reporting. pub mouse_capture: bool, + pub mouse_scroll_lines: usize, pub confirm_close: bool, pub prompt_new_tab_name: bool, pub show_agent_labels_on_pane_borders: bool, @@ -1239,6 +1240,7 @@ impl AppState { sidebar_section_split: 0.5, agent_panel_scope: AgentPanelScope::AllWorkspaces, mouse_capture: true, + mouse_scroll_lines: crate::config::DEFAULT_MOUSE_SCROLL_LINES, confirm_close: true, prompt_new_tab_name: true, show_agent_labels_on_pane_borders: false, diff --git a/src/config.rs b/src/config.rs index b6406874..4eb29af0 100644 --- a/src/config.rs +++ b/src/config.rs @@ -29,6 +29,7 @@ pub(crate) use self::io::upsert_top_level_bool; pub const CONFIG_PATH_ENV_VAR: &str = "HERDR_CONFIG_PATH"; pub const DEFAULT_SCROLLBACK_LIMIT_BYTES: usize = 10_000_000; +pub const DEFAULT_MOUSE_SCROLL_LINES: usize = 3; #[cfg(test)] pub(crate) fn app_dir_name() -> &'static str { diff --git a/src/config/model.rs b/src/config/model.rs index e099d510..f270a69c 100644 --- a/src/config/model.rs +++ b/src/config/model.rs @@ -1,7 +1,10 @@ +use std::num::NonZeroUsize; + use serde::{Deserialize, Deserializer, Serialize}; use super::{ - BindingConfig, CommandKeybindConfig, SoundConfig, ThemeConfig, DEFAULT_SCROLLBACK_LIMIT_BYTES, + BindingConfig, CommandKeybindConfig, SoundConfig, ThemeConfig, DEFAULT_MOUSE_SCROLL_LINES, + DEFAULT_SCROLLBACK_LIMIT_BYTES, }; #[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize, Default)] @@ -193,6 +196,8 @@ pub struct UiConfig { pub sidebar_max_width: u16, /// Capture mouse input for Herdr's mouse UI. Default: true. pub mouse_capture: bool, + /// Lines to scroll per mouse wheel notch. Default: 3. + pub mouse_scroll_lines: Option<NonZeroUsize>, /// Ask for confirmation before closing a workspace. Default: true. pub confirm_close: bool, /// Ask for a tab name before creating a new tab. Default: true. @@ -328,6 +333,7 @@ impl Default for UiConfig { sidebar_min_width: 18, sidebar_max_width: 36, mouse_capture: true, + mouse_scroll_lines: None, confirm_close: true, prompt_new_tab_name: true, show_agent_labels_on_pane_borders: false, @@ -339,6 +345,14 @@ impl Default for UiConfig { } } +impl UiConfig { + pub fn mouse_scroll_lines(&self) -> usize { + self.mouse_scroll_lines + .map(NonZeroUsize::get) + .unwrap_or(DEFAULT_MOUSE_SCROLL_LINES) + } +} + impl Default for ToastConfig { fn default() -> Self { Self { @@ -517,6 +531,31 @@ mouse_capture = false } #[test] + fn mouse_scroll_lines_defaults_to_three_and_parses() { + let default_config = Config::default(); + assert_eq!( + default_config.ui.mouse_scroll_lines(), + DEFAULT_MOUSE_SCROLL_LINES + ); + + let toml = r#" +[ui] +mouse_scroll_lines = 1 +"#; + let config: Config = toml::from_str(toml).unwrap(); + assert_eq!(config.ui.mouse_scroll_lines(), 1); + } + + #[test] + fn mouse_scroll_lines_rejects_zero() { + let toml = r#" +[ui] +mouse_scroll_lines = 0 +"#; + assert!(toml::from_str::<Config>(toml).is_err()); + } + + #[test] fn toast_config_parses() { let toml = r#" [ui.toast] diff --git a/src/main.rs b/src/main.rs index 5eb48d9f..59aac658 100644 --- a/src/main.rs +++ b/src/main.rs @@ -159,6 +159,9 @@ const DEFAULT_CONFIG: &str = r##"# herdr configuration # Pane apps like lazygit and btop can still receive mouse when they request it. # mouse_capture = true +# Pane scrollback lines to scroll per mouse wheel notch. +# mouse_scroll_lines = 3 + # Ask for confirmation before closing a workspace # confirm_close = true |
