From 2a63c5f9512c076be88fc33af9e8f34f7b0e044b Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Wed, 11 Jun 2025 16:51:46 -0600 Subject: [PATCH] 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 --- crates/editor/src/actions.rs | 1 + crates/editor/src/code_context_menus.rs | 4 +- crates/editor/src/editor.rs | 320 +++++++++++++----------- 3 files changed, 175 insertions(+), 150 deletions(-) diff --git a/crates/editor/src/actions.rs b/crates/editor/src/actions.rs index 44136f5420..5a66c37bee 100644 --- a/crates/editor/src/actions.rs +++ b/crates/editor/src/actions.rs @@ -87,6 +87,7 @@ pub struct ToggleCodeActions { #[derive(PartialEq, Clone, Debug)] pub enum CodeActionSource { Indicator(DisplayRow), + RunMenu(DisplayRow), QuickActionBar, } diff --git a/crates/editor/src/code_context_menus.rs b/crates/editor/src/code_context_menus.rs index 7144bee9fe..2c17f9566a 100644 --- a/crates/editor/src/code_context_menus.rs +++ b/crates/editor/src/code_context_menus.rs @@ -1401,7 +1401,9 @@ impl CodeActionsMenu { fn origin(&self) -> ContextMenuOrigin { 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, None => ContextMenuOrigin::Cursor, } diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 7215a9e0a2..af463ac438 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -5711,70 +5711,60 @@ impl Editor { drop(context_menu); let snapshot = self.snapshot(window, cx); let deployed_from = action.deployed_from.clone(); - let mut task = self.code_actions_task.take(); let action = action.clone(); - 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())?; + self.completion_tasks.clear(); + self.discard_inline_completion(false, cx); + + let multibuffer_point = match &action.deployed_from { + Some(CodeActionSource::Indicator(row)) => { + DisplayPoint::new(*row, 0).to_point(&snapshot) } + _ => self.selections.newest::(cx).head(), + }; + let Some((buffer, buffer_row)) = snapshot + .buffer_snapshot + .buffer_line_for_row(MultiBufferRow(multibuffer_point.row)) + .and_then(|(buffer_snapshot, range)| { + self.buffer() + .read(cx) + .buffer(buffer_snapshot.remote_id()) + .map(|buffer| (buffer, range.start.row)) + }) + else { + return; + }; + let buffer_id = buffer.read(cx).remote_id(); + let tasks = self + .tasks + .get(&(buffer_id, buffer_row)) + .map(|t| Arc::new(t.to_owned())); - 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 { - Some(CodeActionSource::Indicator(row)) => { - DisplayPoint::new(*row, 0).to_point(&snapshot) - } - _ => editor.selections.newest::(cx).head(), - }; - let (buffer, buffer_row) = snapshot - .buffer_snapshot - .buffer_line_for_row(MultiBufferRow(multibuffer_point.row)) - .and_then(|(buffer_snapshot, range)| { - editor - .buffer - .read(cx) - .buffer(buffer_snapshot.remote_id()) - .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(); - let buffer_id = buffer.read(cx).remote_id(); - let tasks = editor - .tasks - .get(&(buffer_id, buffer_row)) - .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(); - editor.discard_inline_completion(false, cx); - let task_context = - tasks - .as_ref() - .zip(editor.project.clone()) - .map(|(tasks, project)| { - Self::build_tasks_context(&project, &buffer, buffer_row, tasks, cx) - }); + cx.spawn_in(window, { + let buffer = buffer.clone(); + async move |editor, cx| { + let task_context = task_context_task.await; - 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 = tasks .zip(task_context.clone()) @@ -5786,103 +5776,135 @@ impl Editor { )), }); let debug_scenarios = editor.update(cx, |editor, cx| { - if cx.has_flag::() { - 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) - })?; - - 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![] - } + editor.debug_scenarios(&resolved_tasks, &buffer, cx) })?; - let spawn_straight_away = quick_launch - && resolved_tasks - .as_ref() - .map_or(false, |tasks| tasks.templates.len() == 1) - && code_actions - .as_ref() - .map_or(true, |actions| actions.is_empty()) - && debug_scenarios.is_empty(); - if let Ok(task) = editor.update_in(cx, |editor, window, cx| { - crate::hover_popover::hide_hover(editor, cx); - *editor.context_menu.borrow_mut() = - Some(CodeContextMenu::CodeActions(CodeActionsMenu { - buffer, - actions: CodeActionContents::new( - resolved_tasks, - code_actions, - debug_scenarios, - task_context.unwrap_or_default(), - ), - selected_item: Default::default(), - scroll_handle: UniformListScrollHandle::default(), - deployed_from, - })); - if spawn_straight_away { - if let Some(task) = editor.confirm_code_action( - &ConfirmCodeAction { item_ix: Some(0) }, - window, - cx, - ) { - cx.notify(); - 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((resolved_tasks, debug_scenarios, task_context)) + } + }) } + }; - anyhow::Ok(()) + 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 + && resolved_tasks + .as_ref() + .map_or(false, |tasks| tasks.templates.len() == 1) + && code_actions + .as_ref() + .map_or(true, |actions| actions.is_empty()) + && debug_scenarios.is_empty(); + + editor.update_in(cx, |editor, window, cx| { + crate::hover_popover::hide_hover(editor, cx); + *editor.context_menu.borrow_mut() = + Some(CodeContextMenu::CodeActions(CodeActionsMenu { + buffer, + actions: CodeActionContents::new( + resolved_tasks, + code_actions, + debug_scenarios, + task_context.unwrap_or_default(), + ), + selected_item: Default::default(), + scroll_handle: UniformListScrollHandle::default(), + deployed_from, + })); + if spawn_straight_away { + if let Some(task) = editor.confirm_code_action( + &ConfirmCodeAction { item_ix: Some(0) }, + window, + cx, + ) { + cx.notify(); + return task; + } + } + + Task::ready(Ok(())) + }) }) .detach_and_log_err(cx); } + fn debug_scenarios( + &mut self, + resolved_tasks: &Option, + buffer: &Entity, + cx: &mut App, + ) -> Vec { + if cx.has_flag::() { + 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, + ) -> Task>> { + 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( &mut self, action: &ConfirmCodeAction, @@ -7920,7 +7942,7 @@ impl Editor { window.focus(&editor.focus_handle(cx)); editor.toggle_code_actions( &ToggleCodeActions { - deployed_from: Some(CodeActionSource::Indicator(row)), + deployed_from: Some(CodeActionSource::RunMenu(row)), quick_launch, }, window,