Add "Fix with Assistant" code action on lines with diagnostics (#18163)
Release Notes: - Added a new "Fix with Assistant" action on code with errors or warnings. --------- Co-authored-by: Nathan <nathan@zed.dev>
This commit is contained in:
co-authored by
Nathan
parent
1efe87029b
commit
7051bc00c2
+133
-36
@@ -68,7 +68,7 @@ use element::LineWithInvisibles;
|
||||
pub use element::{
|
||||
CursorLayout, EditorElement, HighlightedRange, HighlightedRangeLine, PointForPosition,
|
||||
};
|
||||
use futures::FutureExt;
|
||||
use futures::{future, FutureExt};
|
||||
use fuzzy::{StringMatch, StringMatchCandidate};
|
||||
use git::blame::GitBlame;
|
||||
use git::diff_hunk_to_display;
|
||||
@@ -569,8 +569,8 @@ pub struct Editor {
|
||||
find_all_references_task_sources: Vec<Anchor>,
|
||||
next_completion_id: CompletionId,
|
||||
completion_documentation_pre_resolve_debounce: DebouncedDelay,
|
||||
available_code_actions: Option<(Location, Arc<[CodeAction]>)>,
|
||||
code_actions_task: Option<Task<()>>,
|
||||
available_code_actions: Option<(Location, Arc<[AvailableCodeAction]>)>,
|
||||
code_actions_task: Option<Task<Result<()>>>,
|
||||
document_highlights_task: Option<Task<()>>,
|
||||
linked_editing_range_task: Option<Task<Option<()>>>,
|
||||
linked_edit_ranges: linked_editing_ranges::LinkedEditingRanges,
|
||||
@@ -590,6 +590,7 @@ pub struct Editor {
|
||||
gutter_hovered: bool,
|
||||
hovered_link_state: Option<HoveredLinkState>,
|
||||
inline_completion_provider: Option<RegisteredInlineCompletionProvider>,
|
||||
code_action_providers: Vec<Arc<dyn CodeActionProvider>>,
|
||||
active_inline_completion: Option<CompletionState>,
|
||||
// enable_inline_completions is a switch that Vim can use to disable
|
||||
// inline completions based on its mode.
|
||||
@@ -1360,10 +1361,16 @@ impl CompletionsMenu {
|
||||
}
|
||||
}
|
||||
|
||||
struct AvailableCodeAction {
|
||||
excerpt_id: ExcerptId,
|
||||
action: CodeAction,
|
||||
provider: Arc<dyn CodeActionProvider>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct CodeActionContents {
|
||||
tasks: Option<Arc<ResolvedTasks>>,
|
||||
actions: Option<Arc<[CodeAction]>>,
|
||||
actions: Option<Arc<[AvailableCodeAction]>>,
|
||||
}
|
||||
|
||||
impl CodeActionContents {
|
||||
@@ -1395,9 +1402,11 @@ impl CodeActionContents {
|
||||
.map(|(kind, task)| CodeActionsItem::Task(kind.clone(), task.clone()))
|
||||
})
|
||||
.chain(self.actions.iter().flat_map(|actions| {
|
||||
actions
|
||||
.iter()
|
||||
.map(|action| CodeActionsItem::CodeAction(action.clone()))
|
||||
actions.iter().map(|available| CodeActionsItem::CodeAction {
|
||||
excerpt_id: available.excerpt_id,
|
||||
action: available.action.clone(),
|
||||
provider: available.provider.clone(),
|
||||
})
|
||||
}))
|
||||
}
|
||||
fn get(&self, index: usize) -> Option<CodeActionsItem> {
|
||||
@@ -1410,10 +1419,13 @@ impl CodeActionContents {
|
||||
.cloned()
|
||||
.map(|(kind, task)| CodeActionsItem::Task(kind, task))
|
||||
} else {
|
||||
actions
|
||||
.get(index - tasks.templates.len())
|
||||
.cloned()
|
||||
.map(CodeActionsItem::CodeAction)
|
||||
actions.get(index - tasks.templates.len()).map(|available| {
|
||||
CodeActionsItem::CodeAction {
|
||||
excerpt_id: available.excerpt_id,
|
||||
action: available.action.clone(),
|
||||
provider: available.provider.clone(),
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
(Some(tasks), None) => tasks
|
||||
@@ -1421,7 +1433,15 @@ impl CodeActionContents {
|
||||
.get(index)
|
||||
.cloned()
|
||||
.map(|(kind, task)| CodeActionsItem::Task(kind, task)),
|
||||
(None, Some(actions)) => actions.get(index).cloned().map(CodeActionsItem::CodeAction),
|
||||
(None, Some(actions)) => {
|
||||
actions
|
||||
.get(index)
|
||||
.map(|available| CodeActionsItem::CodeAction {
|
||||
excerpt_id: available.excerpt_id,
|
||||
action: available.action.clone(),
|
||||
provider: available.provider.clone(),
|
||||
})
|
||||
}
|
||||
(None, None) => None,
|
||||
}
|
||||
}
|
||||
@@ -1431,7 +1451,11 @@ impl CodeActionContents {
|
||||
#[derive(Clone)]
|
||||
enum CodeActionsItem {
|
||||
Task(TaskSourceKind, ResolvedTask),
|
||||
CodeAction(CodeAction),
|
||||
CodeAction {
|
||||
excerpt_id: ExcerptId,
|
||||
action: CodeAction,
|
||||
provider: Arc<dyn CodeActionProvider>,
|
||||
},
|
||||
}
|
||||
|
||||
impl CodeActionsItem {
|
||||
@@ -1442,14 +1466,14 @@ impl CodeActionsItem {
|
||||
Some(task)
|
||||
}
|
||||
fn as_code_action(&self) -> Option<&CodeAction> {
|
||||
let Self::CodeAction(action) = self else {
|
||||
let Self::CodeAction { action, .. } = self else {
|
||||
return None;
|
||||
};
|
||||
Some(action)
|
||||
}
|
||||
fn label(&self) -> String {
|
||||
match self {
|
||||
Self::CodeAction(action) => action.lsp_action.title.clone(),
|
||||
Self::CodeAction { action, .. } => action.lsp_action.title.clone(),
|
||||
Self::Task(_, task) => task.resolved_label.clone(),
|
||||
}
|
||||
}
|
||||
@@ -1588,7 +1612,9 @@ impl CodeActionsMenu {
|
||||
.enumerate()
|
||||
.max_by_key(|(_, action)| match action {
|
||||
CodeActionsItem::Task(_, task) => task.resolved_label.chars().count(),
|
||||
CodeActionsItem::CodeAction(action) => action.lsp_action.title.chars().count(),
|
||||
CodeActionsItem::CodeAction { action, .. } => {
|
||||
action.lsp_action.title.chars().count()
|
||||
}
|
||||
})
|
||||
.map(|(ix, _)| ix),
|
||||
)
|
||||
@@ -1864,6 +1890,11 @@ impl Editor {
|
||||
None
|
||||
};
|
||||
|
||||
let mut code_action_providers = Vec::new();
|
||||
if let Some(project) = project.clone() {
|
||||
code_action_providers.push(Arc::new(project) as Arc<_>);
|
||||
}
|
||||
|
||||
let mut this = Self {
|
||||
focus_handle,
|
||||
show_cursor_when_unfocused: false,
|
||||
@@ -1915,6 +1946,7 @@ impl Editor {
|
||||
next_completion_id: 0,
|
||||
completion_documentation_pre_resolve_debounce: DebouncedDelay::new(),
|
||||
next_inlay_id: 0,
|
||||
code_action_providers,
|
||||
available_code_actions: Default::default(),
|
||||
code_actions_task: Default::default(),
|
||||
document_highlights_task: Default::default(),
|
||||
@@ -4553,7 +4585,7 @@ impl Editor {
|
||||
let action = action.clone();
|
||||
cx.spawn(|editor, mut cx| async move {
|
||||
while let Some(prev_task) = task {
|
||||
prev_task.await;
|
||||
prev_task.await.log_err();
|
||||
task = editor.update(&mut cx, |this, _| this.code_actions_task.take())?;
|
||||
}
|
||||
|
||||
@@ -4727,17 +4759,16 @@ impl Editor {
|
||||
Some(Task::ready(Ok(())))
|
||||
})
|
||||
}
|
||||
CodeActionsItem::CodeAction(action) => {
|
||||
let apply_code_actions = workspace
|
||||
.read(cx)
|
||||
.project()
|
||||
.clone()
|
||||
.update(cx, |project, cx| {
|
||||
project.apply_code_action(buffer, action, true, cx)
|
||||
});
|
||||
CodeActionsItem::CodeAction {
|
||||
excerpt_id,
|
||||
action,
|
||||
provider,
|
||||
} => {
|
||||
let apply_code_action =
|
||||
provider.apply_code_action(buffer, action, excerpt_id, true, cx);
|
||||
let workspace = workspace.downgrade();
|
||||
Some(cx.spawn(|editor, cx| async move {
|
||||
let project_transaction = apply_code_actions.await?;
|
||||
let project_transaction = apply_code_action.await?;
|
||||
Self::open_project_transaction(
|
||||
&editor,
|
||||
workspace,
|
||||
@@ -4835,8 +4866,16 @@ impl Editor {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn push_code_action_provider(
|
||||
&mut self,
|
||||
provider: Arc<dyn CodeActionProvider>,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) {
|
||||
self.code_action_providers.push(provider);
|
||||
self.refresh_code_actions(cx);
|
||||
}
|
||||
|
||||
fn refresh_code_actions(&mut self, cx: &mut ViewContext<Self>) -> Option<()> {
|
||||
let project = self.project.clone()?;
|
||||
let buffer = self.buffer.read(cx);
|
||||
let newest_selection = self.selections.newest_anchor().clone();
|
||||
let (start_buffer, start) = buffer.text_anchor_for_position(newest_selection.start, cx)?;
|
||||
@@ -4850,13 +4889,30 @@ impl Editor {
|
||||
.timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
|
||||
.await;
|
||||
|
||||
let actions = if let Ok(code_actions) = project.update(&mut cx, |project, cx| {
|
||||
project.code_actions(&start_buffer, start..end, cx)
|
||||
}) {
|
||||
code_actions.await
|
||||
} else {
|
||||
Vec::new()
|
||||
};
|
||||
let (providers, tasks) = this.update(&mut cx, |this, cx| {
|
||||
let providers = this.code_action_providers.clone();
|
||||
let tasks = this
|
||||
.code_action_providers
|
||||
.iter()
|
||||
.map(|provider| provider.code_actions(&start_buffer, start..end, 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 {
|
||||
excerpt_id: newest_selection.start.excerpt_id,
|
||||
action,
|
||||
provider: provider.clone(),
|
||||
}
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.available_code_actions = if actions.is_empty() {
|
||||
@@ -4872,7 +4928,6 @@ impl Editor {
|
||||
};
|
||||
cx.notify();
|
||||
})
|
||||
.log_err();
|
||||
}));
|
||||
None
|
||||
}
|
||||
@@ -9685,7 +9740,7 @@ impl Editor {
|
||||
})
|
||||
.context("location tasks preparation")?;
|
||||
|
||||
let locations = futures::future::join_all(location_tasks)
|
||||
let locations = future::join_all(location_tasks)
|
||||
.await
|
||||
.into_iter()
|
||||
.filter_map(|location| location.transpose())
|
||||
@@ -12574,6 +12629,48 @@ pub trait CompletionProvider {
|
||||
}
|
||||
}
|
||||
|
||||
pub trait CodeActionProvider {
|
||||
fn code_actions(
|
||||
&self,
|
||||
buffer: &Model<Buffer>,
|
||||
range: Range<text::Anchor>,
|
||||
cx: &mut WindowContext,
|
||||
) -> Task<Result<Vec<CodeAction>>>;
|
||||
|
||||
fn apply_code_action(
|
||||
&self,
|
||||
buffer_handle: Model<Buffer>,
|
||||
action: CodeAction,
|
||||
excerpt_id: ExcerptId,
|
||||
push_to_history: bool,
|
||||
cx: &mut WindowContext,
|
||||
) -> Task<Result<ProjectTransaction>>;
|
||||
}
|
||||
|
||||
impl CodeActionProvider for Model<Project> {
|
||||
fn code_actions(
|
||||
&self,
|
||||
buffer: &Model<Buffer>,
|
||||
range: Range<text::Anchor>,
|
||||
cx: &mut WindowContext,
|
||||
) -> Task<Result<Vec<CodeAction>>> {
|
||||
self.update(cx, |project, cx| project.code_actions(buffer, range, cx))
|
||||
}
|
||||
|
||||
fn apply_code_action(
|
||||
&self,
|
||||
buffer_handle: Model<Buffer>,
|
||||
action: CodeAction,
|
||||
_excerpt_id: ExcerptId,
|
||||
push_to_history: bool,
|
||||
cx: &mut WindowContext,
|
||||
) -> Task<Result<ProjectTransaction>> {
|
||||
self.update(cx, |project, cx| {
|
||||
project.apply_code_action(buffer_handle, action, push_to_history, cx)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn snippet_completions(
|
||||
project: &Project,
|
||||
buffer: &Model<Buffer>,
|
||||
|
||||
Reference in New Issue
Block a user