Update Haskell grammar

Pseudomata created

Copied queries from nvim-tree-sitter, see https://github.com/nvim-treesitter/nvim-treesitter/tree/master/queries/haskell

Change summary

crates/zed/src/languages/haskell/config.toml    |   5 
crates/zed/src/languages/haskell/folds.scm      |  18 
crates/zed/src/languages/haskell/highlights.scm | 564 +++++++++++++++++-
crates/zed/src/languages/haskell/injections.scm |  89 ++
4 files changed, 630 insertions(+), 46 deletions(-)

Detailed changes

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

@@ -7,6 +7,7 @@ brackets = [
     { start = "{", end = "}", close = true, newline = true },
     { start = "[", end = "]", close = true, newline = true },
     { start = "(", end = ")", close = true, newline = true },
-    { start = "\"", end = "\"", close = false, newline = false },
-    { start = "'", end = "'", close = false, newline = false },
+    { 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 🔗

@@ -11,24 +11,77 @@
 ;; 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
 
-;; ----------------------------------------------------------------------------
-;; Literals and comments
+(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)) @float
+
+(exp_literal
+  (float)) @number.float
+
 (char) @character
+
 (string) @string
 
-(con_unit) @symbol  ; unit, as in ()
+(con_unit) @string.special.symbol ; unit, as in ()
 
 (comment) @comment
 
-
-;; ----------------------------------------------------------------------------
-;; Punctuation
-
+; 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
 [
   "("
   ")"
@@ -43,16 +96,14 @@
   ";"
 ] @punctuation.delimiter
 
-
-;; ----------------------------------------------------------------------------
-;; Keywords, operators, includes
-
+; ----------------------------------------------------------------------------
+; Keywords, operators, includes
 [
   "forall"
   "∀"
-] @repeat
+] @keyword.repeat
 
-(pragma) @constant.macro
+(pragma) @keyword.directive
 
 [
   "if"
@@ -60,24 +111,26 @@
   "else"
   "case"
   "of"
-] @conditional
-
-(exp_lambda_cases "\\" ("cases" @conditional))
+] @keyword.conditional
 
 [
   "import"
   "qualified"
   "module"
-] @include
+] @keyword.import
 
 [
   (operator)
   (constructor_operator)
   (type_operator)
   (tycon_arrow)
-  (qualified_module)  ; grabs the `.` (dot), ex: import System.IO
+  (qualified_module) ; grabs the `.` (dot), ex: import System.IO
+  (qualified_type)
+  (qualified_variable)
   (all_names)
   (wildcard)
+  "."
+  ".."
   "="
   "|"
   "::"
@@ -89,7 +142,26 @@
   "@"
 ] @operator
 
-(module) @namespace
+(module) @module
+
+((qualified_module
+  (module) @constructor)
+  .
+  (module))
+
+(qualified_type
+  (module) @module)
+
+(qualified_variable
+  (module) @module)
+
+(import
+  (module) @module)
+
+(import
+  (module) @constructor
+  .
+  (module))
 
 [
   (where)
@@ -97,6 +169,7 @@
   "in"
   "class"
   "instance"
+  "pattern"
   "data"
   "newtype"
   "family"
@@ -115,42 +188,445 @@
   "infixr"
 ] @keyword
 
+; ----------------------------------------------------------------------------
+; Functions and variables
+(signature
+  name: (variable) @function)
 
-;; ----------------------------------------------------------------------------
-;; Functions and variables
+(function
+  name: (variable) @function)
 
-(variable) @variable
-(pat_wildcard) @variable
+(function
+  name: (variable) @variable
+  rhs:
+    [
+      (exp_literal)
+      (exp_apply
+        (exp_name
+          [
+            (constructor)
+            (variable)
+            (qualified_variable)
+          ]))
+      (quasiquote)
+      ((exp_name)
+        .
+        (operator))
+    ])
 
-(signature name: (variable) @type)
+(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 (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)))
+((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
 
-;; ----------------------------------------------------------------------------
-;; Types
+(type_star) @type
 
-(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
+((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"))