mirror of
https://github.com/zed-industries/zed.git
synced 2024-12-25 20:11:49 +03:00
Refactor LSP adapter methods to compute labels in batches (#10097)
Once we enable extensions to customize the labels of completions and symbols, this new structure will allow this to be done with a single WASM call, instead of one WASM call per completion / symbol. Release Notes: - N/A --------- Co-authored-by: Marshall Bowers <elliott.codes@gmail.com> Co-authored-by: Marshall <marshall@zed.dev>
This commit is contained in:
parent
ef3d04efe6
commit
256b446bdf
@ -9,12 +9,12 @@ use gpui::{
|
||||
Render, SharedString, Task, TextStyle, View, ViewContext, WeakView, WhiteSpace,
|
||||
};
|
||||
use language::{
|
||||
language_settings::SoftWrap, Anchor, Buffer, BufferSnapshot, CodeLabel, Completion,
|
||||
LanguageRegistry, LanguageServerId, ToOffset,
|
||||
language_settings::SoftWrap, Anchor, Buffer, BufferSnapshot, CodeLabel, LanguageRegistry,
|
||||
LanguageServerId, ToOffset,
|
||||
};
|
||||
use lazy_static::lazy_static;
|
||||
use parking_lot::RwLock;
|
||||
use project::search::SearchQuery;
|
||||
use project::{search::SearchQuery, Completion};
|
||||
use settings::Settings;
|
||||
use std::{ops::Range, sync::Arc, time::Duration};
|
||||
use theme::ThemeSettings;
|
||||
@ -48,7 +48,7 @@ impl CompletionProvider for MessageEditorCompletionProvider {
|
||||
buffer: &Model<Buffer>,
|
||||
buffer_position: language::Anchor,
|
||||
cx: &mut ViewContext<Editor>,
|
||||
) -> Task<anyhow::Result<Vec<language::Completion>>> {
|
||||
) -> Task<anyhow::Result<Vec<Completion>>> {
|
||||
let Some(handle) = self.0.upgrade() else {
|
||||
return Task::ready(Ok(Vec::new()));
|
||||
};
|
||||
@ -60,7 +60,7 @@ impl CompletionProvider for MessageEditorCompletionProvider {
|
||||
fn resolve_completions(
|
||||
&self,
|
||||
_completion_indices: Vec<usize>,
|
||||
_completions: Arc<RwLock<Box<[language::Completion]>>>,
|
||||
_completions: Arc<RwLock<Box<[Completion]>>>,
|
||||
_cx: &mut ViewContext<Editor>,
|
||||
) -> Task<anyhow::Result<bool>> {
|
||||
Task::ready(Ok(false))
|
||||
|
@ -74,12 +74,12 @@ use inlay_hint_cache::{InlayHintCache, InlaySplice, InvalidationStrategy};
|
||||
pub use inline_completion_provider::*;
|
||||
pub use items::MAX_TAB_TITLE_LEN;
|
||||
use itertools::Itertools;
|
||||
use language::{char_kind, CharKind};
|
||||
use language::{
|
||||
char_kind,
|
||||
language_settings::{self, all_language_settings, InlayHintSettings},
|
||||
markdown, point_from_lsp, AutoindentMode, BracketPair, Buffer, Capability, CodeAction,
|
||||
CodeLabel, Completion, CursorShape, Diagnostic, Documentation, IndentKind, IndentSize,
|
||||
Language, OffsetRangeExt, Point, Selection, SelectionGoal, TransactionId,
|
||||
markdown, point_from_lsp, AutoindentMode, BracketPair, Buffer, Capability, CharKind, CodeLabel,
|
||||
CursorShape, Diagnostic, Documentation, IndentKind, IndentSize, Language, OffsetRangeExt,
|
||||
Point, Selection, SelectionGoal, TransactionId,
|
||||
};
|
||||
|
||||
use hover_links::{HoverLink, HoveredLinkState, InlayHighlight};
|
||||
@ -94,7 +94,9 @@ pub use multi_buffer::{
|
||||
use ordered_float::OrderedFloat;
|
||||
use parking_lot::{Mutex, RwLock};
|
||||
use project::project_settings::{GitGutterSetting, ProjectSettings};
|
||||
use project::{FormatTrigger, Item, Location, Project, ProjectPath, ProjectTransaction};
|
||||
use project::{
|
||||
CodeAction, Completion, FormatTrigger, Item, Location, Project, ProjectPath, ProjectTransaction,
|
||||
};
|
||||
use rand::prelude::*;
|
||||
use rpc::proto::*;
|
||||
use scroll::{Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager, ScrollbarAutoHide};
|
||||
|
@ -13,7 +13,7 @@ use crate::{
|
||||
SyntaxLayer, SyntaxMap, SyntaxMapCapture, SyntaxMapCaptures, SyntaxMapMatches,
|
||||
SyntaxSnapshot, ToTreeSitterPoint,
|
||||
},
|
||||
CodeLabel, LanguageScope, Outline,
|
||||
LanguageScope, Outline,
|
||||
};
|
||||
use anyhow::{anyhow, Context, Result};
|
||||
pub use clock::ReplicaId;
|
||||
@ -250,34 +250,6 @@ pub enum Documentation {
|
||||
MultiLineMarkdown(ParsedMarkdown),
|
||||
}
|
||||
|
||||
/// A completion provided by a language server
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Completion {
|
||||
/// The range of the buffer that will be replaced.
|
||||
pub old_range: Range<Anchor>,
|
||||
/// The new text that will be inserted.
|
||||
pub new_text: String,
|
||||
/// A label for this completion that is shown in the menu.
|
||||
pub label: CodeLabel,
|
||||
/// The id of the language server that produced this completion.
|
||||
pub server_id: LanguageServerId,
|
||||
/// The documentation for this completion.
|
||||
pub documentation: Option<Documentation>,
|
||||
/// The raw completion provided by the language server.
|
||||
pub lsp_completion: lsp::CompletionItem,
|
||||
}
|
||||
|
||||
/// A code action provided by a language server.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct CodeAction {
|
||||
/// The id of the language server that produced this code action.
|
||||
pub server_id: LanguageServerId,
|
||||
/// The range of the buffer where this code action is applicable.
|
||||
pub range: Range<Anchor>,
|
||||
/// The raw code action provided by the language server.
|
||||
pub lsp_action: lsp::CodeAction,
|
||||
}
|
||||
|
||||
/// An operation used to synchronize this buffer with its other replicas.
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum Operation {
|
||||
@ -2526,6 +2498,11 @@ impl BufferSnapshot {
|
||||
.last()
|
||||
}
|
||||
|
||||
/// Returns the main [Language]
|
||||
pub fn language(&self) -> Option<&Arc<Language>> {
|
||||
self.language.as_ref()
|
||||
}
|
||||
|
||||
/// Returns the [Language] at the given location.
|
||||
pub fn language_at<D: ToOffset>(&self, position: D) -> Option<&Arc<Language>> {
|
||||
self.syntax_layer_at(position)
|
||||
@ -3508,24 +3485,6 @@ impl IndentSize {
|
||||
}
|
||||
}
|
||||
|
||||
impl Completion {
|
||||
/// A key that can be used to sort completions when displaying
|
||||
/// them to the user.
|
||||
pub fn sort_key(&self) -> (usize, &str) {
|
||||
let kind_key = match self.lsp_completion.kind {
|
||||
Some(lsp::CompletionItemKind::KEYWORD) => 0,
|
||||
Some(lsp::CompletionItemKind::VARIABLE) => 1,
|
||||
_ => 2,
|
||||
};
|
||||
(kind_key, &self.label.text[self.label.filter_range.clone()])
|
||||
}
|
||||
|
||||
/// Whether this completion is a snippet.
|
||||
pub fn is_snippet(&self) -> bool {
|
||||
self.lsp_completion.insert_text_format == Some(lsp::InsertTextFormat::SNIPPET)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub struct TestFile {
|
||||
pub path: Arc<Path>,
|
||||
|
@ -205,27 +205,26 @@ impl CachedLspAdapter {
|
||||
self.adapter.process_diagnostics(params)
|
||||
}
|
||||
|
||||
pub async fn process_completion(&self, completion_item: &mut lsp::CompletionItem) {
|
||||
self.adapter.process_completion(completion_item).await
|
||||
pub async fn process_completions(&self, completion_items: &mut [lsp::CompletionItem]) {
|
||||
self.adapter.process_completions(completion_items).await
|
||||
}
|
||||
|
||||
pub async fn label_for_completion(
|
||||
pub async fn labels_for_completions(
|
||||
&self,
|
||||
completion_item: &lsp::CompletionItem,
|
||||
completion_items: &[lsp::CompletionItem],
|
||||
language: &Arc<Language>,
|
||||
) -> Option<CodeLabel> {
|
||||
) -> Vec<Option<CodeLabel>> {
|
||||
self.adapter
|
||||
.label_for_completion(completion_item, language)
|
||||
.labels_for_completions(completion_items, language)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn label_for_symbol(
|
||||
pub async fn labels_for_symbols(
|
||||
&self,
|
||||
name: &str,
|
||||
kind: lsp::SymbolKind,
|
||||
symbols: &[(String, lsp::SymbolKind)],
|
||||
language: &Arc<Language>,
|
||||
) -> Option<CodeLabel> {
|
||||
self.adapter.label_for_symbol(name, kind, language).await
|
||||
) -> Vec<Option<CodeLabel>> {
|
||||
self.adapter.labels_for_symbols(symbols, language).await
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
@ -382,10 +381,24 @@ pub trait LspAdapter: 'static + Send + Sync {
|
||||
|
||||
fn process_diagnostics(&self, _: &mut lsp::PublishDiagnosticsParams) {}
|
||||
|
||||
/// A callback called for each [`lsp::CompletionItem`] obtained from LSP server.
|
||||
/// Some LspAdapter implementations might want to modify the obtained item to
|
||||
/// change how it's displayed.
|
||||
async fn process_completion(&self, _: &mut lsp::CompletionItem) {}
|
||||
/// Post-processes completions provided by the language server.
|
||||
async fn process_completions(&self, _: &mut [lsp::CompletionItem]) {}
|
||||
|
||||
async fn labels_for_completions(
|
||||
&self,
|
||||
completions: &[lsp::CompletionItem],
|
||||
language: &Arc<Language>,
|
||||
) -> Vec<Option<CodeLabel>> {
|
||||
let mut labels = Vec::new();
|
||||
for (ix, completion) in completions.into_iter().enumerate() {
|
||||
let label = self.label_for_completion(completion, language).await;
|
||||
if let Some(label) = label {
|
||||
labels.resize(ix + 1, None);
|
||||
*labels.last_mut().unwrap() = Some(label);
|
||||
}
|
||||
}
|
||||
labels
|
||||
}
|
||||
|
||||
async fn label_for_completion(
|
||||
&self,
|
||||
@ -395,6 +408,22 @@ pub trait LspAdapter: 'static + Send + Sync {
|
||||
None
|
||||
}
|
||||
|
||||
async fn labels_for_symbols(
|
||||
&self,
|
||||
symbols: &[(String, lsp::SymbolKind)],
|
||||
language: &Arc<Language>,
|
||||
) -> Vec<Option<CodeLabel>> {
|
||||
let mut labels = Vec::new();
|
||||
for (ix, (name, kind)) in symbols.into_iter().enumerate() {
|
||||
let label = self.label_for_symbol(name, *kind, language).await;
|
||||
if let Some(label) = label {
|
||||
labels.resize(ix + 1, None);
|
||||
*labels.last_mut().unwrap() = Some(label);
|
||||
}
|
||||
}
|
||||
labels
|
||||
}
|
||||
|
||||
async fn label_for_symbol(
|
||||
&self,
|
||||
_: &str,
|
||||
|
@ -1,9 +1,6 @@
|
||||
//! Handles conversions of `language` items to and from the [`rpc`] protocol.
|
||||
|
||||
use crate::{
|
||||
diagnostic_set::DiagnosticEntry, CodeAction, CodeLabel, Completion, CursorShape, Diagnostic,
|
||||
Language, LanguageRegistry,
|
||||
};
|
||||
use crate::{diagnostic_set::DiagnosticEntry, CursorShape, Diagnostic};
|
||||
use anyhow::{anyhow, Result};
|
||||
use clock::ReplicaId;
|
||||
use lsp::{DiagnosticSeverity, LanguageServerId};
|
||||
@ -466,85 +463,6 @@ pub fn lamport_timestamp_for_operation(operation: &proto::Operation) -> Option<c
|
||||
})
|
||||
}
|
||||
|
||||
/// Serializes a [`Completion`] to be sent over RPC.
|
||||
pub fn serialize_completion(completion: &Completion) -> proto::Completion {
|
||||
proto::Completion {
|
||||
old_start: Some(serialize_anchor(&completion.old_range.start)),
|
||||
old_end: Some(serialize_anchor(&completion.old_range.end)),
|
||||
new_text: completion.new_text.clone(),
|
||||
server_id: completion.server_id.0 as u64,
|
||||
lsp_completion: serde_json::to_vec(&completion.lsp_completion).unwrap(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Deserializes a [`Completion`] from the RPC representation.
|
||||
pub async fn deserialize_completion(
|
||||
completion: proto::Completion,
|
||||
language: Option<Arc<Language>>,
|
||||
language_registry: &Arc<LanguageRegistry>,
|
||||
) -> Result<Completion> {
|
||||
let old_start = completion
|
||||
.old_start
|
||||
.and_then(deserialize_anchor)
|
||||
.ok_or_else(|| anyhow!("invalid old start"))?;
|
||||
let old_end = completion
|
||||
.old_end
|
||||
.and_then(deserialize_anchor)
|
||||
.ok_or_else(|| anyhow!("invalid old end"))?;
|
||||
let lsp_completion = serde_json::from_slice(&completion.lsp_completion)?;
|
||||
|
||||
let mut label = None;
|
||||
if let Some(language) = language {
|
||||
if let Some(adapter) = language_registry.lsp_adapters(&language).first() {
|
||||
label = adapter
|
||||
.label_for_completion(&lsp_completion, &language)
|
||||
.await;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Completion {
|
||||
old_range: old_start..old_end,
|
||||
new_text: completion.new_text,
|
||||
label: label.unwrap_or_else(|| {
|
||||
CodeLabel::plain(
|
||||
lsp_completion.label.clone(),
|
||||
lsp_completion.filter_text.as_deref(),
|
||||
)
|
||||
}),
|
||||
documentation: None,
|
||||
server_id: LanguageServerId(completion.server_id as usize),
|
||||
lsp_completion,
|
||||
})
|
||||
}
|
||||
|
||||
/// Serializes a [`CodeAction`] to be sent over RPC.
|
||||
pub fn serialize_code_action(action: &CodeAction) -> proto::CodeAction {
|
||||
proto::CodeAction {
|
||||
server_id: action.server_id.0 as u64,
|
||||
start: Some(serialize_anchor(&action.range.start)),
|
||||
end: Some(serialize_anchor(&action.range.end)),
|
||||
lsp_action: serde_json::to_vec(&action.lsp_action).unwrap(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Deserializes a [`CodeAction`] from the RPC representation.
|
||||
pub fn deserialize_code_action(action: proto::CodeAction) -> Result<CodeAction> {
|
||||
let start = action
|
||||
.start
|
||||
.and_then(deserialize_anchor)
|
||||
.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)?;
|
||||
Ok(CodeAction {
|
||||
server_id: LanguageServerId(action.server_id as usize),
|
||||
range: start..end,
|
||||
lsp_action,
|
||||
})
|
||||
}
|
||||
|
||||
/// Serializes a [`Transaction`] to be sent over RPC.
|
||||
pub fn serialize_transaction(transaction: &Transaction) -> proto::Transaction {
|
||||
proto::Transaction {
|
||||
|
@ -83,7 +83,7 @@ impl LspAdapter for PythonLspAdapter {
|
||||
get_cached_server_binary(container_dir, &*self.node).await
|
||||
}
|
||||
|
||||
async fn process_completion(&self, item: &mut lsp::CompletionItem) {
|
||||
async fn process_completions(&self, items: &mut [lsp::CompletionItem]) {
|
||||
// Pyright assigns each completion item a `sortText` of the form `XX.YYYY.name`.
|
||||
// Where `XX` is the sorting category, `YYYY` is based on most recent usage,
|
||||
// and `name` is the symbol name itself.
|
||||
@ -94,14 +94,16 @@ impl LspAdapter for PythonLspAdapter {
|
||||
// to allow our own fuzzy score to be used to break ties.
|
||||
//
|
||||
// see https://github.com/microsoft/pyright/blob/95ef4e103b9b2f129c9320427e51b73ea7cf78bd/packages/pyright-internal/src/languageService/completionProvider.ts#LL2873
|
||||
let Some(sort_text) = &mut item.sort_text else {
|
||||
return;
|
||||
};
|
||||
let mut parts = sort_text.split('.');
|
||||
let Some(first) = parts.next() else { return };
|
||||
let Some(second) = parts.next() else { return };
|
||||
let Some(_) = parts.next() else { return };
|
||||
sort_text.replace_range(first.len() + second.len() + 1.., "");
|
||||
for item in items {
|
||||
let Some(sort_text) = &mut item.sort_text else {
|
||||
continue;
|
||||
};
|
||||
let mut parts = sort_text.split('.');
|
||||
let Some(first) = parts.next() else { continue };
|
||||
let Some(second) = parts.next() else { continue };
|
||||
let Some(_) = parts.next() else { continue };
|
||||
sort_text.replace_range(first.len() + second.len() + 1.., "");
|
||||
}
|
||||
}
|
||||
|
||||
async fn label_for_completion(
|
||||
|
@ -7,7 +7,6 @@ use collections::{BTreeMap, Bound, HashMap, HashSet};
|
||||
use futures::{channel::mpsc, SinkExt};
|
||||
use git::diff::DiffHunk;
|
||||
use gpui::{AppContext, EventEmitter, Model, ModelContext};
|
||||
pub use language::Completion;
|
||||
use language::{
|
||||
char_kind,
|
||||
language_settings::{language_settings, LanguageSettings},
|
||||
|
@ -1,7 +1,7 @@
|
||||
use crate::{
|
||||
DocumentHighlight, Hover, HoverBlock, HoverBlockKind, InlayHint, InlayHintLabel,
|
||||
InlayHintLabelPart, InlayHintLabelPartTooltip, InlayHintTooltip, Location, LocationLink,
|
||||
MarkupContent, Project, ProjectTransaction, ResolveState,
|
||||
CodeAction, CoreCompletion, DocumentHighlight, Hover, HoverBlock, HoverBlockKind, InlayHint,
|
||||
InlayHintLabel, InlayHintLabelPart, InlayHintLabelPartTooltip, InlayHintTooltip, Location,
|
||||
LocationLink, MarkupContent, Project, ProjectTransaction, ResolveState,
|
||||
};
|
||||
use anyhow::{anyhow, Context, Result};
|
||||
use async_trait::async_trait;
|
||||
@ -10,11 +10,10 @@ use futures::future;
|
||||
use gpui::{AppContext, AsyncAppContext, Model};
|
||||
use language::{
|
||||
language_settings::{language_settings, InlayHintKind},
|
||||
point_from_lsp, point_to_lsp, prepare_completion_documentation,
|
||||
point_from_lsp, point_to_lsp,
|
||||
proto::{deserialize_anchor, deserialize_version, serialize_anchor, serialize_version},
|
||||
range_from_lsp, range_to_lsp, Anchor, Bias, Buffer, BufferSnapshot, CachedLspAdapter, CharKind,
|
||||
CodeAction, Completion, OffsetRangeExt, PointUtf16, ToOffset, ToPointUtf16, Transaction,
|
||||
Unclipped,
|
||||
OffsetRangeExt, PointUtf16, ToOffset, ToPointUtf16, Transaction, Unclipped,
|
||||
};
|
||||
use lsp::{
|
||||
CompletionListItemDefaultsEditRange, DocumentHighlightKind, LanguageServer, LanguageServerId,
|
||||
@ -1429,7 +1428,7 @@ impl LspCommand for GetHover {
|
||||
|
||||
#[async_trait(?Send)]
|
||||
impl LspCommand for GetCompletions {
|
||||
type Response = Vec<Completion>;
|
||||
type Response = Vec<CoreCompletion>;
|
||||
type LspRequest = lsp::request::Completion;
|
||||
type ProtoRequest = proto::GetCompletions;
|
||||
|
||||
@ -1458,9 +1457,9 @@ impl LspCommand for GetCompletions {
|
||||
buffer: Model<Buffer>,
|
||||
server_id: LanguageServerId,
|
||||
mut cx: AsyncAppContext,
|
||||
) -> Result<Vec<Completion>> {
|
||||
) -> Result<Self::Response> {
|
||||
let mut response_list = None;
|
||||
let completions = if let Some(completions) = completions {
|
||||
let mut completions = if let Some(completions) = completions {
|
||||
match completions {
|
||||
lsp::CompletionResponse::Array(completions) => completions,
|
||||
|
||||
@ -1480,147 +1479,120 @@ impl LspCommand for GetCompletions {
|
||||
})?
|
||||
.ok_or_else(|| anyhow!("no such language server"))?;
|
||||
|
||||
let completions = buffer.update(&mut cx, |buffer, cx| {
|
||||
let language_registry = project.read(cx).languages().clone();
|
||||
let language = buffer.language().cloned();
|
||||
let mut completion_edits = Vec::new();
|
||||
buffer.update(&mut cx, |buffer, _cx| {
|
||||
let snapshot = buffer.snapshot();
|
||||
let clipped_position = buffer.clip_point_utf16(Unclipped(self.position), Bias::Left);
|
||||
|
||||
let mut range_for_token = None;
|
||||
completions
|
||||
.into_iter()
|
||||
.filter_map(move |mut lsp_completion| {
|
||||
let (old_range, mut new_text) = match lsp_completion.text_edit.as_ref() {
|
||||
// If the language server provides a range to overwrite, then
|
||||
// check that the range is valid.
|
||||
Some(lsp::CompletionTextEdit::Edit(edit)) => {
|
||||
let range = range_from_lsp(edit.range);
|
||||
completions.retain_mut(|lsp_completion| {
|
||||
let edit = match lsp_completion.text_edit.as_ref() {
|
||||
// If the language server provides a range to overwrite, then
|
||||
// check that the range is valid.
|
||||
Some(lsp::CompletionTextEdit::Edit(edit)) => {
|
||||
let range = range_from_lsp(edit.range);
|
||||
let start = snapshot.clip_point_utf16(range.start, Bias::Left);
|
||||
let end = snapshot.clip_point_utf16(range.end, Bias::Left);
|
||||
if start != range.start.0 || end != range.end.0 {
|
||||
log::info!("completion out of expected range");
|
||||
return false;
|
||||
}
|
||||
(
|
||||
snapshot.anchor_before(start)..snapshot.anchor_after(end),
|
||||
edit.new_text.clone(),
|
||||
)
|
||||
}
|
||||
|
||||
// If the language server does not provide a range, then infer
|
||||
// the range based on the syntax tree.
|
||||
None => {
|
||||
if self.position != clipped_position {
|
||||
log::info!("completion out of expected range");
|
||||
return false;
|
||||
}
|
||||
|
||||
let default_edit_range = response_list
|
||||
.as_ref()
|
||||
.and_then(|list| list.item_defaults.as_ref())
|
||||
.and_then(|defaults| defaults.edit_range.as_ref())
|
||||
.and_then(|range| match range {
|
||||
CompletionListItemDefaultsEditRange::Range(r) => Some(r),
|
||||
_ => None,
|
||||
});
|
||||
|
||||
let range = if let Some(range) = default_edit_range {
|
||||
let range = range_from_lsp(*range);
|
||||
let start = snapshot.clip_point_utf16(range.start, Bias::Left);
|
||||
let end = snapshot.clip_point_utf16(range.end, Bias::Left);
|
||||
if start != range.start.0 || end != range.end.0 {
|
||||
log::info!("completion out of expected range");
|
||||
return None;
|
||||
}
|
||||
(
|
||||
snapshot.anchor_before(start)..snapshot.anchor_after(end),
|
||||
edit.new_text.clone(),
|
||||
)
|
||||
}
|
||||
|
||||
// If the language server does not provide a range, then infer
|
||||
// the range based on the syntax tree.
|
||||
None => {
|
||||
if self.position != clipped_position {
|
||||
log::info!("completion out of expected range");
|
||||
return None;
|
||||
return false;
|
||||
}
|
||||
|
||||
let default_edit_range = response_list
|
||||
.as_ref()
|
||||
.and_then(|list| list.item_defaults.as_ref())
|
||||
.and_then(|defaults| defaults.edit_range.as_ref())
|
||||
.and_then(|range| match range {
|
||||
CompletionListItemDefaultsEditRange::Range(r) => Some(r),
|
||||
_ => None,
|
||||
});
|
||||
|
||||
let range = if let Some(range) = default_edit_range {
|
||||
let range = range_from_lsp(*range);
|
||||
let start = snapshot.clip_point_utf16(range.start, Bias::Left);
|
||||
let end = snapshot.clip_point_utf16(range.end, Bias::Left);
|
||||
if start != range.start.0 || end != range.end.0 {
|
||||
log::info!("completion out of expected range");
|
||||
return None;
|
||||
}
|
||||
|
||||
snapshot.anchor_before(start)..snapshot.anchor_after(end)
|
||||
} else {
|
||||
range_for_token
|
||||
.get_or_insert_with(|| {
|
||||
let offset = self.position.to_offset(&snapshot);
|
||||
let (range, kind) = snapshot.surrounding_word(offset);
|
||||
let range = if kind == Some(CharKind::Word) {
|
||||
range
|
||||
} else {
|
||||
offset..offset
|
||||
};
|
||||
|
||||
snapshot.anchor_before(range.start)
|
||||
..snapshot.anchor_after(range.end)
|
||||
})
|
||||
.clone()
|
||||
};
|
||||
|
||||
let text = lsp_completion
|
||||
.insert_text
|
||||
.as_ref()
|
||||
.unwrap_or(&lsp_completion.label)
|
||||
.clone();
|
||||
(range, text)
|
||||
}
|
||||
|
||||
Some(lsp::CompletionTextEdit::InsertAndReplace(edit)) => {
|
||||
let range = range_from_lsp(edit.insert);
|
||||
|
||||
let start = snapshot.clip_point_utf16(range.start, Bias::Left);
|
||||
let end = snapshot.clip_point_utf16(range.end, Bias::Left);
|
||||
if start != range.start.0 || end != range.end.0 {
|
||||
log::info!("completion out of expected range");
|
||||
return None;
|
||||
}
|
||||
(
|
||||
snapshot.anchor_before(start)..snapshot.anchor_after(end),
|
||||
edit.new_text.clone(),
|
||||
)
|
||||
}
|
||||
};
|
||||
|
||||
let language_registry = language_registry.clone();
|
||||
let language = language.clone();
|
||||
let language_server_adapter = language_server_adapter.clone();
|
||||
LineEnding::normalize(&mut new_text);
|
||||
Some(async move {
|
||||
let mut label = None;
|
||||
if let Some(language) = &language {
|
||||
language_server_adapter
|
||||
.process_completion(&mut lsp_completion)
|
||||
.await;
|
||||
label = language_server_adapter
|
||||
.label_for_completion(&lsp_completion, language)
|
||||
.await;
|
||||
}
|
||||
|
||||
let documentation = if let Some(lsp_docs) = &lsp_completion.documentation {
|
||||
Some(
|
||||
prepare_completion_documentation(
|
||||
lsp_docs,
|
||||
&language_registry,
|
||||
language.clone(),
|
||||
)
|
||||
.await,
|
||||
)
|
||||
snapshot.anchor_before(start)..snapshot.anchor_after(end)
|
||||
} else {
|
||||
None
|
||||
range_for_token
|
||||
.get_or_insert_with(|| {
|
||||
let offset = self.position.to_offset(&snapshot);
|
||||
let (range, kind) = snapshot.surrounding_word(offset);
|
||||
let range = if kind == Some(CharKind::Word) {
|
||||
range
|
||||
} else {
|
||||
offset..offset
|
||||
};
|
||||
|
||||
snapshot.anchor_before(range.start)
|
||||
..snapshot.anchor_after(range.end)
|
||||
})
|
||||
.clone()
|
||||
};
|
||||
|
||||
Completion {
|
||||
old_range,
|
||||
new_text,
|
||||
label: label.unwrap_or_else(|| {
|
||||
language::CodeLabel::plain(
|
||||
lsp_completion.label.clone(),
|
||||
lsp_completion.filter_text.as_deref(),
|
||||
)
|
||||
}),
|
||||
documentation,
|
||||
server_id,
|
||||
lsp_completion,
|
||||
let text = lsp_completion
|
||||
.insert_text
|
||||
.as_ref()
|
||||
.unwrap_or(&lsp_completion.label)
|
||||
.clone();
|
||||
(range, text)
|
||||
}
|
||||
|
||||
Some(lsp::CompletionTextEdit::InsertAndReplace(edit)) => {
|
||||
let range = range_from_lsp(edit.insert);
|
||||
|
||||
let start = snapshot.clip_point_utf16(range.start, Bias::Left);
|
||||
let end = snapshot.clip_point_utf16(range.end, Bias::Left);
|
||||
if start != range.start.0 || end != range.end.0 {
|
||||
log::info!("completion out of expected range");
|
||||
return false;
|
||||
}
|
||||
})
|
||||
})
|
||||
(
|
||||
snapshot.anchor_before(start)..snapshot.anchor_after(end),
|
||||
edit.new_text.clone(),
|
||||
)
|
||||
}
|
||||
};
|
||||
|
||||
completion_edits.push(edit);
|
||||
true
|
||||
});
|
||||
})?;
|
||||
|
||||
Ok(future::join_all(completions).await)
|
||||
language_server_adapter
|
||||
.process_completions(&mut completions)
|
||||
.await;
|
||||
|
||||
Ok(completions
|
||||
.into_iter()
|
||||
.zip(completion_edits)
|
||||
.map(|(lsp_completion, (old_range, mut new_text))| {
|
||||
LineEnding::normalize(&mut new_text);
|
||||
CoreCompletion {
|
||||
old_range,
|
||||
new_text,
|
||||
server_id,
|
||||
lsp_completion,
|
||||
}
|
||||
})
|
||||
.collect())
|
||||
}
|
||||
|
||||
fn to_proto(&self, project_id: u64, buffer: &Buffer) -> proto::GetCompletions {
|
||||
@ -1656,7 +1628,7 @@ impl LspCommand for GetCompletions {
|
||||
}
|
||||
|
||||
fn response_to_proto(
|
||||
completions: Vec<Completion>,
|
||||
completions: Vec<CoreCompletion>,
|
||||
_: &mut Project,
|
||||
_: PeerId,
|
||||
buffer_version: &clock::Global,
|
||||
@ -1665,7 +1637,7 @@ impl LspCommand for GetCompletions {
|
||||
proto::GetCompletionsResponse {
|
||||
completions: completions
|
||||
.iter()
|
||||
.map(language::proto::serialize_completion)
|
||||
.map(Project::serialize_completion)
|
||||
.collect(),
|
||||
version: serialize_version(buffer_version),
|
||||
}
|
||||
@ -1674,26 +1646,21 @@ impl LspCommand for GetCompletions {
|
||||
async fn response_from_proto(
|
||||
self,
|
||||
message: proto::GetCompletionsResponse,
|
||||
project: Model<Project>,
|
||||
_project: Model<Project>,
|
||||
buffer: Model<Buffer>,
|
||||
mut cx: AsyncAppContext,
|
||||
) -> Result<Vec<Completion>> {
|
||||
) -> Result<Self::Response> {
|
||||
buffer
|
||||
.update(&mut cx, |buffer, _| {
|
||||
buffer.wait_for_version(deserialize_version(&message.version))
|
||||
})?
|
||||
.await?;
|
||||
|
||||
let language = buffer.update(&mut cx, |buffer, _| buffer.language().cloned())?;
|
||||
let language_registry = project.update(&mut cx, |project, _| project.languages.clone())?;
|
||||
let completions = message.completions.into_iter().map(|completion| {
|
||||
language::proto::deserialize_completion(
|
||||
completion,
|
||||
language.clone(),
|
||||
&language_registry,
|
||||
)
|
||||
});
|
||||
future::try_join_all(completions).await
|
||||
message
|
||||
.completions
|
||||
.into_iter()
|
||||
.map(Project::deserialize_completion)
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn buffer_id_from_proto(message: &proto::GetCompletions) -> Result<BufferId> {
|
||||
@ -1816,7 +1783,7 @@ impl LspCommand for GetCodeActions {
|
||||
proto::GetCodeActionsResponse {
|
||||
actions: code_actions
|
||||
.iter()
|
||||
.map(language::proto::serialize_code_action)
|
||||
.map(Project::serialize_code_action)
|
||||
.collect(),
|
||||
version: serialize_version(buffer_version),
|
||||
}
|
||||
@ -1837,7 +1804,7 @@ impl LspCommand for GetCodeActions {
|
||||
message
|
||||
.actions
|
||||
.into_iter()
|
||||
.map(language::proto::deserialize_code_action)
|
||||
.map(Project::deserialize_code_action)
|
||||
.collect()
|
||||
}
|
||||
|
||||
|
@ -40,16 +40,16 @@ use gpui::{
|
||||
use itertools::Itertools;
|
||||
use language::{
|
||||
language_settings::{language_settings, FormatOnSave, Formatter, InlayHintKind},
|
||||
markdown, point_to_lsp,
|
||||
markdown, point_to_lsp, prepare_completion_documentation,
|
||||
proto::{
|
||||
deserialize_anchor, deserialize_line_ending, deserialize_version, serialize_anchor,
|
||||
serialize_version, split_operations,
|
||||
},
|
||||
range_from_lsp, Bias, Buffer, BufferSnapshot, CachedLspAdapter, Capability, CodeAction,
|
||||
CodeLabel, Completion, Diagnostic, DiagnosticEntry, DiagnosticSet, Diff, Documentation,
|
||||
Event as BufferEvent, File as _, Language, LanguageRegistry, LanguageServerName, LocalFile,
|
||||
LspAdapterDelegate, Operation, Patch, PendingLanguageServer, PointUtf16, TextBufferSnapshot,
|
||||
ToOffset, ToPointUtf16, Transaction, Unclipped,
|
||||
range_from_lsp, Bias, Buffer, BufferSnapshot, CachedLspAdapter, Capability, CodeLabel,
|
||||
Diagnostic, DiagnosticEntry, DiagnosticSet, Diff, Documentation, Event as BufferEvent,
|
||||
File as _, Language, LanguageRegistry, LanguageServerName, LocalFile, LspAdapterDelegate,
|
||||
Operation, Patch, PendingLanguageServer, PointUtf16, TextBufferSnapshot, ToOffset,
|
||||
ToPointUtf16, Transaction, Unclipped,
|
||||
};
|
||||
use log::error;
|
||||
use lsp::{
|
||||
@ -81,7 +81,7 @@ use std::{
|
||||
env,
|
||||
ffi::OsStr,
|
||||
hash::Hash,
|
||||
io, mem,
|
||||
io, iter, mem,
|
||||
num::NonZeroU32,
|
||||
ops::Range,
|
||||
path::{self, Component, Path, PathBuf},
|
||||
@ -379,6 +379,43 @@ pub struct InlayHint {
|
||||
pub resolve_state: ResolveState,
|
||||
}
|
||||
|
||||
/// A completion provided by a language server
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Completion {
|
||||
/// The range of the buffer that will be replaced.
|
||||
pub old_range: Range<Anchor>,
|
||||
/// The new text that will be inserted.
|
||||
pub new_text: String,
|
||||
/// A label for this completion that is shown in the menu.
|
||||
pub label: CodeLabel,
|
||||
/// The id of the language server that produced this completion.
|
||||
pub server_id: LanguageServerId,
|
||||
/// The documentation for this completion.
|
||||
pub documentation: Option<Documentation>,
|
||||
/// The raw completion provided by the language server.
|
||||
pub lsp_completion: lsp::CompletionItem,
|
||||
}
|
||||
|
||||
/// A completion provided by a language server
|
||||
#[derive(Clone, Debug)]
|
||||
struct CoreCompletion {
|
||||
old_range: Range<Anchor>,
|
||||
new_text: String,
|
||||
server_id: LanguageServerId,
|
||||
lsp_completion: lsp::CompletionItem,
|
||||
}
|
||||
|
||||
/// A code action provided by a language server.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct CodeAction {
|
||||
/// The id of the language server that produced this code action.
|
||||
pub server_id: LanguageServerId,
|
||||
/// The range of the buffer where this code action is applicable.
|
||||
pub range: Range<Anchor>,
|
||||
/// The raw code action provided by the language server.
|
||||
pub lsp_action: lsp::CodeAction,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum ResolveState {
|
||||
Resolved,
|
||||
@ -450,6 +487,17 @@ pub struct Symbol {
|
||||
pub signature: [u8; 32],
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
struct CoreSymbol {
|
||||
pub language_server_name: LanguageServerName,
|
||||
pub source_worktree_id: WorktreeId,
|
||||
pub path: ProjectPath,
|
||||
pub name: String,
|
||||
pub kind: lsp::SymbolKind,
|
||||
pub range: Range<Unclipped<PointUtf16>>,
|
||||
pub signature: [u8; 32],
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct HoverBlock {
|
||||
pub text: String,
|
||||
@ -4931,6 +4979,8 @@ impl Project {
|
||||
}
|
||||
|
||||
pub fn symbols(&self, query: &str, cx: &mut ModelContext<Self>) -> Task<Result<Vec<Symbol>>> {
|
||||
let language_registry = self.languages.clone();
|
||||
|
||||
if self.is_local() {
|
||||
let mut requests = Vec::new();
|
||||
for ((worktree_id, _), server_id) in self.language_server_ids.iter() {
|
||||
@ -5005,18 +5055,14 @@ impl Project {
|
||||
None => return Ok(Vec::new()),
|
||||
};
|
||||
|
||||
let symbols = this.update(&mut cx, |this, cx| {
|
||||
let mut symbols = Vec::new();
|
||||
for (
|
||||
adapter,
|
||||
adapter_language,
|
||||
source_worktree,
|
||||
worktree_abs_path,
|
||||
lsp_symbols,
|
||||
) in responses
|
||||
{
|
||||
symbols.extend(lsp_symbols.into_iter().filter_map(
|
||||
|(symbol_name, symbol_kind, symbol_location)| {
|
||||
let mut symbols = Vec::new();
|
||||
for (adapter, adapter_language, source_worktree, worktree_abs_path, lsp_symbols) in
|
||||
responses
|
||||
{
|
||||
let core_symbols = this.update(&mut cx, |this, cx| {
|
||||
lsp_symbols
|
||||
.into_iter()
|
||||
.filter_map(|(symbol_name, symbol_kind, symbol_location)| {
|
||||
let abs_path = symbol_location.uri.to_file_path().ok()?;
|
||||
let source_worktree = source_worktree.upgrade()?;
|
||||
let source_worktree_id = source_worktree.read(cx).id();
|
||||
@ -5039,62 +5085,52 @@ impl Project {
|
||||
path: path.into(),
|
||||
};
|
||||
let signature = this.symbol_signature(&project_path);
|
||||
let adapter_language = adapter_language.clone();
|
||||
let language = this
|
||||
.languages
|
||||
.language_for_file_path(&project_path.path)
|
||||
.unwrap_or_else(move |_| adapter_language);
|
||||
let adapter = adapter.clone();
|
||||
Some(async move {
|
||||
let language = language.await;
|
||||
let label = adapter
|
||||
.label_for_symbol(&symbol_name, symbol_kind, &language)
|
||||
.await;
|
||||
|
||||
Symbol {
|
||||
language_server_name: adapter.name.clone(),
|
||||
source_worktree_id,
|
||||
path: project_path,
|
||||
label: label.unwrap_or_else(|| {
|
||||
CodeLabel::plain(symbol_name.clone(), None)
|
||||
}),
|
||||
kind: symbol_kind,
|
||||
name: symbol_name,
|
||||
range: range_from_lsp(symbol_location.range),
|
||||
signature,
|
||||
}
|
||||
Some(CoreSymbol {
|
||||
language_server_name: adapter.name.clone(),
|
||||
source_worktree_id,
|
||||
path: project_path,
|
||||
kind: symbol_kind,
|
||||
name: symbol_name,
|
||||
range: range_from_lsp(symbol_location.range),
|
||||
signature,
|
||||
})
|
||||
},
|
||||
));
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
})?;
|
||||
|
||||
symbols
|
||||
})?;
|
||||
populate_labels_for_symbols(
|
||||
core_symbols,
|
||||
&language_registry,
|
||||
Some(adapter_language),
|
||||
Some(adapter),
|
||||
&mut symbols,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
Ok(futures::future::join_all(symbols).await)
|
||||
Ok(symbols)
|
||||
})
|
||||
} else if let Some(project_id) = self.remote_id() {
|
||||
let request = self.client.request(proto::GetProjectSymbols {
|
||||
project_id,
|
||||
query: query.to_string(),
|
||||
});
|
||||
cx.spawn(move |this, mut cx| async move {
|
||||
cx.foreground_executor().spawn(async move {
|
||||
let response = request.await?;
|
||||
let mut symbols = Vec::new();
|
||||
if let Some(this) = this.upgrade() {
|
||||
let new_symbols = this.update(&mut cx, |this, _| {
|
||||
response
|
||||
.symbols
|
||||
.into_iter()
|
||||
.map(|symbol| this.deserialize_symbol(symbol))
|
||||
.collect::<Vec<_>>()
|
||||
})?;
|
||||
symbols = futures::future::join_all(new_symbols)
|
||||
.await
|
||||
.into_iter()
|
||||
.filter_map(|symbol| symbol.log_err())
|
||||
.collect::<Vec<_>>();
|
||||
}
|
||||
let core_symbols = response
|
||||
.symbols
|
||||
.into_iter()
|
||||
.filter_map(|symbol| Self::deserialize_symbol(symbol).log_err())
|
||||
.collect::<Vec<_>>();
|
||||
populate_labels_for_symbols(
|
||||
core_symbols,
|
||||
&language_registry,
|
||||
None,
|
||||
None,
|
||||
&mut symbols,
|
||||
)
|
||||
.await;
|
||||
Ok(symbols)
|
||||
})
|
||||
} else {
|
||||
@ -5262,10 +5298,13 @@ impl Project {
|
||||
position: PointUtf16,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) -> Task<Result<Vec<Completion>>> {
|
||||
let language_registry = self.languages.clone();
|
||||
|
||||
if self.is_local() {
|
||||
let snapshot = buffer.read(cx).snapshot();
|
||||
let offset = position.to_offset(&snapshot);
|
||||
let scope = snapshot.language_scope_at(offset);
|
||||
let language = snapshot.language().cloned();
|
||||
|
||||
let server_ids: Vec<_> = self
|
||||
.language_servers_for_buffer(buffer.read(cx), cx)
|
||||
@ -5284,30 +5323,69 @@ impl Project {
|
||||
let mut tasks = Vec::with_capacity(server_ids.len());
|
||||
this.update(&mut cx, |this, cx| {
|
||||
for server_id in server_ids {
|
||||
tasks.push(this.request_lsp(
|
||||
buffer.clone(),
|
||||
LanguageServerToQuery::Other(server_id),
|
||||
GetCompletions { position },
|
||||
cx,
|
||||
let lsp_adapter = this.language_server_adapter_for_id(server_id);
|
||||
tasks.push((
|
||||
lsp_adapter,
|
||||
this.request_lsp(
|
||||
buffer.clone(),
|
||||
LanguageServerToQuery::Other(server_id),
|
||||
GetCompletions { position },
|
||||
cx,
|
||||
),
|
||||
));
|
||||
}
|
||||
})?;
|
||||
|
||||
let mut completions = Vec::new();
|
||||
for task in tasks {
|
||||
for (lsp_adapter, task) in tasks {
|
||||
if let Ok(new_completions) = task.await {
|
||||
completions.extend_from_slice(&new_completions);
|
||||
populate_labels_for_completions(
|
||||
new_completions,
|
||||
&language_registry,
|
||||
language.clone(),
|
||||
lsp_adapter,
|
||||
&mut completions,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(completions)
|
||||
})
|
||||
} else if let Some(project_id) = self.remote_id() {
|
||||
self.send_lsp_proto_request(buffer.clone(), project_id, GetCompletions { position }, cx)
|
||||
let task = self.send_lsp_proto_request(
|
||||
buffer.clone(),
|
||||
project_id,
|
||||
GetCompletions { position },
|
||||
cx,
|
||||
);
|
||||
let language = buffer.read(cx).language().cloned();
|
||||
|
||||
// In the future, we should provide project guests with the names of LSP adapters,
|
||||
// so that they can use the correct LSP adapter when computing labels. For now,
|
||||
// guests just use the first LSP adapter associated with the buffer's language.
|
||||
let lsp_adapter = language
|
||||
.as_ref()
|
||||
.and_then(|language| language_registry.lsp_adapters(language).first().cloned());
|
||||
|
||||
cx.foreground_executor().spawn(async move {
|
||||
let completions = task.await?;
|
||||
let mut result = Vec::new();
|
||||
populate_labels_for_completions(
|
||||
completions,
|
||||
&language_registry,
|
||||
language,
|
||||
lsp_adapter,
|
||||
&mut result,
|
||||
)
|
||||
.await;
|
||||
Ok(result)
|
||||
})
|
||||
} else {
|
||||
Task::ready(Ok(Default::default()))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn completions<T: ToOffset + ToPointUtf16>(
|
||||
&self,
|
||||
buffer: &Model<Buffer>,
|
||||
@ -5573,7 +5651,12 @@ impl Project {
|
||||
.request(proto::ApplyCompletionAdditionalEdits {
|
||||
project_id,
|
||||
buffer_id: buffer_id.into(),
|
||||
completion: Some(language::proto::serialize_completion(&completion)),
|
||||
completion: Some(Self::serialize_completion(&CoreCompletion {
|
||||
old_range: completion.old_range,
|
||||
new_text: completion.new_text,
|
||||
server_id: completion.server_id,
|
||||
lsp_completion: completion.lsp_completion,
|
||||
})),
|
||||
})
|
||||
.await?;
|
||||
|
||||
@ -5757,7 +5840,7 @@ impl Project {
|
||||
let request = proto::ApplyCodeAction {
|
||||
project_id,
|
||||
buffer_id: buffer_handle.read(cx).remote_id().into(),
|
||||
action: Some(language::proto::serialize_code_action(&action)),
|
||||
action: Some(Self::serialize_code_action(&action)),
|
||||
};
|
||||
cx.spawn(move |this, mut cx| async move {
|
||||
let response = client
|
||||
@ -8548,30 +8631,40 @@ impl Project {
|
||||
_: Arc<Client>,
|
||||
mut cx: AsyncAppContext,
|
||||
) -> Result<proto::ApplyCompletionAdditionalEditsResponse> {
|
||||
let languages = this.update(&mut cx, |this, _| this.languages.clone())?;
|
||||
let (buffer, completion) = this.update(&mut cx, |this, cx| {
|
||||
let (buffer, completion) = this.update(&mut cx, |this, _| {
|
||||
let buffer_id = BufferId::new(envelope.payload.buffer_id)?;
|
||||
let buffer = this
|
||||
.opened_buffers
|
||||
.get(&buffer_id)
|
||||
.and_then(|buffer| buffer.upgrade())
|
||||
.ok_or_else(|| anyhow!("unknown buffer id {}", buffer_id))?;
|
||||
let language = buffer.read(cx).language();
|
||||
let completion = language::proto::deserialize_completion(
|
||||
let completion = Self::deserialize_completion(
|
||||
envelope
|
||||
.payload
|
||||
.completion
|
||||
.ok_or_else(|| anyhow!("invalid completion"))?,
|
||||
language.cloned(),
|
||||
&languages,
|
||||
);
|
||||
Ok::<_, anyhow::Error>((buffer, completion))
|
||||
)?;
|
||||
anyhow::Ok((buffer, completion))
|
||||
})??;
|
||||
|
||||
let completion = completion.await?;
|
||||
|
||||
let apply_additional_edits = this.update(&mut cx, |this, cx| {
|
||||
this.apply_additional_edits_for_completion(buffer, completion, false, cx)
|
||||
this.apply_additional_edits_for_completion(
|
||||
buffer,
|
||||
Completion {
|
||||
old_range: completion.old_range,
|
||||
new_text: completion.new_text,
|
||||
lsp_completion: completion.lsp_completion,
|
||||
server_id: completion.server_id,
|
||||
documentation: None,
|
||||
label: CodeLabel {
|
||||
text: Default::default(),
|
||||
runs: Default::default(),
|
||||
filter_range: Default::default(),
|
||||
},
|
||||
},
|
||||
false,
|
||||
cx,
|
||||
)
|
||||
})?;
|
||||
|
||||
Ok(proto::ApplyCompletionAdditionalEditsResponse {
|
||||
@ -8623,7 +8716,7 @@ impl Project {
|
||||
mut cx: AsyncAppContext,
|
||||
) -> Result<proto::ApplyCodeActionResponse> {
|
||||
let sender_id = envelope.original_sender_id()?;
|
||||
let action = language::proto::deserialize_code_action(
|
||||
let action = Self::deserialize_code_action(
|
||||
envelope
|
||||
.payload
|
||||
.action
|
||||
@ -8984,9 +9077,7 @@ impl Project {
|
||||
.payload
|
||||
.symbol
|
||||
.ok_or_else(|| anyhow!("invalid symbol"))?;
|
||||
let symbol = this
|
||||
.update(&mut cx, |this, _cx| this.deserialize_symbol(symbol))?
|
||||
.await?;
|
||||
let symbol = Self::deserialize_symbol(symbol)?;
|
||||
let symbol = this.update(&mut cx, |this, _| {
|
||||
let signature = this.symbol_signature(&symbol.path);
|
||||
if signature == symbol.signature {
|
||||
@ -8996,7 +9087,25 @@ impl Project {
|
||||
}
|
||||
})??;
|
||||
let buffer = this
|
||||
.update(&mut cx, |this, cx| this.open_buffer_for_symbol(&symbol, cx))?
|
||||
.update(&mut cx, |this, cx| {
|
||||
this.open_buffer_for_symbol(
|
||||
&Symbol {
|
||||
language_server_name: symbol.language_server_name,
|
||||
source_worktree_id: symbol.source_worktree_id,
|
||||
path: symbol.path,
|
||||
name: symbol.name,
|
||||
kind: symbol.kind,
|
||||
range: symbol.range,
|
||||
signature: symbol.signature,
|
||||
label: CodeLabel {
|
||||
text: Default::default(),
|
||||
runs: Default::default(),
|
||||
filter_range: Default::default(),
|
||||
},
|
||||
},
|
||||
cx,
|
||||
)
|
||||
})?
|
||||
.await?;
|
||||
|
||||
this.update(&mut cx, |this, cx| {
|
||||
@ -9350,11 +9459,7 @@ impl Project {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn deserialize_symbol(
|
||||
&self,
|
||||
serialized_symbol: proto::Symbol,
|
||||
) -> impl Future<Output = Result<Symbol>> {
|
||||
let languages = self.languages.clone();
|
||||
fn deserialize_symbol(serialized_symbol: proto::Symbol) -> Result<CoreSymbol> {
|
||||
let source_worktree_id = WorktreeId::from_proto(serialized_symbol.source_worktree_id);
|
||||
let worktree_id = WorktreeId::from_proto(serialized_symbol.worktree_id);
|
||||
let kind = unsafe { mem::transmute(serialized_symbol.kind) };
|
||||
@ -9362,49 +9467,83 @@ impl Project {
|
||||
worktree_id,
|
||||
path: PathBuf::from(serialized_symbol.path).into(),
|
||||
};
|
||||
let language = languages.language_for_file_path(&path.path);
|
||||
|
||||
async move {
|
||||
let language = language.await.log_err();
|
||||
let adapter = language
|
||||
.as_ref()
|
||||
.and_then(|language| languages.lsp_adapters(language).first().cloned());
|
||||
let start = serialized_symbol
|
||||
.start
|
||||
.ok_or_else(|| anyhow!("invalid start"))?;
|
||||
let end = serialized_symbol
|
||||
.end
|
||||
.ok_or_else(|| anyhow!("invalid end"))?;
|
||||
Ok(Symbol {
|
||||
language_server_name: LanguageServerName(
|
||||
serialized_symbol.language_server_name.into(),
|
||||
),
|
||||
source_worktree_id,
|
||||
path,
|
||||
label: {
|
||||
match language.as_ref().zip(adapter.as_ref()) {
|
||||
Some((language, adapter)) => {
|
||||
adapter
|
||||
.label_for_symbol(&serialized_symbol.name, kind, language)
|
||||
.await
|
||||
}
|
||||
None => None,
|
||||
}
|
||||
.unwrap_or_else(|| CodeLabel::plain(serialized_symbol.name.clone(), None))
|
||||
},
|
||||
let start = serialized_symbol
|
||||
.start
|
||||
.ok_or_else(|| anyhow!("invalid start"))?;
|
||||
let end = serialized_symbol
|
||||
.end
|
||||
.ok_or_else(|| anyhow!("invalid end"))?;
|
||||
Ok(CoreSymbol {
|
||||
language_server_name: LanguageServerName(serialized_symbol.language_server_name.into()),
|
||||
source_worktree_id,
|
||||
path,
|
||||
name: serialized_symbol.name,
|
||||
range: Unclipped(PointUtf16::new(start.row, start.column))
|
||||
..Unclipped(PointUtf16::new(end.row, end.column)),
|
||||
kind,
|
||||
signature: serialized_symbol
|
||||
.signature
|
||||
.try_into()
|
||||
.map_err(|_| anyhow!("invalid signature"))?,
|
||||
})
|
||||
}
|
||||
|
||||
name: serialized_symbol.name,
|
||||
range: Unclipped(PointUtf16::new(start.row, start.column))
|
||||
..Unclipped(PointUtf16::new(end.row, end.column)),
|
||||
kind,
|
||||
signature: serialized_symbol
|
||||
.signature
|
||||
.try_into()
|
||||
.map_err(|_| anyhow!("invalid signature"))?,
|
||||
})
|
||||
fn serialize_completion(completion: &CoreCompletion) -> proto::Completion {
|
||||
proto::Completion {
|
||||
old_start: Some(serialize_anchor(&completion.old_range.start)),
|
||||
old_end: Some(serialize_anchor(&completion.old_range.end)),
|
||||
new_text: completion.new_text.clone(),
|
||||
server_id: completion.server_id.0 as u64,
|
||||
lsp_completion: serde_json::to_vec(&completion.lsp_completion).unwrap(),
|
||||
}
|
||||
}
|
||||
|
||||
fn deserialize_completion(completion: proto::Completion) -> Result<CoreCompletion> {
|
||||
let old_start = completion
|
||||
.old_start
|
||||
.and_then(deserialize_anchor)
|
||||
.ok_or_else(|| anyhow!("invalid old start"))?;
|
||||
let old_end = completion
|
||||
.old_end
|
||||
.and_then(deserialize_anchor)
|
||||
.ok_or_else(|| anyhow!("invalid old end"))?;
|
||||
let lsp_completion = serde_json::from_slice(&completion.lsp_completion)?;
|
||||
|
||||
Ok(CoreCompletion {
|
||||
old_range: old_start..old_end,
|
||||
new_text: completion.new_text,
|
||||
server_id: LanguageServerId(completion.server_id as usize),
|
||||
lsp_completion,
|
||||
})
|
||||
}
|
||||
|
||||
fn serialize_code_action(action: &CodeAction) -> proto::CodeAction {
|
||||
proto::CodeAction {
|
||||
server_id: action.server_id.0 as u64,
|
||||
start: Some(serialize_anchor(&action.range.start)),
|
||||
end: Some(serialize_anchor(&action.range.end)),
|
||||
lsp_action: serde_json::to_vec(&action.lsp_action).unwrap(),
|
||||
}
|
||||
}
|
||||
|
||||
fn deserialize_code_action(action: proto::CodeAction) -> Result<CodeAction> {
|
||||
let start = action
|
||||
.start
|
||||
.and_then(deserialize_anchor)
|
||||
.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)?;
|
||||
Ok(CodeAction {
|
||||
server_id: LanguageServerId(action.server_id as usize),
|
||||
range: start..end,
|
||||
lsp_action,
|
||||
})
|
||||
}
|
||||
|
||||
async fn handle_buffer_saved(
|
||||
this: Model<Self>,
|
||||
envelope: TypedEnvelope<proto::BufferSaved>,
|
||||
@ -9697,6 +9836,114 @@ impl Project {
|
||||
}
|
||||
}
|
||||
|
||||
async fn populate_labels_for_symbols(
|
||||
symbols: Vec<CoreSymbol>,
|
||||
language_registry: &Arc<LanguageRegistry>,
|
||||
default_language: Option<Arc<Language>>,
|
||||
lsp_adapter: Option<Arc<CachedLspAdapter>>,
|
||||
output: &mut Vec<Symbol>,
|
||||
) {
|
||||
let mut symbols_by_language = HashMap::<Option<Arc<Language>>, Vec<CoreSymbol>>::default();
|
||||
|
||||
for symbol in symbols {
|
||||
let language = language_registry
|
||||
.language_for_file_path(&symbol.path.path)
|
||||
.await
|
||||
.log_err()
|
||||
.or_else(|| default_language.clone());
|
||||
symbols_by_language
|
||||
.entry(language)
|
||||
.or_default()
|
||||
.push(symbol);
|
||||
}
|
||||
|
||||
let mut label_params = Vec::new();
|
||||
for (language, mut symbols) in symbols_by_language {
|
||||
label_params.clear();
|
||||
label_params.extend(
|
||||
symbols
|
||||
.iter_mut()
|
||||
.map(|symbol| (mem::take(&mut symbol.name), symbol.kind)),
|
||||
);
|
||||
|
||||
let mut labels = Vec::new();
|
||||
if let Some(language) = language {
|
||||
let lsp_adapter = lsp_adapter
|
||||
.clone()
|
||||
.or_else(|| language_registry.lsp_adapters(&language).first().cloned());
|
||||
if let Some(lsp_adapter) = lsp_adapter {
|
||||
labels = lsp_adapter
|
||||
.labels_for_symbols(&label_params, &language)
|
||||
.await;
|
||||
}
|
||||
}
|
||||
|
||||
for ((symbol, (name, _)), label) in symbols
|
||||
.into_iter()
|
||||
.zip(label_params.drain(..))
|
||||
.zip(labels.into_iter().chain(iter::repeat(None)))
|
||||
{
|
||||
output.push(Symbol {
|
||||
language_server_name: symbol.language_server_name,
|
||||
source_worktree_id: symbol.source_worktree_id,
|
||||
path: symbol.path,
|
||||
label: label.unwrap_or_else(|| CodeLabel::plain(name.clone(), None)),
|
||||
name,
|
||||
kind: symbol.kind,
|
||||
range: symbol.range,
|
||||
signature: symbol.signature,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn populate_labels_for_completions(
|
||||
mut new_completions: Vec<CoreCompletion>,
|
||||
language_registry: &Arc<LanguageRegistry>,
|
||||
language: Option<Arc<Language>>,
|
||||
lsp_adapter: Option<Arc<CachedLspAdapter>>,
|
||||
completions: &mut Vec<Completion>,
|
||||
) {
|
||||
let lsp_completions = new_completions
|
||||
.iter_mut()
|
||||
.map(|completion| mem::take(&mut completion.lsp_completion))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let labels = if let Some((language, lsp_adapter)) = language.as_ref().zip(lsp_adapter) {
|
||||
lsp_adapter
|
||||
.labels_for_completions(&lsp_completions, language)
|
||||
.await
|
||||
} else {
|
||||
Vec::new()
|
||||
};
|
||||
|
||||
for ((completion, lsp_completion), label) in new_completions
|
||||
.into_iter()
|
||||
.zip(lsp_completions)
|
||||
.zip(labels.into_iter().chain(iter::repeat(None)))
|
||||
{
|
||||
let documentation = if let Some(docs) = &lsp_completion.documentation {
|
||||
Some(prepare_completion_documentation(docs, &language_registry, language.clone()).await)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
completions.push(Completion {
|
||||
old_range: completion.old_range,
|
||||
new_text: completion.new_text,
|
||||
label: label.unwrap_or_else(|| {
|
||||
CodeLabel::plain(
|
||||
lsp_completion.label.clone(),
|
||||
lsp_completion.filter_text.as_deref(),
|
||||
)
|
||||
}),
|
||||
server_id: completion.server_id,
|
||||
documentation,
|
||||
lsp_completion,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn deserialize_code_actions(code_actions: &HashMap<String, bool>) -> Vec<lsp::CodeActionKind> {
|
||||
code_actions
|
||||
.iter()
|
||||
@ -10190,6 +10437,24 @@ impl Item for Buffer {
|
||||
}
|
||||
}
|
||||
|
||||
impl Completion {
|
||||
/// A key that can be used to sort completions when displaying
|
||||
/// them to the user.
|
||||
pub fn sort_key(&self) -> (usize, &str) {
|
||||
let kind_key = match self.lsp_completion.kind {
|
||||
Some(lsp::CompletionItemKind::KEYWORD) => 0,
|
||||
Some(lsp::CompletionItemKind::VARIABLE) => 1,
|
||||
_ => 2,
|
||||
};
|
||||
(kind_key, &self.label.text[self.label.filter_range.clone()])
|
||||
}
|
||||
|
||||
/// Whether this completion is a snippet.
|
||||
pub fn is_snippet(&self) -> bool {
|
||||
self.lsp_completion.insert_text_format == Some(lsp::InsertTextFormat::SNIPPET)
|
||||
}
|
||||
}
|
||||
|
||||
async fn wait_for_loading_buffer(
|
||||
mut receiver: postage::watch::Receiver<Option<Result<Model<Buffer>, Arc<anyhow::Error>>>>,
|
||||
) -> Result<Model<Buffer>, Arc<anyhow::Error>> {
|
||||
|
Loading…
Reference in New Issue
Block a user