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:
parent
9032ea9849
commit
2a63c5f951
3 changed files with 175 additions and 150 deletions
|
@ -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,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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,
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 multibuffer_point = match &action.deployed_from {
|
||||||
|
Some(CodeActionSource::Indicator(row)) => {
|
||||||
|
DisplayPoint::new(*row, 0).to_point(&snapshot)
|
||||||
}
|
}
|
||||||
|
_ => self.selections.newest::<Point>(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 !self.focus_handle.is_focused(window) {
|
||||||
if editor.focus_handle.is_focused(window) {
|
return;
|
||||||
let multibuffer_point = match &action.deployed_from {
|
}
|
||||||
Some(CodeActionSource::Indicator(row)) => {
|
let project = self.project.clone();
|
||||||
DisplayPoint::new(*row, 0).to_point(&snapshot)
|
|
||||||
}
|
let code_actions_task = match deployed_from {
|
||||||
_ => editor.selections.newest::<Point>(cx).head(),
|
Some(CodeActionSource::RunMenu(_)) => Task::ready(None),
|
||||||
};
|
_ => self.code_actions(buffer_row, window, cx),
|
||||||
let (buffer, buffer_row) = snapshot
|
};
|
||||||
.buffer_snapshot
|
|
||||||
.buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
|
let runnable_task = match deployed_from {
|
||||||
.and_then(|(buffer_snapshot, range)| {
|
Some(CodeActionSource::Indicator(_)) => Task::ready(Ok(Default::default())),
|
||||||
editor
|
_ => {
|
||||||
.buffer
|
let mut task_context_task = Task::ready(None);
|
||||||
.read(cx)
|
if let Some(tasks) = &tasks {
|
||||||
.buffer(buffer_snapshot.remote_id())
|
if let Some(project) = project {
|
||||||
.map(|buffer| (buffer, range.start.row))
|
task_context_task =
|
||||||
})?;
|
Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
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,103 +5776,135 @@ 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)
|
|
||||||
})?;
|
|
||||||
|
|
||||||
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![]
|
|
||||||
}
|
|
||||||
})?;
|
})?;
|
||||||
let spawn_straight_away = quick_launch
|
anyhow::Ok((resolved_tasks, debug_scenarios, task_context))
|
||||||
&& 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(())
|
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);
|
.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,
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue