From 0b4280c8d6cd9eda442c8d8a7a41c1d5c81417eb Mon Sep 17 00:00:00 2001 From: Ogulcan Celik Date: Thu, 10 Sep 2026 14:15:22 +0300 Subject: [PATCH] fix: use host background for pane selection highlights refs #3798 --- src/client/shell/composition.rs | 5 ++- src/client/shell/input.rs | 22 ++++++---- src/client/shell/state.rs | 2 + src/client/shell/tests/copy.rs | 78 +++++++++++++++++++++++++++++++++ 4 files changed, 98 insertions(+), 9 deletions(-) diff --git a/src/client/shell/composition.rs b/src/client/shell/composition.rs index 92cf3c46..1d5e583d 100644 --- a/src/client/shell/composition.rs +++ b/src/client/shell/composition.rs @@ -351,7 +351,10 @@ impl ClientShellState { hit.inner_rect, hit.scroll, &self.config.palette, - crate::terminal_theme::TerminalTheme::default(), + crate::terminal_theme::TerminalTheme { + background: self.host_background, + ..Default::default() + }, ); } if copy_surface_coherent { diff --git a/src/client/shell/input.rs b/src/client/shell/input.rs index dd7bf87d..41a9f64c 100644 --- a/src/client/shell/input.rs +++ b/src/client/shell/input.rs @@ -267,16 +267,22 @@ impl ClientShellState { RawInputEvent::HostDefaultColor { kind: crate::terminal_theme::DefaultColorKind::Background, color, - } if !self.host_appearance_explicit => { - let appearance = color.inferred_appearance(); - self.host_appearance = Some(appearance); - if self.config.theme_runtime.auto_switch { - self.config.palette = crate::app::client_palette_for_appearance( - &self.config.theme_runtime, - appearance, - ); + } => { + if self.host_background != Some(color) { + self.host_background = Some(color); outcome.repaint = true; } + if !self.host_appearance_explicit { + let appearance = color.inferred_appearance(); + self.host_appearance = Some(appearance); + if self.config.theme_runtime.auto_switch { + self.config.palette = crate::app::client_palette_for_appearance( + &self.config.theme_runtime, + appearance, + ); + outcome.repaint = true; + } + } } RawInputEvent::HostDefaultColor { .. } | RawInputEvent::HostPaletteColors { .. } diff --git a/src/client/shell/state.rs b/src/client/shell/state.rs index 4ac49680..add297e2 100644 --- a/src/client/shell/state.rs +++ b/src/client/shell/state.rs @@ -969,6 +969,7 @@ pub(crate) struct ClientShellState { pub(super) pending_input_source_changes: Vec, pub(super) host_appearance: Option, pub(super) host_appearance_explicit: bool, + pub(super) host_background: Option, pub(super) local_config_diagnostic: Option, pub(super) config_diagnostic: Option, pub(super) endpoint_error: Option, @@ -1124,6 +1125,7 @@ impl ClientShellState { pending_input_source_changes: Vec::new(), host_appearance: None, host_appearance_explicit: false, + host_background: None, config_diagnostic: local_config_diagnostic.clone(), local_config_diagnostic, endpoint_error: None, diff --git a/src/client/shell/tests/copy.rs b/src/client/shell/tests/copy.rs index fa0635af..2e0ca28d 100644 --- a/src/client/shell/tests/copy.rs +++ b/src/client/shell/tests/copy.rs @@ -51,6 +51,84 @@ fn pasted_help_and_copy_queries_strip_control_characters() { ); } +#[test] +fn client_selection_uses_host_background_and_repaints_when_it_changes() { + use crate::terminal_theme::{DefaultColorKind, HostAppearance, RgbColor}; + use ratatui::style::Color; + + for explicit_appearance in [false, true] { + let mut config = ClientShellConfig::from_config(&Config::default()); + config.palette = Palette::terminal(); + config.theme_runtime.auto_switch = false; + let mut state = ClientShellState::new(config); + state.set_snapshot(Box::new(snapshot())); + state.set_pane_surface(surface()); + state.compose(106, 20).expect("composed frame"); + let pane = state.hits.panes[0].clone(); + for (kind, column) in [ + (MouseEventKind::Down(MouseButton::Left), pane.inner_rect.x), + ( + MouseEventKind::Drag(MouseButton::Left), + pane.inner_rect.x + 2, + ), + ] { + state.handle_raw_events(vec![RawInputEvent::Mouse(crossterm::event::MouseEvent { + kind, + column, + row: pane.inner_rect.y, + modifiers: KeyModifiers::empty(), + })]); + } + let cell_index = usize::from(pane.inner_rect.y) * 106 + usize::from(pane.inner_rect.x); + let fallback = state.compose(106, 20).expect("fallback frame"); + assert_eq!( + fallback.cells[cell_index].bg, + crate::protocol::color_to_u32(Color::DarkGray) + ); + if explicit_appearance { + state.handle_raw_events(vec![RawInputEvent::HostColorSchemeChanged( + HostAppearance::Light, + )]); + } + for (background, selected_bg, selected_fg) in [ + ((237, 237, 234), (171, 171, 168), (0, 0, 0)), + ((26, 27, 38), (90, 91, 99), (255, 255, 255)), + ] { + let (r, g, b) = background; + let outcome = state.handle_raw_events(vec![RawInputEvent::HostDefaultColor { + kind: DefaultColorKind::Background, + color: RgbColor { r, g, b }, + }]); + assert!(outcome + .requests + .iter() + .any(|request| matches!(request, ClientMessage::ClientShellHostTheme { .. }))); + let frame = state.compose(106, 20).expect("host-colored selection"); + let cell = &frame.cells[cell_index]; + assert_eq!( + cell.bg, + crate::protocol::color_to_u32(Color::Rgb( + selected_bg.0, + selected_bg.1, + selected_bg.2 + )) + ); + assert_eq!( + cell.fg, + crate::protocol::color_to_u32(Color::Rgb( + selected_fg.0, + selected_fg.1, + selected_fg.2 + )) + ); + assert!( + outcome.repaint, + "host background changes must repaint selection" + ); + } + } +} + #[test] fn client_mouse_selection_highlights_and_copies_through_endpoint_extraction() { let mut state = ClientShellState::new(ClientShellConfig::from_config(&Config::default()));