Fix service selection cursor highlighting and bounds checking

Visual Highlighting Fixes:
- Apply selection highlighting to individual text spans instead of entire paragraph
- Eliminates highlighting of empty space below services on single-service hosts
- Selection now only highlights actual service text content

Selection Logic Improvements:
- Add proper bounds checking in select_next() to prevent invalid selections
- Automatically clamp selection index when services are updated
- Add debug logging to track selection state and movement

Bounds Safety:
- Ensure selection index stays valid when service lists change
- Prevent out-of-bounds access during service updates
- Reset selection to last valid index when services are removed

This fixes the issues where:
1. Single service hosts showed empty space highlighting
2. Multi-service hosts had no visible selection cursor
3. Selection could become invalid when services changed
This commit is contained in:
Christoffer Martinsson 2025-10-23 21:37:10 +02:00
parent c851590aaa
commit 999e7b5db5

View File

@ -202,13 +202,15 @@ impl ServicesWidget {
if self.selected_index > 0 { if self.selected_index > 0 {
self.selected_index -= 1; self.selected_index -= 1;
} }
debug!("Service selection moved up to: {}", self.selected_index);
} }
/// Move selection down /// Move selection down
pub fn select_next(&mut self, total_services: usize) { pub fn select_next(&mut self, total_services: usize) {
if self.selected_index < total_services.saturating_sub(1) { if total_services > 0 && self.selected_index < total_services.saturating_sub(1) {
self.selected_index += 1; self.selected_index += 1;
} }
debug!("Service selection: {}/{}", self.selected_index, total_services);
} }
/// Get currently selected service name (for actions) /// Get currently selected service name (for actions)
@ -361,10 +363,18 @@ impl Widget for ServicesWidget {
self.has_data = !self.parent_services.is_empty() || !self.sub_services.is_empty(); self.has_data = !self.parent_services.is_empty() || !self.sub_services.is_empty();
// Ensure selection index is within bounds after update
let total_count = self.get_total_services_count();
if self.selected_index >= total_count && total_count > 0 {
self.selected_index = total_count - 1;
}
debug!( debug!(
"Services widget updated: {} parent services, {} sub-service groups, status={:?}", "Services widget updated: {} parent services, {} sub-service groups, total={}, selected={}, status={:?}",
self.parent_services.len(), self.parent_services.len(),
self.sub_services.len(), self.sub_services.len(),
total_count,
self.selected_index,
self.status self.status
); );
} }
@ -473,7 +483,7 @@ impl ServicesWidget {
let actual_index = effective_scroll + i; // Real index in the full list let actual_index = effective_scroll + i; // Real index in the full list
let is_selected = actual_index == self.selected_index; let is_selected = actual_index == self.selected_index;
let spans = if *is_sub && sub_info.is_some() { let mut spans = if *is_sub && sub_info.is_some() {
// Use custom sub-service span creation // Use custom sub-service span creation
let (service_info, is_last) = sub_info.as_ref().unwrap(); let (service_info, is_last) = sub_info.as_ref().unwrap();
self.create_sub_service_spans(line_text, service_info, *is_last) self.create_sub_service_spans(line_text, service_info, *is_last)
@ -482,17 +492,17 @@ impl ServicesWidget {
StatusIcons::create_status_spans(*line_status, line_text) StatusIcons::create_status_spans(*line_status, line_text)
}; };
let mut service_para = Paragraph::new(ratatui::text::Line::from(spans)); // Apply selection highlighting to spans
// Apply selection highlighting
if is_selected { if is_selected {
service_para = service_para.style( for span in spans.iter_mut() {
Style::default() span.style = span.style
.bg(Theme::highlight()) .bg(Theme::highlight())
.fg(Theme::background()) .fg(Theme::background());
); }
} }
let service_para = Paragraph::new(ratatui::text::Line::from(spans));
frame.render_widget(service_para, service_chunks[i]); frame.render_widget(service_para, service_chunks[i]);
} }
} }