Detailed changes
@@ -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);
- 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(),
- )
+ 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 None;
- }
-
- 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)
+ // 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;
}
- Some(lsp::CompletionTextEdit::InsertAndReplace(edit)) => {
- let range = range_from_lsp(edit.insert);
-
+ 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;
+ return false;
}
- (
- 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>> {