mirror of
https://github.com/zed-industries/zed.git
synced 2026-06-01 03:14:56 +07:00
bash: Add built-in language server support (#52811)
This PR introduces native LSP support for Bash by integrating `bash-language-server`. Combined with the existing Tree-sitter grammar, Zed now provides a complete, out-of-the-box development experience for shell scripting. The implementation is very similar to other npm-managed language servers. With `shellcheck` installed, standard LSP features—including diagnostics, code actions, go-to-definition, find-references, and code completion—work as expected. Since I am not a frequent user of Bash, I have intentionally limited this implementation to a standard, "out-of-the-box" setup. I lack the hands-on experience to identify specific pain points or advanced LSP features that might require custom integration, so I've avoided adding any speculative or specialized configurations, especially within the `LspAdapter` trait. Self-Review Checklist: - [x] I've reviewed my own diff for quality, security, and reliability - [x] Unsafe blocks (if any) have justifying comments - [x] The content is consistent with the [UI/UX checklist](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist) - [ ] Tests cover the new/changed behavior - [x] Performance impact has been considered and is acceptable Closes #51917 Release Notes: - Added built-in language server support for Bash --------- Co-authored-by: Finn Evers <finn@zed.dev>
This commit is contained in:
parent
fb3218e01e
commit
5dd9082d05
5 changed files with 155 additions and 4 deletions
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ Some work out-of-the box and others rely on 3rd party extensions.
|
|||
- [Ansible](./languages/ansible.md)
|
||||
- [AsciiDoc](./languages/asciidoc.md)
|
||||
- [Astro](./languages/astro.md)
|
||||
- [Bash](./languages/bash.md)
|
||||
- [Bash](./languages/bash.md) \*
|
||||
- [Biome](./languages/biome.md)
|
||||
- [C](./languages/c.md) \*
|
||||
- [C++](./languages/cpp.md) \*
|
||||
|
|
|
|||
|
|
@ -5,14 +5,14 @@ description: "Configure Bash language support in Zed, including language servers
|
|||
|
||||
# Bash
|
||||
|
||||
Bash support is available through the [Bash extension](https://github.com/zed-extensions/bash).
|
||||
Bash support is available natively in Zed.
|
||||
|
||||
- Tree-sitter: [tree-sitter/tree-sitter-bash](https://github.com/tree-sitter/tree-sitter-bash)
|
||||
- Language Server: [bash-lsp/bash-language-server](https://github.com/bash-lsp/bash-language-server)
|
||||
|
||||
## Configuration
|
||||
|
||||
When `shellcheck` is available `bash-language-server` will use it internally to provide diagnostics.
|
||||
It is highly recommended to install `shellcheck`, as `bash-language-server` depends on it to provide diagnostics.
|
||||
|
||||
### Install `shellcheck`:
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue