@@ -77,7 +77,7 @@ const CURRENT_SCHEMA_VERSION: SchemaVersion = SchemaVersion(1);
///
/// These snippets should no longer be downloaded or loaded, because their
/// functionality has been integrated into the core editor.
-const SUPPRESSED_EXTENSIONS: &[&str] = &["snippets", "ruff", "ty", "basedpyright"];
+const SUPPRESSED_EXTENSIONS: &[&str] = &["snippets", "ruff", "ty", "basedpyright", "basher"];
/// Returns the [`SchemaVersion`] range that is compatible with this version of Zed.
pub fn schema_version_range() -> RangeInclusive<SchemaVersion> {
@@ -1,5 +1,14 @@
+use anyhow::Result;
+use async_trait::async_trait;
+use collections::HashMap;
+use language::{LanguageServerName, LspAdapter, LspAdapterDelegate, LspInstaller, Toolchain};
+use lsp::LanguageServerBinary;
+use node_runtime::{NodeRuntime, VersionStrategy};
use project::ContextProviderWithTasks;
+use semver::Version;
+use std::{path::PathBuf, vec};
use task::{TaskTemplate, TaskTemplates, VariableName};
+use util::{ResultExt, maybe};
pub(super) fn bash_task_context() -> ContextProviderWithTasks {
ContextProviderWithTasks::new(TaskTemplates(vec![
@@ -17,6 +26,146 @@ pub(super) fn bash_task_context() -> ContextProviderWithTasks {
]))
}
+pub struct BashLspAdapter {
+ node: NodeRuntime,
+}
+
+impl BashLspAdapter {
+ const PACKAGE_NAME: &str = "bash-language-server";
+ const NODE_MODULE_RELATIVE_SERVER_PATH: &str = "bash-language-server/out/cli.js";
+
+ pub fn new(node: NodeRuntime) -> Self {
+ Self { node }
+ }
+
+ async fn get_cached_server_binary(
+ container_dir: PathBuf,
+ env: HashMap<String, String>,
+ node: &NodeRuntime,
+ ) -> Option<lsp::LanguageServerBinary> {
+ maybe!(async {
+ let server_path = container_dir
+ .join("node_modules")
+ .join(Self::NODE_MODULE_RELATIVE_SERVER_PATH);
+ anyhow::ensure!(
+ server_path.exists(),
+ "missing executable in directory {server_path:?}"
+ );
+ Ok(LanguageServerBinary {
+ path: node.binary_path().await?,
+ env: Some(env),
+ arguments: vec![server_path.into(), "start".into()],
+ })
+ })
+ .await
+ .log_err()
+ }
+}
+
+impl LspInstaller for BashLspAdapter {
+ type BinaryVersion = Version;
+
+ async fn cached_server_binary(
+ &self,
+ container_dir: std::path::PathBuf,
+ delegate: &dyn LspAdapterDelegate,
+ ) -> Option<lsp::LanguageServerBinary> {
+ let env = delegate.shell_env().await;
+ Self::get_cached_server_binary(container_dir, env, &self.node).await
+ }
+
+ async fn check_if_user_installed(
+ &self,
+ delegate: &dyn LspAdapterDelegate,
+ _: Option<Toolchain>,
+ _: &gpui::AsyncApp,
+ ) -> Option<lsp::LanguageServerBinary> {
+ let path = delegate.which(Self::PACKAGE_NAME.as_ref()).await?;
+ let env = delegate.shell_env().await;
+
+ Some(LanguageServerBinary {
+ path,
+ env: Some(env),
+ arguments: vec!["start".into()],
+ })
+ }
+
+ async fn check_if_version_installed(
+ &self,
+ version: &Self::BinaryVersion,
+ container_dir: &PathBuf,
+ delegate: &dyn LspAdapterDelegate,
+ ) -> Option<lsp::LanguageServerBinary> {
+ let server_path = container_dir
+ .join("node_modules")
+ .join(Self::NODE_MODULE_RELATIVE_SERVER_PATH);
+
+ let should_install_language_server = self
+ .node
+ .should_install_npm_package(
+ Self::PACKAGE_NAME,
+ &server_path,
+ container_dir,
+ VersionStrategy::Latest(version),
+ )
+ .await;
+
+ if should_install_language_server {
+ None
+ } else {
+ let env = delegate.shell_env().await;
+ Some(LanguageServerBinary {
+ path: self.node.binary_path().await.ok()?,
+ env: Some(env),
+ arguments: vec![server_path.into(), "start".into()],
+ })
+ }
+ }
+
+ async fn fetch_latest_server_version(
+ &self,
+ _: &dyn LspAdapterDelegate,
+ _: bool,
+ _: &mut gpui::AsyncApp,
+ ) -> Result<Self::BinaryVersion> {
+ self.node
+ .npm_package_latest_version(Self::PACKAGE_NAME)
+ .await
+ }
+
+ async fn fetch_server_binary(
+ &self,
+ latest_version: Self::BinaryVersion,
+ container_dir: std::path::PathBuf,
+ delegate: &dyn LspAdapterDelegate,
+ ) -> Result<lsp::LanguageServerBinary> {
+ let server_path = container_dir
+ .join("node_modules")
+ .join(Self::NODE_MODULE_RELATIVE_SERVER_PATH);
+
+ self.node
+ .npm_install_packages(
+ &container_dir,
+ &[(Self::PACKAGE_NAME, &latest_version.to_string())],
+ )
+ .await?;
+
+ let env = delegate.shell_env().await;
+ Ok(LanguageServerBinary {
+ path: self.node.binary_path().await?,
+ env: Some(env),
+ arguments: vec![server_path.into(), "start".into()],
+ })
+ }
+}
+
+#[async_trait(?Send)]
+impl LspAdapter for BashLspAdapter {
+ fn name(&self) -> LanguageServerName {
+ LanguageServerName::new_static(Self::PACKAGE_NAME)
+ }
+}
+
#[cfg(test)]
mod tests {
use gpui::{AppContext as _, BorrowAppContext, Context, TestAppContext};
@@ -57,6 +57,7 @@ pub fn init(languages: Arc<LanguageRegistry>, fs: Arc<dyn Fs>, node: NodeRuntime
#[cfg(feature = "load-grammars")]
languages.register_native_grammars(grammars::native_grammars());
+ let bash_lsp_adapter = Arc::new(bash::BashLspAdapter::new(node.clone()));
let c_lsp_adapter = Arc::new(c::CLspAdapter);
let css_lsp_adapter = Arc::new(css::CssLspAdapter::new(node.clone()));
let eslint_adapter = Arc::new(eslint::EsLintLspAdapter::new(node.clone(), fs.clone()));
@@ -88,6 +89,7 @@ pub fn init(languages: Arc<LanguageRegistry>, fs: Arc<dyn Fs>, node: NodeRuntime
LanguageInfo {
name: "bash",
context: Some(Arc::new(bash::bash_task_context())),
+ adapters: vec![bash_lsp_adapter],
..Default::default()
},
LanguageInfo {