Extract Erlang support into an extension (#9974)

Marshall Bowers created

This PR extracts Erlang support into an extension and removes the
built-in Erlang support from Zed.

Tested using a Nix shell:

```
nix-shell -p erlang-ls
```

Release Notes:

- Removed built-in support for Erlang, in favor of making it available
as an extension. The Erlang extension will be suggested for download
when you open a `.erl` or `.hrl` file.

Change summary

Cargo.lock                                        | 18 ++---
Cargo.toml                                        |  2 
crates/extensions_ui/src/extension_suggest.rs     |  2 
crates/languages/Cargo.toml                       |  1 
crates/languages/src/erlang.rs                    | 56 -----------------
crates/languages/src/erlang/folds.scm             |  9 --
crates/languages/src/lib.rs                       |  3 
extensions/erlang/Cargo.toml                      | 16 ++++
extensions/erlang/LICENSE-APACHE                  |  1 
extensions/erlang/extension.toml                  | 15 ++++
extensions/erlang/languages/erlang/brackets.scm   |  0 
extensions/erlang/languages/erlang/config.toml    |  0 
extensions/erlang/languages/erlang/highlights.scm |  0 
extensions/erlang/languages/erlang/indents.scm    |  0 
extensions/erlang/languages/erlang/outline.scm    |  0 
extensions/erlang/src/erlang.rs                   | 27 ++++++++
16 files changed, 69 insertions(+), 81 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -5455,7 +5455,6 @@ dependencies = [
  "tree-sitter-elixir",
  "tree-sitter-elm",
  "tree-sitter-embedded-template",
- "tree-sitter-erlang",
  "tree-sitter-glsl",
  "tree-sitter-go",
  "tree-sitter-gomod",
@@ -10440,16 +10439,6 @@ dependencies = [
  "tree-sitter",
 ]
 
-[[package]]
-name = "tree-sitter-erlang"
-version = "0.4.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "93ced5145ebb17f83243bf055b74e108da7cc129e12faab4166df03f59b287f4"
-dependencies = [
- "cc",
- "tree-sitter",
-]
-
 [[package]]
 name = "tree-sitter-glsl"
 version = "0.1.4"
@@ -12677,6 +12666,13 @@ dependencies = [
  "zed_extension_api 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
+[[package]]
+name = "zed_erlang"
+version = "0.0.1"
+dependencies = [
+ "zed_extension_api 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
 [[package]]
 name = "zed_extension_api"
 version = "0.0.4"

Cargo.toml 🔗

@@ -100,6 +100,7 @@ members = [
 
     "extensions/astro",
     "extensions/csharp",
+    "extensions/erlang",
     "extensions/gleam",
     "extensions/haskell",
     "extensions/php",
@@ -306,7 +307,6 @@ tree-sitter-dart = { git = "https://github.com/agent3bood/tree-sitter-dart", rev
 tree-sitter-elixir = { git = "https://github.com/elixir-lang/tree-sitter-elixir", rev = "a2861e88a730287a60c11ea9299c033c7d076e30" }
 tree-sitter-elm = { git = "https://github.com/elm-tooling/tree-sitter-elm", rev = "692c50c0b961364c40299e73c1306aecb5d20f40" }
 tree-sitter-embedded-template = "0.20.0"
-tree-sitter-erlang = "0.4.0"
 tree-sitter-glsl = { git = "https://github.com/theHamsta/tree-sitter-glsl", rev = "2a56fb7bc8bb03a1892b4741279dd0a8758b7fb3" }
 tree-sitter-go = { git = "https://github.com/tree-sitter/tree-sitter-go", rev = "aeb2f33b366fd78d5789ff104956ce23508b85db" }
 tree-sitter-gomod = { git = "https://github.com/camdencheek/tree-sitter-go-mod" }

crates/extensions_ui/src/extension_suggest.rs 🔗

@@ -19,6 +19,8 @@ fn suggested_extensions() -> &'static HashMap<&'static str, Arc<str>> {
             ("csharp", "cs"),
             ("dockerfile", "Dockerfile"),
             ("elisp", "el"),
+            ("erlang", "erl"),
+            ("erlang", "hrl"),
             ("fish", "fish"),
             ("git-firefly", ".gitconfig"),
             ("git-firefly", ".gitignore"),

crates/languages/Cargo.toml 🔗

@@ -45,7 +45,6 @@ tree-sitter-dart.workspace = true
 tree-sitter-elixir.workspace = true
 tree-sitter-elm.workspace = true
 tree-sitter-embedded-template.workspace = true
-tree-sitter-erlang.workspace = true
 tree-sitter-glsl.workspace = true
 tree-sitter-go.workspace = true
 tree-sitter-gomod.workspace = true

crates/languages/src/erlang.rs 🔗

@@ -1,56 +0,0 @@
-use anyhow::{anyhow, Result};
-use async_trait::async_trait;
-use language::{LanguageServerName, LspAdapter, LspAdapterDelegate};
-use lsp::LanguageServerBinary;
-use std::{any::Any, path::PathBuf};
-
-pub struct ErlangLspAdapter;
-
-#[async_trait(?Send)]
-impl LspAdapter for ErlangLspAdapter {
-    fn name(&self) -> LanguageServerName {
-        LanguageServerName("erlang_ls".into())
-    }
-
-    async fn fetch_latest_server_version(
-        &self,
-        _: &dyn LspAdapterDelegate,
-    ) -> Result<Box<dyn 'static + Send + Any>> {
-        Ok(Box::new(()) as Box<_>)
-    }
-
-    async fn fetch_server_binary(
-        &self,
-        _version: Box<dyn 'static + Send + Any>,
-        _container_dir: PathBuf,
-        _: &dyn LspAdapterDelegate,
-    ) -> Result<LanguageServerBinary> {
-        Err(anyhow!(
-            "erlang_ls must be installed and available in your $PATH"
-        ))
-    }
-
-    async fn cached_server_binary(
-        &self,
-        _: PathBuf,
-        _: &dyn LspAdapterDelegate,
-    ) -> Option<LanguageServerBinary> {
-        Some(LanguageServerBinary {
-            path: "erlang_ls".into(),
-            env: None,
-            arguments: vec![],
-        })
-    }
-
-    fn can_be_reinstalled(&self) -> bool {
-        false
-    }
-
-    async fn installation_test_binary(&self, _: PathBuf) -> Option<LanguageServerBinary> {
-        Some(LanguageServerBinary {
-            path: "erlang_ls".into(),
-            env: None,
-            arguments: vec!["--version".into()],
-        })
-    }
-}

crates/languages/src/lib.rs 🔗

@@ -18,7 +18,6 @@ mod dart;
 mod deno;
 mod elixir;
 mod elm;
-mod erlang;
 mod go;
 mod html;
 mod json;
@@ -68,7 +67,6 @@ pub fn init(
             "embedded_template",
             tree_sitter_embedded_template::language(),
         ),
-        ("erlang", tree_sitter_erlang::language()),
         ("glsl", tree_sitter_glsl::language()),
         ("go", tree_sitter_go::language()),
         ("gomod", tree_sitter_gomod::language()),
@@ -199,7 +197,6 @@ pub fn init(
             );
         }
     }
-    language!("erlang", vec![Arc::new(erlang::ErlangLspAdapter)]);
     language!("go", vec![Arc::new(go::GoLspAdapter)]);
     language!("gomod");
     language!("gowork");

extensions/erlang/Cargo.toml 🔗

@@ -0,0 +1,16 @@
+[package]
+name = "zed_erlang"
+version = "0.0.1"
+edition = "2021"
+publish = false
+license = "Apache-2.0"
+
+[lints]
+workspace = true
+
+[lib]
+path = "src/erlang.rs"
+crate-type = ["cdylib"]
+
+[dependencies]
+zed_extension_api = "0.0.4"

extensions/erlang/extension.toml 🔗

@@ -0,0 +1,15 @@
+id = "erlang"
+name = "Erlang"
+description = "Erlang support."
+version = "0.0.1"
+schema_version = 1
+authors = ["Dairon M <dairon.medina@gmail.com>"]
+repository = "https://github.com/zed-industries/zed"
+
+[language_servers.erlang-ls]
+name = "Erlang Language Server"
+language = "Erlang"
+
+[grammars.erlang]
+repository = "https://github.com/WhatsApp/tree-sitter-erlang"
+commit = "57e69513efd831f9cc8207d65d96bad917ca4aa4"

extensions/erlang/src/erlang.rs 🔗

@@ -0,0 +1,27 @@
+use zed_extension_api::{self as zed, Result};
+
+struct ErlangExtension;
+
+impl zed::Extension for ErlangExtension {
+    fn new() -> Self {
+        Self
+    }
+
+    fn language_server_command(
+        &mut self,
+        _config: zed::LanguageServerConfig,
+        worktree: &zed::Worktree,
+    ) -> Result<zed::Command> {
+        let path = worktree
+            .which("erlang_ls")
+            .ok_or_else(|| "erlang_ls must be installed and available on your $PATH".to_string())?;
+
+        Ok(zed::Command {
+            command: path,
+            args: Vec::new(),
+            env: Default::default(),
+        })
+    }
+}
+
+zed::register_extension!(ErlangExtension);