Use selection instead of just the cursor when fetching code actions

This commit is contained in:
Antonio Scandurra 2022-02-14 14:13:36 +01:00
parent 1eea2f3653
commit fadb94afb2
6 changed files with 88 additions and 63 deletions

View file

@ -2230,12 +2230,17 @@ impl Editor {
fn refresh_code_actions(&mut self, cx: &mut ViewContext<Self>) -> Option<()> { fn refresh_code_actions(&mut self, cx: &mut ViewContext<Self>) -> Option<()> {
let project = self.project.as_ref()?; let project = self.project.as_ref()?;
let new_cursor_position = self.newest_anchor_selection().head(); let buffer = self.buffer.read(cx);
let (buffer, head) = self let newest_selection = self.newest_anchor_selection().clone();
.buffer let (start_buffer, start) = buffer.text_anchor_for_position(newest_selection.start, cx)?;
.read(cx) let (end_buffer, end) = buffer.text_anchor_for_position(newest_selection.end, cx)?;
.text_anchor_for_position(new_cursor_position, cx)?; if start_buffer != end_buffer {
let actions = project.update(cx, |project, cx| project.code_actions(&buffer, head, cx)); return None;
}
let actions = project.update(cx, |project, cx| {
project.code_actions(&start_buffer, start..end, cx)
});
self.code_actions_task = Some(cx.spawn_weak(|this, mut cx| async move { self.code_actions_task = Some(cx.spawn_weak(|this, mut cx| async move {
let actions = actions.await; let actions = actions.await;
if let Some(this) = this.upgrade(&cx) { if let Some(this) = this.upgrade(&cx) {
@ -2244,7 +2249,7 @@ impl Editor {
if actions.is_empty() { if actions.is_empty() {
None None
} else { } else {
Some((buffer, actions.into())) Some((start_buffer, actions.into()))
} }
}); });
cx.notify(); cx.notify();

View file

@ -123,7 +123,7 @@ pub struct Completion {
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct CodeAction { pub struct CodeAction {
pub position: Anchor, pub range: Range<Anchor>,
pub lsp_action: lsp::CodeAction, pub lsp_action: lsp::CodeAction,
} }

View file

@ -428,19 +428,24 @@ pub fn deserialize_completion(
pub fn serialize_code_action(action: &CodeAction) -> proto::CodeAction { pub fn serialize_code_action(action: &CodeAction) -> proto::CodeAction {
proto::CodeAction { proto::CodeAction {
position: Some(serialize_anchor(&action.position)), start: Some(serialize_anchor(&action.range.start)),
end: Some(serialize_anchor(&action.range.end)),
lsp_action: serde_json::to_vec(&action.lsp_action).unwrap(), lsp_action: serde_json::to_vec(&action.lsp_action).unwrap(),
} }
} }
pub fn deserialize_code_action(action: proto::CodeAction) -> Result<CodeAction> { pub fn deserialize_code_action(action: proto::CodeAction) -> Result<CodeAction> {
let position = action let start = action
.position .start
.and_then(deserialize_anchor) .and_then(deserialize_anchor)
.ok_or_else(|| anyhow!("invalid position"))?; .ok_or_else(|| anyhow!("invalid start"))?;
let end = action
.end
.and_then(deserialize_anchor)
.ok_or_else(|| anyhow!("invalid end"))?;
let lsp_action = serde_json::from_slice(&action.lsp_action)?; let lsp_action = serde_json::from_slice(&action.lsp_action)?;
Ok(CodeAction { Ok(CodeAction {
position, range: start..end,
lsp_action, lsp_action,
}) })
} }

View file

@ -15,9 +15,9 @@ use gpui::{
use language::{ use language::{
point_from_lsp, point_from_lsp,
proto::{deserialize_anchor, serialize_anchor}, proto::{deserialize_anchor, serialize_anchor},
range_from_lsp, Bias, Buffer, CodeAction, Completion, CompletionLabel, Diagnostic, range_from_lsp, AnchorRangeExt, Bias, Buffer, CodeAction, Completion, CompletionLabel,
DiagnosticEntry, File as _, Language, LanguageRegistry, PointUtf16, ToLspPosition, Diagnostic, DiagnosticEntry, File as _, Language, LanguageRegistry, PointUtf16, ToLspPosition,
ToPointUtf16, Transaction, ToOffset, ToPointUtf16, Transaction,
}; };
use lsp::{DiagnosticSeverity, LanguageServer}; use lsp::{DiagnosticSeverity, LanguageServer};
use postage::{prelude::Stream, watch}; use postage::{prelude::Stream, watch};
@ -1474,32 +1474,30 @@ impl Project {
} }
} }
pub fn code_actions<T: ToPointUtf16>( pub fn code_actions<T: ToOffset>(
&self, &self,
source_buffer_handle: &ModelHandle<Buffer>, buffer_handle: &ModelHandle<Buffer>,
position: T, range: Range<T>,
cx: &mut ModelContext<Self>, cx: &mut ModelContext<Self>,
) -> Task<Result<Vec<CodeAction>>> { ) -> Task<Result<Vec<CodeAction>>> {
let source_buffer_handle = source_buffer_handle.clone(); let buffer_handle = buffer_handle.clone();
let source_buffer = source_buffer_handle.read(cx); let buffer = buffer_handle.read(cx);
let buffer_id = source_buffer.remote_id(); let buffer_id = buffer.remote_id();
let worktree; let worktree;
let buffer_abs_path; let buffer_abs_path;
if let Some(file) = File::from_dyn(source_buffer.file()) { if let Some(file) = File::from_dyn(buffer.file()) {
worktree = file.worktree.clone(); worktree = file.worktree.clone();
buffer_abs_path = file.as_local().map(|f| f.abs_path(cx)); buffer_abs_path = file.as_local().map(|f| f.abs_path(cx));
} else { } else {
return Task::ready(Ok(Default::default())); return Task::ready(Ok(Default::default()));
}; };
let range = buffer.anchor_before(range.start)..buffer.anchor_before(range.end);
let position = position.to_point_utf16(source_buffer);
let anchor = source_buffer.anchor_after(position);
if worktree.read(cx).as_local().is_some() { if worktree.read(cx).as_local().is_some() {
let buffer_abs_path = buffer_abs_path.unwrap(); let buffer_abs_path = buffer_abs_path.unwrap();
let lang_name; let lang_name;
let lang_server; let lang_server;
if let Some(lang) = source_buffer.language() { if let Some(lang) = buffer.language() {
lang_name = lang.name().to_string(); lang_name = lang.name().to_string();
if let Some(server) = self if let Some(server) = self
.language_servers .language_servers
@ -1513,15 +1511,14 @@ impl Project {
return Task::ready(Ok(Default::default())); return Task::ready(Ok(Default::default()));
} }
cx.foreground().spawn(async move { let actions =
let actions = lang_server lang_server.request::<lsp::request::CodeActionRequest>(lsp::CodeActionParams {
.request::<lsp::request::CodeActionRequest>(lsp::CodeActionParams {
text_document: lsp::TextDocumentIdentifier::new( text_document: lsp::TextDocumentIdentifier::new(
lsp::Url::from_file_path(buffer_abs_path).unwrap(), lsp::Url::from_file_path(buffer_abs_path).unwrap(),
), ),
range: lsp::Range::new( range: lsp::Range::new(
position.to_lsp_position(), range.start.to_point_utf16(buffer).to_lsp_position(),
position.to_lsp_position(), range.end.to_point_utf16(buffer).to_lsp_position(),
), ),
work_done_progress_params: Default::default(), work_done_progress_params: Default::default(),
partial_result_params: Default::default(), partial_result_params: Default::default(),
@ -1533,22 +1530,23 @@ impl Project {
lsp::CodeActionKind::REFACTOR_EXTRACT, lsp::CodeActionKind::REFACTOR_EXTRACT,
]), ]),
}, },
}) });
cx.foreground().spawn(async move {
Ok(actions
.await? .await?
.unwrap_or_default() .unwrap_or_default()
.into_iter() .into_iter()
.filter_map(|entry| { .filter_map(|entry| {
if let lsp::CodeActionOrCommand::CodeAction(lsp_action) = entry { if let lsp::CodeActionOrCommand::CodeAction(lsp_action) = entry {
Some(CodeAction { Some(CodeAction {
position: anchor.clone(), range: range.clone(),
lsp_action, lsp_action,
}) })
} else { } else {
None None
} }
}) })
.collect(); .collect())
Ok(actions)
}) })
} else if let Some(project_id) = self.remote_id() { } else if let Some(project_id) = self.remote_id() {
let rpc = self.client.clone(); let rpc = self.client.clone();
@ -1557,7 +1555,8 @@ impl Project {
.request(proto::GetCodeActions { .request(proto::GetCodeActions {
project_id, project_id,
buffer_id, buffer_id,
position: Some(language::proto::serialize_anchor(&anchor)), start: Some(language::proto::serialize_anchor(&range.start)),
end: Some(language::proto::serialize_anchor(&range.end)),
}) })
.await?; .await?;
response response
@ -1590,25 +1589,29 @@ impl Project {
} else { } else {
return Task::ready(Err(anyhow!("buffer does not have a language server"))); return Task::ready(Err(anyhow!("buffer does not have a language server")));
}; };
let position = action.position.to_point_utf16(buffer).to_lsp_position(); let range = action.range.to_point_utf16(buffer);
let fs = self.fs.clone(); let fs = self.fs.clone();
cx.spawn(|this, mut cx| async move { cx.spawn(|this, mut cx| async move {
if let Some(range) = action if let Some(lsp_range) = action
.lsp_action .lsp_action
.data .data
.as_mut() .as_mut()
.and_then(|d| d.get_mut("codeActionParams")) .and_then(|d| d.get_mut("codeActionParams"))
.and_then(|d| d.get_mut("range")) .and_then(|d| d.get_mut("range"))
{ {
*range = serde_json::to_value(&lsp::Range::new(position, position)).unwrap(); *lsp_range = serde_json::to_value(&lsp::Range::new(
range.start.to_lsp_position(),
range.end.to_lsp_position(),
))
.unwrap();
action.lsp_action = lang_server action.lsp_action = lang_server
.request::<lsp::request::CodeActionResolveRequest>(action.lsp_action) .request::<lsp::request::CodeActionResolveRequest>(action.lsp_action)
.await?; .await?;
} else { } else {
let actions = this let actions = this
.update(&mut cx, |this, cx| { .update(&mut cx, |this, cx| {
this.code_actions(&buffer_handle, action.position.clone(), cx) this.code_actions(&buffer_handle, action.range, cx)
}) })
.await?; .await?;
action.lsp_action = actions action.lsp_action = actions
@ -2357,18 +2360,23 @@ impl Project {
mut cx: AsyncAppContext, mut cx: AsyncAppContext,
) -> Result<proto::GetCodeActionsResponse> { ) -> Result<proto::GetCodeActionsResponse> {
let sender_id = envelope.original_sender_id()?; let sender_id = envelope.original_sender_id()?;
let position = envelope let start = envelope
.payload .payload
.position .start
.and_then(language::proto::deserialize_anchor) .and_then(language::proto::deserialize_anchor)
.ok_or_else(|| anyhow!("invalid position"))?; .ok_or_else(|| anyhow!("invalid start"))?;
let end = envelope
.payload
.end
.and_then(language::proto::deserialize_anchor)
.ok_or_else(|| anyhow!("invalid end"))?;
let code_actions = this.update(&mut cx, |this, cx| { let code_actions = this.update(&mut cx, |this, cx| {
let buffer = this let buffer = this
.shared_buffers .shared_buffers
.get(&sender_id) .get(&sender_id)
.and_then(|shared_buffers| shared_buffers.get(&envelope.payload.buffer_id).cloned()) .and_then(|shared_buffers| shared_buffers.get(&envelope.payload.buffer_id).cloned())
.ok_or_else(|| anyhow!("unknown buffer id {}", envelope.payload.buffer_id))?; .ok_or_else(|| anyhow!("unknown buffer id {}", envelope.payload.buffer_id))?;
Ok::<_, anyhow::Error>(this.code_actions(&buffer, position, cx)) Ok::<_, anyhow::Error>(this.code_actions(&buffer, start..end, cx))
})?; })?;
Ok(proto::GetCodeActionsResponse { Ok(proto::GetCodeActionsResponse {

View file

@ -246,7 +246,8 @@ message Completion {
message GetCodeActions { message GetCodeActions {
uint64 project_id = 1; uint64 project_id = 1;
uint64 buffer_id = 2; uint64 buffer_id = 2;
Anchor position = 3; Anchor start = 3;
Anchor end = 4;
} }
message GetCodeActionsResponse { message GetCodeActionsResponse {
@ -264,8 +265,9 @@ message ApplyCodeActionResponse {
} }
message CodeAction { message CodeAction {
Anchor position = 1; Anchor start = 1;
bytes lsp_action = 2; Anchor end = 2;
bytes lsp_action = 3;
} }
message ProjectTransaction { message ProjectTransaction {

View file

@ -1,5 +1,5 @@
use super::{Point, ToOffset}; use super::{Point, ToOffset};
use crate::{rope::TextDimension, BufferSnapshot}; use crate::{rope::TextDimension, BufferSnapshot, PointUtf16, ToPointUtf16};
use anyhow::Result; use anyhow::Result;
use std::{cmp::Ordering, fmt::Debug, ops::Range}; use std::{cmp::Ordering, fmt::Debug, ops::Range};
use sum_tree::Bias; use sum_tree::Bias;
@ -78,6 +78,7 @@ pub trait AnchorRangeExt {
fn cmp(&self, b: &Range<Anchor>, buffer: &BufferSnapshot) -> Result<Ordering>; fn cmp(&self, b: &Range<Anchor>, buffer: &BufferSnapshot) -> Result<Ordering>;
fn to_offset(&self, content: &BufferSnapshot) -> Range<usize>; fn to_offset(&self, content: &BufferSnapshot) -> Range<usize>;
fn to_point(&self, content: &BufferSnapshot) -> Range<Point>; fn to_point(&self, content: &BufferSnapshot) -> Range<Point>;
fn to_point_utf16(&self, content: &BufferSnapshot) -> Range<PointUtf16>;
} }
impl AnchorRangeExt for Range<Anchor> { impl AnchorRangeExt for Range<Anchor> {
@ -95,4 +96,8 @@ impl AnchorRangeExt for Range<Anchor> {
fn to_point(&self, content: &BufferSnapshot) -> Range<Point> { fn to_point(&self, content: &BufferSnapshot) -> Range<Point> {
self.start.summary::<Point>(&content)..self.end.summary::<Point>(&content) self.start.summary::<Point>(&content)..self.end.summary::<Point>(&content)
} }
fn to_point_utf16(&self, content: &BufferSnapshot) -> Range<PointUtf16> {
self.start.to_point_utf16(content)..self.end.to_point_utf16(content)
}
} }