Stop eagerly clearing available code actions on row change (#53571)
This commit is contained in:
1 parent
76883bb983
commit
828a64c3da
3 files changed
+165
-154
No files matched your search
+150
-142
@@ -1127,6 +1127,18 @@ pub(crate) struct DiffReviewOverlay {
|
||||
_subscription: Subscription,
|
||||
}
|
||||
|
||||
enum CodeActionsForSelection {
|
||||
None,
|
||||
Fetching(Shared<Task<Option<ActionFetchReady>>>),
|
||||
Ready(ActionFetchReady),
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct ActionFetchReady {
|
||||
location: Location,
|
||||
actions: Rc<[AvailableCodeAction]>,
|
||||
}
|
||||
|
||||
/// Zed's primary implementation of text input, allowing users to edit a [`MultiBuffer`].
|
||||
///
|
||||
/// See the [module level documentation](self) for more information.
|
||||
@@ -1215,8 +1227,8 @@ pub struct Editor {
|
||||
auto_signature_help: Option<bool>,
|
||||
find_all_references_task_sources: Vec<Anchor>,
|
||||
next_completion_id: CompletionId,
|
||||
available_code_actions: Option<(Location, Rc<[AvailableCodeAction]>)>,
|
||||
code_actions_task: Option<Task<Result<()>>>,
|
||||
code_actions_for_selection: CodeActionsForSelection,
|
||||
runnables_for_selection_toggle: Task<()>,
|
||||
quick_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
|
||||
debounced_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
|
||||
debounced_selection_highlight_complete: bool,
|
||||
@@ -2231,7 +2243,7 @@ impl Editor {
|
||||
editor.update_lsp_data(Some(buffer_id), window, cx);
|
||||
editor.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
|
||||
refresh_linked_ranges(editor, window, cx);
|
||||
editor.refresh_code_actions(window, cx);
|
||||
editor.refresh_code_actions_for_selection(window, cx);
|
||||
editor.refresh_document_highlights(cx);
|
||||
}
|
||||
}
|
||||
@@ -2479,8 +2491,8 @@ impl Editor {
|
||||
next_completion_id: 0,
|
||||
next_inlay_id: 0,
|
||||
code_action_providers,
|
||||
available_code_actions: None,
|
||||
code_actions_task: None,
|
||||
code_actions_for_selection: CodeActionsForSelection::None,
|
||||
runnables_for_selection_toggle: Task::ready(()),
|
||||
quick_selection_highlight_task: None,
|
||||
debounced_selection_highlight_task: None,
|
||||
debounced_selection_highlight_complete: false,
|
||||
@@ -3805,12 +3817,7 @@ impl Editor {
|
||||
|
||||
hide_hover(self, cx);
|
||||
|
||||
if old_cursor_position.to_display_point(&display_map).row()
|
||||
!= new_cursor_position.to_display_point(&display_map).row()
|
||||
{
|
||||
self.available_code_actions.take();
|
||||
}
|
||||
self.refresh_code_actions(window, cx);
|
||||
self.refresh_code_actions_for_selection(window, cx);
|
||||
self.refresh_document_highlights(cx);
|
||||
refresh_linked_ranges(self, window, cx);
|
||||
|
||||
@@ -6950,6 +6957,9 @@ impl Editor {
|
||||
}))
|
||||
}
|
||||
|
||||
/// Toggles an action selection menu for the latest selection.
|
||||
/// May show LSP code actions, code lens' command, runnables and potentially more entities applicable as actions.
|
||||
/// Previous menu toggled with this method will be closed.
|
||||
pub fn toggle_code_actions(
|
||||
&mut self,
|
||||
action: &ToggleCodeActions,
|
||||
@@ -7004,16 +7014,7 @@ impl Editor {
|
||||
.runnables((buffer_id, buffer_row))
|
||||
.map(|t| Arc::new(t.to_owned()));
|
||||
|
||||
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())),
|
||||
_ => {
|
||||
@@ -7051,19 +7052,42 @@ impl Editor {
|
||||
}
|
||||
};
|
||||
|
||||
cx.spawn_in(window, async move |editor, cx| {
|
||||
let toggle_task = 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()
|
||||
.is_some_and(|tasks| tasks.templates.len() == 1)
|
||||
&& code_actions
|
||||
.as_ref()
|
||||
.is_none_or(|actions| actions.is_empty())
|
||||
&& debug_scenarios.is_empty();
|
||||
|
||||
let code_actions = if let Some(CodeActionSource::RunMenu(_)) = &deployed_from {
|
||||
None
|
||||
} else {
|
||||
editor.update(cx, |editor, _cx| match &editor.code_actions_for_selection {
|
||||
CodeActionsForSelection::None => None,
|
||||
CodeActionsForSelection::Fetching(task) => Some(task.clone()),
|
||||
CodeActionsForSelection::Ready(action_fetch_ready) => {
|
||||
Some(Task::ready(Some(action_fetch_ready.clone())).shared())
|
||||
}
|
||||
})?
|
||||
};
|
||||
let code_actions = match code_actions {
|
||||
Some(code_actions) => code_actions
|
||||
.await
|
||||
.filter(|ActionFetchReady { location, .. }| {
|
||||
let snapshot = location.buffer.read_with(cx, |buffer, _| buffer.snapshot());
|
||||
let point_range = location.range.to_point(&snapshot);
|
||||
(point_range.start.row..=point_range.end.row).contains(&buffer_row)
|
||||
})
|
||||
.map(|ActionFetchReady { actions, .. }| actions),
|
||||
None => None,
|
||||
};
|
||||
|
||||
editor.update_in(cx, |editor, window, cx| {
|
||||
let spawn_straight_away = quick_launch
|
||||
&& resolved_tasks
|
||||
.as_ref()
|
||||
.is_some_and(|tasks| tasks.templates.len() == 1)
|
||||
&& code_actions
|
||||
.as_ref()
|
||||
.is_none_or(|actions| actions.is_empty())
|
||||
&& debug_scenarios.is_empty();
|
||||
|
||||
crate::hover_popover::hide_hover(editor, cx);
|
||||
let actions = CodeActionContents::new(
|
||||
resolved_tasks,
|
||||
@@ -7099,8 +7123,16 @@ impl Editor {
|
||||
|
||||
Task::ready(Ok(()))
|
||||
})
|
||||
});
|
||||
self.runnables_for_selection_toggle = cx.background_spawn(async move {
|
||||
match toggle_task.await {
|
||||
Ok(code_action_spawn) => match code_action_spawn.await {
|
||||
Ok(()) => {}
|
||||
Err(e) => log::error!("failed to spawn a toggled code action: {e:#}"),
|
||||
},
|
||||
Err(e) => log::error!("failed to toggle code actions: {e:#}"),
|
||||
}
|
||||
})
|
||||
.detach_and_log_err(cx);
|
||||
}
|
||||
|
||||
fn debug_scenarios(
|
||||
@@ -7144,42 +7176,6 @@ impl Editor {
|
||||
.unwrap_or_else(|| Task::ready(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(
|
||||
&mut self,
|
||||
action: &ConfirmCodeAction,
|
||||
@@ -7393,11 +7389,6 @@ impl Editor {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn clear_code_action_providers(&mut self) {
|
||||
self.code_action_providers.clear();
|
||||
self.available_code_actions.take();
|
||||
}
|
||||
|
||||
pub fn add_code_action_provider(
|
||||
&mut self,
|
||||
provider: Rc<dyn CodeActionProvider>,
|
||||
@@ -7413,7 +7404,7 @@ impl Editor {
|
||||
}
|
||||
|
||||
self.code_action_providers.push(provider);
|
||||
self.refresh_code_actions(window, cx);
|
||||
self.refresh_code_actions_for_selection(window, cx);
|
||||
}
|
||||
|
||||
pub fn remove_code_action_provider(
|
||||
@@ -7424,7 +7415,7 @@ impl Editor {
|
||||
) {
|
||||
self.code_action_providers
|
||||
.retain(|provider| provider.id() != id);
|
||||
self.refresh_code_actions(window, cx);
|
||||
self.refresh_code_actions_for_selection(window, cx);
|
||||
}
|
||||
|
||||
pub fn code_actions_enabled_for_toolbar(&self, cx: &App) -> bool {
|
||||
@@ -7432,10 +7423,12 @@ impl Editor {
|
||||
&& EditorSettings::get_global(cx).toolbar.code_actions
|
||||
}
|
||||
|
||||
pub fn has_available_code_actions(&self) -> bool {
|
||||
self.available_code_actions
|
||||
.as_ref()
|
||||
.is_some_and(|(_, actions)| !actions.is_empty())
|
||||
pub fn has_available_code_actions_for_selection(&self) -> bool {
|
||||
if let CodeActionsForSelection::Ready(ready) = &self.code_actions_for_selection {
|
||||
!ready.actions.is_empty()
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
fn render_inline_code_actions(
|
||||
@@ -7487,73 +7480,88 @@ impl Editor {
|
||||
&self.context_menu
|
||||
}
|
||||
|
||||
fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
||||
self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
|
||||
cx.background_executor()
|
||||
.timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
|
||||
.await;
|
||||
fn refresh_code_actions_for_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
||||
self.code_actions_for_selection = CodeActionsForSelection::Fetching(
|
||||
cx.spawn_in(window, async move |editor, cx| {
|
||||
cx.background_executor()
|
||||
.timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
|
||||
.await;
|
||||
|
||||
let (start_buffer, start, _, end, _newest_selection) = this
|
||||
.update(cx, |this, cx| {
|
||||
let newest_selection = this.selections.newest_anchor().clone();
|
||||
if newest_selection.head().diff_base_anchor().is_some() {
|
||||
return None;
|
||||
}
|
||||
let display_snapshot = this.display_snapshot(cx);
|
||||
let newest_selection_adjusted =
|
||||
this.selections.newest_adjusted(&display_snapshot);
|
||||
let buffer = this.buffer.read(cx);
|
||||
|
||||
let (start_buffer, start) =
|
||||
buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
|
||||
let (end_buffer, end) =
|
||||
buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
|
||||
|
||||
Some((start_buffer, start, end_buffer, end, newest_selection))
|
||||
})?
|
||||
.filter(|(start_buffer, _, end_buffer, _, _)| start_buffer == end_buffer)
|
||||
.context(
|
||||
"Expected selection to lie in a single buffer when refreshing code actions",
|
||||
)?;
|
||||
let (providers, tasks) = this.update_in(cx, |this, window, cx| {
|
||||
let providers = this.code_action_providers.clone();
|
||||
let tasks = this
|
||||
.code_action_providers
|
||||
.iter()
|
||||
.map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
|
||||
.collect::<Vec<_>>();
|
||||
(providers, tasks)
|
||||
})?;
|
||||
|
||||
let mut actions = Vec::new();
|
||||
for (provider, provider_actions) in
|
||||
providers.into_iter().zip(future::join_all(tasks).await)
|
||||
{
|
||||
if let Some(provider_actions) = provider_actions.log_err() {
|
||||
actions.extend(provider_actions.into_iter().map(|action| {
|
||||
AvailableCodeAction {
|
||||
action,
|
||||
provider: provider.clone(),
|
||||
let (start_buffer, start, _, end, _newest_selection) = editor
|
||||
.update(cx, |editor, cx| {
|
||||
let newest_selection = editor.selections.newest_anchor().clone();
|
||||
if newest_selection.head().diff_base_anchor().is_some() {
|
||||
return None;
|
||||
}
|
||||
}));
|
||||
}
|
||||
}
|
||||
let display_snapshot = editor.display_snapshot(cx);
|
||||
let newest_selection_adjusted =
|
||||
editor.selections.newest_adjusted(&display_snapshot);
|
||||
let buffer = editor.buffer.read(cx);
|
||||
|
||||
this.update(cx, |this, cx| {
|
||||
this.available_code_actions = if actions.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some((
|
||||
Location {
|
||||
buffer: start_buffer,
|
||||
range: start..end,
|
||||
},
|
||||
actions.into(),
|
||||
))
|
||||
};
|
||||
cx.notify();
|
||||
let (start_buffer, start) =
|
||||
buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
|
||||
let (end_buffer, end) =
|
||||
buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
|
||||
|
||||
Some((start_buffer, start, end_buffer, end, newest_selection))
|
||||
})
|
||||
.ok()
|
||||
.flatten()
|
||||
.filter(|(start_buffer, _, end_buffer, _, _)| start_buffer == end_buffer)?;
|
||||
|
||||
let (providers, tasks) = editor
|
||||
.update_in(cx, |editor, window, cx| {
|
||||
let providers = editor.code_action_providers.clone();
|
||||
let tasks = editor
|
||||
.code_action_providers
|
||||
.iter()
|
||||
.map(|provider| {
|
||||
provider.code_actions(&start_buffer, start..end, window, cx)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
(providers, tasks)
|
||||
})
|
||||
.ok()?;
|
||||
|
||||
let mut actions = Vec::new();
|
||||
for (provider, provider_actions) in
|
||||
providers.into_iter().zip(future::join_all(tasks).await)
|
||||
{
|
||||
if let Some(provider_actions) = provider_actions.log_err() {
|
||||
actions.extend(provider_actions.into_iter().map(|action| {
|
||||
AvailableCodeAction {
|
||||
action,
|
||||
provider: provider.clone(),
|
||||
}
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
editor
|
||||
.update(cx, |editor, cx| {
|
||||
let new_actions = if actions.is_empty() {
|
||||
editor.code_actions_for_selection = CodeActionsForSelection::None;
|
||||
None
|
||||
} else {
|
||||
let new_actions = ActionFetchReady {
|
||||
location: Location {
|
||||
buffer: start_buffer,
|
||||
range: start..end,
|
||||
},
|
||||
actions: Rc::from(actions),
|
||||
};
|
||||
editor.code_actions_for_selection =
|
||||
CodeActionsForSelection::Ready(new_actions.clone());
|
||||
Some(new_actions)
|
||||
};
|
||||
cx.notify();
|
||||
new_actions
|
||||
})
|
||||
.ok()
|
||||
.flatten()
|
||||
})
|
||||
}));
|
||||
.shared(),
|
||||
);
|
||||
}
|
||||
|
||||
fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
||||
@@ -24835,7 +24843,7 @@ impl Editor {
|
||||
self.scrollbar_marker_state.dirty = true;
|
||||
self.active_indent_guides_state.dirty = true;
|
||||
self.refresh_active_diagnostics(cx);
|
||||
self.refresh_code_actions(window, cx);
|
||||
self.refresh_code_actions_for_selection(window, cx);
|
||||
self.refresh_single_line_folds(window, cx);
|
||||
let snapshot = self.snapshot(window, cx);
|
||||
self.refresh_matching_bracket_highlights(&snapshot, cx);
|
||||
|
||||
@@ -2538,7 +2538,9 @@ impl EditorElement {
|
||||
|
||||
let icon_size = ui::IconSize::XSmall;
|
||||
let mut button = self.editor.update(cx, |editor, cx| {
|
||||
editor.available_code_actions.as_ref()?;
|
||||
if !editor.has_available_code_actions_for_selection() {
|
||||
return None;
|
||||
}
|
||||
let active = editor
|
||||
.context_menu
|
||||
.borrow()
|
||||
|
||||
@@ -133,7 +133,7 @@ impl Render for QuickActionBar {
|
||||
editor_value.edit_predictions_enabled_at_cursor(cx);
|
||||
let supports_minimap = editor_value.supports_minimap(cx);
|
||||
let minimap_enabled = supports_minimap && editor_value.minimap().is_some();
|
||||
let has_available_code_actions = editor_value.has_available_code_actions();
|
||||
let has_available_code_actions = editor_value.has_available_code_actions_for_selection();
|
||||
let code_action_enabled = editor_value.code_actions_enabled_for_toolbar(cx);
|
||||
let focus_handle = editor_value.focus_handle(cx);
|
||||
|
||||
@@ -169,7 +169,6 @@ impl Render for QuickActionBar {
|
||||
);
|
||||
|
||||
let code_actions_dropdown = code_action_enabled.then(|| {
|
||||
let focus = editor.focus_handle(cx);
|
||||
let is_deployed = {
|
||||
let menu_ref = editor.read(cx).context_menu().borrow();
|
||||
let code_action_menu = menu_ref
|
||||
@@ -211,16 +210,18 @@ impl Render for QuickActionBar {
|
||||
)
|
||||
})
|
||||
.on_click({
|
||||
let focus = focus;
|
||||
let editor = editor.clone();
|
||||
move |_, window, cx| {
|
||||
focus.dispatch_action(
|
||||
&ToggleCodeActions {
|
||||
deployed_from: Some(CodeActionSource::QuickActionBar),
|
||||
quick_launch: false,
|
||||
},
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
editor.update(cx, |editor, cx| {
|
||||
editor.toggle_code_actions(
|
||||
&ToggleCodeActions {
|
||||
deployed_from: Some(CodeActionSource::QuickActionBar),
|
||||
quick_launch: false,
|
||||
},
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
})
|
||||
}
|
||||
}),
|
||||
)
|
||||
|
||||
Reference in new issue
Block a user