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.
This commit is contained in:
Ruslan Semagin authored and GitHub committed 2026-07-22 09:03:13 +00:00
1 parent 870ffffcd8
commit 185c6986da
2 files changed
+58 -2

No files matched your search

+27 -1
View File
@@ -4151,6 +4151,7 @@ impl Editor {
fn gutter_context_menu(
&self,
anchor: Anchor,
display_row: DisplayRow,
window: &mut Window,
cx: &mut Context<Self>,
) -> Entity<ContextMenu> {
@@ -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<dyn Action>)> =
@@ -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,
+31 -1
View File
@@ -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>,
) {
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<Self>,
) -> 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,