From 84b753cb51441f104fc35b540b9fe77a409f4529 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Soares?= <37777652+Dnreikronos@users.noreply.github.com> Date: Tue, 16 Jun 2026 14:42:36 -0300 Subject: [PATCH] editor: Refresh active debug line highlight on theme change (#59274) # Objective Fixes #58736 Pause the debugger on a breakpoint, switch your theme, and the active debug line keeps its old highlight color until you step again or restart Zed. ## Solution The highlight stores a concrete color grabbed from the theme back when `go_to_active_debug_line` ran. A theme switch goes through `theme_changed`, which refreshes brackets, semantic tokens, and outline symbols but never re-applies that highlight, so it stays stale. Re-running `go_to_active_debug_line` from `theme_changed` re-resolves the color against the current theme. ## Testing Added a regression test in `debugger_ui` that stops at a debug line, swaps the theme's `editor.debugger_active_line.background`, and checks the highlight follows. It fails on `main` and passes with the fix. Also tested by hand on Windows: started a debugpy session, paused at a breakpoint, switched themes from the theme selector, and watched the active line recolor live without stepping or restarting. ## Self-Review Checklist: - [x] I've reviewed my own diff for quality, security, and reliability - [x] Unsafe blocks (if any) have justifying comments - [ ] The content adheres to Zed's UI standards ([UX/UI](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist) and [icon](https://github.com/zed-industries/zed/blob/main/crates/icons/README.md) guidelines) - [x] Tests cover the new/changed behavior - [x] Performance impact has been considered and is acceptable ## Showcase Paused at a breakpoint, switching themes from the theme selector. | Before | After | | --- | --- | | https://github.com/user-attachments/assets/653a7e1b-3a99-4316-b1c9-b16ed7a7a8ba | https://github.com/user-attachments/assets/22a4c876-30f2-44c5-aad9-0f860639074c | --- Release Notes: - Fixed the active debug line color not updating when switching themes while the debugger is paused ([#58736](https://github.com/zed-industries/zed/issues/58736)). --------- Co-authored-by: dino --- crates/agent_ui/src/inline_assistant.rs | 4 +- .../debugger_ui/src/tests/debugger_panel.rs | 40 +++++++++---------- .../debugger_ui/src/tests/stack_frame_list.rs | 4 +- crates/editor/src/edit_prediction.rs | 2 +- crates/editor/src/editor.rs | 18 ++++----- crates/editor/src/navigation.rs | 10 +---- crates/git_ui/src/conflict_view.rs | 4 +- crates/go_to_line/src/go_to_line.rs | 2 +- crates/gpui/src/color.rs | 1 + crates/outline/src/outline.rs | 4 +- crates/vim/src/command.rs | 2 +- 11 files changed, 43 insertions(+), 48 deletions(-) diff --git a/crates/agent_ui/src/inline_assistant.rs b/crates/agent_ui/src/inline_assistant.rs index 661feabb5f6..4790b72fb92 100644 --- a/crates/agent_ui/src/inline_assistant.rs +++ b/crates/agent_ui/src/inline_assistant.rs @@ -1354,7 +1354,7 @@ impl InlineAssistant { for row_range in inserted_row_ranges { editor.highlight_rows::( row_range, - cx.theme().status().info_background, + |cx| cx.theme().status().info_background, Default::default(), cx, ); @@ -1423,7 +1423,7 @@ impl InlineAssistant { editor.set_show_edit_predictions(Some(false), window, cx); editor.highlight_rows::( Anchor::Min..Anchor::Max, - cx.theme().status().deleted_background, + |cx| cx.theme().status().deleted_background, Default::default(), cx, ); diff --git a/crates/debugger_ui/src/tests/debugger_panel.rs b/crates/debugger_ui/src/tests/debugger_panel.rs index 6978b7850ac..90b36074ccc 100644 --- a/crates/debugger_ui/src/tests/debugger_panel.rs +++ b/crates/debugger_ui/src/tests/debugger_panel.rs @@ -1600,7 +1600,7 @@ async fn test_active_debug_line_setting(executor: BackgroundExecutor, cx: &mut T cx.run_until_parked(); main_editor.update_in(cx, |editor, window, cx| { - let active_debug_lines: Vec<_> = editor.highlighted_rows::().collect(); + let active_debug_lines: Vec<_> = editor.highlighted_rows::(cx).collect(); assert_eq!( active_debug_lines.len(), @@ -1616,8 +1616,8 @@ async fn test_active_debug_line_setting(executor: BackgroundExecutor, cx: &mut T assert_eq!(point.row, 1); }); - second_editor.update(cx, |editor, _| { - let active_debug_lines: Vec<_> = editor.highlighted_rows::().collect(); + second_editor.update(cx, |editor, cx| { + let active_debug_lines: Vec<_> = editor.highlighted_rows::(cx).collect(); assert!( active_debug_lines.is_empty(), @@ -1675,7 +1675,7 @@ async fn test_active_debug_line_setting(executor: BackgroundExecutor, cx: &mut T cx.run_until_parked(); second_editor.update_in(cx, |editor, window, cx| { - let active_debug_lines: Vec<_> = editor.highlighted_rows::().collect(); + let active_debug_lines: Vec<_> = editor.highlighted_rows::(cx).collect(); assert_eq!( active_debug_lines.len(), @@ -1691,8 +1691,8 @@ async fn test_active_debug_line_setting(executor: BackgroundExecutor, cx: &mut T assert_eq!(point.row, 2); }); - main_editor.update(cx, |editor, _| { - let active_debug_lines: Vec<_> = editor.highlighted_rows::().collect(); + main_editor.update(cx, |editor, cx| { + let active_debug_lines: Vec<_> = editor.highlighted_rows::(cx).collect(); assert!( active_debug_lines.is_empty(), @@ -1714,8 +1714,8 @@ async fn test_active_debug_line_setting(executor: BackgroundExecutor, cx: &mut T cx.run_until_parked(); - second_editor.update(cx, |editor, _| { - let active_debug_lines: Vec<_> = editor.highlighted_rows::().collect(); + second_editor.update(cx, |editor, cx| { + let active_debug_lines: Vec<_> = editor.highlighted_rows::(cx).collect(); assert!( active_debug_lines.is_empty(), @@ -1723,8 +1723,8 @@ async fn test_active_debug_line_setting(executor: BackgroundExecutor, cx: &mut T ); }); - main_editor.update(cx, |editor, _| { - let active_debug_lines: Vec<_> = editor.highlighted_rows::().collect(); + main_editor.update(cx, |editor, cx| { + let active_debug_lines: Vec<_> = editor.highlighted_rows::(cx).collect(); assert!( active_debug_lines.is_empty(), @@ -1741,8 +1741,8 @@ async fn test_active_debug_line_setting(executor: BackgroundExecutor, cx: &mut T shutdown_session.await.unwrap(); - main_editor.update(cx, |editor, _| { - let active_debug_lines: Vec<_> = editor.highlighted_rows::().collect(); + main_editor.update(cx, |editor, cx| { + let active_debug_lines: Vec<_> = editor.highlighted_rows::(cx).collect(); assert!( active_debug_lines.is_empty(), @@ -1750,8 +1750,8 @@ async fn test_active_debug_line_setting(executor: BackgroundExecutor, cx: &mut T ); }); - second_editor.update(cx, |editor, _| { - let active_debug_lines: Vec<_> = editor.highlighted_rows::().collect(); + second_editor.update(cx, |editor, cx| { + let active_debug_lines: Vec<_> = editor.highlighted_rows::(cx).collect(); assert!( active_debug_lines.is_empty(), @@ -2076,7 +2076,7 @@ async fn test_breakpoint_jumps_only_in_proper_split_view( if let Some(editor) = item.to_any_view().downcast::().ok() { total_active_debug_lines += editor .read(cx) - .highlighted_rows::() + .highlighted_rows::(cx) .count(); } } @@ -2096,7 +2096,7 @@ async fn test_breakpoint_jumps_only_in_proper_split_view( let active_debug_lines: Vec<_> = pane_b_editor .read(cx) - .highlighted_rows::() + .highlighted_rows::(cx) .collect(); assert_eq!( @@ -2170,7 +2170,7 @@ async fn test_breakpoint_jumps_only_in_proper_split_view( let active_debug_lines: Vec<_> = pane_b_editor .read(cx) - .highlighted_rows::() + .highlighted_rows::(cx) .collect(); assert_eq!( @@ -2190,7 +2190,7 @@ async fn test_breakpoint_jumps_only_in_proper_split_view( if let Some(editor) = item.to_any_view().downcast::().ok() { total_active_debug_lines += editor .read(cx) - .highlighted_rows::() + .highlighted_rows::(cx) .count(); } } @@ -2304,7 +2304,7 @@ async fn test_breakpoint_jumps_only_in_proper_split_view( let active_debug_lines: Vec<_> = pane_c_editor .read(cx) - .highlighted_rows::() + .highlighted_rows::(cx) .collect(); assert_eq!( @@ -2324,7 +2324,7 @@ async fn test_breakpoint_jumps_only_in_proper_split_view( if let Some(editor) = item.to_any_view().downcast::().ok() { total_active_debug_lines += editor .read(cx) - .highlighted_rows::() + .highlighted_rows::(cx) .count(); } } diff --git a/crates/debugger_ui/src/tests/stack_frame_list.rs b/crates/debugger_ui/src/tests/stack_frame_list.rs index c7977efeced..1c83a11440e 100644 --- a/crates/debugger_ui/src/tests/stack_frame_list.rs +++ b/crates/debugger_ui/src/tests/stack_frame_list.rs @@ -345,7 +345,7 @@ async fn test_select_stack_frame(executor: BackgroundExecutor, cx: &mut TestAppC let snapshot = editor.snapshot(window, cx); editor - .highlighted_rows::() + .highlighted_rows::(cx) .map(|(range, _)| { let start = range.start.to_point(&snapshot.buffer_snapshot()); let end = range.end.to_point(&snapshot.buffer_snapshot()); @@ -408,7 +408,7 @@ async fn test_select_stack_frame(executor: BackgroundExecutor, cx: &mut TestAppC let snapshot = editor.snapshot(window, cx); editor - .highlighted_rows::() + .highlighted_rows::(cx) .map(|(range, _)| { let start = range.start.to_point(&snapshot.buffer_snapshot()); let end = range.end.to_point(&snapshot.buffer_snapshot()); diff --git a/crates/editor/src/edit_prediction.rs b/crates/editor/src/edit_prediction.rs index c5d3bb1f165..43e9b2ff375 100644 --- a/crates/editor/src/edit_prediction.rs +++ b/crates/editor/src/edit_prediction.rs @@ -399,7 +399,7 @@ impl Editor { )); self.highlight_rows::( target..target, - cx.theme().colors().editor_highlighted_line_background, + |cx| cx.theme().colors().editor_highlighted_line_background, RowHighlightOptions { autoscroll: true, ..Default::default() diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index e65140d53fe..e165c765f26 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -613,9 +613,6 @@ impl EditorActionId { } } -// type GetFieldEditorTheme = dyn Fn(&theme::Theme) -> theme::FieldEditor; -// type OverrideTextStyle = dyn Fn(&EditorStyle) -> Option; - type BackgroundHighlight = ( Arc Hsla + Send + Sync>, Arc<[Range]>, @@ -1493,7 +1490,7 @@ impl Default for RowHighlightOptions { struct RowHighlight { index: usize, range: Range, - color: Hsla, + color: fn(&App) -> Hsla, options: RowHighlightOptions, type_id: TypeId, } @@ -6219,7 +6216,7 @@ impl Editor { self.go_to_line::( multibuffer_anchor, - Some(cx.theme().colors().editor_debugger_active_line_background), + |cx| cx.theme().colors().editor_debugger_active_line_background, window, cx, ); @@ -8626,7 +8623,7 @@ impl Editor { pub fn highlight_rows( &mut self, range: Range, - color: Hsla, + color: fn(&App) -> Hsla, options: RowHighlightOptions, cx: &mut Context, ) { @@ -8738,12 +8735,15 @@ impl Editor { } /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting. - pub fn highlighted_rows(&self) -> impl '_ + Iterator, Hsla)> { + pub fn highlighted_rows<'a, T: 'static>( + &'a self, + cx: &'a App, + ) -> impl 'a + Iterator, Hsla)> { self.highlighted_rows .get(&TypeId::of::()) .map_or(&[] as &[_], |vec| vec.as_slice()) .iter() - .map(|highlight| (highlight.range.clone(), highlight.color)) + .map(|highlight| (highlight.range.clone(), (highlight.color)(cx))) } /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict. @@ -8780,7 +8780,7 @@ impl Editor { LineHighlight { include_gutter: highlight.options.include_gutter, border: None, - background: highlight.color.into(), + background: (highlight.color)(cx).into(), type_id: Some(highlight.type_id), }, ); diff --git a/crates/editor/src/navigation.rs b/crates/editor/src/navigation.rs index 051b7f0ab42..4f9880c438b 100644 --- a/crates/editor/src/navigation.rs +++ b/crates/editor/src/navigation.rs @@ -1553,7 +1553,7 @@ impl Editor { pub(super) fn go_to_line( &mut self, position: Anchor, - highlight_color: Option, + highlight_color: fn(&App) -> Hsla, window: &mut Window, cx: &mut Context, ) { @@ -1566,13 +1566,7 @@ impl Editor { let start = snapshot.buffer_snapshot().anchor_before(start); let end = snapshot.buffer_snapshot().anchor_before(end); - self.highlight_rows::( - start..end, - highlight_color - .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background), - Default::default(), - cx, - ); + self.highlight_rows::(start..end, highlight_color, Default::default(), cx); if self.buffer.read(cx).is_singleton() { self.request_autoscroll(Autoscroll::center().for_anchor(start), cx); diff --git a/crates/git_ui/src/conflict_view.rs b/crates/git_ui/src/conflict_view.rs index 5be215a166c..309a1180781 100644 --- a/crates/git_ui/src/conflict_view.rs +++ b/crates/git_ui/src/conflict_view.rs @@ -283,8 +283,8 @@ fn update_conflict_highlighting( let ours = buffer.buffer_anchor_range_to_anchor_range(conflict.ours.clone())?; let theirs = buffer.buffer_anchor_range_to_anchor_range(conflict.theirs.clone())?; - let ours_background = cx.theme().colors().version_control_conflict_marker_ours; - let theirs_background = cx.theme().colors().version_control_conflict_marker_theirs; + let ours_background = |cx: &App| cx.theme().colors().version_control_conflict_marker_ours; + let theirs_background = |cx: &App| cx.theme().colors().version_control_conflict_marker_theirs; let options = RowHighlightOptions { include_gutter: true, diff --git a/crates/go_to_line/src/go_to_line.rs b/crates/go_to_line/src/go_to_line.rs index 65abed6998d..de80a5af01d 100644 --- a/crates/go_to_line/src/go_to_line.rs +++ b/crates/go_to_line/src/go_to_line.rs @@ -197,7 +197,7 @@ impl GoToLine { let end = snapshot.anchor_after(end_point); editor.highlight_rows::( start..end, - cx.theme().colors().editor_highlighted_line_background, + |cx| cx.theme().colors().editor_highlighted_line_background, RowHighlightOptions { autoscroll: true, ..Default::default() diff --git a/crates/gpui/src/color.rs b/crates/gpui/src/color.rs index 8ec816f49d7..3914c0a3b9f 100644 --- a/crates/gpui/src/color.rs +++ b/crates/gpui/src/color.rs @@ -956,6 +956,7 @@ impl From for Background { } } } + impl From for Background { fn from(value: Rgba) -> Self { Background { diff --git a/crates/outline/src/outline.rs b/crates/outline/src/outline.rs index 20662f322f4..516ee1c5479 100644 --- a/crates/outline/src/outline.rs +++ b/crates/outline/src/outline.rs @@ -246,7 +246,7 @@ impl OutlineViewDelegate { active_editor.clear_row_highlights::(); active_editor.highlight_rows::( outline_item.range.start..outline_item.range.end, - cx.theme().colors().editor_highlighted_line_background, + |cx| cx.theme().colors().editor_highlighted_line_background, RowHighlightOptions { autoscroll: true, ..Default::default() @@ -377,7 +377,7 @@ impl PickerDelegate for OutlineViewDelegate { self.active_editor.update(cx, |active_editor, cx| { let highlight = active_editor - .highlighted_rows::() + .highlighted_rows::(cx) .next(); if let Some((rows, _)) = highlight { active_editor.change_selections( diff --git a/crates/vim/src/command.rs b/crates/vim/src/command.rs index da7092db699..e7d68caa79c 100644 --- a/crates/vim/src/command.rs +++ b/crates/vim/src/command.rs @@ -2549,7 +2549,7 @@ impl ShellExec { } editor.highlight_rows::( input_range.clone().unwrap(), - cx.theme().status().unreachable_background, + |cx| cx.theme().status().unreachable_background, Default::default(), cx, );