diff options
| author | Can Celik <ogulcancelik@gmail.com> | 2026-08-01 17:25:43 +0300 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-08-01 17:25:43 +0300 |
| commit | f6031f7fb062c89a47fd9fffbe641358f2bd0701 (patch) | |
| tree | 4d9d61c7d7b63c05a48c81f2b54d9bf932470f9b /src | |
| parent | f459a0df315e0963aec5e4dcf2fc394a252ea320 (diff) | |
feat(ui): make pane scrollbars optional (#2166)
refs #2167
Diffstat (limited to 'src')
| -rw-r--r-- | src/app/mod.rs | 2 | ||||
| -rw-r--r-- | src/app/state.rs | 2 | ||||
| -rw-r--r-- | src/config/model.rs | 6 | ||||
| -rw-r--r-- | src/main.rs | 4 | ||||
| -rw-r--r-- | src/ui/panes.rs | 38 |
5 files changed, 43 insertions, 9 deletions
diff --git a/src/app/mod.rs b/src/app/mod.rs index 2a996685..5d070afa 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -630,6 +630,7 @@ impl App { prompt_new_tab_name: config.ui.prompt_new_tab_name, prompt_new_workspace_name: config.ui.prompt_new_workspace_name, pane_borders: config.ui.pane_borders, + pane_scrollbars: config.ui.pane_scrollbars, pane_gaps: config.ui.pane_gaps, show_agent_labels_on_pane_borders: config.ui.show_agent_labels_on_pane_borders, hide_tab_bar_when_single_tab: config.ui.hide_tab_bar_when_single_tab, @@ -1437,6 +1438,7 @@ impl App { self.state.prompt_new_tab_name = config.ui.prompt_new_tab_name; self.state.prompt_new_workspace_name = config.ui.prompt_new_workspace_name; self.state.pane_borders = config.ui.pane_borders; + self.state.pane_scrollbars = config.ui.pane_scrollbars; self.state.pane_gaps = config.ui.pane_gaps; self.state.show_agent_labels_on_pane_borders = config.ui.show_agent_labels_on_pane_borders; diff --git a/src/app/state.rs b/src/app/state.rs index ef903f65..e6c23c4c 100644 --- a/src/app/state.rs +++ b/src/app/state.rs @@ -1493,6 +1493,7 @@ pub struct AppState { pub prompt_new_tab_name: bool, pub prompt_new_workspace_name: bool, pub pane_borders: bool, + pub pane_scrollbars: bool, pub pane_gaps: bool, pub show_agent_labels_on_pane_borders: bool, pub hide_tab_bar_when_single_tab: bool, @@ -1860,6 +1861,7 @@ impl AppState { prompt_new_tab_name: true, prompt_new_workspace_name: false, pane_borders: true, + pane_scrollbars: true, pane_gaps: false, show_agent_labels_on_pane_borders: false, hide_tab_bar_when_single_tab: false, diff --git a/src/config/model.rs b/src/config/model.rs index b1f0ebc3..e56ac087 100644 --- a/src/config/model.rs +++ b/src/config/model.rs @@ -814,6 +814,8 @@ pub struct UiConfig { pub prompt_new_workspace_name: bool, /// Draw borders around split panes. Default: true. pub pane_borders: bool, + /// Draw interactive scrollbars beside terminal panes. Default: true. + pub pane_scrollbars: bool, /// Keep split panes visually separated instead of sharing divider borders. Default: true. pub pane_gaps: bool, /// Show agent labels in split pane borders when no manual pane label is set. Default: false. @@ -1021,6 +1023,7 @@ impl Default for UiConfig { prompt_new_tab_name: true, prompt_new_workspace_name: false, pane_borders: true, + pane_scrollbars: true, pane_gaps: true, show_agent_labels_on_pane_borders: false, hide_tab_bar_when_single_tab: false, @@ -1253,6 +1256,7 @@ agent_panel_scope = "current" fn pane_appearance_defaults_and_parse() { let default_config = Config::default(); assert!(default_config.ui.pane_borders); + assert!(default_config.ui.pane_scrollbars); assert!(default_config.ui.pane_gaps); assert!(!default_config.ui.show_agent_labels_on_pane_borders); assert!(!default_config.ui.hide_tab_bar_when_single_tab); @@ -1264,6 +1268,7 @@ agent_panel_scope = "current" let toml = r#" [ui] pane_borders = false +pane_scrollbars = false pane_gaps = true show_agent_labels_on_pane_borders = true hide_tab_bar_when_single_tab = true @@ -1271,6 +1276,7 @@ tab_bar_position = "bottom" "#; let config: Config = toml::from_str(toml).unwrap(); assert!(!config.ui.pane_borders); + assert!(!config.ui.pane_scrollbars); assert!(config.ui.pane_gaps); assert!(config.ui.show_agent_labels_on_pane_borders); assert!(config.ui.hide_tab_bar_when_single_tab); diff --git a/src/main.rs b/src/main.rs index 7b4058d2..7b8613f8 100644 --- a/src/main.rs +++ b/src/main.rs @@ -305,6 +305,10 @@ const DEFAULT_CONFIG: &str = r##"# herdr configuration # Draw borders around split panes. # pane_borders = true +# Draw interactive scrollbars beside terminal panes. +# Set false to reclaim the scrollbar column and keep it out of terminal-native selections. +# pane_scrollbars = true + # Keep split panes visually separated instead of sharing divider borders. # pane_gaps = true diff --git a/src/ui/panes.rs b/src/ui/panes.rs index 5233060d..b904ce00 100644 --- a/src/ui/panes.rs +++ b/src/ui/panes.rs @@ -31,8 +31,8 @@ fn pane_border_title(label: &str, pane_width: u16, _focused: bool) -> Option<Str Some(format!(" {} ", truncate_end(label, max_label_width))) } -fn stable_terminal_inner_rect(pane_inner: Rect) -> Rect { - if pane_inner.width <= 4 { +fn stable_terminal_inner_rect(pane_inner: Rect, pane_scrollbars: bool) -> Rect { + if !pane_scrollbars || pane_inner.width <= 4 { return pane_inner; } @@ -143,8 +143,12 @@ fn runtime_for_tab_pane<'a>( .map(|runtime| (terminal_id, runtime)) } -fn stable_scrollbar_gutter(rt: &TerminalRuntime, pane_inner: Rect) -> (Rect, Option<Rect>) { - let inner_rect = stable_terminal_inner_rect(pane_inner); +fn stable_scrollbar_gutter( + rt: &TerminalRuntime, + pane_inner: Rect, + pane_scrollbars: bool, +) -> (Rect, Option<Rect>) { + let inner_rect = stable_terminal_inner_rect(pane_inner, pane_scrollbars); if inner_rect == pane_inner { return (inner_rect, None); } @@ -181,7 +185,7 @@ pub(super) fn resize_tab_panes( Borders::NONE }; let pane_inner = pane_inner_rect(area, borders); - let inner_rect = stable_terminal_inner_rect(pane_inner); + let inner_rect = stable_terminal_inner_rect(pane_inner, app.pane_scrollbars); if !app.direct_attach_resize_locks.contains(terminal_id) { rt.resize( inner_rect.height, @@ -198,7 +202,7 @@ pub(super) fn resize_tab_panes( let pane_inner = pane_inner_rect(info.rect, info.borders); if let Some((terminal_id, rt)) = runtime_for_tab_pane(terminal_runtimes, tab, info.id) { - let inner_rect = stable_terminal_inner_rect(pane_inner); + let inner_rect = stable_terminal_inner_rect(pane_inner, app.pane_scrollbars); if !app.direct_attach_resize_locks.contains(terminal_id) { rt.resize( inner_rect.height, @@ -239,7 +243,8 @@ pub(super) fn compute_pane_infos( let mut inner_rect = pane_inner; let mut scrollbar_rect = None; if let Some(rt) = app.runtime_for_pane_in_workspace(terminal_runtimes, ws_idx, focused_id) { - (inner_rect, scrollbar_rect) = stable_scrollbar_gutter(rt, pane_inner); + (inner_rect, scrollbar_rect) = + stable_scrollbar_gutter(rt, pane_inner, app.pane_scrollbars); if resize_panes && ws.terminal_id(focused_id).is_some_and(|terminal_id| { !app.direct_attach_resize_locks.contains(terminal_id) @@ -271,7 +276,8 @@ pub(super) fn compute_pane_infos( let mut inner_rect = pane_inner; let mut scrollbar_rect = None; if let Some(rt) = app.runtime_for_pane_in_workspace(terminal_runtimes, ws_idx, info.id) { - (inner_rect, scrollbar_rect) = stable_scrollbar_gutter(rt, pane_inner); + (inner_rect, scrollbar_rect) = + stable_scrollbar_gutter(rt, pane_inner, app.pane_scrollbars); if resize_panes && ws.terminal_id(info.id).is_some_and(|terminal_id| { !app.direct_attach_resize_locks.contains(terminal_id) @@ -1358,7 +1364,7 @@ mod tests { } #[tokio::test] - async fn pane_scrollbar_reserves_last_column_from_terminal_area() { + async fn pane_scrollbar_setting_controls_reserved_column() { let mut app = AppState::test_new(); let mut workspace = Workspace::test_new("test"); let root_pane = workspace.tabs[0].root_pane; @@ -1388,6 +1394,20 @@ mod tests { assert_eq!(info.rect, area); assert_eq!(info.scrollbar_rect, Some(Rect::new(49, 3, 1, 8))); assert_eq!(info.inner_rect, Rect::new(10, 3, 39, 8)); + + app.pane_scrollbars = false; + let infos = compute_pane_infos( + &app, + &terminal_runtimes, + area, + false, + crate::kitty_graphics::HostCellSize::default(), + ); + let info = &infos[0]; + + assert_eq!(info.rect, area); + assert_eq!(info.scrollbar_rect, None); + assert_eq!(info.inner_rect, area); } #[test] |
