Consolidate all code actions logic into Project

Max Brunsfeld created

Change summary

crates/editor/src/editor.rs       |  45 ++++++++-----
crates/editor/src/multi_buffer.rs |  27 -------
crates/language/src/buffer.rs     |  86 ------------------------
crates/language/src/proto.rs      |   4 
crates/project/src/project.rs     | 111 ++++++++++++++++++++++++++++++--
crates/project/src/worktree.rs    |  32 ---------
6 files changed, 136 insertions(+), 169 deletions(-)

Detailed changes

crates/editor/src/editor.rs 🔗

@@ -654,7 +654,8 @@ impl CompletionsMenu {
 }
 
 struct CodeActionsMenu {
-    actions: Arc<[CodeAction<Anchor>]>,
+    actions: Arc<[CodeAction]>,
+    buffer: ModelHandle<Buffer>,
     selected_item: usize,
     list: UniformListState,
 }
@@ -2021,24 +2022,40 @@ impl Editor {
         }))
     }
 
-    fn show_code_actions(&mut self, _: &ShowCodeActions, cx: &mut ViewContext<Self>) {
-        let position = if let Some(selection) = self.newest_anchor_selection() {
-            selection.head()
+    fn show_code_actions(
+        workspace: &mut Workspace,
+        _: &ShowCodeActions,
+        cx: &mut ViewContext<Workspace>,
+    ) {
+        let active_item = workspace.active_item(cx);
+        let editor_handle = if let Some(editor) = active_item
+            .as_ref()
+            .and_then(|item| item.act_as::<Self>(cx))
+        {
+            editor
         } else {
             return;
         };
 
-        let actions = self
-            .buffer
-            .update(cx, |buffer, cx| buffer.code_actions(position.clone(), cx));
+        let editor = editor_handle.read(cx);
+        let head = if let Some(selection) = editor.newest_anchor_selection() {
+            selection.head()
+        } else {
+            return;
+        };
+        let (buffer, head) = editor.buffer.read(cx).text_anchor_for_position(head, cx);
+        let actions = workspace
+            .project()
+            .update(cx, |project, cx| project.code_actions(&buffer, head, cx));
 
-        cx.spawn(|this, mut cx| async move {
+        cx.spawn(|_, mut cx| async move {
             let actions = actions.await?;
             if !actions.is_empty() {
-                this.update(&mut cx, |this, cx| {
+                editor_handle.update(&mut cx, |this, cx| {
                     if this.focused {
                         this.show_context_menu(
                             ContextMenu::CodeActions(CodeActionsMenu {
+                                buffer,
                                 actions: actions.into(),
                                 selected_item: 0,
                                 list: UniformListState::default(),
@@ -2069,15 +2086,7 @@ impl Editor {
                 };
             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))
+            Some((actions_menu.buffer, action))
         })?;
 
         let apply_code_actions = workspace.project().update(cx, |project, cx| {

crates/editor/src/multi_buffer.rs 🔗

@@ -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,
@@ -861,31 +861,6 @@ impl MultiBuffer {
         })
     }
 
-    pub fn code_actions<T>(
-        &self,
-        position: T,
-        cx: &mut ModelContext<Self>,
-    ) -> 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.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>(
         &self,
         position: T,

crates/language/src/buffer.rs 🔗

@@ -119,8 +119,8 @@ pub struct Completion<T> {
 }
 
 #[derive(Clone, Debug)]
-pub struct CodeAction<T> {
-    pub position: T,
+pub struct CodeAction {
+    pub position: Anchor,
     pub lsp_action: lsp::CodeAction,
 }
 
@@ -216,13 +216,6 @@ pub trait File {
         cx: &mut MutableAppContext,
     ) -> Task<Result<Option<Transaction>>>;
 
-    fn code_actions(
-        &self,
-        buffer_id: u64,
-        position: Anchor,
-        cx: &mut MutableAppContext,
-    ) -> Task<Result<Vec<CodeAction<Anchor>>>>;
-
     fn buffer_updated(&self, buffer_id: u64, operation: Operation, cx: &mut MutableAppContext);
 
     fn buffer_removed(&self, buffer_id: u64, cx: &mut MutableAppContext);
@@ -311,15 +304,6 @@ impl File for FakeFile {
         Task::ready(Ok(Default::default()))
     }
 
-    fn code_actions(
-        &self,
-        _: u64,
-        _: Anchor,
-        _: &mut MutableAppContext,
-    ) -> Task<Result<Vec<CodeAction<Anchor>>>> {
-        Task::ready(Ok(Default::default()))
-    }
-
     fn buffer_updated(&self, _: u64, _: Operation, _: &mut MutableAppContext) {}
 
     fn buffer_removed(&self, _: u64, _: &mut MutableAppContext) {}
@@ -1861,72 +1845,6 @@ impl Buffer {
         }
     }
 
-    pub fn code_actions<T>(
-        &self,
-        position: T,
-        cx: &mut ModelContext<Self>,
-    ) -> Task<Result<Vec<CodeAction<Anchor>>>>
-    where
-        T: ToPointUtf16,
-    {
-        let file = if let Some(file) = self.file.as_ref() {
-            file
-        } else {
-            return Task::ready(Ok(Default::default()));
-        };
-        let position = position.to_point_utf16(self);
-        let anchor = self.anchor_after(position);
-
-        if let Some(file) = file.as_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 abs_path = file.abs_path(cx);
-
-            cx.foreground().spawn(async move {
-                let actions = server
-                    .request::<lsp::request::CodeActionRequest>(lsp::CodeActionParams {
-                        text_document: lsp::TextDocumentIdentifier::new(
-                            lsp::Url::from_file_path(abs_path).unwrap(),
-                        ),
-                        range: lsp::Range::new(
-                            position.to_lsp_position(),
-                            position.to_lsp_position(),
-                        ),
-                        work_done_progress_params: Default::default(),
-                        partial_result_params: Default::default(),
-                        context: lsp::CodeActionContext {
-                            diagnostics: Default::default(),
-                            only: Some(vec![
-                                lsp::CodeActionKind::QUICKFIX,
-                                lsp::CodeActionKind::REFACTOR,
-                                lsp::CodeActionKind::REFACTOR_EXTRACT,
-                            ]),
-                        },
-                    })
-                    .await?
-                    .unwrap_or_default()
-                    .into_iter()
-                    .filter_map(|entry| {
-                        if let lsp::CodeActionOrCommand::CodeAction(lsp_action) = entry {
-                            Some(CodeAction {
-                                position: anchor.clone(),
-                                lsp_action,
-                            })
-                        } else {
-                            None
-                        }
-                    })
-                    .collect();
-                Ok(actions)
-            })
-        } else {
-            file.code_actions(self.remote_id(), anchor, cx.as_mut())
-        }
-    }
-
     pub fn apply_additional_edits_for_completion(
         &mut self,
         completion: Completion<Anchor>,

crates/language/src/proto.rs 🔗

@@ -426,14 +426,14 @@ pub fn deserialize_completion(
     })
 }
 
-pub fn serialize_code_action(action: &CodeAction<Anchor>) -> proto::CodeAction {
+pub fn serialize_code_action(action: &CodeAction) -> proto::CodeAction {
     proto::CodeAction {
         position: Some(serialize_anchor(&action.position)),
         lsp_action: serde_json::to_vec(&action.lsp_action).unwrap(),
     }
 }
 
-pub fn deserialize_code_action(action: proto::CodeAction) -> Result<CodeAction<Anchor>> {
+pub fn deserialize_code_action(action: proto::CodeAction) -> Result<CodeAction> {
     let position = action
         .position
         .and_then(deserialize_anchor)

crates/project/src/project.rs 🔗

@@ -1171,10 +1171,107 @@ impl Project {
         }
     }
 
+    pub fn code_actions<T: ToPointUtf16>(
+        &self,
+        source_buffer_handle: &ModelHandle<Buffer>,
+        position: T,
+        cx: &mut ModelContext<Self>,
+    ) -> Task<Result<Vec<CodeAction>>> {
+        let source_buffer_handle = source_buffer_handle.clone();
+        let source_buffer = source_buffer_handle.read(cx);
+        let buffer_id = source_buffer.remote_id();
+        let worktree;
+        let buffer_abs_path;
+        if let Some(file) = File::from_dyn(source_buffer.file()) {
+            worktree = file.worktree.clone();
+            buffer_abs_path = file.as_local().map(|f| f.abs_path(cx));
+        } else {
+            return Task::ready(Err(anyhow!("buffer does not belong to any worktree")));
+        };
+
+        let position = position.to_point_utf16(source_buffer);
+        let anchor = source_buffer.anchor_after(position);
+
+        if worktree.read(cx).as_local().is_some() {
+            let buffer_abs_path = buffer_abs_path.unwrap();
+            let lang_name;
+            let lang_server;
+            if let Some(lang) = source_buffer.language() {
+                lang_name = lang.name().to_string();
+                if let Some(server) = self
+                    .language_servers
+                    .get(&(worktree.read(cx).id(), lang_name.clone()))
+                {
+                    lang_server = server.clone();
+                } else {
+                    return Task::ready(Err(anyhow!("buffer does not have a language server")));
+                };
+            } else {
+                return Task::ready(Err(anyhow!("buffer does not have a language")));
+            }
+
+            cx.foreground().spawn(async move {
+                let actions = lang_server
+                    .request::<lsp::request::CodeActionRequest>(lsp::CodeActionParams {
+                        text_document: lsp::TextDocumentIdentifier::new(
+                            lsp::Url::from_file_path(buffer_abs_path).unwrap(),
+                        ),
+                        range: lsp::Range::new(
+                            position.to_lsp_position(),
+                            position.to_lsp_position(),
+                        ),
+                        work_done_progress_params: Default::default(),
+                        partial_result_params: Default::default(),
+                        context: lsp::CodeActionContext {
+                            diagnostics: Default::default(),
+                            only: Some(vec![
+                                lsp::CodeActionKind::QUICKFIX,
+                                lsp::CodeActionKind::REFACTOR,
+                                lsp::CodeActionKind::REFACTOR_EXTRACT,
+                            ]),
+                        },
+                    })
+                    .await?
+                    .unwrap_or_default()
+                    .into_iter()
+                    .filter_map(|entry| {
+                        if let lsp::CodeActionOrCommand::CodeAction(lsp_action) = entry {
+                            Some(CodeAction {
+                                position: anchor.clone(),
+                                lsp_action,
+                            })
+                        } else {
+                            None
+                        }
+                    })
+                    .collect();
+                Ok(actions)
+            })
+        } else if let Some(project_id) = self.remote_id() {
+            let rpc = self.client.clone();
+            cx.foreground().spawn(async move {
+                let response = rpc
+                    .request(proto::GetCodeActions {
+                        project_id,
+                        buffer_id,
+                        position: Some(language::proto::serialize_anchor(&anchor)),
+                    })
+                    .await?;
+                response
+                    .actions
+                    .into_iter()
+                    .map(language::proto::deserialize_code_action)
+                    .collect()
+            })
+        } else {
+            Task::ready(Err(anyhow!("project does not have a remote id")))
+        }
+    }
+
     pub fn apply_code_action(
         &self,
         buffer_handle: ModelHandle<Buffer>,
-        mut action: CodeAction<language::Anchor>,
+        mut action: CodeAction,
         push_to_history: bool,
         cx: &mut ModelContext<Self>,
     ) -> Task<Result<ProjectTransaction>> {
@@ -1206,9 +1303,9 @@ impl Project {
                         .request::<lsp::request::CodeActionResolveRequest>(action.lsp_action)
                         .await?;
                 } else {
-                    let actions = buffer_handle
-                        .update(&mut cx, |buffer, cx| {
-                            buffer.code_actions(action.position.clone(), cx)
+                    let actions = this
+                        .update(&mut cx, |this, cx| {
+                            this.code_actions(&buffer_handle, action.position.clone(), cx)
                         })
                         .await?;
                     action.lsp_action = actions
@@ -2018,9 +2115,9 @@ impl Project {
             .position
             .and_then(language::proto::deserialize_anchor)
             .ok_or_else(|| anyhow!("invalid position"))?;
-        cx.spawn(|_, mut cx| async move {
-            match buffer
-                .update(&mut cx, |buffer, cx| buffer.code_actions(position, cx))
+        cx.spawn(|this, mut cx| async move {
+            match this
+                .update(&mut cx, |this, cx| this.code_actions(&buffer, position, cx))
                 .await
             {
                 Ok(completions) => rpc.respond(

crates/project/src/worktree.rs 🔗

@@ -1474,38 +1474,6 @@ impl language::File for File {
         })
     }
 
-    fn code_actions(
-        &self,
-        buffer_id: u64,
-        position: Anchor,
-        cx: &mut MutableAppContext,
-    ) -> Task<Result<Vec<language::CodeAction<Anchor>>>> {
-        let worktree = self.worktree.read(cx);
-        let worktree = if let Some(worktree) = worktree.as_remote() {
-            worktree
-        } else {
-            return Task::ready(Err(anyhow!(
-                "remote code actions requested on a local worktree"
-            )));
-        };
-        let rpc = worktree.client.clone();
-        let project_id = worktree.project_id;
-        cx.foreground().spawn(async move {
-            let response = rpc
-                .request(proto::GetCodeActions {
-                    project_id,
-                    buffer_id,
-                    position: Some(language::proto::serialize_anchor(&position)),
-                })
-                .await?;
-            response
-                .actions
-                .into_iter()
-                .map(language::proto::deserialize_code_action)
-                .collect()
-        })
-    }
-
     fn buffer_updated(&self, buffer_id: u64, operation: Operation, cx: &mut MutableAppContext) {
         self.worktree.update(cx, |worktree, cx| {
             worktree.send_buffer_update(buffer_id, operation, cx);