Add Haskell support (#5281) (#6786)

Mikayla Maki created

This PR adds the Haskell tree-sitter grammar copied from
[`nvim-treesitter`](https://github.com/nvim-treesitter/nvim-treesitter/tree/master/queries/haskell).
It also adds the Haskell Language Server.

This is a joint effort by myself (adding the grammar) and @leifu1128
(who is adding the language server integration).

This PR resolves https://github.com/zed-industries/zed/issues/5281

Release Notes:

- Added Haskell support
([#5281](https://github.com/zed-industries/zed/issues/5281)).

Change summary

Cargo.lock                                      |  10 +
Cargo.toml                                      |   1 
crates/zed/Cargo.toml                           |   1 
crates/zed/src/languages.rs                     |   7 
crates/zed/src/languages/haskell.rs             |  55 ++++++
crates/zed/src/languages/haskell/brackets.scm   |   3 
crates/zed/src/languages/haskell/config.toml    |  13 +
crates/zed/src/languages/haskell/highlights.scm | 156 +++++++++++++++++++
crates/zed/src/languages/haskell/indents.scm    |   3 
9 files changed, 249 insertions(+)

Detailed changes

Cargo.lock 🔗

@@ -8457,6 +8457,15 @@ dependencies = [
  "tree-sitter",
 ]
 
+[[package]]
+name = "tree-sitter-haskell"
+version = "0.14.0"
+source = "git+https://github.com/tree-sitter/tree-sitter-haskell?rev=dd924b8df1eb76261f009e149fc6f3291c5081c2#dd924b8df1eb76261f009e149fc6f3291c5081c2"
+dependencies = [
+ "cc",
+ "tree-sitter",
+]
+
 [[package]]
 name = "tree-sitter-heex"
 version = "0.0.1"
@@ -9722,6 +9731,7 @@ dependencies = [
  "tree-sitter-gleam",
  "tree-sitter-glsl",
  "tree-sitter-go",
+ "tree-sitter-haskell",
  "tree-sitter-heex",
  "tree-sitter-html",
  "tree-sitter-json 0.20.0",

Cargo.toml 🔗

@@ -151,6 +151,7 @@ tree-sitter-python = "0.20.2"
 tree-sitter-toml = { git = "https://github.com/tree-sitter/tree-sitter-toml", rev = "342d9be207c2dba869b9967124c679b5e6fd0ebe" }
 tree-sitter-typescript = { git = "https://github.com/tree-sitter/tree-sitter-typescript", rev = "5d20856f34315b068c41edaee2ac8a100081d259" }
 tree-sitter-ruby = "0.20.0"
+tree-sitter-haskell = { git = "https://github.com/tree-sitter/tree-sitter-haskell", rev = "dd924b8df1eb76261f009e149fc6f3291c5081c2" }
 tree-sitter-html = "0.19.0"
 tree-sitter-scheme = { git = "https://github.com/6cdh/tree-sitter-scheme", rev = "af0fd1fa452cb2562dc7b5c8a8c55551c39273b9"}
 tree-sitter-svelte = { git = "https://github.com/Himujjal/tree-sitter-svelte", rev = "697bb515471871e85ff799ea57a76298a71a9cca"}

crates/zed/Cargo.toml 🔗

@@ -131,6 +131,7 @@ tree-sitter-python.workspace = true
 tree-sitter-toml.workspace = true
 tree-sitter-typescript.workspace = true
 tree-sitter-ruby.workspace = true
+tree-sitter-haskell.workspace = true
 tree-sitter-html.workspace = true
 tree-sitter-php.workspace = true
 tree-sitter-scheme.workspace = true

crates/zed/src/languages.rs 🔗

@@ -15,6 +15,7 @@ mod deno;
 mod elixir;
 mod gleam;
 mod go;
+mod haskell;
 mod html;
 mod json;
 #[cfg(feature = "plugin_runtime")]
@@ -201,6 +202,12 @@ pub fn init(
             );
         }
     }
+
+    language(
+        "haskell",
+        tree_sitter_haskell::language(),
+        vec![Arc::new(haskell::HaskellLanguageServer {})],
+    );
     language(
         "html",
         tree_sitter_html::language(),

crates/zed/src/languages/haskell.rs 🔗

@@ -0,0 +1,55 @@
+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]
+impl LspAdapter for HaskellLanguageServer {
+    fn name(&self) -> LanguageServerName {
+        LanguageServerName("hls".into())
+    }
+
+    fn short_name(&self) -> &'static str {
+        "hls"
+    }
+
+    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(),
+            arguments: vec!["lsp".into()],
+        })
+    }
+
+    fn can_be_reinstalled(&self) -> bool {
+        false
+    }
+
+    async fn installation_test_binary(&self, _: PathBuf) -> Option<LanguageServerBinary> {
+        None
+    }
+}

