extension: Add support for additional_workspace_configuration
and additional_initialization_options
(#27407)
Closes #22410 With this PR extensions can provide additional workspace configuration for other LSP Adapters. This allows extensions like Astro, Svelte, Vue, etc to provide plugins for vtsls typescript server, fixing issues like: https://github.com/zed-industries/zed/issues/4577, https://github.com/zed-industries/zed/issues/21697, https://github.com/zed-industries/zed/issues/26901#issuecomment-2737485096 Todo: - [x] Test case when extension is installed, does vtsls workspace config refreshes? Before: <img width="450" alt="image" src="https://github.com/user-attachments/assets/f242167c-5264-44ab-b5a7-8c90eb75c6a1" /> After: <img width="450" alt="image" src="https://github.com/user-attachments/assets/6a5f1afe-a0e1-4f64-8a95-919b0bf97614" /> Release Notes: - N/A
This commit is contained in:
parent
9468b9699e
commit
8f1023360d
9 changed files with 360 additions and 19 deletions
|
@ -76,6 +76,20 @@ pub trait Extension: Send + Sync + 'static {
|
|||
worktree: Arc<dyn WorktreeDelegate>,
|
||||
) -> Result<Option<String>>;
|
||||
|
||||
async fn language_server_additional_initialization_options(
|
||||
&self,
|
||||
language_server_id: LanguageServerName,
|
||||
target_language_server_id: LanguageServerName,
|
||||
worktree: Arc<dyn WorktreeDelegate>,
|
||||
) -> Result<Option<String>>;
|
||||
|
||||
async fn language_server_additional_workspace_configuration(
|
||||
&self,
|
||||
language_server_id: LanguageServerName,
|
||||
target_language_server_id: LanguageServerName,
|
||||
worktree: Arc<dyn WorktreeDelegate>,
|
||||
) -> Result<Option<String>>;
|
||||
|
||||
async fn labels_for_completions(
|
||||
&self,
|
||||
language_server_id: LanguageServerName,
|
||||
|
|
|
@ -93,6 +93,26 @@ pub trait Extension: Send + Sync {
|
|||
Ok(None)
|
||||
}
|
||||
|
||||
/// Returns the initialization options to pass to the other language server.
|
||||
fn language_server_additional_initialization_options(
|
||||
&mut self,
|
||||
_language_server_id: &LanguageServerId,
|
||||
_target_language_server_id: &LanguageServerId,
|
||||
_worktree: &Worktree,
|
||||
) -> Result<Option<serde_json::Value>> {
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
/// Returns the workspace configuration options to pass to the other language server.
|
||||
fn language_server_additional_workspace_configuration(
|
||||
&mut self,
|
||||
_language_server_id: &LanguageServerId,
|
||||
_target_language_server_id: &LanguageServerId,
|
||||
_worktree: &Worktree,
|
||||
) -> Result<Option<serde_json::Value>> {
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
/// Returns the label for the given completion.
|
||||
fn label_for_completion(
|
||||
&self,
|
||||
|
@ -235,6 +255,38 @@ impl wit::Guest for Component {
|
|||
.and_then(|value| serde_json::to_string(&value).ok()))
|
||||
}
|
||||
|
||||
fn language_server_additional_initialization_options(
|
||||
language_server_id: String,
|
||||
target_language_server_id: String,
|
||||
worktree: &Worktree,
|
||||
) -> Result<Option<String>, String> {
|
||||
let language_server_id = LanguageServerId(language_server_id);
|
||||
let target_language_server_id = LanguageServerId(target_language_server_id);
|
||||
Ok(extension()
|
||||
.language_server_additional_initialization_options(
|
||||
&language_server_id,
|
||||
&target_language_server_id,
|
||||
worktree,
|
||||
)?
|
||||
.and_then(|value| serde_json::to_string(&value).ok()))
|
||||
}
|
||||
|
||||
fn language_server_additional_workspace_configuration(
|
||||
language_server_id: String,
|
||||
target_language_server_id: String,
|
||||
worktree: &Worktree,
|
||||
) -> Result<Option<String>, String> {
|
||||
let language_server_id = LanguageServerId(language_server_id);
|
||||
let target_language_server_id = LanguageServerId(target_language_server_id);
|
||||
Ok(extension()
|
||||
.language_server_additional_workspace_configuration(
|
||||
&language_server_id,
|
||||
&target_language_server_id,
|
||||
worktree,
|
||||
)?
|
||||
.and_then(|value| serde_json::to_string(&value).ok()))
|
||||
}
|
||||
|
||||
fn labels_for_completions(
|
||||
language_server_id: String,
|
||||
completions: Vec<Completion>,
|
||||
|
|
|
@ -95,6 +95,12 @@ world extension {
|
|||
/// Returns the workspace configuration options to pass to the language server.
|
||||
export language-server-workspace-configuration: func(language-server-id: string, worktree: borrow<worktree>) -> result<option<string>, string>;
|
||||
|
||||
/// Returns the initialization options to pass to the other language server.
|
||||
export language-server-additional-initialization-options: func(language-server-id: string, target-language-server-id: string, worktree: borrow<worktree>) -> result<option<string>, string>;
|
||||
|
||||
/// Returns the workspace configuration options to pass to the other language server.
|
||||
export language-server-additional-workspace-configuration: func(language-server-id: string, target-language-server-id: string, worktree: borrow<worktree>) -> result<option<string>, string>;
|
||||
|
||||
/// A label containing some code.
|
||||
record code-label {
|
||||
/// The source code to parse with Tree-sitter.
|
||||
|
|
|
@ -140,6 +140,56 @@ impl extension::Extension for WasmExtension {
|
|||
.await
|
||||
}
|
||||
|
||||
async fn language_server_additional_initialization_options(
|
||||
&self,
|
||||
language_server_id: LanguageServerName,
|
||||
target_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_additional_initialization_options(
|
||||
store,
|
||||
&language_server_id,
|
||||
&target_language_server_id,
|
||||
resource,
|
||||
)
|
||||
.await?
|
||||
.map_err(|err| anyhow!("{err}"))?;
|
||||
anyhow::Ok(options)
|
||||
}
|
||||
.boxed()
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
async fn language_server_additional_workspace_configuration(
|
||||
&self,
|
||||
language_server_id: LanguageServerName,
|
||||
target_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_additional_workspace_configuration(
|
||||
store,
|
||||
&language_server_id,
|
||||
&target_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,
|
||||
|
|
|
@ -365,6 +365,58 @@ impl Extension {
|
|||
}
|
||||
}
|
||||
|
||||
pub async fn call_language_server_additional_initialization_options(
|
||||
&self,
|
||||
store: &mut Store<WasmState>,
|
||||
language_server_id: &LanguageServerName,
|
||||
target_language_server_id: &LanguageServerName,
|
||||
resource: Resource<Arc<dyn WorktreeDelegate>>,
|
||||
) -> Result<Result<Option<String>, String>> {
|
||||
match self {
|
||||
Extension::V040(ext) => {
|
||||
ext.call_language_server_additional_initialization_options(
|
||||
store,
|
||||
&language_server_id.0,
|
||||
&target_language_server_id.0,
|
||||
resource,
|
||||
)
|
||||
.await
|
||||
}
|
||||
Extension::V030(_)
|
||||
| Extension::V020(_)
|
||||
| Extension::V010(_)
|
||||
| Extension::V006(_)
|
||||
| Extension::V004(_)
|
||||
| Extension::V001(_) => Ok(Ok(None)),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn call_language_server_additional_workspace_configuration(
|
||||
&self,
|
||||
store: &mut Store<WasmState>,
|
||||
language_server_id: &LanguageServerName,
|
||||
target_language_server_id: &LanguageServerName,
|
||||
resource: Resource<Arc<dyn WorktreeDelegate>>,
|
||||
) -> Result<Result<Option<String>, String>> {
|
||||
match self {
|
||||
Extension::V040(ext) => {
|
||||
ext.call_language_server_additional_workspace_configuration(
|
||||
store,
|
||||
&language_server_id.0,
|
||||
&target_language_server_id.0,
|
||||
resource,
|
||||
)
|
||||
.await
|
||||
}
|
||||
Extension::V030(_)
|
||||
| Extension::V020(_)
|
||||
| Extension::V010(_)
|
||||
| Extension::V006(_)
|
||||
| Extension::V004(_)
|
||||
| Extension::V001(_) => Ok(Ok(None)),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn call_labels_for_completions(
|
||||
&self,
|
||||
store: &mut Store<WasmState>,
|
||||
|
|
|
@ -305,6 +305,7 @@ pub trait LspAdapterDelegate: Send + Sync {
|
|||
fn worktree_root_path(&self) -> &Path;
|
||||
fn exists(&self, path: &Path, is_dir: Option<bool>) -> bool;
|
||||
fn update_status(&self, language: LanguageServerName, status: BinaryStatus);
|
||||
fn registered_lsp_adapters(&self) -> Vec<Arc<dyn LspAdapter>>;
|
||||
async fn language_server_download_dir(&self, name: &LanguageServerName) -> Option<Arc<Path>>;
|
||||
|
||||
async fn npm_package_installed_version(
|
||||
|
@ -515,6 +516,26 @@ pub trait LspAdapter: 'static + Send + Sync {
|
|||
Ok(serde_json::json!({}))
|
||||
}
|
||||
|
||||
async fn additional_initialization_options(
|
||||
self: Arc<Self>,
|
||||
_target_language_server_id: LanguageServerName,
|
||||
_: &dyn Fs,
|
||||
_: &Arc<dyn LspAdapterDelegate>,
|
||||
) -> Result<Option<Value>> {
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
async fn additional_workspace_configuration(
|
||||
self: Arc<Self>,
|
||||
_target_language_server_id: LanguageServerName,
|
||||
_: &dyn Fs,
|
||||
_: &Arc<dyn LspAdapterDelegate>,
|
||||
_: Arc<dyn LanguageToolchainStore>,
|
||||
_cx: &mut AsyncApp,
|
||||
) -> Result<Option<Value>> {
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
/// Returns a list of code actions supported by a given LspAdapter
|
||||
fn code_action_kinds(&self) -> Option<Vec<CodeActionKind>> {
|
||||
Some(vec![
|
||||
|
|
|
@ -912,6 +912,15 @@ impl LanguageRegistry {
|
|||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
pub fn all_lsp_adapters(&self) -> Vec<Arc<CachedLspAdapter>> {
|
||||
self.state
|
||||
.read()
|
||||
.all_lsp_adapters
|
||||
.values()
|
||||
.cloned()
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn adapter_for_name(&self, name: &LanguageServerName) -> Option<Arc<CachedLspAdapter>> {
|
||||
self.state.read().all_lsp_adapters.get(name).cloned()
|
||||
}
|
||||
|
|
|
@ -267,6 +267,58 @@ impl LspAdapter for ExtensionLspAdapter {
|
|||
})
|
||||
}
|
||||
|
||||
async fn additional_initialization_options(
|
||||
self: Arc<Self>,
|
||||
target_language_server_id: LanguageServerName,
|
||||
_: &dyn Fs,
|
||||
delegate: &Arc<dyn LspAdapterDelegate>,
|
||||
) -> Result<Option<serde_json::Value>> {
|
||||
let delegate = Arc::new(WorktreeDelegateAdapter(delegate.clone())) as _;
|
||||
let json_options: Option<String> = self
|
||||
.extension
|
||||
.language_server_additional_initialization_options(
|
||||
self.language_server_id.clone(),
|
||||
target_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 additional_initialization_options from extension: {json_options}"
|
||||
)
|
||||
})?
|
||||
} else {
|
||||
None
|
||||
})
|
||||
}
|
||||
|
||||
async fn additional_workspace_configuration(
|
||||
self: Arc<Self>,
|
||||
target_language_server_id: LanguageServerName,
|
||||
_: &dyn Fs,
|
||||
delegate: &Arc<dyn LspAdapterDelegate>,
|
||||
_: Arc<dyn LanguageToolchainStore>,
|
||||
_cx: &mut AsyncApp,
|
||||
) -> Result<Option<serde_json::Value>> {
|
||||
let delegate = Arc::new(WorktreeDelegateAdapter(delegate.clone())) as _;
|
||||
let json_options: Option<String> = self
|
||||
.extension
|
||||
.language_server_additional_workspace_configuration(
|
||||
self.language_server_id.clone(),
|
||||
target_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 additional_workspace_configuration from extension: {json_options}")
|
||||
})?
|
||||
} else {
|
||||
None
|
||||
})
|
||||
}
|
||||
|
||||
async fn labels_for_completions(
|
||||
self: Arc<Self>,
|
||||
completions: &[lsp::CompletionItem],
|
||||
|
|
|
@ -251,17 +251,21 @@ impl LocalLspStore {
|
|||
let toolchains = this.update(cx, |this, cx| this.toolchain_store(cx))?;
|
||||
let language_server = pending_server.await?;
|
||||
|
||||
let workspace_config = adapter
|
||||
.adapter
|
||||
.clone()
|
||||
.workspace_configuration(fs.as_ref(), &delegate, toolchains.clone(), cx)
|
||||
.await?;
|
||||
let workspace_config = Self::workspace_configuration_for_adapter(
|
||||
adapter.adapter.clone(),
|
||||
fs.as_ref(),
|
||||
&delegate,
|
||||
toolchains.clone(),
|
||||
cx,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let mut initialization_options = adapter
|
||||
.adapter
|
||||
.clone()
|
||||
.initialization_options(fs.as_ref(), &(delegate))
|
||||
.await?;
|
||||
let mut initialization_options = Self::initialization_options_for_adapter(
|
||||
adapter.adapter.clone(),
|
||||
fs.as_ref(),
|
||||
&delegate,
|
||||
)
|
||||
.await?;
|
||||
|
||||
match (&mut initialization_options, override_options) {
|
||||
(Some(initialization_options), Some(override_options)) => {
|
||||
|
@ -478,9 +482,16 @@ impl LocalLspStore {
|
|||
async move {
|
||||
let toolchains =
|
||||
this.update(&mut cx, |this, cx| this.toolchain_store(cx))?;
|
||||
let workspace_config = adapter
|
||||
.workspace_configuration(fs.as_ref(), &delegate, toolchains, &mut cx)
|
||||
.await?;
|
||||
|
||||
let workspace_config = Self::workspace_configuration_for_adapter(
|
||||
adapter.clone(),
|
||||
fs.as_ref(),
|
||||
&delegate,
|
||||
toolchains.clone(),
|
||||
&mut cx,
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(params
|
||||
.items
|
||||
.into_iter()
|
||||
|
@ -3225,6 +3236,67 @@ impl LocalLspStore {
|
|||
|
||||
self.rebuild_watched_paths(language_server_id, cx);
|
||||
}
|
||||
|
||||
async fn initialization_options_for_adapter(
|
||||
adapter: Arc<dyn LspAdapter>,
|
||||
fs: &dyn Fs,
|
||||
delegate: &Arc<dyn LspAdapterDelegate>,
|
||||
) -> Result<Option<serde_json::Value>> {
|
||||
let Some(mut initialization_config) =
|
||||
adapter.clone().initialization_options(fs, delegate).await?
|
||||
else {
|
||||
return Ok(None);
|
||||
};
|
||||
|
||||
for other_adapter in delegate.registered_lsp_adapters() {
|
||||
if other_adapter.name() == adapter.name() {
|
||||
continue;
|
||||
}
|
||||
if let Ok(Some(target_config)) = other_adapter
|
||||
.clone()
|
||||
.additional_initialization_options(adapter.name(), fs, delegate)
|
||||
.await
|
||||
{
|
||||
merge_json_value_into(target_config.clone(), &mut initialization_config);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Some(initialization_config))
|
||||
}
|
||||
|
||||
async fn workspace_configuration_for_adapter(
|
||||
adapter: Arc<dyn LspAdapter>,
|
||||
fs: &dyn Fs,
|
||||
delegate: &Arc<dyn LspAdapterDelegate>,
|
||||
toolchains: Arc<dyn LanguageToolchainStore>,
|
||||
cx: &mut AsyncApp,
|
||||
) -> Result<serde_json::Value> {
|
||||
let mut workspace_config = adapter
|
||||
.clone()
|
||||
.workspace_configuration(fs, delegate, toolchains.clone(), cx)
|
||||
.await?;
|
||||
|
||||
for other_adapter in delegate.registered_lsp_adapters() {
|
||||
if other_adapter.name() == adapter.name() {
|
||||
continue;
|
||||
}
|
||||
if let Ok(Some(target_config)) = other_adapter
|
||||
.clone()
|
||||
.additional_workspace_configuration(
|
||||
adapter.name(),
|
||||
fs,
|
||||
delegate,
|
||||
toolchains.clone(),
|
||||
cx,
|
||||
)
|
||||
.await
|
||||
{
|
||||
merge_json_value_into(target_config.clone(), &mut workspace_config);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(workspace_config)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
|
@ -3764,8 +3836,8 @@ impl LspStore {
|
|||
for (adapter, server, delegate) in servers {
|
||||
adapter.clear_zed_json_schema_cache().await;
|
||||
|
||||
let Some(json_workspace_config) = adapter
|
||||
.workspace_configuration(
|
||||
let Some(json_workspace_config) = LocalLspStore::workspace_configuration_for_adapter(
|
||||
adapter,
|
||||
fs.as_ref(),
|
||||
&delegate,
|
||||
toolchain_store.clone(),
|
||||
|
@ -6065,10 +6137,15 @@ impl LspStore {
|
|||
|
||||
let toolchain_store = this.update(cx, |this, cx| this.toolchain_store(cx)).ok()?;
|
||||
for (adapter, server, delegate) in servers {
|
||||
let settings = adapter
|
||||
.workspace_configuration(fs.as_ref(), &delegate, toolchain_store.clone(), cx)
|
||||
.await
|
||||
.ok()?;
|
||||
let settings = LocalLspStore::workspace_configuration_for_adapter(
|
||||
adapter,
|
||||
fs.as_ref(),
|
||||
&delegate,
|
||||
toolchain_store.clone(),
|
||||
cx,
|
||||
)
|
||||
.await
|
||||
.ok()?;
|
||||
|
||||
server
|
||||
.notify::<lsp::notification::DidChangeConfiguration>(
|
||||
|
@ -9811,6 +9888,14 @@ impl LspAdapterDelegate for LocalLspAdapterDelegate {
|
|||
.update_lsp_status(server_name, status);
|
||||
}
|
||||
|
||||
fn registered_lsp_adapters(&self) -> Vec<Arc<dyn LspAdapter>> {
|
||||
self.language_registry
|
||||
.all_lsp_adapters()
|
||||
.into_iter()
|
||||
.map(|adapter| adapter.adapter.clone() as Arc<dyn LspAdapter>)
|
||||
.collect()
|
||||
}
|
||||
|
||||
async fn language_server_download_dir(&self, name: &LanguageServerName) -> Option<Arc<Path>> {
|
||||
let dir = self.language_registry.language_server_download_dir(name)?;
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue