Use `Extension` trait in `ExtensionLspAdapter` (#20704)

Marshall Bowers created

This PR updates the `ExtensionLspAdapter` to go through the `Extension`
trait for interacting with extensions rather than going through the
`WasmHost` directly.

Release Notes:

- N/A

Change summary

crates/extension/src/extension.rs                          |  46 
crates/extension/src/types.rs                              |  49 
crates/extension/src/types/lsp.rs                          |  96 +
crates/extension/src/types/slash_command.rs                |   0 
crates/extension_host/src/extension_host.rs                |  10 
crates/extension_host/src/extension_lsp_adapter.rs         | 398 +++----
crates/extension_host/src/wasm_host.rs                     | 139 ++
crates/extension_host/src/wasm_host/wit.rs                 |  41 
crates/extension_host/src/wasm_host/wit/since_v0_2_0.rs    | 154 +++
crates/indexed_docs/src/extension_indexed_docs_provider.rs |   1 
10 files changed, 662 insertions(+), 272 deletions(-)

Detailed changes

crates/extension/src/extension.rs 🔗

@@ -1,17 +1,20 @@
 pub mod extension_builder;
 mod extension_manifest;
-mod slash_command;
+mod types;
 
 use std::path::{Path, PathBuf};
 use std::sync::Arc;
 
+use ::lsp::LanguageServerName;
 use anyhow::{anyhow, bail, Context as _, Result};
 use async_trait::async_trait;
+use fs::normalize_path;
 use gpui::Task;
+use language::LanguageName;
 use semantic_version::SemanticVersion;
 
 pub use crate::extension_manifest::*;
-pub use crate::slash_command::*;
+pub use crate::types::*;
 
 #[async_trait]
 pub trait WorktreeDelegate: Send + Sync + 'static {
@@ -34,6 +37,43 @@ pub trait Extension: Send + Sync + 'static {
     /// Returns the path to this extension's working directory.
     fn work_dir(&self) -> Arc<Path>;
 
+    /// Returns a path relative to this extension's working directory.
+    fn path_from_extension(&self, path: &Path) -> PathBuf {
+        normalize_path(&self.work_dir().join(path))
+    }
+
+    async fn language_server_command(
+        &self,
+        language_server_id: LanguageServerName,
+        language_name: LanguageName,
+        worktree: Arc<dyn WorktreeDelegate>,
+    ) -> Result<Command>;
+
+    async fn language_server_initialization_options(
+        &self,
+        language_server_id: LanguageServerName,
+        language_name: LanguageName,
+        worktree: Arc<dyn WorktreeDelegate>,
+    ) -> Result<Option<String>>;
+
+    async fn language_server_workspace_configuration(
+        &self,
+        language_server_id: LanguageServerName,
+        worktree: Arc<dyn WorktreeDelegate>,
+    ) -> Result<Option<String>>;
+
+    async fn labels_for_completions(
+        &self,
+        language_server_id: LanguageServerName,
+        completions: Vec<Completion>,
+    ) -> Result<Vec<Option<CodeLabel>>>;
+
+    async fn labels_for_symbols(
+        &self,
+        language_server_id: LanguageServerName,
+        symbols: Vec<Symbol>,
+    ) -> Result<Vec<Option<CodeLabel>>>;
+
     async fn complete_slash_command_argument(
         &self,
         command: SlashCommand,
@@ -44,7 +84,7 @@ pub trait Extension: Send + Sync + 'static {
         &self,
         command: SlashCommand,
         arguments: Vec<String>,
-        resource: Option<Arc<dyn WorktreeDelegate>>,
+        worktree: Option<Arc<dyn WorktreeDelegate>>,
     ) -> Result<SlashCommandOutput>;
 
     async fn suggest_docs_packages(&self, provider: Arc<str>) -> Result<Vec<String>>;

crates/extension/src/types.rs 🔗

@@ -0,0 +1,49 @@
+mod lsp;
+mod slash_command;
+
+use std::ops::Range;
+
+pub use lsp::*;
+pub use slash_command::*;
+
+/// A list of environment variables.
+pub type EnvVars = Vec<(String, String)>;
+
+/// A command.
+pub struct Command {
+    /// The command to execute.
+    pub command: String,
+    /// The arguments to pass to the command.
+    pub args: Vec<String>,
+    /// The environment variables to set for the command.
+    pub env: EnvVars,
+}
+
+/// A label containing some code.
+#[derive(Debug, Clone)]
+pub struct CodeLabel {
+    /// The source code to parse with Tree-sitter.
+    pub code: String,
+    /// The spans to display in the label.
+    pub spans: Vec<CodeLabelSpan>,
+    /// The range of the displayed label to include when filtering.
+    pub filter_range: Range<usize>,
+}
+
+/// A span within a code label.
+#[derive(Debug, Clone)]
+pub enum CodeLabelSpan {
+    /// A range into the parsed code.
+    CodeRange(Range<usize>),
+    /// A span containing a code literal.
+    Literal(CodeLabelSpanLiteral),
+}
+
+/// A span containing a code literal.
+#[derive(Debug, Clone)]
+pub struct CodeLabelSpanLiteral {
+    /// The literal text.
+    pub text: String,
+    /// The name of the highlight to use for this literal.
+    pub highlight_name: Option<String>,
+}

crates/extension/src/types/lsp.rs 🔗

@@ -0,0 +1,96 @@
+use std::option::Option;
+
+/// An LSP completion.
+#[derive(Debug, Clone)]
+pub struct Completion {
+    pub label: String,
+    pub label_details: Option<CompletionLabelDetails>,
+    pub detail: Option<String>,
+    pub kind: Option<CompletionKind>,
+    pub insert_text_format: Option<InsertTextFormat>,
+}
+
+/// The kind of an LSP completion.
+#[derive(Debug, Clone, Copy)]
+pub enum CompletionKind {
+    Text,
+    Method,
+    Function,
+    Constructor,
+    Field,
+    Variable,
+    Class,
+    Interface,
+    Module,
+    Property,
+    Unit,
+    Value,
+    Enum,
+    Keyword,
+    Snippet,
+    Color,
+    File,
+    Reference,
+    Folder,
+    EnumMember,
+    Constant,
+    Struct,
+    Event,
+    Operator,
+    TypeParameter,
+    Other(i32),
+}
+
+/// Label details for an LSP completion.
+#[derive(Debug, Clone)]
+pub struct CompletionLabelDetails {
+    pub detail: Option<String>,
+    pub description: Option<String>,
+}
+
+/// Defines how to interpret the insert text in a completion item.
+#[derive(Debug, Clone, Copy)]
+pub enum InsertTextFormat {
+    PlainText,
+    Snippet,
+    Other(i32),
+}
+
+/// An LSP symbol.
+#[derive(Debug, Clone)]
+pub struct Symbol {
+    pub kind: SymbolKind,
+    pub name: String,
+}
+
+/// The kind of an LSP symbol.
+#[derive(Debug, Clone, Copy)]
+pub enum SymbolKind {
+    File,
+    Module,
+    Namespace,
+    Package,
+    Class,
+    Method,
+    Property,
+    Field,
+    Constructor,
+    Enum,
+    Interface,
+    Function,
+    Variable,
+    Constant,
+    String,
+    Number,
+    Boolean,
+    Array,
+    Object,
+    Key,
+    Null,
+    EnumMember,
+    Struct,
+    Event,
+    Operator,
+    TypeParameter,
+    Other(i32),
+}

crates/extension_host/src/extension_host.rs 🔗

@@ -5,7 +5,7 @@ pub mod wasm_host;
 #[cfg(test)]
 mod extension_store_test;
 
-use crate::{extension_lsp_adapter::ExtensionLspAdapter, wasm_host::wit};
+use crate::extension_lsp_adapter::ExtensionLspAdapter;
 use anyhow::{anyhow, bail, Context as _, Result};
 use async_compression::futures::bufread::GzipDecoder;
 use async_tar::Archive;
@@ -1238,13 +1238,9 @@ impl ExtensionStore {
                             this.registration_hooks.register_lsp_adapter(
                                 language.clone(),
                                 ExtensionLspAdapter {
-                                    extension: wasm_extension.clone(),
-                                    host: this.wasm_host.clone(),
+                                    extension: extension.clone(),
                                     language_server_id: language_server_id.clone(),
-                                    config: wit::LanguageServerConfig {
-                                        name: language_server_id.0.to_string(),
-                                        language_name: language.to_string(),
-                                    },
+                                    language_name: language.clone(),
                                 },
                             );
                         }

crates/extension_host/src/extension_lsp_adapter.rs 🔗

@@ -1,15 +1,12 @@
-use crate::wasm_host::{
-    wit::{self, LanguageServerConfig},
-    WasmExtension, WasmHost,
-};
-use anyhow::{anyhow, Context, Result};
+use anyhow::{Context, Result};
 use async_trait::async_trait;
 use collections::HashMap;
-use extension::WorktreeDelegate;
+use extension::{Extension, WorktreeDelegate};
 use futures::{Future, FutureExt};
 use gpui::AsyncAppContext;
 use language::{
-    CodeLabel, HighlightId, Language, LanguageToolchainStore, LspAdapter, LspAdapterDelegate,
+    CodeLabel, HighlightId, Language, LanguageName, LanguageToolchainStore, LspAdapter,
+    LspAdapterDelegate,
 };
 use lsp::{CodeActionKind, LanguageServerBinary, LanguageServerBinaryOptions, LanguageServerName};
 use serde::Serialize;
@@ -17,7 +14,6 @@ use serde_json::Value;
 use std::ops::Range;
 use std::{any::Any, path::PathBuf, pin::Pin, sync::Arc};
 use util::{maybe, ResultExt};
-use wasmtime_wasi::WasiView as _;
 
 /// An adapter that allows an [`LspAdapterDelegate`] to be used as a [`WorktreeDelegate`].
 pub struct WorktreeDelegateAdapter(pub Arc<dyn LspAdapterDelegate>);
@@ -49,16 +45,15 @@ impl WorktreeDelegate for WorktreeDelegateAdapter {
 }
 
 pub struct ExtensionLspAdapter {
-    pub(crate) extension: WasmExtension,
+    pub(crate) extension: Arc<dyn Extension>,
     pub(crate) language_server_id: LanguageServerName,
-    pub(crate) config: LanguageServerConfig,
-    pub(crate) host: Arc<WasmHost>,
+    pub(crate) language_name: LanguageName,
 }
 
 #[async_trait(?Send)]
 impl LspAdapter for ExtensionLspAdapter {
     fn name(&self) -> LanguageServerName {
-        LanguageServerName(self.config.name.clone().into())
+        self.language_server_id.clone()
     }
 
     fn get_language_server_command<'a>(
@@ -69,33 +64,17 @@ impl LspAdapter for ExtensionLspAdapter {
         _: &'a mut AsyncAppContext,
     ) -> Pin<Box<dyn 'a + Future<Output = Result<LanguageServerBinary>>>> {
         async move {
+            let delegate = Arc::new(WorktreeDelegateAdapter(delegate.clone())) as _;
             let command = self
                 .extension
-                .call({
-                    let this = self.clone();
-                    |extension, store| {
-                        async move {
-                            let delegate = Arc::new(WorktreeDelegateAdapter(delegate.clone())) as _;
-                            let resource = store.data_mut().table().push(delegate)?;
-                            let command = extension
-                                .call_language_server_command(
-                                    store,
-                                    &this.language_server_id,
-                                    &this.config,
-                                    resource,
-                                )
-                                .await?
-                                .map_err(|e| anyhow!("{}", e))?;
-                            anyhow::Ok(command)
-                        }
-                        .boxed()
-                    }
-                })
+                .language_server_command(
+                    self.language_server_id.clone(),
+                    self.language_name.clone(),
+                    delegate,
+                )
                 .await?;
 
-            let path = self
-                .host
-                .path_from_extension(&self.extension.manifest.id, command.command.as_ref());
+            let path = self.extension.path_from_extension(command.command.as_ref());
 
             // TODO: This should now be done via the `zed::make_file_executable` function in
             // Zed extension API, but we're leaving these existing usages in place temporarily
@@ -104,8 +83,8 @@ impl LspAdapter for ExtensionLspAdapter {
             // We can remove once the following extension versions no longer see any use:
             // - toml@0.0.2
             // - zig@0.0.1
-            if ["toml", "zig"].contains(&self.extension.manifest.id.as_ref())
-                && path.starts_with(&self.host.work_dir)
+            if ["toml", "zig"].contains(&self.extension.manifest().id.as_ref())
+                && path.starts_with(&self.extension.work_dir())
             {
                 #[cfg(not(windows))]
                 {
@@ -153,7 +132,7 @@ impl LspAdapter for ExtensionLspAdapter {
     fn code_action_kinds(&self) -> Option<Vec<CodeActionKind>> {
         let code_action_kinds = self
             .extension
-            .manifest
+            .manifest()
             .language_servers
             .get(&self.language_server_id)
             .and_then(|server| server.code_action_kinds.clone());
@@ -174,14 +153,14 @@ impl LspAdapter for ExtensionLspAdapter {
         //
         // We can remove once the following extension versions no longer see any use:
         // - php@0.0.1
-        if self.extension.manifest.id.as_ref() == "php" {
+        if self.extension.manifest().id.as_ref() == "php" {
             return HashMap::from_iter([("PHP".into(), "php".into())]);
         }
 
         self.extension
-            .manifest
+            .manifest()
             .language_servers
-            .get(&LanguageServerName(self.config.name.clone().into()))
+            .get(&self.language_server_id)
             .map(|server| server.language_ids.clone())
             .unwrap_or_default()
     }
@@ -190,29 +169,14 @@ impl LspAdapter for ExtensionLspAdapter {
         self: Arc<Self>,
         delegate: &Arc<dyn LspAdapterDelegate>,
     ) -> Result<Option<serde_json::Value>> {
-        let delegate = delegate.clone();
+        let delegate = Arc::new(WorktreeDelegateAdapter(delegate.clone())) as _;
         let json_options = self
             .extension
-            .call({
-                let this = self.clone();
-                |extension, store| {
-                    async move {
-                        let delegate = Arc::new(WorktreeDelegateAdapter(delegate.clone())) as _;
-                        let resource = store.data_mut().table().push(delegate)?;
-                        let options = extension
-                            .call_language_server_initialization_options(
-                                store,
-                                &this.language_server_id,
-                                &this.config,
-                                resource,
-                            )
-                            .await?
-                            .map_err(|e| anyhow!("{}", e))?;
-                        anyhow::Ok(options)
-                    }
-                    .boxed()
-                }
-            })
+            .language_server_initialization_options(
+                self.language_server_id.clone(),
+                self.language_name.clone(),
+                delegate,
+            )
             .await?;
         Ok(if let Some(json_options) = json_options {
             serde_json::from_str(&json_options).with_context(|| {
@@ -229,32 +193,14 @@ impl LspAdapter for ExtensionLspAdapter {
         _: Arc<dyn LanguageToolchainStore>,
         _cx: &mut AsyncAppContext,
     ) -> Result<Value> {
-        let delegate = delegate.clone();
+        let delegate = Arc::new(WorktreeDelegateAdapter(delegate.clone())) as _;
         let json_options: Option<String> = self
             .extension
-            .call({
-                let this = self.clone();
-                |extension, store| {
-                    async move {
-                        let delegate = Arc::new(WorktreeDelegateAdapter(delegate.clone())) as _;
-                        let resource = store.data_mut().table().push(delegate)?;
-                        let options = extension
-                            .call_language_server_workspace_configuration(
-                                store,
-                                &this.language_server_id,
-                                resource,
-                            )
-                            .await?
-                            .map_err(|e| anyhow!("{}", e))?;
-                        anyhow::Ok(options)
-                    }
-                    .boxed()
-                }
-            })
+            .language_server_workspace_configuration(self.language_server_id.clone(), delegate)
             .await?;
         Ok(if let Some(json_options) = json_options {
             serde_json::from_str(&json_options).with_context(|| {
-                format!("failed to parse initialization_options from extension: {json_options}")
+                format!("failed to parse workspace_configuration from extension: {json_options}")
             })?
         } else {
             serde_json::json!({})
@@ -268,30 +214,16 @@ impl LspAdapter for ExtensionLspAdapter {
     ) -> Result<Vec<Option<CodeLabel>>> {
         let completions = completions
             .iter()
-            .map(|completion| wit::Completion::from(completion.clone()))
+            .cloned()
+            .map(lsp_completion_to_extension)
             .collect::<Vec<_>>();
 
         let labels = self
             .extension
-            .call({
-                let this = self.clone();
-                |extension, store| {
-                    async move {
-                        extension
-                            .call_labels_for_completions(
-                                store,
-                                &this.language_server_id,
-                                completions,
-                            )
-                            .await?
-                            .map_err(|e| anyhow!("{}", e))
-                    }
-                    .boxed()
-                }
-            })
+            .labels_for_completions(self.language_server_id.clone(), completions)
             .await?;
 
-        Ok(labels_from_wit(labels, language))
+        Ok(labels_from_extension(labels, language))
     }
 
     async fn labels_for_symbols(
@@ -302,34 +234,29 @@ impl LspAdapter for ExtensionLspAdapter {
         let symbols = symbols
             .iter()
             .cloned()
-            .map(|(name, kind)| wit::Symbol {
+            .map(|(name, kind)| extension::Symbol {
                 name,
-                kind: kind.into(),
+                kind: lsp_symbol_kind_to_extension(kind),
             })
             .collect::<Vec<_>>();
 
         let labels = self
             .extension
-            .call({
-                let this = self.clone();
-                |extension, store| {
-                    async move {
-                        extension
-                            .call_labels_for_symbols(store, &this.language_server_id, symbols)
-                            .await?
-                            .map_err(|e| anyhow!("{}", e))
-                    }
-                    .boxed()
-                }
-            })
+            .labels_for_symbols(self.language_server_id.clone(), symbols)
             .await?;
 
-        Ok(labels_from_wit(labels, language))
+        Ok(labels_from_extension(
+            labels
+                .into_iter()
+                .map(|label| label.map(Into::into))
+                .collect(),
+            language,
+        ))
     }
 }
 
-fn labels_from_wit(
-    labels: Vec<Option<wit::CodeLabel>>,
+fn labels_from_extension(
+    labels: Vec<Option<extension::CodeLabel>>,
     language: &Arc<Language>,
 ) -> Vec<Option<CodeLabel>> {
     labels
@@ -347,7 +274,7 @@ fn labels_from_wit(
 }
 
 fn build_code_label(
-    label: &wit::CodeLabel,
+    label: &extension::CodeLabel,
     parsed_runs: &[(Range<usize>, HighlightId)],
     language: &Arc<Language>,
 ) -> Option<CodeLabel> {
@@ -356,8 +283,7 @@ fn build_code_label(
 
     for span in &label.spans {
         match span {
-            wit::CodeLabelSpan::CodeRange(range) => {
-                let range = Range::from(*range);
+            extension::CodeLabelSpan::CodeRange(range) => {
                 let code_span = &label.code.get(range.clone())?;
                 let mut input_ix = range.start;
                 let mut output_ix = text.len();
@@ -383,7 +309,7 @@ fn build_code_label(
 
                 text.push_str(code_span);
             }
-            wit::CodeLabelSpan::Literal(span) => {
+            extension::CodeLabelSpan::Literal(span) => {
                 let highlight_id = language
                     .grammar()
                     .zip(span.highlight_name.as_ref())
@@ -398,7 +324,7 @@ fn build_code_label(
         }
     }
 
-    let filter_range = Range::from(label.filter_range);
+    let filter_range = label.filter_range.clone();
     text.get(filter_range.clone())?;
     Some(CodeLabel {
         text,
@@ -407,109 +333,101 @@ fn build_code_label(
     })
 }
 
-impl From<wit::Range> for Range<usize> {
-    fn from(range: wit::Range) -> Self {
-        let start = range.start as usize;
-        let end = range.end as usize;
-        start..end
-    }
-}
-
-impl From<lsp::CompletionItem> for wit::Completion {
-    fn from(value: lsp::CompletionItem) -> Self {
-        Self {
-            label: value.label,
-            label_details: value.label_details.map(Into::into),
-            detail: value.detail,
-            kind: value.kind.map(Into::into),
-            insert_text_format: value.insert_text_format.map(Into::into),
-        }
+fn lsp_completion_to_extension(value: lsp::CompletionItem) -> extension::Completion {
+    extension::Completion {
+        label: value.label,
+        label_details: value
+            .label_details
+            .map(lsp_completion_item_label_details_to_extension),
+        detail: value.detail,
+        kind: value.kind.map(lsp_completion_item_kind_to_extension),
+        insert_text_format: value
+            .insert_text_format
+            .map(lsp_insert_text_format_to_extension),
     }
 }
 
-impl From<lsp::CompletionItemLabelDetails> for wit::CompletionLabelDetails {
-    fn from(value: lsp::CompletionItemLabelDetails) -> Self {
-        Self {
-            detail: value.detail,
-            description: value.description,
-        }
+fn lsp_completion_item_label_details_to_extension(
+    value: lsp::CompletionItemLabelDetails,
+) -> extension::CompletionLabelDetails {
+    extension::CompletionLabelDetails {
+        detail: value.detail,
+        description: value.description,
     }
 }
 
-impl From<lsp::CompletionItemKind> for wit::CompletionKind {
-    fn from(value: lsp::CompletionItemKind) -> Self {
-        match value {
-            lsp::CompletionItemKind::TEXT => Self::Text,
-            lsp::CompletionItemKind::METHOD => Self::Method,
-            lsp::CompletionItemKind::FUNCTION => Self::Function,
-            lsp::CompletionItemKind::CONSTRUCTOR => Self::Constructor,
-            lsp::CompletionItemKind::FIELD => Self::Field,
-            lsp::CompletionItemKind::VARIABLE => Self::Variable,
-            lsp::CompletionItemKind::CLASS => Self::Class,
-            lsp::CompletionItemKind::INTERFACE => Self::Interface,
-            lsp::CompletionItemKind::MODULE => Self::Module,
-            lsp::CompletionItemKind::PROPERTY => Self::Property,
-            lsp::CompletionItemKind::UNIT => Self::Unit,
-            lsp::CompletionItemKind::VALUE => Self::Value,
-            lsp::CompletionItemKind::ENUM => Self::Enum,
-            lsp::CompletionItemKind::KEYWORD => Self::Keyword,
-            lsp::CompletionItemKind::SNIPPET => Self::Snippet,
-            lsp::CompletionItemKind::COLOR => Self::Color,
-            lsp::CompletionItemKind::FILE => Self::File,
-            lsp::CompletionItemKind::REFERENCE => Self::Reference,
-            lsp::CompletionItemKind::FOLDER => Self::Folder,
-            lsp::CompletionItemKind::ENUM_MEMBER => Self::EnumMember,
-            lsp::CompletionItemKind::CONSTANT => Self::Constant,
-            lsp::CompletionItemKind::STRUCT => Self::Struct,
-            lsp::CompletionItemKind::EVENT => Self::Event,
-            lsp::CompletionItemKind::OPERATOR => Self::Operator,
-            lsp::CompletionItemKind::TYPE_PARAMETER => Self::TypeParameter,
-            _ => Self::Other(extract_int(value)),
-        }
+fn lsp_completion_item_kind_to_extension(
+    value: lsp::CompletionItemKind,
+) -> extension::CompletionKind {
+    match value {
+        lsp::CompletionItemKind::TEXT => extension::CompletionKind::Text,
+        lsp::CompletionItemKind::METHOD => extension::CompletionKind::Method,
+        lsp::CompletionItemKind::FUNCTION => extension::CompletionKind::Function,
+        lsp::CompletionItemKind::CONSTRUCTOR => extension::CompletionKind::Constructor,
+        lsp::CompletionItemKind::FIELD => extension::CompletionKind::Field,
+        lsp::CompletionItemKind::VARIABLE => extension::CompletionKind::Variable,
+        lsp::CompletionItemKind::CLASS => extension::CompletionKind::Class,
+        lsp::CompletionItemKind::INTERFACE => extension::CompletionKind::Interface,
+        lsp::CompletionItemKind::MODULE => extension::CompletionKind::Module,
+        lsp::CompletionItemKind::PROPERTY => extension::CompletionKind::Property,
+        lsp::CompletionItemKind::UNIT => extension::CompletionKind::Unit,
+        lsp::CompletionItemKind::VALUE => extension::CompletionKind::Value,
+        lsp::CompletionItemKind::ENUM => extension::CompletionKind::Enum,
+        lsp::CompletionItemKind::KEYWORD => extension::CompletionKind::Keyword,
+        lsp::CompletionItemKind::SNIPPET => extension::CompletionKind::Snippet,
+        lsp::CompletionItemKind::COLOR => extension::CompletionKind::Color,
+        lsp::CompletionItemKind::FILE => extension::CompletionKind::File,
+        lsp::CompletionItemKind::REFERENCE => extension::CompletionKind::Reference,
+        lsp::CompletionItemKind::FOLDER => extension::CompletionKind::Folder,
+        lsp::CompletionItemKind::ENUM_MEMBER => extension::CompletionKind::EnumMember,
+        lsp::CompletionItemKind::CONSTANT => extension::CompletionKind::Constant,
+        lsp::CompletionItemKind::STRUCT => extension::CompletionKind::Struct,
+        lsp::CompletionItemKind::EVENT => extension::CompletionKind::Event,
+        lsp::CompletionItemKind::OPERATOR => extension::CompletionKind::Operator,
+        lsp::CompletionItemKind::TYPE_PARAMETER => extension::CompletionKind::TypeParameter,
+        _ => extension::CompletionKind::Other(extract_int(value)),
     }
 }
 
-impl From<lsp::InsertTextFormat> for wit::InsertTextFormat {
-    fn from(value: lsp::InsertTextFormat) -> Self {
-        match value {
-            lsp::InsertTextFormat::PLAIN_TEXT => Self::PlainText,
-            lsp::InsertTextFormat::SNIPPET => Self::Snippet,
-            _ => Self::Other(extract_int(value)),
-        }
+fn lsp_insert_text_format_to_extension(
+    value: lsp::InsertTextFormat,
+) -> extension::InsertTextFormat {
+    match value {
+        lsp::InsertTextFormat::PLAIN_TEXT => extension::InsertTextFormat::PlainText,
+        lsp::InsertTextFormat::SNIPPET => extension::InsertTextFormat::Snippet,
+        _ => extension::InsertTextFormat::Other(extract_int(value)),
     }
 }
 
-impl From<lsp::SymbolKind> for wit::SymbolKind {
-    fn from(value: lsp::SymbolKind) -> Self {
-        match value {
-            lsp::SymbolKind::FILE => Self::File,
-            lsp::SymbolKind::MODULE => Self::Module,
-            lsp::SymbolKind::NAMESPACE => Self::Namespace,
-            lsp::SymbolKind::PACKAGE => Self::Package,
-            lsp::SymbolKind::CLASS => Self::Class,
-            lsp::SymbolKind::METHOD => Self::Method,
-            lsp::SymbolKind::PROPERTY => Self::Property,
-            lsp::SymbolKind::FIELD => Self::Field,
-            lsp::SymbolKind::CONSTRUCTOR => Self::Constructor,
-            lsp::SymbolKind::ENUM => Self::Enum,
-            lsp::SymbolKind::INTERFACE => Self::Interface,
-            lsp::SymbolKind::FUNCTION => Self::Function,
-            lsp::SymbolKind::VARIABLE => Self::Variable,
-            lsp::SymbolKind::CONSTANT => Self::Constant,
-            lsp::SymbolKind::STRING => Self::String,
-            lsp::SymbolKind::NUMBER => Self::Number,
-            lsp::SymbolKind::BOOLEAN => Self::Boolean,
-            lsp::SymbolKind::ARRAY => Self::Array,
-            lsp::SymbolKind::OBJECT => Self::Object,
-            lsp::SymbolKind::KEY => Self::Key,
-            lsp::SymbolKind::NULL => Self::Null,
-            lsp::SymbolKind::ENUM_MEMBER => Self::EnumMember,
-            lsp::SymbolKind::STRUCT => Self::Struct,
-            lsp::SymbolKind::EVENT => Self::Event,
-            lsp::SymbolKind::OPERATOR => Self::Operator,
-            lsp::SymbolKind::TYPE_PARAMETER => Self::TypeParameter,
-            _ => Self::Other(extract_int(value)),
-        }
+fn lsp_symbol_kind_to_extension(value: lsp::SymbolKind) -> extension::SymbolKind {
+    match value {
+        lsp::SymbolKind::FILE => extension::SymbolKind::File,
+        lsp::SymbolKind::MODULE => extension::SymbolKind::Module,
+        lsp::SymbolKind::NAMESPACE => extension::SymbolKind::Namespace,
+        lsp::SymbolKind::PACKAGE => extension::SymbolKind::Package,
+        lsp::SymbolKind::CLASS => extension::SymbolKind::Class,
+        lsp::SymbolKind::METHOD => extension::SymbolKind::Method,
+        lsp::SymbolKind::PROPERTY => extension::SymbolKind::Property,
+        lsp::SymbolKind::FIELD => extension::SymbolKind::Field,
+        lsp::SymbolKind::CONSTRUCTOR => extension::SymbolKind::Constructor,
+        lsp::SymbolKind::ENUM => extension::SymbolKind::Enum,
+        lsp::SymbolKind::INTERFACE => extension::SymbolKind::Interface,
+        lsp::SymbolKind::FUNCTION => extension::SymbolKind::Function,
+        lsp::SymbolKind::VARIABLE => extension::SymbolKind::Variable,
+        lsp::SymbolKind::CONSTANT => extension::SymbolKind::Constant,
+        lsp::SymbolKind::STRING => extension::SymbolKind::String,
+        lsp::SymbolKind::NUMBER => extension::SymbolKind::Number,
+        lsp::SymbolKind::BOOLEAN => extension::SymbolKind::Boolean,
+        lsp::SymbolKind::ARRAY => extension::SymbolKind::Array,
+        lsp::SymbolKind::OBJECT => extension::SymbolKind::Object,
+        lsp::SymbolKind::KEY => extension::SymbolKind::Key,
+        lsp::SymbolKind::NULL => extension::SymbolKind::Null,
+        lsp::SymbolKind::ENUM_MEMBER => extension::SymbolKind::EnumMember,
+        lsp::SymbolKind::STRUCT => extension::SymbolKind::Struct,
+        lsp::SymbolKind::EVENT => extension::SymbolKind::Event,
+        lsp::SymbolKind::OPERATOR => extension::SymbolKind::Operator,
+        lsp::SymbolKind::TYPE_PARAMETER => extension::SymbolKind::TypeParameter,
+        _ => extension::SymbolKind::Other(extract_int(value)),
     }
 }
 
@@ -536,21 +454,14 @@ fn test_build_code_label() {
         .collect::<Vec<_>>();
 
     let label = build_code_label(
-        &wit::CodeLabel {
+        &extension::CodeLabel {
             spans: vec![
-                wit::CodeLabelSpan::CodeRange(wit::Range {
-                    start: code.find("pqrs").unwrap() as u32,
-                    end: code.len() as u32,
-                }),
-                wit::CodeLabelSpan::CodeRange(wit::Range {
-                    start: code.find(": fn").unwrap() as u32,
-                    end: code.find(" = ").unwrap() as u32,
-                }),
+                extension::CodeLabelSpan::CodeRange(code.find("pqrs").unwrap()..code.len()),
+                extension::CodeLabelSpan::CodeRange(
+                    code.find(": fn").unwrap()..code.find(" = ").unwrap(),
+                ),
             ],
-            filter_range: wit::Range {
-                start: 0,
-                end: "pqrs.tuv".len() as u32,
-            },
+            filter_range: 0.."pqrs.tuv".len(),
             code,
         },
         &code_runs,
@@ -588,21 +499,14 @@ fn test_build_code_label_with_invalid_ranges() {
     // A span uses a code range that is invalid because it starts inside of
     // a multi-byte character.
     let label = build_code_label(
-        &wit::CodeLabel {
+        &extension::CodeLabel {
             spans: vec![
-                wit::CodeLabelSpan::CodeRange(wit::Range {
-                    start: code.find('B').unwrap() as u32,
-                    end: code.find(" = ").unwrap() as u32,
-                }),
-                wit::CodeLabelSpan::CodeRange(wit::Range {
-                    start: code.find('🏀').unwrap() as u32 + 1,
-                    end: code.len() as u32,
-                }),
+                extension::CodeLabelSpan::CodeRange(
+                    code.find('B').unwrap()..code.find(" = ").unwrap(),
+                ),
+                extension::CodeLabelSpan::CodeRange((code.find('🏀').unwrap() + 1)..code.len()),
             ],
-            filter_range: wit::Range {
-                start: 0,
-                end: "B".len() as u32,
-            },
+            filter_range: 0.."B".len(),
             code,
         },
         &code_runs,
@@ -612,12 +516,14 @@ fn test_build_code_label_with_invalid_ranges() {
 
     // Filter range extends beyond actual text
     let label = build_code_label(
-        &wit::CodeLabel {
-            spans: vec![wit::CodeLabelSpan::Literal(wit::CodeLabelSpanLiteral {
-                text: "abc".into(),
-                highlight_name: Some("type".into()),
-            })],
-            filter_range: wit::Range { start: 0, end: 5 },
+        &extension::CodeLabel {
+            spans: vec![extension::CodeLabelSpan::Literal(
+                extension::CodeLabelSpanLiteral {
+                    text: "abc".into(),
+                    highlight_name: Some("type".into()),
+                },
+            )],
+            filter_range: 0..5,
             code: String::new(),
         },
         &code_runs,

crates/extension_host/src/wasm_host.rs 🔗

@@ -4,8 +4,8 @@ use crate::{ExtensionManifest, ExtensionRegistrationHooks};
 use anyhow::{anyhow, bail, Context as _, Result};
 use async_trait::async_trait;
 use extension::{
-    KeyValueStoreDelegate, SlashCommand, SlashCommandArgumentCompletion, SlashCommandOutput,
-    WorktreeDelegate,
+    CodeLabel, Command, Completion, KeyValueStoreDelegate, SlashCommand,
+    SlashCommandArgumentCompletion, SlashCommandOutput, Symbol, WorktreeDelegate,
 };
 use fs::{normalize_path, Fs};
 use futures::future::LocalBoxFuture;
@@ -19,6 +19,8 @@ use futures::{
 };
 use gpui::{AppContext, AsyncAppContext, BackgroundExecutor, Task};
 use http_client::HttpClient;
+use language::LanguageName;
+use lsp::LanguageServerName;
 use node_runtime::NodeRuntime;
 use release_channel::ReleaseChannel;
 use semantic_version::SemanticVersion;
@@ -65,6 +67,132 @@ impl extension::Extension for WasmExtension {
         self.work_dir.clone()
     }
 
+    async fn language_server_command(
+        &self,
+        language_server_id: LanguageServerName,
+        language_name: LanguageName,
+        worktree: Arc<dyn WorktreeDelegate>,
+    ) -> Result<Command> {
+        self.call(|extension, store| {
+            async move {
+                let resource = store.data_mut().table().push(worktree)?;
+                let command = extension
+                    .call_language_server_command(
+                        store,
+                        &language_server_id,
+                        &language_name,
+                        resource,
+                    )
+                    .await?
+                    .map_err(|err| anyhow!("{err}"))?;
+
+                Ok(command.into())
+            }
+            .boxed()
+        })
+        .await
+    }
+
+    async fn language_server_initialization_options(
+        &self,
+        language_server_id: LanguageServerName,
+        language_name: LanguageName,
+        worktree: Arc<dyn WorktreeDelegate>,
+    ) -> Result<Option<String>> {
+        self.call(|extension, store| {
+            async move {
+                let resource = store.data_mut().table().push(worktree)?;
+                let options = extension
+                    .call_language_server_initialization_options(
+                        store,
+                        &language_server_id,
+                        &language_name,
+                        resource,
+                    )
+                    .await?
+                    .map_err(|err| anyhow!("{err}"))?;
+                anyhow::Ok(options)
+            }
+            .boxed()
+        })
+        .await
+    }
+
+    async fn language_server_workspace_configuration(
+        &self,
+        language_server_id: LanguageServerName,
+        worktree: Arc<dyn WorktreeDelegate>,
+    ) -> Result<Option<String>> {
+        self.call(|extension, store| {
+            async move {
+                let resource = store.data_mut().table().push(worktree)?;
+                let options = extension
+                    .call_language_server_workspace_configuration(
+                        store,
+                        &language_server_id,
+                        resource,
+                    )
+                    .await?
+                    .map_err(|err| anyhow!("{err}"))?;
+                anyhow::Ok(options)
+            }
+            .boxed()
+        })
+        .await
+    }
+
+    async fn labels_for_completions(
+        &self,
+        language_server_id: LanguageServerName,
+        completions: Vec<Completion>,
+    ) -> Result<Vec<Option<CodeLabel>>> {
+        self.call(|extension, store| {
+            async move {
+                let labels = extension
+                    .call_labels_for_completions(
+                        store,
+                        &language_server_id,
+                        completions.into_iter().map(Into::into).collect(),
+                    )
+                    .await?
+                    .map_err(|err| anyhow!("{err}"))?;
+
+                Ok(labels
+                    .into_iter()
+                    .map(|label| label.map(Into::into))
+                    .collect())
+            }
+            .boxed()
+        })
+        .await
+    }
+
+    async fn labels_for_symbols(
+        &self,
+        language_server_id: LanguageServerName,
+        symbols: Vec<Symbol>,
+    ) -> Result<Vec<Option<CodeLabel>>> {
+        self.call(|extension, store| {
+            async move {
+                let labels = extension
+                    .call_labels_for_symbols(
+                        store,
+                        &language_server_id,
+                        symbols.into_iter().map(Into::into).collect(),
+                    )
+                    .await?
+                    .map_err(|err| anyhow!("{err}"))?;
+
+                Ok(labels
+                    .into_iter()
+                    .map(|label| label.map(Into::into))
+                    .collect())
+            }
+            .boxed()
+        })
+        .await
+    }
+
     async fn complete_slash_command_argument(
         &self,
         command: SlashCommand,
@@ -255,7 +383,7 @@ impl WasmHost {
 
             Ok(WasmExtension {
                 manifest: manifest.clone(),
-                work_dir: this.work_dir.clone().into(),
+                work_dir: this.work_dir.join(manifest.id.as_ref()).into(),
                 tx,
                 zed_api_version,
             })
@@ -286,11 +414,6 @@ impl WasmHost {
             .build())
     }
 
-    pub fn path_from_extension(&self, id: &Arc<str>, path: &Path) -> PathBuf {
-        let extension_work_dir = self.work_dir.join(id.as_ref());
-        normalize_path(&extension_work_dir.join(path))
-    }
-
     pub fn writeable_path_from_extension(&self, id: &Arc<str>, path: &Path) -> Result<PathBuf> {
         let extension_work_dir = self.work_dir.join(id.as_ref());
         let path = normalize_path(&extension_work_dir.join(path));

crates/extension_host/src/wasm_host/wit.rs 🔗

@@ -4,6 +4,7 @@ mod since_v0_0_6;
 mod since_v0_1_0;
 mod since_v0_2_0;
 use extension::{KeyValueStoreDelegate, WorktreeDelegate};
+use language::LanguageName;
 use lsp::LanguageServerName;
 use release_channel::ReleaseChannel;
 use since_v0_2_0 as latest;
@@ -163,7 +164,7 @@ impl Extension {
         &self,
         store: &mut Store<WasmState>,
         language_server_id: &LanguageServerName,
-        config: &LanguageServerConfig,
+        language_name: &LanguageName,
         resource: Resource<Arc<dyn WorktreeDelegate>>,
     ) -> Result<Result<Command, String>> {
         match self {
@@ -180,11 +181,26 @@ impl Extension {
                 .await?
                 .map(|command| command.into())),
             Extension::V004(ext) => Ok(ext
-                .call_language_server_command(store, config, resource)
+                .call_language_server_command(
+                    store,
+                    &LanguageServerConfig {
+                        name: language_server_id.0.to_string(),
+                        language_name: language_name.to_string(),
+                    },
+                    resource,
+                )
                 .await?
                 .map(|command| command.into())),
             Extension::V001(ext) => Ok(ext
-                .call_language_server_command(store, &config.clone().into(), resource)
+                .call_language_server_command(
+                    store,
+                    &LanguageServerConfig {
+                        name: language_server_id.0.to_string(),
+                        language_name: language_name.to_string(),
+                    }
+                    .into(),
+                    resource,
+                )
                 .await?
                 .map(|command| command.into())),
         }
@@ -194,7 +210,7 @@ impl Extension {
         &self,
         store: &mut Store<WasmState>,
         language_server_id: &LanguageServerName,
-        config: &LanguageServerConfig,
+        language_name: &LanguageName,
         resource: Resource<Arc<dyn WorktreeDelegate>>,
     ) -> Result<Result<Option<String>, String>> {
         match self {
@@ -223,13 +239,24 @@ impl Extension {
                 .await
             }
             Extension::V004(ext) => {
-                ext.call_language_server_initialization_options(store, config, resource)
-                    .await
+                ext.call_language_server_initialization_options(
+                    store,
+                    &LanguageServerConfig {
+                        name: language_server_id.0.to_string(),
+                        language_name: language_name.to_string(),
+                    },
+                    resource,
+                )
+                .await
             }
             Extension::V001(ext) => {
                 ext.call_language_server_initialization_options(
                     store,
-                    &config.clone().into(),
+                    &LanguageServerConfig {
+                        name: language_server_id.0.to_string(),
+                        language_name: language_name.to_string(),
+                    }
+                    .into(),
                     resource,
                 )
                 .await

crates/extension_host/src/wasm_host/wit/since_v0_2_0.rs 🔗

@@ -1,4 +1,5 @@
 use crate::wasm_host::wit::since_v0_2_0::slash_command::SlashCommandOutputSection;
+use crate::wasm_host::wit::{CompletionKind, CompletionLabelDetails, InsertTextFormat, SymbolKind};
 use crate::wasm_host::{wit::ToWasmtimeResult, WasmState};
 use ::http_client::{AsyncBody, HttpRequestExt};
 use ::settings::{Settings, WorktreeId};
@@ -55,6 +56,159 @@ pub fn linker() -> &'static Linker<WasmState> {
     LINKER.get_or_init(|| super::new_linker(Extension::add_to_linker))
 }
 
+impl From<Range> for std::ops::Range<usize> {
+    fn from(range: Range) -> Self {
+        let start = range.start as usize;
+        let end = range.end as usize;
+        start..end
+    }
+}
+
+impl From<Command> for extension::Command {
+    fn from(value: Command) -> Self {
+        Self {
+            command: value.command,
+            args: value.args,
+            env: value.env,
+        }
+    }
+}
+
+impl From<CodeLabel> for extension::CodeLabel {
+    fn from(value: CodeLabel) -> Self {
+        Self {
+            code: value.code,
+            spans: value.spans.into_iter().map(Into::into).collect(),
+            filter_range: value.filter_range.into(),
+        }
+    }
+}
+
+impl From<CodeLabelSpan> for extension::CodeLabelSpan {
+    fn from(value: CodeLabelSpan) -> Self {
+        match value {
+            CodeLabelSpan::CodeRange(range) => Self::CodeRange(range.into()),
+            CodeLabelSpan::Literal(literal) => Self::Literal(literal.into()),
+        }
+    }
+}
+
+impl From<CodeLabelSpanLiteral> for extension::CodeLabelSpanLiteral {
+    fn from(value: CodeLabelSpanLiteral) -> Self {
+        Self {
+            text: value.text,
+            highlight_name: value.highlight_name,
+        }
+    }
+}
+
+impl From<extension::Completion> for Completion {
+    fn from(value: extension::Completion) -> Self {
+        Self {
+            label: value.label,
+            label_details: value.label_details.map(Into::into),
+            detail: value.detail,
+            kind: value.kind.map(Into::into),
+            insert_text_format: value.insert_text_format.map(Into::into),
+        }
+    }
+}
+
+impl From<extension::CompletionLabelDetails> for CompletionLabelDetails {
+    fn from(value: extension::CompletionLabelDetails) -> Self {
+        Self {
+            detail: value.detail,
+            description: value.description,
+        }
+    }
+}
+
+impl From<extension::CompletionKind> for CompletionKind {
+    fn from(value: extension::CompletionKind) -> Self {
+        match value {
+            extension::CompletionKind::Text => Self::Text,
+            extension::CompletionKind::Method => Self::Method,
+            extension::CompletionKind::Function => Self::Function,
+            extension::CompletionKind::Constructor => Self::Constructor,
+            extension::CompletionKind::Field => Self::Field,
+            extension::CompletionKind::Variable => Self::Variable,
+            extension::CompletionKind::Class => Self::Class,
+            extension::CompletionKind::Interface => Self::Interface,
+            extension::CompletionKind::Module => Self::Module,
+            extension::CompletionKind::Property => Self::Property,
+            extension::CompletionKind::Unit => Self::Unit,
+            extension::CompletionKind::Value => Self::Value,
+            extension::CompletionKind::Enum => Self::Enum,
+            extension::CompletionKind::Keyword => Self::Keyword,
+            extension::CompletionKind::Snippet => Self::Snippet,
+            extension::CompletionKind::Color => Self::Color,
+            extension::CompletionKind::File => Self::File,
+            extension::CompletionKind::Reference => Self::Reference,
+            extension::CompletionKind::Folder => Self::Folder,
+            extension::CompletionKind::EnumMember => Self::EnumMember,
+            extension::CompletionKind::Constant => Self::Constant,
+            extension::CompletionKind::Struct => Self::Struct,
+            extension::CompletionKind::Event => Self::Event,
+            extension::CompletionKind::Operator => Self::Operator,
+            extension::CompletionKind::TypeParameter => Self::TypeParameter,
+            extension::CompletionKind::Other(value) => Self::Other(value),
+        }
+    }
+}
+
+impl From<extension::InsertTextFormat> for InsertTextFormat {
+    fn from(value: extension::InsertTextFormat) -> Self {
+        match value {
+            extension::InsertTextFormat::PlainText => Self::PlainText,
+            extension::InsertTextFormat::Snippet => Self::Snippet,
+            extension::InsertTextFormat::Other(value) => Self::Other(value),
+        }
+    }
+}
+
+impl From<extension::Symbol> for Symbol {
+    fn from(value: extension::Symbol) -> Self {
+        Self {
+            kind: value.kind.into(),
+            name: value.name,
+        }
+    }
+}
+
+impl From<extension::SymbolKind> for SymbolKind {
+    fn from(value: extension::SymbolKind) -> Self {
+        match value {
+            extension::SymbolKind::File => Self::File,
+            extension::SymbolKind::Module => Self::Module,
+            extension::SymbolKind::Namespace => Self::Namespace,
+            extension::SymbolKind::Package => Self::Package,
+            extension::SymbolKind::Class => Self::Class,
+            extension::SymbolKind::Method => Self::Method,
+            extension::SymbolKind::Property => Self::Property,
+            extension::SymbolKind::Field => Self::Field,
+            extension::SymbolKind::Constructor => Self::Constructor,
+            extension::SymbolKind::Enum => Self::Enum,
+            extension::SymbolKind::Interface => Self::Interface,
+            extension::SymbolKind::Function => Self::Function,
+            extension::SymbolKind::Variable => Self::Variable,
+            extension::SymbolKind::Constant => Self::Constant,
+            extension::SymbolKind::String => Self::String,
+            extension::SymbolKind::Number => Self::Number,
+            extension::SymbolKind::Boolean => Self::Boolean,
+            extension::SymbolKind::Array => Self::Array,
+            extension::SymbolKind::Object => Self::Object,
+            extension::SymbolKind::Key => Self::Key,
+            extension::SymbolKind::Null => Self::Null,
+            extension::SymbolKind::EnumMember => Self::EnumMember,
+            extension::SymbolKind::Struct => Self::Struct,
+            extension::SymbolKind::Event => Self::Event,
+            extension::SymbolKind::Operator => Self::Operator,
+            extension::SymbolKind::TypeParameter => Self::TypeParameter,
+            extension::SymbolKind::Other(value) => Self::Other(value),
+        }
+    }
+}
+
 impl From<extension::SlashCommand> for SlashCommand {
     fn from(value: extension::SlashCommand) -> Self {
         Self {

crates/indexed_docs/src/extension_indexed_docs_provider.rs 🔗

@@ -26,7 +26,6 @@ impl IndexedDocsProvider for ExtensionIndexedDocsProvider {
 
     fn database_path(&self) -> PathBuf {
         let mut database_path = PathBuf::from(self.extension.work_dir().as_ref());
-        database_path.push(self.extension.manifest().id.as_ref());
         database_path.push("docs");
         database_path.push(format!("{}.0.mdb", self.id));