diff --git a/Cargo.lock b/Cargo.lock
index ed428585bf..7b3a5ba6f3 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -8812,6 +8812,16 @@ dependencies = [
"tree-sitter",
]
+[[package]]
+name = "tree-sitter-erlang"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "93ced5145ebb17f83243bf055b74e108da7cc129e12faab4166df03f59b287f4"
+dependencies = [
+ "cc",
+ "tree-sitter",
+]
+
[[package]]
name = "tree-sitter-gitcommit"
version = "0.3.3"
@@ -10392,6 +10402,7 @@ dependencies = [
"tree-sitter-elixir",
"tree-sitter-elm",
"tree-sitter-embedded-template",
+ "tree-sitter-erlang",
"tree-sitter-gitcommit",
"tree-sitter-gleam",
"tree-sitter-glsl",
diff --git a/Cargo.toml b/Cargo.toml
index 00d69c8786..f46d32a53c 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -142,6 +142,7 @@ tree-sitter-css = { git = "https://github.com/tree-sitter/tree-sitter-css", rev
tree-sitter-elixir = { git = "https://github.com/elixir-lang/tree-sitter-elixir", rev = "a2861e88a730287a60c11ea9299c033c7d076e30" }
tree-sitter-elm = { git = "https://github.com/elm-tooling/tree-sitter-elm", rev = "692c50c0b961364c40299e73c1306aecb5d20f40" }
tree-sitter-embedded-template = "0.20.0"
+tree-sitter-erlang = "0.4.0"
tree-sitter-gitcommit = { git = "https://github.com/gbprod/tree-sitter-gitcommit" }
tree-sitter-gleam = { git = "https://github.com/gleam-lang/tree-sitter-gleam", rev = "58b7cac8fc14c92b0677c542610d8738c373fa81" }
tree-sitter-glsl = { git = "https://github.com/theHamsta/tree-sitter-glsl", rev = "2a56fb7bc8bb03a1892b4741279dd0a8758b7fb3" }
diff --git a/assets/icons/file_icons/erlang.svg b/assets/icons/file_icons/erlang.svg
new file mode 100644
index 0000000000..2dd57910b8
--- /dev/null
+++ b/assets/icons/file_icons/erlang.svg
@@ -0,0 +1 @@
+
diff --git a/assets/icons/file_icons/file_types.json b/assets/icons/file_icons/file_types.json
index 993dfe53b0..8cf7b314c9 100644
--- a/assets/icons/file_icons/file_types.json
+++ b/assets/icons/file_icons/file_types.json
@@ -1,7 +1,9 @@
{
"suffixes": {
+ "Emakefile": "erlang",
"aac": "audio",
"accdb": "storage",
+ "app.src": "erlang",
"avif": "image",
"bak": "backup",
"bash": "terminal",
@@ -23,6 +25,8 @@
"doc": "document",
"docx": "document",
"eex": "elixir",
+ "erl": "erlang",
+ "escript": "erlang",
"eslintrc": "eslint",
"eslintrc.js": "eslint",
"eslintrc.json": "eslint",
@@ -37,17 +41,18 @@
"gif": "image",
"gitattributes": "vcs",
"gitignore": "vcs",
- "gitmodules": "vcs",
"gitkeep": "vcs",
+ "gitmodules": "vcs",
"go": "go",
"h": "code",
"handlebars": "code",
"hbs": "template",
"heex": "elixir",
"heif": "image",
+ "hrl": "erlang",
+ "hs": "haskell",
"htm": "template",
"html": "template",
- "hs": "haskell",
"ib": "storage",
"ico": "image",
"ini": "settings",
@@ -85,6 +90,7 @@
"psd": "image",
"py": "python",
"rb": "ruby",
+ "rebar.config": "erlang",
"rkt": "code",
"rs": "rust",
"rtf": "document",
@@ -104,13 +110,15 @@
"txt": "document",
"vue": "vue",
"wav": "audio",
- "webp": "image",
"webm": "video",
+ "webp": "image",
"xls": "document",
"xlsx": "document",
"xml": "template",
+ "xrl": "erlang",
"yaml": "yaml",
"yml": "yaml",
+ "yrl": "erlang",
"zlogin": "terminal",
"zsh": "terminal",
"zsh_aliases": "terminal",
@@ -133,7 +141,7 @@
"icon": "icons/file_icons/folder.svg"
},
"css": {
- "icon": "icons/file_icons/css.svg"
+ "icon": "icons/file_icons/css.svg"
},
"default": {
"icon": "icons/file_icons/file.svg"
@@ -144,6 +152,9 @@
"elixir": {
"icon": "icons/file_icons/elixir.svg"
},
+ "erlang": {
+ "icon": "icons/file_icons/erlang.svg"
+ },
"eslint": {
"icon": "icons/file_icons/eslint.svg"
},
diff --git a/crates/zed/Cargo.toml b/crates/zed/Cargo.toml
index f17d3053a0..b87b79e2f0 100644
--- a/crates/zed/Cargo.toml
+++ b/crates/zed/Cargo.toml
@@ -114,6 +114,7 @@ tree-sitter-css.workspace = true
tree-sitter-elixir.workspace = true
tree-sitter-elm.workspace = true
tree-sitter-embedded-template.workspace = true
+tree-sitter-erlang.workspace = true
tree-sitter-gitcommit.workspace = true
tree-sitter-gleam.workspace = true
tree-sitter-glsl.workspace = true
diff --git a/crates/zed/src/languages.rs b/crates/zed/src/languages.rs
index f0217af1d9..add9f9c192 100644
--- a/crates/zed/src/languages.rs
+++ b/crates/zed/src/languages.rs
@@ -15,6 +15,7 @@ mod css;
mod deno;
mod elixir;
mod elm;
+mod erlang;
mod gleam;
mod go;
mod haskell;
@@ -113,6 +114,12 @@ pub fn init(
),
}
language("gitcommit", tree_sitter_gitcommit::language(), vec![]);
+ language(
+ "erlang",
+ tree_sitter_erlang::language(),
+ vec![Arc::new(erlang::ErlangLspAdapter)],
+ );
+
language(
"gleam",
tree_sitter_gleam::language(),
diff --git a/crates/zed/src/languages/erlang.rs b/crates/zed/src/languages/erlang.rs
new file mode 100644
index 0000000000..b50b6e7564
--- /dev/null
+++ b/crates/zed/src/languages/erlang.rs
@@ -0,0 +1,58 @@
+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 ErlangLspAdapter;
+
+#[async_trait]
+impl LspAdapter for ErlangLspAdapter {
+ fn name(&self) -> LanguageServerName {
+ LanguageServerName("erlang_ls".into())
+ }
+
+ fn short_name(&self) -> &'static str {
+ "erlang_ls"
+ }
+
+ async fn fetch_latest_server_version(
+ &self,
+ _: &dyn LspAdapterDelegate,
+ ) -> Result> {
+ Ok(Box::new(()) as Box<_>)
+ }
+
+ async fn fetch_server_binary(
+ &self,
+ _version: Box,
+ _container_dir: PathBuf,
+ _: &dyn LspAdapterDelegate,
+ ) -> Result {
+ Err(anyhow!(
+ "erlang_ls must be installed and available in your $PATH"
+ ))
+ }
+
+ async fn cached_server_binary(
+ &self,
+ _: PathBuf,
+ _: &dyn LspAdapterDelegate,
+ ) -> Option {
+ Some(LanguageServerBinary {
+ path: "erlang_ls".into(),
+ arguments: vec![],
+ })
+ }
+
+ fn can_be_reinstalled(&self) -> bool {
+ false
+ }
+
+ async fn installation_test_binary(&self, _: PathBuf) -> Option {
+ Some(LanguageServerBinary {
+ path: "erlang_ls".into(),
+ arguments: vec!["--version".into()],
+ })
+ }
+}
diff --git a/crates/zed/src/languages/erlang/brackets.scm b/crates/zed/src/languages/erlang/brackets.scm
new file mode 100644
index 0000000000..191fd9c084
--- /dev/null
+++ b/crates/zed/src/languages/erlang/brackets.scm
@@ -0,0 +1,3 @@
+("(" @open ")" @close)
+("[" @open "]" @close)
+("{" @open "}" @close)
diff --git a/crates/zed/src/languages/erlang/config.toml b/crates/zed/src/languages/erlang/config.toml
new file mode 100644
index 0000000000..5f92c0fe27
--- /dev/null
+++ b/crates/zed/src/languages/erlang/config.toml
@@ -0,0 +1,23 @@
+name = "Erlang"
+# TODO: support parsing rebar.config files
+# # https://github.com/WhatsApp/tree-sitter-erlang/issues/3
+path_suffixes = ["erl", "hrl", "app.src", "escript", "xrl", "yrl", "Emakefile", "rebar.config"]
+line_comments = ["% ", "%% ", "%%% "]
+autoclose_before = ";:.,=}])>"
+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, not_in = ["string"] },
+ { start = "\"", end = "\"", close = true, newline = false, not_in = ["string"] },
+ { start = "'", end = "'", close = true, newline = false, not_in = ["string", "comment"] },
+]
+# Indent if a line ends brackets, "->" or most keywords. Also if prefixed
+# with "||". This should work with most formatting models.
+# The ([^%]).* is to ensure this doesn't match inside comments.
+increase_indent_pattern = "^([^%]).*([{(\\[]]|\\->|after|begin|case|catch|fun|if|of|try|when|maybe|else|(\\|\\|.*))\\s*$"
+
+# Dedent after brackets, end or lone "->". The latter happens in a spec
+# with indented types, typically after "when". Only do this if it's _only_
+# preceded by whitespace.
+decrease_indent_pattern = "^\\s*([)}\\]]|end|else|\\->\\s*$)"
diff --git a/crates/zed/src/languages/erlang/folds.scm b/crates/zed/src/languages/erlang/folds.scm
new file mode 100644
index 0000000000..65c2d8ed19
--- /dev/null
+++ b/crates/zed/src/languages/erlang/folds.scm
@@ -0,0 +1,9 @@
+[
+ (fun_decl)
+ (anonymous_fun)
+ (case_expr)
+ (maybe_expr)
+ (map_expr)
+ (export_attribute)
+ (export_type_attribute)
+] @fold
diff --git a/crates/zed/src/languages/erlang/highlights.scm b/crates/zed/src/languages/erlang/highlights.scm
new file mode 100644
index 0000000000..c4abf04776
--- /dev/null
+++ b/crates/zed/src/languages/erlang/highlights.scm
@@ -0,0 +1,231 @@
+;; Copyright (c) Facebook, Inc. and its affiliates.
+;;
+;; 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.
+;; ---------------------------------------------------------------------
+
+;; Based initially on the contents of https://github.com/WhatsApp/tree-sitter-erlang/issues/2 by @Wilfred
+;; and https://github.com/the-mikedavis/tree-sitter-erlang/blob/main/queries/highlights.scm
+;;
+;; The tests are also based on those in
+;; https://github.com/the-mikedavis/tree-sitter-erlang/tree/main/test/highlight
+;;
+
+
+;; First match wins in this file
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+;; Attributes
+
+;; module attribute
+(module_attribute
+ name: (atom) @module)
+
+;; behaviour
+(behaviour_attribute name: (atom) @module)
+
+;; export
+
+;; Import attribute
+(import_attribute
+ module: (atom) @module)
+
+;; export_type
+
+;; optional_callbacks
+
+;; compile
+(compile_options_attribute
+ options: (tuple
+ expr: (atom)
+ expr: (list
+ exprs: (binary_op_expr
+ lhs: (atom)
+ rhs: (integer)))))
+
+;; file attribute
+
+;; record
+(record_decl name: (atom) @type)
+(record_decl name: (macro_call_expr name: (var) @constant))
+(record_field name: (atom) @property)
+
+;; type alias
+
+;; opaque
+
+;; Spec attribute
+(spec fun: (atom) @function)
+(spec
+ module: (module name: (atom) @module)
+ fun: (atom) @function)
+
+;; callback
+(callback fun: (atom) @function)
+
+;; fun decl
+
+;; include/include_lib
+
+;; ifdef/ifndef
+(pp_ifdef name: (_) @keyword.directive)
+(pp_ifndef name: (_) @keyword.directive)
+
+;; define
+(pp_define
+ lhs: (macro_lhs
+ name: (_) @keyword.directive
+ args: (var_args args: (var))))
+(pp_define
+ lhs: (macro_lhs
+ name: (var) @constant))
+
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; Functions
+(fa fun: (atom) @function)
+(type_name name: (atom) @function)
+(call expr: (atom) @function)
+(function_clause name: (atom) @function)
+(internal_fun fun: (atom) @function)
+
+;; This is a fudge, we should check that the operator is '/'
+;; But our grammar does not (currently) provide it
+(binary_op_expr lhs: (atom) @function rhs: (integer))
+
+;; Others
+(remote_module module: (atom) @module)
+(remote fun: (atom) @function)
+(macro_call_expr name: (var) @keyword.directive args: (_) )
+(macro_call_expr name: (var) @constant)
+(macro_call_expr name: (atom) @keyword.directive)
+(record_field_name name: (atom) @property)
+(record_name name: (atom) @type)
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; Reserved words
+[ "after"
+ "and"
+ "band"
+ "begin"
+ "behavior"
+ "behaviour"
+ "bnot"
+ "bor"
+ "bsl"
+ "bsr"
+ "bxor"
+ "callback"
+ "case"
+ "catch"
+ "compile"
+ "define"
+ "deprecated"
+ "div"
+ "elif"
+ "else"
+ "end"
+ "endif"
+ "export"
+ "export_type"
+ "file"
+ "fun"
+ "if"
+ "ifdef"
+ "ifndef"
+ "import"
+ "include"
+ "include_lib"
+ "maybe"
+ "module"
+ "of"
+ "opaque"
+ "optional_callbacks"
+ "or"
+ "receive"
+ "record"
+ "spec"
+ "try"
+ "type"
+ "undef"
+ "unit"
+ "when"
+ "xor"] @keyword
+
+["andalso" "orelse"] @keyword.operator
+
+;; Punctuation
+["," "." ";"] @punctuation.delimiter
+["(" ")" "{" "}" "[" "]" "<<" ">>"] @punctuation.bracket
+
+;; Operators
+["!"
+ "->"
+ "<-"
+ "#"
+ "::"
+ "|"
+ ":"
+ "="
+ "||"
+
+ "+"
+ "-"
+ "bnot"
+ "not"
+
+ "/"
+ "*"
+ "div"
+ "rem"
+ "band"
+ "and"
+
+ "+"
+ "-"
+ "bor"
+ "bxor"
+ "bsl"
+ "bsr"
+ "or"
+ "xor"
+
+ "++"
+ "--"
+
+ "=="
+ "/="
+ "=<"
+ "<"
+ ">="
+ ">"
+ "=:="
+ "=/="
+ ] @operator
+
+;;; Comments
+((var) @comment.discard
+ (#match? @comment.discard "^_"))
+
+(dotdotdot) @comment.discard
+(comment) @comment
+
+;; Primitive types
+(string) @string
+(char) @constant
+(integer) @number
+(var) @variable
+(atom) @string.special.symbol
+
+;; wild attribute (Should take precedence over atoms, otherwise they are highlighted as atoms)
+(wild_attribute name: (attr_name name: (_) @keyword))
diff --git a/crates/zed/src/languages/erlang/indents.scm b/crates/zed/src/languages/erlang/indents.scm
new file mode 100644
index 0000000000..112b414aa4
--- /dev/null
+++ b/crates/zed/src/languages/erlang/indents.scm
@@ -0,0 +1,3 @@
+(_ "[" "]" @end) @indent
+(_ "{" "}" @end) @indent
+(_ "(" ")" @end) @indent
diff --git a/crates/zed/src/languages/erlang/outline.scm b/crates/zed/src/languages/erlang/outline.scm
new file mode 100644
index 0000000000..294f109702
--- /dev/null
+++ b/crates/zed/src/languages/erlang/outline.scm
@@ -0,0 +1,31 @@
+(module_attribute
+ "module" @context
+ name: (_) @name) @item
+
+(behaviour_attribute
+ "behaviour" @context
+ (atom) @name) @item
+
+(type_alias
+ "type" @context
+ name: (_) @name) @item
+
+(opaque
+ "opaque" @context
+ name: (_) @name) @item
+
+(pp_define
+ "define" @context
+ lhs: (_) @name) @item
+
+(record_decl
+ "record" @context
+ name: (_) @name) @item
+
+(callback
+ "callback" @context
+ fun: (_) @function ( (_) @name)) @item
+
+(fun_decl (function_clause
+ name: (_) @name
+ args: (_) @context)) @item
diff --git a/docs/src/languages/erlang.md b/docs/src/languages/erlang.md
new file mode 100644
index 0000000000..3343168faf
--- /dev/null
+++ b/docs/src/languages/erlang.md
@@ -0,0 +1,4 @@
+# Erlang
+
+- Tree Sitter: [tree-sitter-erlang](https://github.com/WhatsApp/tree-sitter-erlang)
+- Language Server: [erlang_ls](https://github.com/erlang-ls/erlang_ls)