diff --git a/CHANGELOG.md b/CHANGELOG.md index 0a1a9d5c1..9b3901e8c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) * fix: kitty image size on startup and clearing with stacked unlisted full framed panes (https://github.com/zellij-org/zellij/pull/5526) * fix: focus event in remote attach (https://github.com/zellij-org/zellij/pull/5527) * fix: editing scrollback issue with a fullscreen floating pane (https://github.com/zellij-org/zellij/pull/5528) +* feat: allow opting-out of scroll mode sync (https://github.com/zellij-org/zellij/pull/5532) ## [0.45.0] - 2026-08-20 * feat: allow tabs to have different sizes if clients aren't focused on the same one (https://github.com/zellij-org/zellij/pull/5133) diff --git a/example/default.kdl b/example/default.kdl index 0f69f8f51..2c5b2b236 100644 --- a/example/default.kdl +++ b/example/default.kdl @@ -540,6 +540,11 @@ load_plugins { // // mouse_scroll_resize false +// Whether scrolling a pane implicitly enters and exits Scroll mode +// Default: true +// +// scroll_mode_sync false + // Whether to enable mouse hover visual effects (frame highlight and help text) // Default: true // diff --git a/zellij-server/src/lib.rs b/zellij-server/src/lib.rs index 44a307729..02446de45 100644 --- a/zellij-server/src/lib.rs +++ b/zellij-server/src/lib.rs @@ -471,6 +471,7 @@ impl SessionMetaData { .advanced_mouse_actions .unwrap_or(true), mouse_scroll_resize: new_config.options.mouse_scroll_resize.unwrap_or(true), + scroll_mode_sync: new_config.options.scroll_mode_sync.unwrap_or(true), mouse_hover_effects: new_config.options.mouse_hover_effects.unwrap_or(true), mouse_hover_tips: new_config.options.mouse_hover_tips.unwrap_or(true), visual_bell: new_config.options.visual_bell.unwrap_or(true), diff --git a/zellij-server/src/screen.rs b/zellij-server/src/screen.rs index 646167859..bf85a8dd5 100644 --- a/zellij-server/src/screen.rs +++ b/zellij-server/src/screen.rs @@ -816,6 +816,7 @@ pub enum ScreenInstruction { default_editor: Option, advanced_mouse_actions: bool, mouse_scroll_resize: bool, + scroll_mode_sync: bool, mouse_hover_effects: bool, mouse_hover_tips: bool, visual_bell: bool, @@ -1577,6 +1578,7 @@ pub(crate) struct Screen { osc133_command_selection: bool, word_separators: String, mouse_scroll_resize: bool, + scroll_mode_sync: bool, mouse_hover_effects: bool, mouse_hover_tips: bool, visual_bell: bool, @@ -1707,6 +1709,7 @@ impl Screen { osc133_command_selection: bool, word_separators: String, mouse_scroll_resize: bool, + scroll_mode_sync: bool, mouse_hover_effects: bool, mouse_hover_tips: bool, visual_bell: bool, @@ -1776,6 +1779,7 @@ impl Screen { osc133_command_selection, word_separators, mouse_scroll_resize, + scroll_mode_sync, mouse_hover_effects, mouse_hover_tips, visual_bell, @@ -6015,6 +6019,9 @@ impl Screen { // under unlock-first), so a client in another mode (Pane, Tab, Search, ...) is never // pulled out of it. See #638. fn sync_scroll_mode_on_focus(&mut self, client_id: ClientId) -> Result<()> { + if !self.scroll_mode_sync { + return Ok(()); + } // base_mode is the default config reloads keep current; .mode is the fallback. let default_mode = self .default_mode_info @@ -6025,6 +6032,7 @@ impl Screen { } let current_mode = match self.mode_info.get(&client_id) { Some(mode_info) => mode_info.mode, + None if self.active_tab_ids.contains_key(&client_id) => default_mode, None => return Ok(()), }; let active_pane_is_scrolled = self.active_pane_is_scrolled(client_id); @@ -6803,6 +6811,7 @@ impl Screen { default_editor: Option, advanced_mouse_actions: bool, mouse_scroll_resize: bool, + scroll_mode_sync: bool, mouse_hover_effects: bool, mouse_hover_tips: bool, visual_bell: bool, @@ -6835,6 +6844,7 @@ impl Screen { self.pane_frame_style = pane_frame_style; self.advanced_mouse_actions = advanced_mouse_actions; self.mouse_scroll_resize = mouse_scroll_resize; + self.scroll_mode_sync = scroll_mode_sync; self.mouse_hover_effects = mouse_hover_effects; self.mouse_hover_tips = mouse_hover_tips; self.visual_bell = visual_bell; @@ -8029,6 +8039,7 @@ pub(crate) fn screen_thread_main( .clone() .unwrap_or_else(|| DEFAULT_WORD_SEPARATORS.to_owned()); let mouse_scroll_resize = config_options.mouse_scroll_resize.unwrap_or(true); + let scroll_mode_sync = config_options.scroll_mode_sync.unwrap_or(true); let mouse_hover_effects = config_options.mouse_hover_effects.unwrap_or(true); let mouse_hover_tips = config_options.mouse_hover_tips.unwrap_or(true); let visual_bell = config_options.visual_bell.unwrap_or(true); @@ -8080,6 +8091,7 @@ pub(crate) fn screen_thread_main( osc133_command_selection, word_separators, mouse_scroll_resize, + scroll_mode_sync, mouse_hover_effects, mouse_hover_tips, visual_bell, @@ -11467,6 +11479,7 @@ pub(crate) fn screen_thread_main( default_editor, advanced_mouse_actions, mouse_scroll_resize, + scroll_mode_sync, mouse_hover_effects, mouse_hover_tips, visual_bell, @@ -11499,6 +11512,7 @@ pub(crate) fn screen_thread_main( default_editor, advanced_mouse_actions, mouse_scroll_resize, + scroll_mode_sync, mouse_hover_effects, mouse_hover_tips, visual_bell, diff --git a/zellij-server/src/unit/screen_tests.rs b/zellij-server/src/unit/screen_tests.rs index 54de9ecff..119f74345 100644 --- a/zellij-server/src/unit/screen_tests.rs +++ b/zellij-server/src/unit/screen_tests.rs @@ -385,6 +385,7 @@ fn create_new_screen_with_capture( true, DEFAULT_WORD_SEPARATORS.to_owned(), mouse_scroll_resize, + true, mouse_hover_effects, true, visual_bell, @@ -5819,6 +5820,7 @@ fn create_new_screen_with_message_capture( true, true, true, + true, visual_bell, false, // focus_follows_mouse false, // mouse_click_through @@ -8930,6 +8932,7 @@ fn create_new_screen_with_forward_capture(size: Size) -> (Screen, ForwardCapture true, true, true, + true, visual_bell, false, // focus_follows_mouse false, // mouse_click_through @@ -9782,6 +9785,7 @@ fn create_new_screen_with_theme_capture(size: Size) -> (Screen, ThemeCapture) { true, true, true, + true, false, false, web_server_ip, @@ -10312,6 +10316,7 @@ fn create_non_mirrored_screen(size: Size) -> Screen { true, DEFAULT_WORD_SEPARATORS.to_owned(), true, // mouse_scroll_resize + true, true, // mouse_hover_effects true, true, // visual_bell @@ -12492,6 +12497,50 @@ pub fn focus_pane_with_id_syncs_scroll_mode() { mock_screen.teardown(vec![server_thread, screen_thread]); } +#[test] +pub fn scrolling_syncs_scroll_mode_for_a_client_that_never_changed_mode() { + let size = Size { cols: 80, rows: 10 }; + let mut initial_layout = TiledPaneLayout::default(); + initial_layout.children_split_direction = SplitDirection::Vertical; + initial_layout.children = vec![TiledPaneLayout::default(), TiledPaneLayout::default()]; + let mut mock_screen = MockScreen::new(size); + mock_screen.config.options.default_mode = Some(InputMode::Locked); + let client_id = mock_screen.main_client_id; + let session_metadata = mock_screen.clone_session_metadata(); + let screen_thread = mock_screen.run(Some(initial_layout), vec![]); + let received_server_instructions = Arc::new(Mutex::new(vec![])); + let server_receiver = mock_screen.server_receiver.take().unwrap(); + let server_thread = log_actions_in_thread!( + received_server_instructions, + ServerInstruction::KillSession, + server_receiver + ); + + let mut pane_contents = String::new(); + for i in 0..20 { + pane_contents.push_str(&format!("fill pane up with something {}\n\r", i)); + } + let _ = mock_screen.to_screen.send(ScreenInstruction::PtyBytes( + 0, + pane_contents.as_bytes().to_vec(), + )); + std::thread::sleep(std::time::Duration::from_millis(100)); + + send_cli_action_to_server( + &session_metadata, + CliAction::ScrollUp { pane_id: None }, + client_id, + ); + std::thread::sleep(std::time::Duration::from_millis(100)); + assert_eq!( + last_change_mode_for_client(&received_server_instructions, client_id), + Some(InputMode::Scroll), + "a client that never switched modes should still enter Scroll mode when scrolling", + ); + + mock_screen.teardown(vec![server_thread, screen_thread]); +} + #[test] pub fn scrolling_the_focused_pane_syncs_scroll_mode() { let size = Size { cols: 80, rows: 10 }; @@ -12608,6 +12657,89 @@ pub fn scrolling_the_focused_pane_with_the_mouse_syncs_scroll_mode() { mock_screen.teardown(vec![server_thread, screen_thread]); } +#[test] +pub fn scrolling_does_not_sync_scroll_mode_when_disabled_in_the_config() { + let size = Size { cols: 80, rows: 10 }; + let mut initial_layout = TiledPaneLayout::default(); + initial_layout.children_split_direction = SplitDirection::Vertical; + initial_layout.children = vec![TiledPaneLayout::default(), TiledPaneLayout::default()]; + let mut mock_screen = MockScreen::new(size); + mock_screen.config.options.scroll_mode_sync = Some(false); + let client_id = mock_screen.main_client_id; + let session_metadata = mock_screen.clone_session_metadata(); + let screen_thread = mock_screen.run(Some(initial_layout), vec![]); + let received_server_instructions = Arc::new(Mutex::new(vec![])); + let server_receiver = mock_screen.server_receiver.take().unwrap(); + let server_thread = log_actions_in_thread!( + received_server_instructions, + ServerInstruction::KillSession, + server_receiver + ); + + let mut pane_contents = String::new(); + for i in 0..20 { + pane_contents.push_str(&format!("fill pane up with something {}\n\r", i)); + } + let _ = mock_screen.to_screen.send(ScreenInstruction::PtyBytes( + 0, + pane_contents.as_bytes().to_vec(), + )); + let _ = mock_screen.to_screen.send(ScreenInstruction::ChangeMode( + InputMode::Normal, + None, + client_id, + None, + )); + std::thread::sleep(std::time::Duration::from_millis(100)); + received_server_instructions.lock().unwrap().clear(); + + send_cli_action_to_server( + &session_metadata, + CliAction::ScrollUp { pane_id: None }, + client_id, + ); + std::thread::sleep(std::time::Duration::from_millis(100)); + assert_eq!( + last_change_mode_for_client(&received_server_instructions, client_id), + None, + "scrolling the focused pane up should not change the mode when scroll_mode_sync is false", + ); + + let _ = mock_screen.to_screen.send(ScreenInstruction::MouseEvent( + MouseEvent::new_scroll_up_event(Position::new(5, 10)), + client_id, + None, + )); + std::thread::sleep(std::time::Duration::from_millis(100)); + assert_eq!( + last_change_mode_for_client(&received_server_instructions, client_id), + None, + "wheel-scrolling the focused pane should not change the mode when scroll_mode_sync is false", + ); + + let _ = mock_screen.to_screen.send(ScreenInstruction::ChangeMode( + InputMode::Scroll, + None, + client_id, + None, + )); + std::thread::sleep(std::time::Duration::from_millis(100)); + received_server_instructions.lock().unwrap().clear(); + send_cli_action_to_server( + &session_metadata, + CliAction::ScrollToBottom { pane_id: None }, + client_id, + ); + std::thread::sleep(std::time::Duration::from_millis(100)); + assert_eq!( + last_change_mode_for_client(&received_server_instructions, client_id), + None, + "scrolling back to the bottom should not leave Scroll mode when scroll_mode_sync is false", + ); + + mock_screen.teardown(vec![server_thread, screen_thread]); +} + #[test] pub fn scrolling_an_unfocused_pane_does_not_sync_scroll_mode() { let size = Size { cols: 80, rows: 10 }; diff --git a/zellij-utils/assets/config/default.kdl b/zellij-utils/assets/config/default.kdl index 2645fb7bd..807c12714 100644 --- a/zellij-utils/assets/config/default.kdl +++ b/zellij-utils/assets/config/default.kdl @@ -567,6 +567,11 @@ load_plugins { // // mouse_scroll_resize false +// Whether scrolling a pane implicitly enters and exits Scroll mode +// Default: true +// +// scroll_mode_sync false + // Whether to enable mouse hover visual effects (frame highlight and help text) // Default: true // diff --git a/zellij-utils/assets/plugins/about.wasm b/zellij-utils/assets/plugins/about.wasm index 0afd0bb23..2b6f70b13 100755 Binary files a/zellij-utils/assets/plugins/about.wasm and b/zellij-utils/assets/plugins/about.wasm differ diff --git a/zellij-utils/assets/plugins/compact-bar.wasm b/zellij-utils/assets/plugins/compact-bar.wasm index 39a543291..863493a9b 100755 Binary files a/zellij-utils/assets/plugins/compact-bar.wasm and b/zellij-utils/assets/plugins/compact-bar.wasm differ diff --git a/zellij-utils/assets/plugins/configuration.wasm b/zellij-utils/assets/plugins/configuration.wasm index 32e3dc190..ecfdb746c 100755 Binary files a/zellij-utils/assets/plugins/configuration.wasm and b/zellij-utils/assets/plugins/configuration.wasm differ diff --git a/zellij-utils/assets/plugins/fixture-plugin-for-tests.wasm b/zellij-utils/assets/plugins/fixture-plugin-for-tests.wasm index 409879499..6153c445f 100755 Binary files a/zellij-utils/assets/plugins/fixture-plugin-for-tests.wasm and b/zellij-utils/assets/plugins/fixture-plugin-for-tests.wasm differ diff --git a/zellij-utils/assets/plugins/layout-manager.wasm b/zellij-utils/assets/plugins/layout-manager.wasm index a45367b0e..f9cf47747 100755 Binary files a/zellij-utils/assets/plugins/layout-manager.wasm and b/zellij-utils/assets/plugins/layout-manager.wasm differ diff --git a/zellij-utils/assets/plugins/link.wasm b/zellij-utils/assets/plugins/link.wasm index c34f12292..21596f9f4 100755 Binary files a/zellij-utils/assets/plugins/link.wasm and b/zellij-utils/assets/plugins/link.wasm differ diff --git a/zellij-utils/assets/plugins/multiple-select.wasm b/zellij-utils/assets/plugins/multiple-select.wasm index 86ac202dd..910e47117 100755 Binary files a/zellij-utils/assets/plugins/multiple-select.wasm and b/zellij-utils/assets/plugins/multiple-select.wasm differ diff --git a/zellij-utils/assets/plugins/plugin-manager.wasm b/zellij-utils/assets/plugins/plugin-manager.wasm index 054e47f06..46829b772 100755 Binary files a/zellij-utils/assets/plugins/plugin-manager.wasm and b/zellij-utils/assets/plugins/plugin-manager.wasm differ diff --git a/zellij-utils/assets/plugins/session-manager.wasm b/zellij-utils/assets/plugins/session-manager.wasm index afe57d1ca..2c1b32dce 100755 Binary files a/zellij-utils/assets/plugins/session-manager.wasm and b/zellij-utils/assets/plugins/session-manager.wasm differ diff --git a/zellij-utils/assets/plugins/share.wasm b/zellij-utils/assets/plugins/share.wasm index 1c869dad4..f489ada3f 100755 Binary files a/zellij-utils/assets/plugins/share.wasm and b/zellij-utils/assets/plugins/share.wasm differ diff --git a/zellij-utils/assets/plugins/status-bar.wasm b/zellij-utils/assets/plugins/status-bar.wasm index 045ab1b39..ea0df34c2 100755 Binary files a/zellij-utils/assets/plugins/status-bar.wasm and b/zellij-utils/assets/plugins/status-bar.wasm differ diff --git a/zellij-utils/assets/plugins/strider.wasm b/zellij-utils/assets/plugins/strider.wasm index a33477c54..8e5d955d9 100755 Binary files a/zellij-utils/assets/plugins/strider.wasm and b/zellij-utils/assets/plugins/strider.wasm differ diff --git a/zellij-utils/assets/plugins/tab-bar.wasm b/zellij-utils/assets/plugins/tab-bar.wasm index 48de9b93a..7bc3da6f1 100755 Binary files a/zellij-utils/assets/plugins/tab-bar.wasm and b/zellij-utils/assets/plugins/tab-bar.wasm differ diff --git a/zellij-utils/assets/prost_ipc/client_server_contract.rs b/zellij-utils/assets/prost_ipc/client_server_contract.rs index 981de8f21..49f927ba3 100644 --- a/zellij-utils/assets/prost_ipc/client_server_contract.rs +++ b/zellij-utils/assets/prost_ipc/client_server_contract.rs @@ -2062,6 +2062,8 @@ pub struct Options { pub mouse_hover_tips: ::core::option::Option, #[prost(string, optional, tag="67")] pub host_notification_protocol: ::core::option::Option<::prost::alloc::string::String>, + #[prost(bool, optional, tag="68")] + pub scroll_mode_sync: ::core::option::Option, } /// Pane-targeting action messages #[allow(clippy::derive_partial_eq_without_eq)] diff --git a/zellij-utils/src/client_server_contract/common_types.proto b/zellij-utils/src/client_server_contract/common_types.proto index 5cfcea101..0cf78027e 100644 --- a/zellij-utils/src/client_server_contract/common_types.proto +++ b/zellij-utils/src/client_server_contract/common_types.proto @@ -1223,6 +1223,7 @@ message Options { optional bool dangerously_enable_paste_buffer_read = 58; optional bool mouse_hover_tips = 59; optional string host_notification_protocol = 67; + optional bool scroll_mode_sync = 68; } enum OnForceClose { diff --git a/zellij-utils/src/input/options.rs b/zellij-utils/src/input/options.rs index 99da6e6ac..ffe5d33f7 100644 --- a/zellij-utils/src/input/options.rs +++ b/zellij-utils/src/input/options.rs @@ -337,6 +337,12 @@ pub struct Options { #[serde(default)] pub mouse_scroll_resize: Option, + /// Whether scrolling a pane implicitly enters (and leaving the scroll implicitly exits) Scroll mode + /// default is true + #[clap(long, value_parser)] + #[serde(default)] + pub scroll_mode_sync: Option, + /// Whether to enable mouse hover visual effects (frame highlight and help text) /// default is true #[clap(long, value_parser)] @@ -553,6 +559,7 @@ impl Options { let show_release_notes = other.show_release_notes.or(self.show_release_notes); let advanced_mouse_actions = other.advanced_mouse_actions.or(self.advanced_mouse_actions); let mouse_scroll_resize = other.mouse_scroll_resize.or(self.mouse_scroll_resize); + let scroll_mode_sync = other.scroll_mode_sync.or(self.scroll_mode_sync); let mouse_hover_effects = other.mouse_hover_effects.or(self.mouse_hover_effects); let mouse_hover_tips = other.mouse_hover_tips.or(self.mouse_hover_tips); let visual_bell = other.visual_bell.or(self.visual_bell); @@ -630,6 +637,7 @@ impl Options { show_release_notes, advanced_mouse_actions, mouse_scroll_resize, + scroll_mode_sync, mouse_hover_effects, mouse_hover_tips, visual_bell, @@ -720,6 +728,7 @@ impl Options { let show_release_notes = other.show_release_notes.or(self.show_release_notes); let advanced_mouse_actions = other.advanced_mouse_actions.or(self.advanced_mouse_actions); let mouse_scroll_resize = other.mouse_scroll_resize.or(self.mouse_scroll_resize); + let scroll_mode_sync = other.scroll_mode_sync.or(self.scroll_mode_sync); let mouse_hover_effects = other.mouse_hover_effects.or(self.mouse_hover_effects); let mouse_hover_tips = other.mouse_hover_tips.or(self.mouse_hover_tips); let visual_bell = other.visual_bell.or(self.visual_bell); @@ -797,6 +806,7 @@ impl Options { show_release_notes, advanced_mouse_actions, mouse_scroll_resize, + scroll_mode_sync, mouse_hover_effects, mouse_hover_tips, visual_bell, diff --git a/zellij-utils/src/ipc/protobuf_conversion.rs b/zellij-utils/src/ipc/protobuf_conversion.rs index 488ce88b3..a8a6de006 100644 --- a/zellij-utils/src/ipc/protobuf_conversion.rs +++ b/zellij-utils/src/ipc/protobuf_conversion.rs @@ -945,6 +945,7 @@ impl From show_release_notes: options.show_release_notes, advanced_mouse_actions: options.advanced_mouse_actions, mouse_scroll_resize: options.mouse_scroll_resize, + scroll_mode_sync: options.scroll_mode_sync, mouse_hover_effects: options.mouse_hover_effects, mouse_hover_tips: options.mouse_hover_tips, web_server_ip: options.web_server_ip.map(|ip| ip.to_string()), @@ -1075,6 +1076,7 @@ impl TryFrom show_release_notes: options.show_release_notes, advanced_mouse_actions: options.advanced_mouse_actions, mouse_scroll_resize: options.mouse_scroll_resize, + scroll_mode_sync: options.scroll_mode_sync, mouse_hover_effects: options.mouse_hover_effects, mouse_hover_tips: options.mouse_hover_tips, web_server_ip: options diff --git a/zellij-utils/src/ipc/tests/roundtrip_tests.rs b/zellij-utils/src/ipc/tests/roundtrip_tests.rs index 774340336..cf8731501 100644 --- a/zellij-utils/src/ipc/tests/roundtrip_tests.rs +++ b/zellij-utils/src/ipc/tests/roundtrip_tests.rs @@ -492,6 +492,7 @@ fn test_client_messages() { show_release_notes: Some(true), advanced_mouse_actions: Some(true), mouse_scroll_resize: Some(true), + scroll_mode_sync: Some(true), web_server_ip: Some("1.1.1.1".parse().unwrap()), web_server_port: Some(8080), web_server_cert: Some(PathBuf::from("web_server_cert")), diff --git a/zellij-utils/src/kdl/mod.rs b/zellij-utils/src/kdl/mod.rs index 833a1f3db..f48a52223 100644 --- a/zellij-utils/src/kdl/mod.rs +++ b/zellij-utils/src/kdl/mod.rs @@ -2886,6 +2886,9 @@ impl Options { let mouse_scroll_resize = kdl_property_first_arg_as_bool_or_error!(kdl_options, "mouse_scroll_resize") .map(|(v, _)| v); + let scroll_mode_sync = + kdl_property_first_arg_as_bool_or_error!(kdl_options, "scroll_mode_sync") + .map(|(v, _)| v); let mouse_hover_effects = kdl_property_first_arg_as_bool_or_error!(kdl_options, "mouse_hover_effects") .map(|(v, _)| v); @@ -3019,6 +3022,7 @@ impl Options { show_release_notes, advanced_mouse_actions, mouse_scroll_resize, + scroll_mode_sync, mouse_hover_effects, mouse_hover_tips, visual_bell, @@ -4308,6 +4312,33 @@ impl Options { None } } + fn scroll_mode_sync_to_kdl(&self, add_comments: bool) -> Option { + let comment_text = format!( + "{}\n{}\n{}", + " ", + "// Whether scrolling a pane implicitly enters and exits Scroll mode", + "// default is true", + ); + + let create_node = |node_value: bool| -> KdlNode { + let mut node = KdlNode::new("scroll_mode_sync"); + node.push(KdlValue::Bool(node_value)); + node + }; + if let Some(scroll_mode_sync) = self.scroll_mode_sync { + let mut node = create_node(scroll_mode_sync); + if add_comments { + node.set_leading(format!("{}\n", comment_text)); + } + Some(node) + } else if add_comments { + let mut node = create_node(false); + node.set_leading(format!("{}\n// ", comment_text)); + Some(node) + } else { + None + } + } fn mouse_hover_tips_to_kdl(&self, add_comments: bool) -> Option { let comment_text = format!( "{}\n{}\n{}", @@ -4849,6 +4880,9 @@ impl Options { if let Some(mouse_scroll_resize) = self.mouse_scroll_resize_to_kdl(add_comments) { nodes.push(mouse_scroll_resize); } + if let Some(scroll_mode_sync) = self.scroll_mode_sync_to_kdl(add_comments) { + nodes.push(scroll_mode_sync); + } if let Some(mouse_hover_effects) = self.mouse_hover_effects_to_kdl(add_comments) { nodes.push(mouse_hover_effects); } @@ -7657,6 +7691,42 @@ fn selection_options_default_to_none_when_unspecified() { assert_eq!(deserialized.word_separators, None); } +#[test] +fn scroll_mode_sync_from_kdl() { + let fake_config = r##" + scroll_mode_sync false + "##; + let document: KdlDocument = fake_config.parse().unwrap(); + let deserialized = Options::from_kdl(&document).unwrap(); + assert_eq!(deserialized.scroll_mode_sync, Some(false)); + + let empty_document: KdlDocument = "".parse().unwrap(); + let deserialized_empty = Options::from_kdl(&empty_document).unwrap(); + assert_eq!( + deserialized_empty.scroll_mode_sync, None, + "an unspecified scroll_mode_sync stays None so the default applies" + ); +} + +#[test] +fn scroll_mode_sync_round_trips_through_kdl() { + let fake_config = r##" + scroll_mode_sync false + "##; + let document: KdlDocument = fake_config.parse().unwrap(); + let deserialized = Options::from_kdl(&document).unwrap(); + let mut serialized = Options::to_kdl(&deserialized, false); + let mut fake_document = KdlDocument::new(); + fake_document.nodes_mut().append(&mut serialized); + let deserialized_from_serialized = + Options::from_kdl(&fake_document.to_string().parse::().unwrap()).unwrap(); + assert_eq!( + deserialized_from_serialized.scroll_mode_sync, + Some(false), + "scroll_mode_sync survives a serialize/parse round trip" + ); +} + #[test] fn config_options_to_string() { let fake_config = r##" diff --git a/zellij-utils/src/kdl/snapshots/zellij_utils__kdl__bare_config_from_default_assets_to_string_with_comments.snap b/zellij-utils/src/kdl/snapshots/zellij_utils__kdl__bare_config_from_default_assets_to_string_with_comments.snap index c58696143..69aac4ada 100644 --- a/zellij-utils/src/kdl/snapshots/zellij_utils__kdl__bare_config_from_default_assets_to_string_with_comments.snap +++ b/zellij-utils/src/kdl/snapshots/zellij_utils__kdl__bare_config_from_default_assets_to_string_with_comments.snap @@ -574,6 +574,10 @@ web_client { // default is true // mouse_scroll_resize false +// Whether scrolling a pane implicitly enters and exits Scroll mode +// default is true +// scroll_mode_sync false + // Whether to enable mouse hover visual effects (frame highlight and help text) // default is true // mouse_hover_effects false diff --git a/zellij-utils/src/kdl/snapshots/zellij_utils__kdl__config_options_to_string_with_comments.snap b/zellij-utils/src/kdl/snapshots/zellij_utils__kdl__config_options_to_string_with_comments.snap index db90e1d08..ac82cd057 100644 --- a/zellij-utils/src/kdl/snapshots/zellij_utils__kdl__config_options_to_string_with_comments.snap +++ b/zellij-utils/src/kdl/snapshots/zellij_utils__kdl__config_options_to_string_with_comments.snap @@ -284,6 +284,10 @@ web_sharing "disabled" // default is true // mouse_scroll_resize false +// Whether scrolling a pane implicitly enters and exits Scroll mode +// default is true +// scroll_mode_sync false + // Whether to enable mouse hover visual effects (frame highlight and help text) // default is true // mouse_hover_effects false diff --git a/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__cli_arguments_override_config_options.snap b/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__cli_arguments_override_config_options.snap index ca3044771..bad8f6821 100644 --- a/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__cli_arguments_override_config_options.snap +++ b/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__cli_arguments_override_config_options.snap @@ -45,6 +45,7 @@ Options { show_release_notes: None, advanced_mouse_actions: None, mouse_scroll_resize: None, + scroll_mode_sync: None, mouse_hover_effects: None, mouse_hover_tips: None, visual_bell: None, diff --git a/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__cli_arguments_override_layout_options.snap b/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__cli_arguments_override_layout_options.snap index aeeb52a05..c76200b13 100644 --- a/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__cli_arguments_override_layout_options.snap +++ b/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__cli_arguments_override_layout_options.snap @@ -45,6 +45,7 @@ Options { show_release_notes: None, advanced_mouse_actions: None, mouse_scroll_resize: None, + scroll_mode_sync: None, mouse_hover_effects: None, mouse_hover_tips: None, visual_bell: None, diff --git a/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__default_config_with_no_cli_arguments-3.snap b/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__default_config_with_no_cli_arguments-3.snap index 2377150e4..c7020faf3 100644 --- a/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__default_config_with_no_cli_arguments-3.snap +++ b/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__default_config_with_no_cli_arguments-3.snap @@ -43,6 +43,7 @@ Options { show_release_notes: None, advanced_mouse_actions: None, mouse_scroll_resize: None, + scroll_mode_sync: None, mouse_hover_effects: None, mouse_hover_tips: None, visual_bell: None, diff --git a/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__default_config_with_no_cli_arguments.snap b/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__default_config_with_no_cli_arguments.snap index 6da9ba78b..8ca8747c7 100644 --- a/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__default_config_with_no_cli_arguments.snap +++ b/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__default_config_with_no_cli_arguments.snap @@ -6135,6 +6135,7 @@ Config { show_release_notes: None, advanced_mouse_actions: None, mouse_scroll_resize: None, + scroll_mode_sync: None, mouse_hover_effects: None, mouse_hover_tips: None, visual_bell: None, diff --git a/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_env_vars_override_config_env_vars.snap b/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_env_vars_override_config_env_vars.snap index f65d940fb..b8261d4a1 100644 --- a/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_env_vars_override_config_env_vars.snap +++ b/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_env_vars_override_config_env_vars.snap @@ -6135,6 +6135,7 @@ Config { show_release_notes: None, advanced_mouse_actions: None, mouse_scroll_resize: None, + scroll_mode_sync: None, mouse_hover_effects: None, mouse_hover_tips: None, visual_bell: None, diff --git a/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_keybinds_override_config_keybinds.snap b/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_keybinds_override_config_keybinds.snap index 5f6ff86ed..474c81dfd 100644 --- a/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_keybinds_override_config_keybinds.snap +++ b/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_keybinds_override_config_keybinds.snap @@ -130,6 +130,7 @@ Config { show_release_notes: None, advanced_mouse_actions: None, mouse_scroll_resize: None, + scroll_mode_sync: None, mouse_hover_effects: None, mouse_hover_tips: None, visual_bell: None, diff --git a/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_options_override_config_options.snap b/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_options_override_config_options.snap index a2b4c303a..ae5937bc8 100644 --- a/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_options_override_config_options.snap +++ b/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_options_override_config_options.snap @@ -45,6 +45,7 @@ Options { show_release_notes: None, advanced_mouse_actions: None, mouse_scroll_resize: None, + scroll_mode_sync: None, mouse_hover_effects: None, mouse_hover_tips: None, visual_bell: None, diff --git a/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_themes_override_config_themes.snap b/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_themes_override_config_themes.snap index df53f9447..33280a8a7 100644 --- a/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_themes_override_config_themes.snap +++ b/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_themes_override_config_themes.snap @@ -6135,6 +6135,7 @@ Config { show_release_notes: None, advanced_mouse_actions: None, mouse_scroll_resize: None, + scroll_mode_sync: None, mouse_hover_effects: None, mouse_hover_tips: None, visual_bell: None, diff --git a/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_ui_config_overrides_config_ui_config.snap b/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_ui_config_overrides_config_ui_config.snap index e19fae30f..785908a18 100644 --- a/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_ui_config_overrides_config_ui_config.snap +++ b/zellij-utils/src/snapshots/zellij_utils__setup__setup_test__layout_ui_config_overrides_config_ui_config.snap @@ -6135,6 +6135,7 @@ Config { show_release_notes: None, advanced_mouse_actions: None, mouse_scroll_resize: None, + scroll_mode_sync: None, mouse_hover_effects: None, mouse_hover_tips: None, visual_bell: None,