Move the details of completion-resolution logic into Project

Co-authored-by: Conrad <conrad@zed.dev>
This commit is contained in:
Max Brunsfeld 2024-01-19 11:49:18 -08:00
parent 9ec6855e6b
commit 1ceccdf03b
2 changed files with 192 additions and 221 deletions

View file

@ -40,7 +40,7 @@ pub(crate) use actions::*;
use aho_corasick::AhoCorasick; use aho_corasick::AhoCorasick;
use anyhow::{anyhow, Context as _, Result}; use anyhow::{anyhow, Context as _, Result};
use blink_manager::BlinkManager; use blink_manager::BlinkManager;
use client::{Client, Collaborator, ParticipantIndex}; use client::{Collaborator, ParticipantIndex};
use clock::ReplicaId; use clock::ReplicaId;
use collections::{BTreeMap, Bound, HashMap, HashSet, VecDeque}; use collections::{BTreeMap, Bound, HashMap, HashSet, VecDeque};
use convert_case::{Case, Casing}; use convert_case::{Case, Casing};
@ -71,8 +71,7 @@ use language::{
language_settings::{self, all_language_settings, InlayHintSettings}, language_settings::{self, all_language_settings, InlayHintSettings},
markdown, point_from_lsp, AutoindentMode, BracketPair, Buffer, Capability, CodeAction, markdown, point_from_lsp, AutoindentMode, BracketPair, Buffer, Capability, CodeAction,
CodeLabel, Completion, CursorShape, Diagnostic, Documentation, IndentKind, IndentSize, CodeLabel, Completion, CursorShape, Diagnostic, Documentation, IndentKind, IndentSize,
Language, LanguageRegistry, LanguageServerName, OffsetRangeExt, Point, Selection, Language, LanguageServerName, OffsetRangeExt, Point, Selection, SelectionGoal, TransactionId,
SelectionGoal, TransactionId,
}; };
use link_go_to_definition::{GoToDefinitionLink, InlayHighlight, LinkGoToDefinitionState}; use link_go_to_definition::{GoToDefinitionLink, InlayHighlight, LinkGoToDefinitionState};
@ -88,7 +87,7 @@ use ordered_float::OrderedFloat;
use parking_lot::RwLock; use parking_lot::RwLock;
use project::{FormatTrigger, Location, Project, ProjectPath, ProjectTransaction}; use project::{FormatTrigger, Location, Project, ProjectPath, ProjectTransaction};
use rand::prelude::*; use rand::prelude::*;
use rpc::proto::{self, *}; use rpc::proto::*;
use scroll::{Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager, ScrollbarAutoHide}; use scroll::{Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager, ScrollbarAutoHide};
use selections_collection::{resolve_multiple, MutableSelectionsCollection, SelectionsCollection}; use selections_collection::{resolve_multiple, MutableSelectionsCollection, SelectionsCollection};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -735,81 +734,19 @@ impl CompletionsMenu {
return None; return None;
}; };
let client = project.read(cx).client(); let resolve_task = project.update(cx, |project, cx| {
let language_registry = project.read(cx).languages().clone(); project.resolve_completions(
self.matches.iter().map(|m| m.candidate_id).collect(),
self.completions.clone(),
cx,
)
});
let is_remote = project.read(cx).is_remote(); return Some(cx.spawn(move |this, mut cx| async move {
let project_id = project.read(cx).remote_id(); if let Some(true) = resolve_task.await.log_err() {
this.update(&mut cx, |_, cx| cx.notify()).ok();
let completions = self.completions.clone();
let completion_indices: Vec<_> = self.matches.iter().map(|m| m.candidate_id).collect();
Some(cx.spawn(move |this, mut cx| async move {
if is_remote {
let Some(project_id) = project_id else {
log::error!("Remote project without remote_id");
return;
};
for completion_index in completion_indices {
let completions_guard = completions.read();
let completion = &completions_guard[completion_index];
if completion.documentation.is_some() {
continue;
}
let server_id = completion.server_id;
let completion = completion.lsp_completion.clone();
drop(completions_guard);
Self::resolve_completion_documentation_remote(
project_id,
server_id,
completions.clone(),
completion_index,
completion,
client.clone(),
language_registry.clone(),
)
.await;
_ = this.update(&mut cx, |_, cx| cx.notify());
}
} else {
for completion_index in completion_indices {
let completions_guard = completions.read();
let completion = &completions_guard[completion_index];
if completion.documentation.is_some() {
continue;
}
let server_id = completion.server_id;
let completion = completion.lsp_completion.clone();
drop(completions_guard);
let server = project
.read_with(&mut cx, |project, _| {
project.language_server_for_id(server_id)
})
.ok()
.flatten();
let Some(server) = server else {
return;
};
Self::resolve_completion_documentation_local(
server,
completions.clone(),
completion_index,
completion,
language_registry.clone(),
)
.await;
_ = this.update(&mut cx, |_, cx| cx.notify());
}
} }
})) }));
} }
fn attempt_resolve_selected_completion_documentation( fn attempt_resolve_selected_completion_documentation(
@ -826,146 +763,16 @@ impl CompletionsMenu {
let Some(project) = project else { let Some(project) = project else {
return; return;
}; };
let language_registry = project.read(cx).languages().clone();
let completions = self.completions.clone(); let resolve_task = project.update(cx, |project, cx| {
let completions_guard = completions.read(); project.resolve_completions(vec![completion_index], self.completions.clone(), cx)
let completion = &completions_guard[completion_index]; });
if completion.documentation.is_some() { cx.spawn(move |this, mut cx| async move {
return; if let Some(true) = resolve_task.await.log_err() {
} this.update(&mut cx, |_, cx| cx.notify()).ok();
}
let server_id = completion.server_id; })
let completion = completion.lsp_completion.clone(); .detach();
drop(completions_guard);
if project.read(cx).is_remote() {
let Some(project_id) = project.read(cx).remote_id() else {
log::error!("Remote project without remote_id");
return;
};
let client = project.read(cx).client();
cx.spawn(move |this, mut cx| async move {
Self::resolve_completion_documentation_remote(
project_id,
server_id,
completions.clone(),
completion_index,
completion,
client,
language_registry.clone(),
)
.await;
_ = this.update(&mut cx, |_, cx| cx.notify());
})
.detach();
} else {
let Some(server) = project.read(cx).language_server_for_id(server_id) else {
return;
};
cx.spawn(move |this, mut cx| async move {
Self::resolve_completion_documentation_local(
server,
completions,
completion_index,
completion,
language_registry,
)
.await;
_ = this.update(&mut cx, |_, cx| cx.notify());
})
.detach();
}
}
async fn resolve_completion_documentation_remote(
project_id: u64,
server_id: LanguageServerId,
completions: Arc<RwLock<Box<[Completion]>>>,
completion_index: usize,
completion: lsp::CompletionItem,
client: Arc<Client>,
language_registry: Arc<LanguageRegistry>,
) {
let request = proto::ResolveCompletionDocumentation {
project_id,
language_server_id: server_id.0 as u64,
lsp_completion: serde_json::to_string(&completion).unwrap().into_bytes(),
};
let Some(response) = client
.request(request)
.await
.context("completion documentation resolve proto request")
.log_err()
else {
return;
};
if response.text.is_empty() {
let mut completions = completions.write();
let completion = &mut completions[completion_index];
completion.documentation = Some(Documentation::Undocumented);
}
let documentation = if response.is_markdown {
Documentation::MultiLineMarkdown(
markdown::parse_markdown(&response.text, &language_registry, None).await,
)
} else if response.text.lines().count() <= 1 {
Documentation::SingleLine(response.text)
} else {
Documentation::MultiLinePlainText(response.text)
};
let mut completions = completions.write();
let completion = &mut completions[completion_index];
completion.documentation = Some(documentation);
}
async fn resolve_completion_documentation_local(
server: Arc<lsp::LanguageServer>,
completions: Arc<RwLock<Box<[Completion]>>>,
completion_index: usize,
completion: lsp::CompletionItem,
language_registry: Arc<LanguageRegistry>,
) {
let can_resolve = server
.capabilities()
.completion_provider
.as_ref()
.and_then(|options| options.resolve_provider)
.unwrap_or(false);
if !can_resolve {
return;
}
let request = server.request::<lsp::request::ResolveCompletionItem>(completion);
let Some(completion_item) = request.await.log_err() else {
return;
};
if let Some(lsp_documentation) = completion_item.documentation {
let documentation = language::prepare_completion_documentation(
&lsp_documentation,
&language_registry,
None, // TODO: Try to reasonably work out which language the completion is for
)
.await;
let mut completions = completions.write();
let completion = &mut completions[completion_index];
completion.documentation = Some(documentation);
} else {
let mut completions = completions.write();
let completion = &mut completions[completion_index];
completion.documentation = Some(Documentation::Undocumented);
}
} }
fn visible(&self) -> bool { fn visible(&self) -> bool {

View file

@ -34,16 +34,16 @@ use gpui::{
use itertools::Itertools; use itertools::Itertools;
use language::{ use language::{
language_settings::{language_settings, FormatOnSave, Formatter, InlayHintKind}, language_settings::{language_settings, FormatOnSave, Formatter, InlayHintKind},
point_to_lsp, markdown, point_to_lsp,
proto::{ proto::{
deserialize_anchor, deserialize_fingerprint, deserialize_line_ending, deserialize_version, deserialize_anchor, deserialize_fingerprint, deserialize_line_ending, deserialize_version,
serialize_anchor, serialize_version, split_operations, serialize_anchor, serialize_version, split_operations,
}, },
range_from_lsp, range_to_lsp, Bias, Buffer, BufferSnapshot, CachedLspAdapter, Capability, range_from_lsp, range_to_lsp, Bias, Buffer, BufferSnapshot, CachedLspAdapter, Capability,
CodeAction, CodeLabel, Completion, Diagnostic, DiagnosticEntry, DiagnosticSet, Diff, CodeAction, CodeLabel, Completion, Diagnostic, DiagnosticEntry, DiagnosticSet, Diff,
Event as BufferEvent, File as _, Language, LanguageRegistry, LanguageServerName, LocalFile, Documentation, Event as BufferEvent, File as _, Language, LanguageRegistry, LanguageServerName,
LspAdapterDelegate, OffsetRangeExt, Operation, Patch, PendingLanguageServer, PointUtf16, LocalFile, LspAdapterDelegate, OffsetRangeExt, Operation, Patch, PendingLanguageServer,
TextBufferSnapshot, ToOffset, ToPointUtf16, Transaction, Unclipped, PointUtf16, TextBufferSnapshot, ToOffset, ToPointUtf16, Transaction, Unclipped,
}; };
use log::error; use log::error;
use lsp::{ use lsp::{
@ -52,7 +52,7 @@ use lsp::{
}; };
use lsp_command::*; use lsp_command::*;
use node_runtime::NodeRuntime; use node_runtime::NodeRuntime;
use parking_lot::Mutex; use parking_lot::{Mutex, RwLock};
use postage::watch; use postage::watch;
use prettier_support::{DefaultPrettier, PrettierInstance}; use prettier_support::{DefaultPrettier, PrettierInstance};
use project_settings::{LspSettings, ProjectSettings}; use project_settings::{LspSettings, ProjectSettings};
@ -4828,6 +4828,170 @@ impl Project {
} }
} }
pub fn resolve_completions(
&self,
completion_indices: Vec<usize>,
completions: Arc<RwLock<Box<[Completion]>>>,
cx: &mut ModelContext<Self>,
) -> Task<Result<bool>> {
let client = self.client();
let language_registry = self.languages().clone();
let is_remote = self.is_remote();
let project_id = self.remote_id();
cx.spawn(move |this, mut cx| async move {
let mut did_resolve = false;
if is_remote {
let project_id =
project_id.ok_or_else(|| anyhow!("Remote project without remote_id"))?;
for completion_index in completion_indices {
let completions_guard = completions.read();
let completion = &completions_guard[completion_index];
if completion.documentation.is_some() {
continue;
}
did_resolve = true;
let server_id = completion.server_id;
let completion = completion.lsp_completion.clone();
drop(completions_guard);
Self::resolve_completion_documentation_remote(
project_id,
server_id,
completions.clone(),
completion_index,
completion,
client.clone(),
language_registry.clone(),
)
.await;
}
} else {
for completion_index in completion_indices {
let completions_guard = completions.read();
let completion = &completions_guard[completion_index];
if completion.documentation.is_some() {
continue;
}
let server_id = completion.server_id;
let completion = completion.lsp_completion.clone();
drop(completions_guard);
let server = this
.read_with(&mut cx, |project, _| {
project.language_server_for_id(server_id)
})
.ok()
.flatten();
let Some(server) = server else {
continue;
};
did_resolve = true;
Self::resolve_completion_documentation_local(
server,
completions.clone(),
completion_index,
completion,
language_registry.clone(),
)
.await;
}
}
Ok(did_resolve)
})
}
async fn resolve_completion_documentation_local(
server: Arc<lsp::LanguageServer>,
completions: Arc<RwLock<Box<[Completion]>>>,
completion_index: usize,
completion: lsp::CompletionItem,
language_registry: Arc<LanguageRegistry>,
) {
let can_resolve = server
.capabilities()
.completion_provider
.as_ref()
.and_then(|options| options.resolve_provider)
.unwrap_or(false);
if !can_resolve {
return;
}
let request = server.request::<lsp::request::ResolveCompletionItem>(completion);
let Some(completion_item) = request.await.log_err() else {
return;
};
if let Some(lsp_documentation) = completion_item.documentation {
let documentation = language::prepare_completion_documentation(
&lsp_documentation,
&language_registry,
None, // TODO: Try to reasonably work out which language the completion is for
)
.await;
let mut completions = completions.write();
let completion = &mut completions[completion_index];
completion.documentation = Some(documentation);
} else {
let mut completions = completions.write();
let completion = &mut completions[completion_index];
completion.documentation = Some(Documentation::Undocumented);
}
}
async fn resolve_completion_documentation_remote(
project_id: u64,
server_id: LanguageServerId,
completions: Arc<RwLock<Box<[Completion]>>>,
completion_index: usize,
completion: lsp::CompletionItem,
client: Arc<Client>,
language_registry: Arc<LanguageRegistry>,
) {
let request = proto::ResolveCompletionDocumentation {
project_id,
language_server_id: server_id.0 as u64,
lsp_completion: serde_json::to_string(&completion).unwrap().into_bytes(),
};
let Some(response) = client
.request(request)
.await
.context("completion documentation resolve proto request")
.log_err()
else {
return;
};
if response.text.is_empty() {
let mut completions = completions.write();
let completion = &mut completions[completion_index];
completion.documentation = Some(Documentation::Undocumented);
}
let documentation = if response.is_markdown {
Documentation::MultiLineMarkdown(
markdown::parse_markdown(&response.text, &language_registry, None).await,
)
} else if response.text.lines().count() <= 1 {
Documentation::SingleLine(response.text)
} else {
Documentation::MultiLinePlainText(response.text)
};
let mut completions = completions.write();
let completion = &mut completions[completion_index];
completion.documentation = Some(documentation);
}
pub fn apply_additional_edits_for_completion( pub fn apply_additional_edits_for_completion(
&self, &self,
buffer_handle: Model<Buffer>, buffer_handle: Model<Buffer>,