crates/zed/src/languages/haskell/config.toml 🔗

@@ -0,0 +1,13 @@
+name = "Haskell"
+path_suffixes = ["hs"]
+autoclose_before = ",=)}]"
+line_comment = "-- "
+block_comment = ["{- ", " -}"]
+brackets = [
+    { start = "{", end = "}", close = true, newline = true },
+    { start = "[", end = "]", close = true, newline = true },
+    { start = "(", end = ")", close = true, newline = true },
+    { start = "\"", end = "\"", close = true, newline = false },
+    { start = "'", end = "'", close = true, newline = false },
+    { start = "`", end = "`", close = true, newline = false },
+]

crates/zed/src/languages/haskell/highlights.scm 🔗

@@ -0,0 +1,156 @@
+;; Copyright 2022 nvim-treesitter
+;;
+;; Licensed under the Apache License, Version 2.0 (the "License");
+;; you may not use this file except in compliance with the License.
+;; You may obtain a copy of the License at
+;;
+;;     http://www.apache.org/licenses/LICENSE-2.0
+;;
+;; Unless required by applicable law or agreed to in writing, software
+;; distributed under the License is distributed on an "AS IS" BASIS,
+;; WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+;; See the License for the specific language governing permissions and
+;; limitations under the License.
+
+;; ----------------------------------------------------------------------------
+;; Literals and comments
+
+(integer) @number
+(exp_negation) @number
+(exp_literal (float)) @float
+(char) @character
+(string) @string
+
+(con_unit) @symbol  ; unit, as in ()
+
+(comment) @comment
+
+
+;; ----------------------------------------------------------------------------
+;; Punctuation
+
+[
+  "("
+  ")"
+  "{"
+  "}"
+  "["
+  "]"
+] @punctuation.bracket
+
+[
+  (comma)
+  ";"
+] @punctuation.delimiter
+
+
+;; ----------------------------------------------------------------------------
+;; Keywords, operators, includes
+
+[
+  "forall"
+  "∀"
+] @keyword
+
+(pragma) @constant
+
+[
+  "if"
+  "then"
+  "else"
+  "case"
+  "of"
+] @keyword
+
+(exp_lambda_cases "\\" ("cases" @variant))
+
+[
+  "import"
+  "qualified"
+  "module"
+] @keyword
+
+[
+  (operator)
+  (constructor_operator)
+  (type_operator)
+  (tycon_arrow)
+  (qualified_module)  ; grabs the `.` (dot), ex: import System.IO
+  (all_names)
+  (wildcard)
+  "="
+  "|"
+  "::"
+  "=>"
+  "->"
+  "<-"
+  "\\"
+  "`"
+  "@"
+] @operator
+
+(module) @title
+
+[
+  (where)
+  "let"
+  "in"
+  "class"
+  "instance"
+  "data"
+  "newtype"
+  "family"
+  "type"
+  "as"
+  "hiding"
+  "deriving"
+  "via"
+  "stock"
+  "anyclass"
+  "do"
+  "mdo"
+  "rec"
+  "infix"
+  "infixl"
+  "infixr"
+] @keyword
+
+
+;; ----------------------------------------------------------------------------
+;; Functions and variables
+
+(variable) @variable
+(pat_wildcard) @variable
+
+(signature name: (variable) @type)
+(function
+  name: (variable) @function
+  patterns: (patterns))
+((signature (fun)) . (function (variable) @function))
+((signature (context (fun))) . (function (variable) @function))
+((signature (forall (context (fun)))) . (function (variable) @function))
+
+(exp_infix (variable) @operator)  ; consider infix functions as operators
+
+(exp_infix (exp_name) @function (#set! "priority" 101))
+(exp_apply . (exp_name (variable) @function))
+(exp_apply . (exp_name (qualified_variable (variable) @function)))
+
+
+;; ----------------------------------------------------------------------------
+;; Types
+
+(type) @type
+(type_variable) @type
+
+(constructor) @constructor
+
+; True or False
+((constructor) @_bool (#match? @_bool "(True|False)")) @boolean
+
+
+;; ----------------------------------------------------------------------------
+;; Quasi-quotes
+
+(quoter) @function
+; Highlighting of quasiquote_body is handled by injections.scm