Refactor LSP adapter methods to compute labels in batches (#10097)

Max Brunsfeld , Marshall Bowers , and Marshall created

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>

Change summary

crates/collab_ui/src/chat_panel/message_editor.rs |  10 
crates/editor/src/editor.rs                       |  12 
crates/language/src/buffer.rs                     |  53 -
crates/language/src/language.rs                   |  59 +
crates/language/src/proto.rs                      |  84 --
crates/languages/src/python.rs                    |  20 
crates/multi_buffer/src/multi_buffer.rs           |   1 
crates/project/src/lsp_command.rs                 | 259 +++----
crates/project/src/project.rs                     | 531 ++++++++++++----
9 files changed, 585 insertions(+), 444 deletions(-)

Detailed changes

crates/collab_ui/src/chat_panel/message_editor.rs 🔗

@@ -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))

crates/editor/src/editor.rs 🔗

@@ -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};

crates/language/src/buffer.rs 🔗

@@ -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>,

crates/language/src/language.rs 🔗

@@ -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,

crates/language/src/proto.rs 🔗

@@ -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 {

crates/languages/src/python.rs 🔗

@@ -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(

crates/multi_buffer/src/multi_buffer.rs 🔗

@@ -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},

crates/project/src/lsp_command.rs 🔗

@@ -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()
     }
 

crates/project/src/project.rs 🔗

@@ -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>> {