Detailed changes
@@ -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 action = actions_menu
- .actions
- .get(action_ix.unwrap_or(actions_menu.selected_item))?;
-
- dbg!(action);
+ 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))
+ })?;
- 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() }");
});