Fix code actions run confusion (#32579)

Now if you click the triangle you get runnables, if you click the
lightning bolt you get code actions, if you trigger the code actions
menu with the mouse/keyboard you still get both.

Release Notes:

- Fixed the run/code actions menu to not duplicate content when opened
from the respective icons.

---------

Co-authored-by: Anthony Eid <hello@anthonyeid.me>
This commit is contained in:
Conrad Irwin 2025-06-11 16:51:46 -06:00 committed by GitHub
parent 9032ea9849
commit 2a63c5f951
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 175 additions and 150 deletions

View file

@ -87,6 +87,7 @@ pub struct ToggleCodeActions {
#[derive(PartialEq, Clone, Debug)] #[derive(PartialEq, Clone, Debug)]
pub enum CodeActionSource { pub enum CodeActionSource {
Indicator(DisplayRow), Indicator(DisplayRow),
RunMenu(DisplayRow),
QuickActionBar, QuickActionBar,
} }

View file

@ -1401,7 +1401,9 @@ impl CodeActionsMenu {
fn origin(&self) -> ContextMenuOrigin { fn origin(&self) -> ContextMenuOrigin {
match &self.deployed_from { match &self.deployed_from {
Some(CodeActionSource::Indicator(row)) => ContextMenuOrigin::GutterIndicator(*row), Some(CodeActionSource::Indicator(row)) | Some(CodeActionSource::RunMenu(row)) => {
ContextMenuOrigin::GutterIndicator(*row)
}
Some(CodeActionSource::QuickActionBar) => ContextMenuOrigin::QuickActionBar, Some(CodeActionSource::QuickActionBar) => ContextMenuOrigin::QuickActionBar,
None => ContextMenuOrigin::Cursor, None => ContextMenuOrigin::Cursor,
} }

View file

@ -5711,70 +5711,60 @@ impl Editor {
drop(context_menu); drop(context_menu);
let snapshot = self.snapshot(window, cx); let snapshot = self.snapshot(window, cx);
let deployed_from = action.deployed_from.clone(); let deployed_from = action.deployed_from.clone();
let mut task = self.code_actions_task.take();
let action = action.clone(); let action = action.clone();
cx.spawn_in(window, async move |editor, cx| { self.completion_tasks.clear();
while let Some(prev_task) = task { self.discard_inline_completion(false, cx);
prev_task.await.log_err();
task = editor.update(cx, |this, _| this.code_actions_task.take())?;
}
let spawned_test_task = editor.update_in(cx, |editor, window, cx| {
if editor.focus_handle.is_focused(window) {
let multibuffer_point = match &action.deployed_from { let multibuffer_point = match &action.deployed_from {
Some(CodeActionSource::Indicator(row)) => { Some(CodeActionSource::Indicator(row)) => {
DisplayPoint::new(*row, 0).to_point(&snapshot) DisplayPoint::new(*row, 0).to_point(&snapshot)
} }
_ => editor.selections.newest::<Point>(cx).head(), _ => self.selections.newest::<Point>(cx).head(),
}; };
let (buffer, buffer_row) = snapshot let Some((buffer, buffer_row)) = snapshot
.buffer_snapshot .buffer_snapshot
.buffer_line_for_row(MultiBufferRow(multibuffer_point.row)) .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
.and_then(|(buffer_snapshot, range)| { .and_then(|(buffer_snapshot, range)| {
editor self.buffer()
.buffer
.read(cx) .read(cx)
.buffer(buffer_snapshot.remote_id()) .buffer(buffer_snapshot.remote_id())
.map(|buffer| (buffer, range.start.row)) .map(|buffer| (buffer, range.start.row))
})?;
let (_, code_actions) = editor
.available_code_actions
.clone()
.and_then(|(location, code_actions)| {
let snapshot = location.buffer.read(cx).snapshot();
let point_range = location.range.to_point(&snapshot);
let point_range = point_range.start.row..=point_range.end.row;
if point_range.contains(&buffer_row) {
Some((location, code_actions))
} else {
None
}
}) })
.unzip(); else {
return;
};
let buffer_id = buffer.read(cx).remote_id(); let buffer_id = buffer.read(cx).remote_id();
let tasks = editor let tasks = self
.tasks .tasks
.get(&(buffer_id, buffer_row)) .get(&(buffer_id, buffer_row))
.map(|t| Arc::new(t.to_owned())); .map(|t| Arc::new(t.to_owned()));
if tasks.is_none() && code_actions.is_none() {
return None; if !self.focus_handle.is_focused(window) {
return;
}
let project = self.project.clone();
let code_actions_task = match deployed_from {
Some(CodeActionSource::RunMenu(_)) => Task::ready(None),
_ => self.code_actions(buffer_row, window, cx),
};
let runnable_task = match deployed_from {
Some(CodeActionSource::Indicator(_)) => Task::ready(Ok(Default::default())),
_ => {
let mut task_context_task = Task::ready(None);
if let Some(tasks) = &tasks {
if let Some(project) = project {
task_context_task =
Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
}
} }
editor.completion_tasks.clear(); cx.spawn_in(window, {
editor.discard_inline_completion(false, cx); let buffer = buffer.clone();
let task_context = async move |editor, cx| {
tasks let task_context = task_context_task.await;
.as_ref()
.zip(editor.project.clone())
.map(|(tasks, project)| {
Self::build_tasks_context(&project, &buffer, buffer_row, tasks, cx)
});
Some(cx.spawn_in(window, async move |editor, cx| {
let task_context = match task_context {
Some(task_context) => task_context.await,
None => None,
};
let resolved_tasks = let resolved_tasks =
tasks tasks
.zip(task_context.clone()) .zip(task_context.clone())
@ -5786,49 +5776,17 @@ impl Editor {
)), )),
}); });
let debug_scenarios = editor.update(cx, |editor, cx| { let debug_scenarios = editor.update(cx, |editor, cx| {
if cx.has_flag::<DebuggerFeatureFlag>() { editor.debug_scenarios(&resolved_tasks, &buffer, cx)
maybe!({
let project = editor.project.as_ref()?;
let dap_store = project.read(cx).dap_store();
let mut scenarios = vec![];
let resolved_tasks = resolved_tasks.as_ref()?;
let buffer = buffer.read(cx);
let language = buffer.language()?;
let file = buffer.file();
let debug_adapter =
language_settings(language.name().into(), file, cx)
.debuggers
.first()
.map(SharedString::from)
.or_else(|| {
language
.config()
.debuggers
.first()
.map(SharedString::from)
})?; })?;
anyhow::Ok((resolved_tasks, debug_scenarios, task_context))
dap_store.update(cx, |dap_store, cx| {
for (_, task) in &resolved_tasks.templates {
if let Some(scenario) = dap_store
.debug_scenario_for_build_task(
task.original_task().clone(),
debug_adapter.clone().into(),
task.display_label().to_owned().into(),
cx,
)
{
scenarios.push(scenario);
} }
}
});
Some(scenarios)
}) })
.unwrap_or_default()
} else {
vec![]
} }
})?; };
cx.spawn_in(window, async move |editor, cx| {
let (resolved_tasks, debug_scenarios, task_context) = runnable_task.await?;
let code_actions = code_actions_task.await;
let spawn_straight_away = quick_launch let spawn_straight_away = quick_launch
&& resolved_tasks && resolved_tasks
.as_ref() .as_ref()
@ -5837,7 +5795,8 @@ impl Editor {
.as_ref() .as_ref()
.map_or(true, |actions| actions.is_empty()) .map_or(true, |actions| actions.is_empty())
&& debug_scenarios.is_empty(); && debug_scenarios.is_empty();
if let Ok(task) = editor.update_in(cx, |editor, window, cx| {
editor.update_in(cx, |editor, window, cx| {
crate::hover_popover::hide_hover(editor, cx); crate::hover_popover::hide_hover(editor, cx);
*editor.context_menu.borrow_mut() = *editor.context_menu.borrow_mut() =
Some(CodeContextMenu::CodeActions(CodeActionsMenu { Some(CodeContextMenu::CodeActions(CodeActionsMenu {
@ -5862,27 +5821,90 @@ impl Editor {
return task; return task;
} }
} }
cx.notify();
Task::ready(Ok(()))
}) {
task.await
} else {
Ok(())
}
}))
} else {
Some(Task::ready(Ok(())))
}
})?;
if let Some(task) = spawned_test_task {
task.await?;
}
anyhow::Ok(()) Task::ready(Ok(()))
})
}) })
.detach_and_log_err(cx); .detach_and_log_err(cx);
} }
fn debug_scenarios(
&mut self,
resolved_tasks: &Option<ResolvedTasks>,
buffer: &Entity<Buffer>,
cx: &mut App,
) -> Vec<task::DebugScenario> {
if cx.has_flag::<DebuggerFeatureFlag>() {
maybe!({
let project = self.project.as_ref()?;
let dap_store = project.read(cx).dap_store();
let mut scenarios = vec![];
let resolved_tasks = resolved_tasks.as_ref()?;
let buffer = buffer.read(cx);
let language = buffer.language()?;
let file = buffer.file();
let debug_adapter = language_settings(language.name().into(), file, cx)
.debuggers
.first()
.map(SharedString::from)
.or_else(|| language.config().debuggers.first().map(SharedString::from))?;
dap_store.update(cx, |dap_store, cx| {
for (_, task) in &resolved_tasks.templates {
if let Some(scenario) = dap_store.debug_scenario_for_build_task(
task.original_task().clone(),
debug_adapter.clone().into(),
task.display_label().to_owned().into(),
cx,
) {
scenarios.push(scenario);
}
}
});
Some(scenarios)
})
.unwrap_or_default()
} else {
vec![]
}
}
fn code_actions(
&mut self,
buffer_row: u32,
window: &mut Window,
cx: &mut Context<Self>,
) -> Task<Option<Rc<[AvailableCodeAction]>>> {
let mut task = self.code_actions_task.take();
cx.spawn_in(window, async move |editor, cx| {
while let Some(prev_task) = task {
prev_task.await.log_err();
task = editor
.update(cx, |this, _| this.code_actions_task.take())
.ok()?;
}
editor
.update(cx, |editor, cx| {
editor
.available_code_actions
.clone()
.and_then(|(location, code_actions)| {
let snapshot = location.buffer.read(cx).snapshot();
let point_range = location.range.to_point(&snapshot);
let point_range = point_range.start.row..=point_range.end.row;
if point_range.contains(&buffer_row) {
Some(code_actions)
} else {
None
}
})
})
.ok()
.flatten()
})
}
pub fn confirm_code_action( pub fn confirm_code_action(
&mut self, &mut self,
action: &ConfirmCodeAction, action: &ConfirmCodeAction,
@ -7920,7 +7942,7 @@ impl Editor {
window.focus(&editor.focus_handle(cx)); window.focus(&editor.focus_handle(cx));
editor.toggle_code_actions( editor.toggle_code_actions(
&ToggleCodeActions { &ToggleCodeActions {
deployed_from: Some(CodeActionSource::Indicator(row)), deployed_from: Some(CodeActionSource::RunMenu(row)),
quick_launch, quick_launch,
}, },
window, window,