Extract Haskell support into an extension (#9814)

Marshall Bowers created

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

I tested out the extension locally in a Nix shell using `nix-shell -p
ghc haskell-language-server` to confirm the language server still
operated as expected:

<img width="341" alt="Screenshot 2024-03-26 at 11 26 26 AM"
src="https://github.com/zed-industries/zed/assets/1486634/df16fd38-4046-4a45-ac9f-c2b85bffe5c0">

Release Notes:

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

Change summary

Cargo.lock                                          | 17 ++--
Cargo.toml                                          |  2 
crates/extensions_ui/src/extension_suggest.rs       |  1 
crates/languages/Cargo.toml                         |  1 
crates/languages/src/haskell.rs                     | 52 ---------------
crates/languages/src/lib.rs                         |  3 
extensions/haskell/Cargo.toml                       | 16 ++++
extensions/haskell/LICENSE-APACHE                   |  1 
extensions/haskell/extension.toml                   | 19 +++++
extensions/haskell/languages/haskell/brackets.scm   |  0 
extensions/haskell/languages/haskell/config.toml    |  0 
extensions/haskell/languages/haskell/highlights.scm |  0 
extensions/haskell/languages/haskell/indents.scm    |  0 
extensions/haskell/languages/haskell/outline.scm    |  0 
extensions/haskell/src/haskell.rs                   | 27 +++++++
15 files changed, 72 insertions(+), 67 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -5304,7 +5304,6 @@ dependencies = [
  "tree-sitter-go",
  "tree-sitter-gomod",
  "tree-sitter-gowork",
- "tree-sitter-haskell",
  "tree-sitter-hcl",
  "tree-sitter-heex",
  "tree-sitter-html",
@@ -10287,15 +10286,6 @@ dependencies = [
  "tree-sitter",
 ]
 
-[[package]]
-name = "tree-sitter-haskell"
-version = "0.14.0"
-source = "git+https://github.com/tree-sitter/tree-sitter-haskell?rev=8a99848fc734f9c4ea523b3f2a07df133cbbcec2#8a99848fc734f9c4ea523b3f2a07df133cbbcec2"
-dependencies = [
- "cc",
- "tree-sitter",
-]
-
 [[package]]
 name = "tree-sitter-hcl"
 version = "0.0.1"
@@ -12612,6 +12602,13 @@ dependencies = [
  "zed_extension_api 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
+[[package]]
+name = "zed_haskell"
+version = "0.0.1"
+dependencies = [
+ "zed_extension_api 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
 [[package]]
 name = "zed_svelte"
 version = "0.0.1"

Cargo.toml 🔗

@@ -97,6 +97,7 @@ members = [
     "crates/zed_actions",
 
     "extensions/gleam",
+    "extensions/haskell",
     "extensions/uiua",
     "extensions/svelte",
 
@@ -299,7 +300,6 @@ tree-sitter-glsl = { git = "https://github.com/theHamsta/tree-sitter-glsl", rev
 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" }
 tree-sitter-gowork = { git = "https://github.com/d1y/tree-sitter-go-work" }
-tree-sitter-haskell = { git = "https://github.com/tree-sitter/tree-sitter-haskell", rev = "8a99848fc734f9c4ea523b3f2a07df133cbbcec2" }
 tree-sitter-hcl = { git = "https://github.com/MichaHoffmann/tree-sitter-hcl", rev = "v1.1.0" }
 rustc-demangle = "0.1.23"
 tree-sitter-heex = { git = "https://github.com/phoenixframework/tree-sitter-heex", rev = "2e1348c3cf2c9323e87c2744796cf3f3868aa82a" }

crates/extensions_ui/src/extension_suggest.rs 🔗

@@ -32,6 +32,7 @@ pub fn suggested_extension(file_extension_or_name: &str) -> Option<Arc<str>> {
                 ("gleam", "gleam"),
                 ("graphql", "gql"),
                 ("graphql", "graphql"),
+                ("haskell", "haskell"),
                 ("java", "java"),
                 ("kotlin", "kt"),
                 ("latex", "tex"),

crates/languages/Cargo.toml 🔗

@@ -53,7 +53,6 @@ tree-sitter-glsl.workspace = true
 tree-sitter-go.workspace = true
 tree-sitter-gomod.workspace = true
 tree-sitter-gowork.workspace = true
-tree-sitter-haskell.workspace = true
 tree-sitter-hcl.workspace = true
 tree-sitter-heex.workspace = true
 tree-sitter-html.workspace = true

crates/languages/src/haskell.rs 🔗

@@ -1,52 +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 HaskellLanguageServer;
-
-#[async_trait(?Send)]
-impl LspAdapter for HaskellLanguageServer {
-    fn name(&self) -> LanguageServerName {
-        LanguageServerName("hls".into())
-    }
-
-    async fn fetch_latest_server_version(
-        &self,
-        _: &dyn LspAdapterDelegate,
-    ) -> Result<Box<dyn 'static + Any + Send>> {
-        Ok(Box::new(()))
-    }
-
-    async fn fetch_server_binary(
-        &self,
-        _version: Box<dyn 'static + Send + Any>,
-        _container_dir: PathBuf,
-        _: &dyn LspAdapterDelegate,
-    ) -> Result<LanguageServerBinary> {
-        Err(anyhow!(
-            "hls (haskell language server) must be installed via ghcup"
-        ))
-    }
-
-    async fn cached_server_binary(
-        &self,
-        _: PathBuf,
-        _: &dyn LspAdapterDelegate,
-    ) -> Option<LanguageServerBinary> {
-        Some(LanguageServerBinary {
-            path: "haskell-language-server-wrapper".into(),
-            env: None,
-            arguments: vec!["lsp".into()],
-        })
-    }
-
-    fn can_be_reinstalled(&self) -> bool {
-        false
-    }
-
-    async fn installation_test_binary(&self, _: PathBuf) -> Option<LanguageServerBinary> {
-        None
-    }
-}

crates/languages/src/lib.rs 🔗

@@ -23,7 +23,6 @@ mod elixir;
 mod elm;
 mod erlang;
 mod go;
-mod haskell;
 mod html;
 mod json;
 mod lua;
@@ -85,7 +84,6 @@ pub fn init(
         ("go", tree_sitter_go::language()),
         ("gomod", tree_sitter_gomod::language()),
         ("gowork", tree_sitter_gowork::language()),
-        ("haskell", tree_sitter_haskell::language()),
         ("hcl", tree_sitter_hcl::language()),
         ("heex", tree_sitter_heex::language()),
         ("html", tree_sitter_html::language()),
@@ -316,7 +314,6 @@ pub fn init(
         }
     }
 
-    language!("haskell", vec![Arc::new(haskell::HaskellLanguageServer {})]);
     language!(
         "html",
         vec![

extensions/haskell/Cargo.toml 🔗

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

extensions/haskell/extension.toml 🔗

@@ -0,0 +1,19 @@
+id = "haskell"
+name = "Haskell"
+description = "Haskell support for Zed"
+version = "0.0.1"
+schema_version = 1
+authors = [
+    "Marshall Bowers <elliott.codes@gmail.com>",
+    "Pseudomata <pseudomata@proton.me>",
+    "Lei <45155667+leifu1128@users.noreply.github.com>"
+]
+repository = "https://github.com/zed-industries/zed"
+
+[language_servers.hls]
+name = "Haskell Language Server"
+language = "Haskell"
+
+[grammars.haskell]
+repository = "https://github.com/tree-sitter/tree-sitter-haskell"
+commit = "8a99848fc734f9c4ea523b3f2a07df133cbbcec2"

extensions/haskell/src/haskell.rs 🔗

@@ -0,0 +1,27 @@
+use zed_extension_api::{self as zed, Result};
+
+struct HaskellExtension;
+
+impl zed::Extension for HaskellExtension {
+    fn new() -> Self {
+        Self
+    }
+
+    fn language_server_command(
+        &mut self,
+        _config: zed::LanguageServerConfig,
+        worktree: &zed::Worktree,
+    ) -> Result<zed::Command> {
+        let path = worktree
+            .which("haskell-language-server-wrapper")
+            .ok_or_else(|| "hls must be installed via ghcup".to_string())?;
+
+        Ok(zed::Command {
+            command: path,
+            args: vec!["lsp".to_string()],
+            env: Default::default(),
+        })
+    }
+}
+
+zed::register_extension!(HaskellExtension);