Merge pull request #1 from pseudomata/add-haskell-grammar

Pseudomata created

Add haskell grammar

Change summary

Cargo.lock                                      |  10 
Cargo.toml                                      |   1 
crates/zed/Cargo.toml                           |   1 
crates/zed/src/languages.rs                     |   2 
crates/zed/src/languages/haskell.rs             |   1 
crates/zed/src/languages/haskell/brackets.scm   |   3 
crates/zed/src/languages/haskell/config.toml    |  13 
crates/zed/src/languages/haskell/folds.scm      |  18 
crates/zed/src/languages/haskell/highlights.scm | 632 +++++++++++++++++++
crates/zed/src/languages/haskell/indents.scm    |   3 
crates/zed/src/languages/haskell/injections.scm |  89 ++
11 files changed, 773 insertions(+)

Detailed changes

Cargo.lock 🔗

@@ -8455,6 +8455,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"
@@ -9720,6 +9729,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,7 @@ pub fn init(
             );
         }
     }
+    language("haskell", tree_sitter_haskell::language(), vec![]);
     language(
         "html",
         tree_sitter_html::language(),

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/folds.scm 🔗

@@ -0,0 +1,18 @@
+;; 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.
+[
+  (exp_apply)
+  (exp_do)
+  (function)
+] @fold

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

@@ -0,0 +1,632 @@
+;; 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.
+; ----------------------------------------------------------------------------
+; Parameters and variables
+; NOTE: These are at the top, so that they have low priority,
+; and don't override destructured parameters
+(variable) @variable
+
+(pat_wildcard) @variable
+
+(function
+  patterns:
+    (patterns
+      (_) @variable.parameter))
+
+(exp_lambda
+  (_)+ @variable.parameter
+  "->")
+
+(function
+  infix:
+    (infix
+      lhs: (_) @variable.parameter))
+
+(function
+  infix:
+    (infix
+      rhs: (_) @variable.parameter))
+
+; ----------------------------------------------------------------------------
+; Literals and comments
+(integer) @number
+
+(exp_negation) @number
+
+(exp_literal
+  (float)) @number.float
+
+(char) @character
+
+(string) @string
+
+(con_unit) @string.special.symbol ; unit, as in ()
+
+(comment) @comment
+
+; FIXME: The below documentation comment queries are inefficient
+; and need to be anchored, using something like
+; ((comment) @_first . (comment)+ @comment.documentation)
+; once https://github.com/neovim/neovim/pull/24738 has been merged.
+;
+; ((comment) @comment.documentation
+;   (#lua-match? @comment.documentation "^-- |"))
+;
+; ((comment) @_first @comment.documentation
+;  (comment) @comment.documentation
+;   (#lua-match? @_first "^-- |"))
+;
+; ((comment) @comment.documentation
+;   (#lua-match? @comment.documentation "^-- %^"))
+;
+; ((comment) @_first @comment.documentation
+;  (comment) @comment.documentation
+;   (#lua-match? @_first "^-- %^"))
+;
+; ((comment) @comment.documentation
+;   (#lua-match? @comment.documentation "^{-"))
+;
+; ((comment) @_first @comment.documentation
+;  (comment) @comment.documentation
+;   (#lua-match? @_first "^{-"))
+; ----------------------------------------------------------------------------
+; Punctuation
+[
+  "("
+  ")"
+  "{"
+  "}"
+  "["
+  "]"
+] @punctuation.bracket
+
+[
+  (comma)
+  ";"
+] @punctuation.delimiter
+
+; ----------------------------------------------------------------------------
+; Keywords, operators, includes
+[
+  "forall"
+  "∀"
+] @keyword.repeat
+
+(pragma) @keyword.directive
+
+[
+  "if"
+  "then"
+  "else"
+  "case"
+  "of"
+] @keyword.conditional
+
+[
+  "import"
+  "qualified"
+  "module"
+] @keyword.import
+
+[
+  (operator)
+  (constructor_operator)
+  (type_operator)
+  (tycon_arrow)
+  (qualified_module) ; grabs the `.` (dot), ex: import System.IO
+  (qualified_type)
+  (qualified_variable)
+  (all_names)
+  (wildcard)
+  "."
+  ".."
+  "="
+  "|"
+  "::"
+  "=>"
+  "->"
+  "<-"
+  "\\"
+  "`"
+  "@"
+] @operator
+
+(module) @module
+
+((qualified_module
+  (module) @constructor)
+  .
+  (module))
+
+(qualified_type
+  (module) @module)
+
+(qualified_variable
+  (module) @module)
+
+(import
+  (module) @module)
+
+(import
+  (module) @constructor
+  .
+  (module))
+
+[
+  (where)
+  "let"
+  "in"
+  "class"
+  "instance"
+  "pattern"
+  "data"
+  "newtype"
+  "family"
+  "type"
+  "as"
+  "hiding"
+  "deriving"
+  "via"
+  "stock"
+  "anyclass"
+  "do"
+  "mdo"
+  "rec"
+  "infix"
+  "infixl"
+  "infixr"
+] @keyword
+
+; ----------------------------------------------------------------------------
+; Functions and variables
+(signature
+  name: (variable) @function)
+
+(function
+  name: (variable) @function)
+
+(function
+  name: (variable) @variable
+  rhs:
+    [
+      (exp_literal)
+      (exp_apply
+        (exp_name
+          [
+            (constructor)
+            (variable)
+            (qualified_variable)
+          ]))
+      (quasiquote)
+      ((exp_name)
+        .
+        (operator))
+    ])
+
+(function
+  name: (variable) @variable
+  rhs:
+    (exp_infix
+      [
+        (exp_literal)
+        (exp_apply
+          (exp_name
+            [
+              (constructor)
+              (variable)
+              (qualified_variable)
+            ]))
+        (quasiquote)
+        ((exp_name)
+          .
+          (operator))
+      ]))
+
+; Consider signatures (and accompanying functions)
+; with only one value on the rhs as variables
+(signature
+  .
+  (variable) @variable
+  .
+  (_) .)
+
+((signature
+  .
+  (variable) @_name
+  .
+  (_) .)
+  .
+  (function
+    name: (variable) @variable)
+  (#eq? @_name @variable))
+
+; but consider a type that involves 'IO' a function
+(signature
+  name: (variable) @function
+  .
+  (type_apply
+    (type_name) @_type)
+  (#eq? @_type "IO"))
+
+((signature
+  name: (variable) @_name
+  .
+  (type_apply
+    (type_name) @_type)
+  (#eq? @_type "IO"))
+  .
+  (function
+    name: (variable) @function)
+  (#eq? @_name @function))
+
+; functions with parameters
+; + accompanying signatures
+(function
+  name: (variable) @function
+  patterns: (patterns))
+
+((signature) @function
+  .
+  (function
+    name: (variable) @function
+    patterns: (patterns)))
+
+(function
+  name: (variable) @function
+  rhs: (exp_lambda))
+
+; view patterns
+(pat_view
+  (exp_name
+    [
+      (variable) @function.call
+      (qualified_variable
+        (variable) @function.call)
+    ]))
+
+; consider infix functions as operators
+(exp_infix
+  [
+    (variable) @operator
+    (qualified_variable
+      (variable) @operator)
+  ])
+
+; partially applied infix functions (sections) also get highlighted as operators
+(exp_section_right
+  [
+    (variable) @operator
+    (qualified_variable
+      (variable) @operator)
+  ])
+
+(exp_section_left
+  [
+    (variable) @operator
+    (qualified_variable
+      (variable) @operator)
+  ])
+
+; function calls with an infix operator
+; e.g. func <$> a <*> b
+(exp_infix
+  (exp_name
+    [
+      (variable) @function.call
+      (qualified_variable
+        ((module) @module
+          (variable) @function.call))
+    ])
+  .
+  (operator))
+
+; infix operators applied to variables
+((exp_name
+  (variable) @variable)
+  .
+  (operator))
+
+((operator)
+  .
+  (exp_name
+    [
+      (variable) @variable
+      (qualified_variable
+        (variable) @variable)
+    ]))
+
+; function calls with infix operators
+((exp_name
+  [
+    (variable) @function.call
+    (qualified_variable
+      (variable) @function.call)
+  ])
+  .
+  (operator) @_op
+  (#any-of? @_op "$" "<$>" ">>=" "=<<"))
+
+; right hand side of infix operator
+((exp_infix
+  [
+    (operator)
+    (variable)
+  ] ; infix or `func`
+  .
+  (exp_name
+    [
+      (variable) @function.call
+      (qualified_variable
+        (variable) @function.call)
+    ]))
+  .
+  (operator) @_op
+  (#any-of? @_op "$" "<$>" "=<<"))
+
+; function composition, arrows, monadic composition (lhs)
+((exp_name
+  [
+    (variable) @function
+    (qualified_variable
+      (variable) @function)
+  ])
+  .
+  (operator) @_op
+  (#any-of? @_op "." ">>>" "***" ">=>" "<=<"))
+
+; right hand side of infix operator
+((exp_infix
+  [
+    (operator)
+    (variable)
+  ] ; infix or `func`
+  .
+  (exp_name
+    [
+      (variable) @function
+      (qualified_variable
+        (variable) @function)
+    ]))
+  .
+  (operator) @_op
+  (#any-of? @_op "." ">>>" "***" ">=>" "<=<"))
+
+; function composition, arrows, monadic composition (rhs)
+((operator) @_op
+  .
+  (exp_name
+    [
+      (variable) @function
+      (qualified_variable
+        (variable) @function)
+    ])
+  (#any-of? @_op "." ">>>" "***" ">=>" "<=<"))
+
+; function defined in terms of a function composition
+(function
+  name: (variable) @function
+  rhs:
+    (exp_infix
+      (_)
+      .
+      (operator) @_op
+      .
+      (_)
+      (#any-of? @_op "." ">>>" "***" ">=>" "<=<")))
+
+(exp_apply
+  (exp_name
+    [
+      (variable) @function.call
+      (qualified_variable
+        (variable) @function.call)
+    ]))
+
+; function compositions, in parentheses, applied
+; lhs
+(exp_apply
+  .
+  (exp_parens
+    (exp_infix
+      (exp_name
+        [
+          (variable) @function.call
+          (qualified_variable
+            (variable) @function.call)
+        ])
+      .
+      (operator))))
+
+; rhs
+(exp_apply
+  .
+  (exp_parens
+    (exp_infix
+      (operator)
+      .
+      (exp_name
+        [
+          (variable) @function.call
+          (qualified_variable
+            (variable) @function.call)
+        ]))))
+
+; variables being passed to a function call
+(exp_apply
+  (_)+
+  .
+  (exp_name
+    [
+      (variable) @variable
+      (qualified_variable
+        (variable) @variable)
+    ]))
+
+; Consider functions with only one value on the rhs
+; as variables, e.g. x = Rec {} or x = foo
+(function
+  .
+  (variable) @variable
+  .
+  [
+    (exp_record)
+    (exp_name
+      [
+        (variable)
+        (qualified_variable)
+      ])
+    (exp_list)
+    (exp_tuple)
+    (exp_cond)
+  ] .)
+
+; main is always a function
+; (this prevents `main = undefined` from being highlighted as a variable)
+(function
+  name: (variable) @function
+  (#eq? @function "main"))
+
+; scoped function types (func :: a -> b)
+(pat_typed
+  pattern:
+    (pat_name
+      (variable) @function)
+  type: (fun))
+
+; signatures that have a function type
+; + functions that follow them
+(signature
+  (variable) @function
+  (fun))
+
+((signature
+  (variable) @_type
+  (fun))
+  .
+  (function
+    (variable) @function)
+  (#eq? @function @_type))
+
+(signature
+  (variable) @function
+  (context
+    (fun)))
+
+((signature
+  (variable) @_type
+  (context
+    (fun)))
+  .
+  (function
+    (variable) @function)
+  (#eq? @function @_type))
+
+((signature
+  (variable) @function
+  (forall
+    (context
+      (fun))))
+  .
+  (function
+    (variable)))
+
+((signature
+  (variable) @_type
+  (forall
+    (context
+      (fun))))
+  .
+  (function
+    (variable) @function)
+  (#eq? @function @_type))
+
+; ----------------------------------------------------------------------------
+; Types
+(type) @type
+
+(type_star) @type
+
+(type_variable) @type
+
+(constructor) @constructor
+
+; True or False
+((constructor) @boolean
+  (#any-of? @boolean "True" "False"))
+
+; otherwise (= True)
+((variable) @boolean
+  (#eq? @boolean "otherwise"))
+
+; ----------------------------------------------------------------------------
+; Quasi-quotes
+(quoter) @function.call
+
+(quasiquote
+  [
+    (quoter) @_name
+    (_
+      (variable) @_name)
+  ]
+  (#eq? @_name "qq")
+  (quasiquote_body) @string)
+
+(quasiquote
+  (_
+    (variable) @_name)
+  (#eq? @_name "qq")
+  (quasiquote_body) @string)
+
+; namespaced quasi-quoter
+(quasiquote
+  (_
+    (module) @module
+    .
+    (variable) @function.call))
+
+; Highlighting of quasiquote_body for other languages is handled by injections.scm
+; ----------------------------------------------------------------------------
+; Exceptions/error handling
+((variable) @keyword.exception
+  (#any-of? @keyword.exception "error" "undefined" "try" "tryJust" "tryAny" "catch" "catches" "catchJust" "handle" "handleJust" "throw" "throwIO" "throwTo" "throwError" "ioError" "mask" "mask_" "uninterruptibleMask" "uninterruptibleMask_" "bracket" "bracket_" "bracketOnErrorSource" "finally" "fail" "onException" "expectationFailure"))
+
+; ----------------------------------------------------------------------------
+; Debugging
+((variable) @keyword.debug
+  (#any-of? @keyword.debug "trace" "traceId" "traceShow" "traceShowId" "traceWith" "traceShowWith" "traceStack" "traceIO" "traceM" "traceShowM" "traceEvent" "traceEventWith" "traceEventIO" "flushEventLog" "traceMarker" "traceMarkerIO"))
+
+; ----------------------------------------------------------------------------
+; Fields
+(field
+  (variable) @variable.member)
+
+(pat_field
+  (variable) @variable.member)
+
+(exp_projection
+  field: (variable) @variable.member)
+
+(import_item
+  (type)
+  .
+  (import_con_names
+    (variable) @variable.member))
+
+(exp_field
+  field:
+    [
+      (variable) @variable.member
+      (qualified_variable
+        (variable) @variable.member)
+    ])

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

@@ -0,0 +1,89 @@
+;; 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.
+; -----------------------------------------------------------------------------
+; General language injection
+(quasiquote
+  (quoter) @injection.language
+  (quasiquote_body) @injection.content)
+
+((comment) @injection.content
+  (#set! injection.language "comment"))
+
+; -----------------------------------------------------------------------------
+; shakespeare library
+; NOTE: doesn't support templating
+; TODO: add once CoffeeScript parser is added
+; ; CoffeeScript: Text.Coffee
+; (quasiquote
+;  (quoter) @_name
+;  (#eq? @_name "coffee")
+;  ((quasiquote_body) @injection.content
+;   (#set! injection.language "coffeescript")))
+; CSS: Text.Cassius, Text.Lucius
+(quasiquote
+  (quoter) @_name
+  (#any-of? @_name "cassius" "lucius")
+  (quasiquote_body) @injection.content
+  (#set! injection.language "css"))
+
+; HTML: Text.Hamlet
+(quasiquote
+  (quoter) @_name
+  (#any-of? @_name "shamlet" "xshamlet" "hamlet" "xhamlet" "ihamlet")
+  (quasiquote_body) @injection.content
+  (#set! injection.language "html"))
+
+; JS: Text.Julius
+(quasiquote
+  (quoter) @_name
+  (#any-of? @_name "js" "julius")
+  (quasiquote_body) @injection.content
+  (#set! injection.language "javascript"))
+
+; TS: Text.TypeScript
+(quasiquote
+  (quoter) @_name
+  (#any-of? @_name "tsc" "tscJSX")
+  (quasiquote_body) @injection.content
+  (#set! injection.language "typescript"))
+
+; -----------------------------------------------------------------------------
+; HSX
+(quasiquote
+  (quoter) @_name
+  (#eq? @_name "hsx")
+  (quasiquote_body) @injection.content
+  (#set! injection.language "html"))
+
+; -----------------------------------------------------------------------------
+; Inline JSON from aeson
+(quasiquote
+  (quoter) @_name
+  (#eq? @_name "aesonQQ")
+  (quasiquote_body) @injection.content
+  (#set! injection.language "json"))
+
+; -----------------------------------------------------------------------------
+; SQL
+; postgresql-simple
+(quasiquote
+  (quoter) @injection.language
+  (#eq? @injection.language "sql")
+  (quasiquote_body) @injection.content)
+
+(quasiquote
+  (quoter) @_name
+  (#any-of? @_name "persistUpperCase" "persistLowerCase" "persistWith")
+  (quasiquote_body) @injection.content
+  (#set! injection.language "haskell_persistent"))