diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 9f2988ada9..ae9381d2f0 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -654,7 +654,8 @@ impl CompletionsMenu { } struct CodeActionsMenu { - actions: Arc<[CodeAction]>, + actions: Arc<[CodeAction]>, + buffer: ModelHandle, selected_item: usize, list: UniformListState, } @@ -2021,24 +2022,40 @@ impl Editor { })) } - fn show_code_actions(&mut self, _: &ShowCodeActions, cx: &mut ViewContext) { - let position = if let Some(selection) = self.newest_anchor_selection() { - selection.head() + fn show_code_actions( + workspace: &mut Workspace, + _: &ShowCodeActions, + cx: &mut ViewContext, + ) { + let active_item = workspace.active_item(cx); + let editor_handle = if let Some(editor) = active_item + .as_ref() + .and_then(|item| item.act_as::(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| { diff --git a/crates/editor/src/multi_buffer.rs b/crates/editor/src/multi_buffer.rs index d8071ebbda..9a33de3a0f 100644 --- a/crates/editor/src/multi_buffer.rs +++ b/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( - &self, - position: T, - cx: &mut ModelContext, - ) -> Task>>> - 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( &self, position: T, diff --git a/crates/language/src/buffer.rs b/crates/language/src/buffer.rs index 87385f4a9a..2b5c979455 100644 --- a/crates/language/src/buffer.rs +++ b/crates/language/src/buffer.rs @@ -119,8 +119,8 @@ pub struct Completion { } #[derive(Clone, Debug)] -pub struct CodeAction { - 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>>; - fn code_actions( - &self, - buffer_id: u64, - position: Anchor, - cx: &mut MutableAppContext, - ) -> Task>>>; - 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>>> { - 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( - &self, - position: T, - cx: &mut ModelContext, - ) -> Task>>> - 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::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, diff --git a/crates/language/src/proto.rs b/crates/language/src/proto.rs index 296594ae4c..fbcd5efa78 100644 --- a/crates/language/src/proto.rs +++ b/crates/language/src/proto.rs @@ -426,14 +426,14 @@ pub fn deserialize_completion( }) } -pub fn serialize_code_action(action: &CodeAction) -> 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> { +pub fn deserialize_code_action(action: proto::CodeAction) -> Result { let position = action .position .and_then(deserialize_anchor) diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index f7fb3ce37f..77bea77d10 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -1171,10 +1171,107 @@ impl Project { } } + pub fn code_actions( + &self, + source_buffer_handle: &ModelHandle, + position: T, + cx: &mut ModelContext, + ) -> Task>> { + 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::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, - mut action: CodeAction, + mut action: CodeAction, push_to_history: bool, cx: &mut ModelContext, ) -> Task> { @@ -1206,9 +1303,9 @@ impl Project { .request::(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( diff --git a/crates/project/src/worktree.rs b/crates/project/src/worktree.rs index 904835c1b5..049ebc4e16 100644 --- a/crates/project/src/worktree.rs +++ b/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>>> { - 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);