diff --git a/Cargo.lock b/Cargo.lock index 870fd9a32084922d810930e843eca2d723a917bc..fa06b9bd745816c9aca3f8de098d1543fbcbe17b 100644 --- a/Cargo.lock +++ b/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", diff --git a/Cargo.toml b/Cargo.toml index bad9e149bc50d96abc32b000d4002665f7545685..e01f24f727f29605dcd30511196058458363da6b 100644 --- a/Cargo.toml +++ b/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"} diff --git a/crates/zed/Cargo.toml b/crates/zed/Cargo.toml index a4084a7bd9437b14502843e61c7cff8ccf3a8326..d14c59e54fdb2fd99de639d1e74c79d8d3cf109f 100644 --- a/crates/zed/Cargo.toml +++ b/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 diff --git a/crates/zed/src/languages.rs b/crates/zed/src/languages.rs index cb557ced8dd1eee42d24ba6eb306d048587b8bc8..ef0cc1b146d60663640ddd72c8e731555e972558 100644 --- a/crates/zed/src/languages.rs +++ b/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(), diff --git a/crates/zed/src/languages/haskell.rs b/crates/zed/src/languages/haskell.rs new file mode 100644 index 0000000000000000000000000000000000000000..8b137891791fe96927ad78e64b0aad7bded08bdc --- /dev/null +++ b/crates/zed/src/languages/haskell.rs @@ -0,0 +1 @@ + diff --git a/crates/zed/src/languages/haskell/brackets.scm b/crates/zed/src/languages/haskell/brackets.scm new file mode 100644 index 0000000000000000000000000000000000000000..191fd9c084a52eced37428281971ff9e569a4932 --- /dev/null +++ b/crates/zed/src/languages/haskell/brackets.scm @@ -0,0 +1,3 @@ +("(" @open ")" @close) +("[" @open "]" @close) +("{" @open "}" @close) diff --git a/crates/zed/src/languages/haskell/config.toml b/crates/zed/src/languages/haskell/config.toml new file mode 100644 index 0000000000000000000000000000000000000000..9d0c3f602ce9e2985ae831302b1aa08ae616cccd --- /dev/null +++ b/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 }, +] diff --git a/crates/zed/src/languages/haskell/folds.scm b/crates/zed/src/languages/haskell/folds.scm new file mode 100644 index 0000000000000000000000000000000000000000..e743ecf52cc9981ed55eec9eff72be829a32f147 --- /dev/null +++ b/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 diff --git a/crates/zed/src/languages/haskell/highlights.scm b/crates/zed/src/languages/haskell/highlights.scm new file mode 100644 index 0000000000000000000000000000000000000000..a81089d419276d282c34765d47f6df1e4d75c6df --- /dev/null +++ b/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) + ]) diff --git a/crates/zed/src/languages/haskell/indents.scm b/crates/zed/src/languages/haskell/indents.scm new file mode 100644 index 0000000000000000000000000000000000000000..112b414aa45f277138d0c681851129a608ee96e0 --- /dev/null +++ b/crates/zed/src/languages/haskell/indents.scm @@ -0,0 +1,3 @@ +(_ "[" "]" @end) @indent +(_ "{" "}" @end) @indent +(_ "(" ")" @end) @indent diff --git a/crates/zed/src/languages/haskell/injections.scm b/crates/zed/src/languages/haskell/injections.scm new file mode 100644 index 0000000000000000000000000000000000000000..237dbfa0ee4e8247035ef6ec673c0336ea02884c --- /dev/null +++ b/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"))