WIP: Start on applying code actions
This commit is contained in:
+31
-19
@@ -30,7 +30,7 @@ use gpui::{
|
||||
use items::BufferItemHandle;
|
||||
use itertools::Itertools as _;
|
||||
use language::{
|
||||
AnchorRangeExt as _, BracketPair, Buffer, Completion, CompletionLabel, Diagnostic,
|
||||
AnchorRangeExt as _, BracketPair, Buffer, CodeAction, Completion, CompletionLabel, Diagnostic,
|
||||
DiagnosticSeverity, Language, Point, Selection, SelectionGoal, TransactionId,
|
||||
};
|
||||
use multi_buffer::MultiBufferChunks;
|
||||
@@ -654,7 +654,7 @@ impl CompletionsMenu {
|
||||
}
|
||||
|
||||
struct CodeActionsMenu {
|
||||
actions: Arc<[lsp::CodeAction]>,
|
||||
actions: Arc<[CodeAction<Anchor>]>,
|
||||
selected_item: usize,
|
||||
list: UniformListState,
|
||||
}
|
||||
@@ -699,7 +699,7 @@ impl CodeActionsMenu {
|
||||
settings.style.autocomplete.item
|
||||
};
|
||||
|
||||
Text::new(action.title.clone(), settings.style.text.clone())
|
||||
Text::new(action.lsp_action.title.clone(), settings.style.text.clone())
|
||||
.with_soft_wrap(false)
|
||||
.contained()
|
||||
.with_style(item_style)
|
||||
@@ -717,7 +717,7 @@ impl CodeActionsMenu {
|
||||
self.actions
|
||||
.iter()
|
||||
.enumerate()
|
||||
.max_by_key(|(_, action)| action.title.chars().count())
|
||||
.max_by_key(|(_, action)| action.lsp_action.title.chars().count())
|
||||
.map(|(ix, _)| ix),
|
||||
)
|
||||
.contained()
|
||||
@@ -1935,7 +1935,7 @@ impl Editor {
|
||||
self.completion_tasks.push((id, task));
|
||||
}
|
||||
|
||||
fn confirm_completion(
|
||||
pub fn confirm_completion(
|
||||
&mut self,
|
||||
ConfirmCompletion(completion_ix): &ConfirmCompletion,
|
||||
cx: &mut ViewContext<Self>,
|
||||
@@ -2051,23 +2051,35 @@ impl Editor {
|
||||
}
|
||||
|
||||
fn confirm_code_action(
|
||||
&mut self,
|
||||
workspace: &mut Workspace,
|
||||
ConfirmCodeAction(action_ix): &ConfirmCodeAction,
|
||||
cx: &mut ViewContext<Self>,
|
||||
cx: &mut ViewContext<Workspace>,
|
||||
) -> Option<Task<Result<()>>> {
|
||||
let actions_menu = if let ContextMenu::CodeActions(menu) = self.hide_context_menu(cx)? {
|
||||
menu
|
||||
} else {
|
||||
return None;
|
||||
};
|
||||
let active_item = workspace.active_item(cx)?;
|
||||
let editor = active_item.act_as::<Self>(cx)?;
|
||||
let (buffer, action) = editor.update(cx, |editor, cx| {
|
||||
let actions_menu =
|
||||
if let ContextMenu::CodeActions(menu) = editor.hide_context_menu(cx)? {
|
||||
menu
|
||||
} else {
|
||||
return None;
|
||||
};
|
||||
let action_ix = action_ix.unwrap_or(actions_menu.selected_item);
|
||||
let action = actions_menu.actions.get(action_ix)?.clone();
|
||||
let (buffer, position) = editor
|
||||
.buffer
|
||||
.read(cx)
|
||||
.text_anchor_for_position(action.position, cx);
|
||||
let action = CodeAction {
|
||||
position,
|
||||
lsp_action: action.lsp_action,
|
||||
};
|
||||
Some((buffer, action))
|
||||
})?;
|
||||
|
||||
let action = actions_menu
|
||||
.actions
|
||||
.get(action_ix.unwrap_or(actions_menu.selected_item))?;
|
||||
|
||||
dbg!(action);
|
||||
|
||||
None
|
||||
Some(workspace.project().update(cx, |project, cx| {
|
||||
project.apply_code_action(buffer, action, cx)
|
||||
}))
|
||||
}
|
||||
|
||||
pub fn showing_context_menu(&self) -> bool {
|
||||
|
||||
@@ -5,11 +5,11 @@ use anyhow::Result;
|
||||
use clock::ReplicaId;
|
||||
use collections::{HashMap, HashSet};
|
||||
use gpui::{AppContext, Entity, ModelContext, ModelHandle, Task};
|
||||
pub use language::Completion;
|
||||
use language::{
|
||||
Buffer, BufferChunks, BufferSnapshot, Chunk, DiagnosticEntry, Event, File, Language, Outline,
|
||||
OutlineItem, Selection, ToOffset as _, ToPoint as _, ToPointUtf16 as _, TransactionId,
|
||||
};
|
||||
pub use language::{CodeAction, Completion};
|
||||
use std::{
|
||||
cell::{Ref, RefCell},
|
||||
cmp, fmt, io,
|
||||
@@ -864,15 +864,25 @@ impl MultiBuffer {
|
||||
&self,
|
||||
position: T,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) -> Task<Result<Vec<lsp::CodeAction>>>
|
||||
) -> Task<Result<Vec<CodeAction<Anchor>>>>
|
||||
where
|
||||
T: ToOffset,
|
||||
{
|
||||
let anchor = self.read(cx).anchor_before(position);
|
||||
let buffer = self.buffers.borrow()[&anchor.buffer_id].buffer.clone();
|
||||
let code_actions =
|
||||
buffer.update(cx, |buffer, cx| buffer.code_actions(anchor.text_anchor, cx));
|
||||
cx.spawn(|this, cx| async move { code_actions.await })
|
||||
let code_actions = buffer.update(cx, |buffer, cx| {
|
||||
buffer.code_actions(anchor.text_anchor.clone(), cx)
|
||||
});
|
||||
cx.foreground().spawn(async move {
|
||||
Ok(code_actions
|
||||
.await?
|
||||
.into_iter()
|
||||
.map(|action| CodeAction {
|
||||
position: anchor.clone(),
|
||||
lsp_action: action.lsp_action,
|
||||
})
|
||||
.collect())
|
||||
})
|
||||
}
|
||||
|
||||
pub fn completions<T>(
|
||||
|
||||
@@ -118,6 +118,12 @@ pub struct Completion<T> {
|
||||
pub lsp_completion: lsp::CompletionItem,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct CodeAction<T> {
|
||||
pub position: T,
|
||||
pub lsp_action: lsp::CodeAction,
|
||||
}
|
||||
|
||||
struct LanguageServerState {
|
||||
server: Arc<LanguageServer>,
|
||||
latest_snapshot: watch::Sender<Option<LanguageServerSnapshot>>,
|
||||
@@ -1852,7 +1858,7 @@ impl Buffer {
|
||||
&self,
|
||||
position: T,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) -> Task<Result<Vec<lsp::CodeAction>>>
|
||||
) -> Task<Result<Vec<CodeAction<Anchor>>>>
|
||||
where
|
||||
T: ToPointUtf16,
|
||||
{
|
||||
@@ -1870,8 +1876,9 @@ impl Buffer {
|
||||
};
|
||||
let abs_path = file.abs_path(cx);
|
||||
let position = position.to_point_utf16(self);
|
||||
let anchor = self.anchor_after(position);
|
||||
|
||||
cx.spawn(|this, mut cx| async move {
|
||||
cx.foreground().spawn(async move {
|
||||
let actions = server
|
||||
.request::<lsp::request::CodeActionRequest>(lsp::CodeActionParams {
|
||||
text_document: lsp::TextDocumentIdentifier::new(
|
||||
@@ -1896,8 +1903,16 @@ impl Buffer {
|
||||
.unwrap_or_default()
|
||||
.into_iter()
|
||||
.filter_map(|entry| {
|
||||
if let lsp::CodeActionOrCommand::CodeAction(action) = entry {
|
||||
Some(action)
|
||||
if let lsp::CodeActionOrCommand::CodeAction(lsp_action) = entry {
|
||||
if lsp_action.data.is_none() {
|
||||
log::warn!("skipping code action without data {lsp_action:?}");
|
||||
None
|
||||
} else {
|
||||
Some(CodeAction {
|
||||
position: anchor.clone(),
|
||||
lsp_action,
|
||||
})
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
|
||||
@@ -247,6 +247,10 @@ impl LanguageServer {
|
||||
],
|
||||
},
|
||||
}),
|
||||
data_support: Some(true),
|
||||
resolve_support: Some(CodeActionCapabilityResolveSupport {
|
||||
properties: vec!["edit".to_string()],
|
||||
}),
|
||||
..Default::default()
|
||||
}),
|
||||
completion: Some(CompletionClientCapabilities {
|
||||
|
||||
@@ -15,8 +15,8 @@ use gpui::{
|
||||
use language::{
|
||||
point_from_lsp,
|
||||
proto::{deserialize_anchor, serialize_anchor},
|
||||
range_from_lsp, Bias, Buffer, Diagnostic, DiagnosticEntry, File as _, Language,
|
||||
LanguageRegistry, PointUtf16, ToOffset,
|
||||
range_from_lsp, Bias, Buffer, CodeAction, Diagnostic, DiagnosticEntry, File as _, Language,
|
||||
LanguageRegistry, PointUtf16, ToLspPosition, ToOffset, ToPointUtf16,
|
||||
};
|
||||
use lsp::{DiagnosticSeverity, LanguageServer};
|
||||
use postage::{prelude::Stream, watch};
|
||||
@@ -1151,6 +1151,87 @@ impl Project {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn apply_code_action(
|
||||
&self,
|
||||
buffer: ModelHandle<Buffer>,
|
||||
mut action: CodeAction<language::Anchor>,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) -> Task<Result<()>> {
|
||||
if self.is_local() {
|
||||
let buffer = buffer.read(cx);
|
||||
let server = if let Some(language_server) = buffer.language_server() {
|
||||
language_server.clone()
|
||||
} else {
|
||||
return Task::ready(Ok(Default::default()));
|
||||
};
|
||||
let position = action.position.to_point_utf16(buffer).to_lsp_position();
|
||||
|
||||
cx.spawn(|this, mut cx| async move {
|
||||
let range = action
|
||||
.lsp_action
|
||||
.data
|
||||
.as_mut()
|
||||
.and_then(|d| d.get_mut("codeActionParams"))
|
||||
.and_then(|d| d.get_mut("range"))
|
||||
.ok_or_else(|| anyhow!("code action has no range"))?;
|
||||
*range = serde_json::to_value(&lsp::Range::new(position, position)).unwrap();
|
||||
let action = server
|
||||
.request::<lsp::request::CodeActionResolveRequest>(action.lsp_action)
|
||||
.await?;
|
||||
let edit = action
|
||||
.edit
|
||||
.ok_or_else(|| anyhow!("code action has no edit"));
|
||||
// match edit {
|
||||
// Ok(edit) => edit.,
|
||||
// Err(_) => todo!(),
|
||||
// }
|
||||
Ok(Default::default())
|
||||
})
|
||||
} else {
|
||||
log::info!("applying code actions is not implemented for guests");
|
||||
Task::ready(Ok(Default::default()))
|
||||
}
|
||||
// let file = if let Some(file) = self.file.as_ref() {
|
||||
// file
|
||||
// } else {
|
||||
// return Task::ready(Ok(Default::default()));
|
||||
// };
|
||||
|
||||
// if file.is_local() {
|
||||
// let server = if let Some(language_server) = self.language_server.as_ref() {
|
||||
// language_server.server.clone()
|
||||
// } else {
|
||||
// return Task::ready(Ok(Default::default()));
|
||||
// };
|
||||
// let position = action.position.to_point_utf16(self).to_lsp_position();
|
||||
|
||||
// cx.spawn(|this, mut cx| async move {
|
||||
// let range = action
|
||||
// .lsp_action
|
||||
// .data
|
||||
// .as_mut()
|
||||
// .and_then(|d| d.get_mut("codeActionParams"))
|
||||
// .and_then(|d| d.get_mut("range"))
|
||||
// .ok_or_else(|| anyhow!("code action has no range"))?;
|
||||
// *range = serde_json::to_value(&lsp::Range::new(position, position)).unwrap();
|
||||
// let action = server
|
||||
// .request::<lsp::request::CodeActionResolveRequest>(action.lsp_action)
|
||||
// .await?;
|
||||
// let edit = action
|
||||
// .edit
|
||||
// .ok_or_else(|| anyhow!("code action has no edit"));
|
||||
// match edit {
|
||||
// Ok(edit) => edit.,
|
||||
// Err(_) => todo!(),
|
||||
// }
|
||||
// Ok(Default::default())
|
||||
// })
|
||||
// } else {
|
||||
// log::info!("applying code actions is not implemented for guests");
|
||||
// Task::ready(Ok(Default::default()))
|
||||
// }
|
||||
}
|
||||
|
||||
pub fn find_or_create_local_worktree(
|
||||
&self,
|
||||
abs_path: impl AsRef<Path>,
|
||||
|
||||
@@ -1237,7 +1237,7 @@ mod tests {
|
||||
self, test::FakeHttpClient, Channel, ChannelDetails, ChannelList, Client, Credentials,
|
||||
EstablishConnectionError, UserStore,
|
||||
},
|
||||
editor::{Editor, EditorSettings, Input, MultiBuffer},
|
||||
editor::{ConfirmCompletion, Editor, EditorSettings, Input, MultiBuffer},
|
||||
fs::{FakeFs, Fs as _},
|
||||
language::{
|
||||
tree_sitter_rust, AnchorRangeExt, Diagnostic, DiagnosticEntry, Language,
|
||||
@@ -2469,7 +2469,7 @@ mod tests {
|
||||
editor_b.next_notification(&cx_b).await;
|
||||
editor_b.update(&mut cx_b, |editor, cx| {
|
||||
assert!(editor.showing_context_menu());
|
||||
editor.confirm_completion(Some(0), cx);
|
||||
editor.confirm_completion(&ConfirmCompletion(Some(0)), cx);
|
||||
assert_eq!(editor.text(cx), "fn main() { a.first_method() }");
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user