From 185c6986da4f9d062ddb9db56fc00c20e331b795 Mon Sep 17 00:00:00 2001 From: Ruslan Semagin Date: Wed, 22 Jul 2026 09:03:13 +0000 Subject: [PATCH] Add gutter action to clear run status (#61375) # Objective Add a way to manually clear completed run statuses from the gutter. Folow-up to #61095 This follows up on feedback from the previous gutter run status PR. Currently, editing a file clears run statuses automatically, but there was no direct way to clear a completed status without changing the file. ## Solution - Added a `Clear Run Status` entry to the gutter context menu. - Show the entry only when the row has a completed run status. - Clear the stored run status for that runnable when selected. - Keep running statuses unchanged, so this does not behave like task cancellation. ## Testing - Ran `cargo check -p editor`. - Manually ran a test from the gutter and waited for a completed status. - Verified right-clicking the gutter icon shows `Clear Run Status`. - Verified selecting `Clear Run Status` clears the status icon. - Verified the action is not shown for rows without a completed run status. ## Self-Review Checklist: - [x] I've reviewed my own diff for quality, security, and reliability - [x] Unsafe blocks (if any) have justifying comments - [x] 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) - [ ] Tests cover the new/changed behavior - [x] Performance impact has been considered and is acceptable [after.webm](https://github.com/user-attachments/assets/1ae284e1-03b1-4842-a63e-9d66c290b325) --- Release Notes: - Improved gutter run statuses by allowing completed statuses to be cleared from the gutter context menu. --- crates/editor/src/editor.rs | 28 +++++++++++++++++++++++++++- crates/editor/src/runnables.rs | 32 +++++++++++++++++++++++++++++++- 2 files changed, 58 insertions(+), 2 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 4a93aae5df..5203bcbf1b 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -4151,6 +4151,7 @@ impl Editor { fn gutter_context_menu( &self, anchor: Anchor, + display_row: DisplayRow, window: &mut Window, cx: &mut Context, ) -> Entity { @@ -4213,6 +4214,15 @@ impl Editor { }; let has_bookmark = bookmark.as_ref().is_some(); + let clear_runnable_task_status = self + .runnable_task_key_for_display_row(display_row, window, cx) + .filter(|(buffer_id, buffer_row)| { + matches!( + self.runnable_task_status(*buffer_id, *buffer_row), + Some(RunnableTaskStatus::Passed | RunnableTaskStatus::Failed) + ) + }); + let run_to_cursor = window.is_action_available(&RunToCursor, cx); let toggle_state_entry: Option<(&str, Box)> = @@ -4231,6 +4241,22 @@ impl Editor { ContextMenu::build(window, cx, |menu, _, _cx| { menu.on_blur_subscription(Subscription::new(|| {})) .context(focus_handle) + .when_some( + clear_runnable_task_status, + |this, (buffer_id, buffer_row)| { + this.entry("Clear Run Status", None, { + let weak_editor = weak_editor.clone(); + move |_window, cx| { + weak_editor + .update(cx, |this, cx| { + this.clear_runnable_task_status(buffer_id, buffer_row, cx); + }) + .log_err(); + } + }) + .separator() + }, + ) .when(run_to_cursor, |this| { let weak_editor = weak_editor.clone(); this.entry( @@ -5847,7 +5873,7 @@ impl Editor { return; } - let context_menu = self.gutter_context_menu(anchor, window, cx); + let context_menu = self.gutter_context_menu(anchor, display_row, window, cx); self.mouse_context_menu = MouseContextMenu::pinned_to_editor( self, diff --git a/crates/editor/src/runnables.rs b/crates/editor/src/runnables.rs index 7e0cc27706..7ccecf101f 100644 --- a/crates/editor/src/runnables.rs +++ b/crates/editor/src/runnables.rs @@ -18,7 +18,8 @@ use ui::{Clickable as _, Color, IconButton, IconSize, Toggleable as _}; use crate::{ CodeActionSource, Editor, EditorSettings, EditorStyle, RangeToAnchorExt, SpawnNearestTask, - ToggleCodeActions, UPDATE_DEBOUNCE, display_map::DisplayRow, + ToggleCodeActions, UPDATE_DEBOUNCE, + display_map::{DisplayPoint, DisplayRow}, }; #[derive(Debug)] @@ -67,6 +68,10 @@ impl RunnableData { self.task_statuses.insert((buffer_id, buffer_row), status); } + pub fn clear_task_status(&mut self, (buffer_id, buffer_row): (BufferId, BufferRow)) { + self.task_statuses.remove(&(buffer_id, buffer_row)); + } + pub fn task_key_for_offset( &self, buffer_id: BufferId, @@ -441,6 +446,31 @@ impl Editor { cx.notify(); } + pub(crate) fn clear_runnable_task_status( + &mut self, + buffer_id: BufferId, + buffer_row: BufferRow, + cx: &mut Context, + ) { + self.runnables.clear_task_status((buffer_id, buffer_row)); + cx.notify(); + } + + pub(crate) fn runnable_task_key_for_display_row( + &self, + display_row: DisplayRow, + window: &mut Window, + cx: &mut Context, + ) -> Option<(BufferId, BufferRow)> { + let snapshot = self.snapshot(window, cx); + let multibuffer_row = + MultiBufferRow(DisplayPoint::new(display_row, 0).to_point(&snapshot).row); + let (buffer_snapshot, range) = snapshot + .buffer_snapshot() + .buffer_line_for_row(multibuffer_row)?; + Some((buffer_snapshot.remote_id(), range.start.row)) + } + pub(crate) fn runnable_task_key_for_offset( &self, buffer_id: BufferId,