Use Extension trait in ExtensionLspAdapter (#20704)

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
This commit is contained in:
Marshall Bowers 2024-11-14 20:44:57 -05:00 committed by GitHub
parent 332b33716a
commit 1855a312d0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 662 additions and 272 deletions

View file

@ -1,17 +1,20 @@
pub mod extension_builder; pub mod extension_builder;
mod extension_manifest; mod extension_manifest;
mod slash_command; mod types;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::sync::Arc; use std::sync::Arc;
use ::lsp::LanguageServerName;
use anyhow::{anyhow, bail, Context as _, Result}; use anyhow::{anyhow, bail, Context as _, Result};
use async_trait::async_trait; use async_trait::async_trait;
use fs::normalize_path;
use gpui::Task; use gpui::Task;
use language::LanguageName;
use semantic_version::SemanticVersion; use semantic_version::SemanticVersion;
pub use crate::extension_manifest::*; pub use crate::extension_manifest::*;
pub use crate::slash_command::*; pub use crate::types::*;
#[async_trait] #[async_trait]
pub trait WorktreeDelegate: Send + Sync + 'static { 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. /// Returns the path to this extension's working directory.
fn work_dir(&self) -> Arc<Path>; 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( async fn complete_slash_command_argument(
&self, &self,
command: SlashCommand, command: SlashCommand,
@ -44,7 +84,7 @@ pub trait Extension: Send + Sync + 'static {
&self, &self,
command: SlashCommand, command: SlashCommand,
arguments: Vec<String>, arguments: Vec<String>,
resource: Option<Arc<dyn WorktreeDelegate>>, worktree: Option<Arc<dyn WorktreeDelegate>>,
) -> Result<SlashCommandOutput>; ) -> Result<SlashCommandOutput>;
async fn suggest_docs_packages(&self, provider: Arc<str>) -> Result<Vec<String>>; async fn suggest_docs_packages(&self, provider: Arc<str>) -> Result<Vec<String>>;

View file

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

View file

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

View file

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

View file

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

View file

@ -4,8 +4,8 @@ use crate::{ExtensionManifest, ExtensionRegistrationHooks};
use anyhow::{anyhow, bail, Context as _, Result}; use anyhow::{anyhow, bail, Context as _, Result};
use async_trait::async_trait; use async_trait::async_trait;
use extension::{ use extension::{
KeyValueStoreDelegate, SlashCommand, SlashCommandArgumentCompletion, SlashCommandOutput, CodeLabel, Command, Completion, KeyValueStoreDelegate, SlashCommand,
WorktreeDelegate, SlashCommandArgumentCompletion, SlashCommandOutput, Symbol, WorktreeDelegate,
}; };
use fs::{normalize_path, Fs}; use fs::{normalize_path, Fs};
use futures::future::LocalBoxFuture; use futures::future::LocalBoxFuture;
@ -19,6 +19,8 @@ use futures::{
}; };
use gpui::{AppContext, AsyncAppContext, BackgroundExecutor, Task}; use gpui::{AppContext, AsyncAppContext, BackgroundExecutor, Task};
use http_client::HttpClient; use http_client::HttpClient;
use language::LanguageName;
use lsp::LanguageServerName;
use node_runtime::NodeRuntime; use node_runtime::NodeRuntime;
use release_channel::ReleaseChannel; use release_channel::ReleaseChannel;
use semantic_version::SemanticVersion; use semantic_version::SemanticVersion;
@ -65,6 +67,132 @@ impl extension::Extension for WasmExtension {
self.work_dir.clone() 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( async fn complete_slash_command_argument(
&self, &self,
command: SlashCommand, command: SlashCommand,
@ -255,7 +383,7 @@ impl WasmHost {
Ok(WasmExtension { Ok(WasmExtension {
manifest: manifest.clone(), manifest: manifest.clone(),
work_dir: this.work_dir.clone().into(), work_dir: this.work_dir.join(manifest.id.as_ref()).into(),
tx, tx,
zed_api_version, zed_api_version,
}) })
@ -286,11 +414,6 @@ impl WasmHost {
.build()) .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> { 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 extension_work_dir = self.work_dir.join(id.as_ref());
let path = normalize_path(&extension_work_dir.join(path)); let path = normalize_path(&extension_work_dir.join(path));

View file

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

View file

@ -1,4 +1,5 @@
use crate::wasm_host::wit::since_v0_2_0::slash_command::SlashCommandOutputSection; 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 crate::wasm_host::{wit::ToWasmtimeResult, WasmState};
use ::http_client::{AsyncBody, HttpRequestExt}; use ::http_client::{AsyncBody, HttpRequestExt};
use ::settings::{Settings, WorktreeId}; 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)) 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 { impl From<extension::SlashCommand> for SlashCommand {
fn from(value: extension::SlashCommand) -> Self { fn from(value: extension::SlashCommand) -> Self {
Self { Self {

View file

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