erlang: Extract to zed-extensions/erlang repository (#26248)

Marshall Bowers created

This PR extracts the Erlang extension to the
[zed-extensions/erlang](https://github.com/zed-extensions/erlang)
repository.

Release Notes:

- N/A

Change summary

Cargo.lock                                                         |   7 
Cargo.toml                                                         |   1 
docs/src/languages/erlang.md                                       |   2 
extensions/erlang/Cargo.toml                                       |  16 
extensions/erlang/LICENSE-APACHE                                   |   1 
extensions/erlang/extension.toml                                   |  19 
extensions/erlang/languages/erlang/brackets.scm                    |   3 
extensions/erlang/languages/erlang/config.toml                     |  24 
extensions/erlang/languages/erlang/highlights.scm                  | 231 
extensions/erlang/languages/erlang/indents.scm                     |   3 
extensions/erlang/languages/erlang/outline.scm                     |  31 
extensions/erlang/languages/erlang/textobjects.scm                 |   6 
extensions/erlang/src/erlang.rs                                    |  46 
extensions/erlang/src/language_servers.rs                          |   5 
extensions/erlang/src/language_servers/erlang_language_platform.rs | 112 
extensions/erlang/src/language_servers/erlang_ls.rs                |  21 
16 files changed, 1 insertion(+), 527 deletions(-)

Detailed changes

Cargo.lock πŸ”—

@@ -17035,13 +17035,6 @@ dependencies = [
  "zed_extension_api 0.1.0",
 ]
 
-[[package]]
-name = "zed_erlang"
-version = "0.1.1"
-dependencies = [
- "zed_extension_api 0.1.0",
-]
-
 [[package]]
 name = "zed_extension_api"
 version = "0.1.0"

Cargo.toml πŸ”—

@@ -170,7 +170,6 @@ members = [
     #
 
     "extensions/emmet",
-    "extensions/erlang",
     "extensions/glsl",
     "extensions/haskell",
     "extensions/html",

docs/src/languages/erlang.md πŸ”—

@@ -1,6 +1,6 @@
 # Erlang
 
-Erlang support is available through the [Erlang extension](https://github.com/zed-industries/zed/tree/main/extensions/erlang).
+Erlang support is available through the [Erlang extension](https://github.com/zed-extensions/erlang).
 
 - Tree-sitter: [WhatsApp/tree-sitter-erlang](https://github.com/WhatsApp/tree-sitter-erlang)
 - Language Server: [erlang-ls/erlang_ls](https://github.com/erlang-ls/erlang_ls)

extensions/erlang/Cargo.toml πŸ”—

@@ -1,16 +0,0 @@
-[package]
-name = "zed_erlang"
-version = "0.1.1"
-edition.workspace = true
-publish.workspace = true
-license = "Apache-2.0"
-
-[lints]
-workspace = true
-
-[lib]
-path = "src/erlang.rs"
-crate-type = ["cdylib"]
-
-[dependencies]
-zed_extension_api = "0.1.0"

extensions/erlang/extension.toml πŸ”—

@@ -1,19 +0,0 @@
-id = "erlang"
-name = "Erlang"
-description = "Erlang support."
-version = "0.1.1"
-schema_version = 1
-authors = ["Dairon M <dairon.medina@gmail.com>", "Fabian BergstrΓΆm <fabian@fmbb.se>"]
-repository = "https://github.com/zed-industries/zed"
-
-[language_servers.erlang-ls]
-name = "Erlang Language Server"
-language = "Erlang"
-
-[language_servers.elp]
-name = "Erlang Language Platform"
-language = "Erlang"
-
-[grammars.erlang]
-repository = "https://github.com/WhatsApp/tree-sitter-erlang"
-commit = "b4ddbbd277532b2df50d4c87242d650789a5e124"

extensions/erlang/languages/erlang/config.toml πŸ”—

@@ -1,24 +0,0 @@
-name = "Erlang"
-grammar = "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*$)"

extensions/erlang/languages/erlang/highlights.scm πŸ”—

@@ -1,231 +0,0 @@
-;; 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
-;;
-
-;; Last match wins in this file.
-;; As of https://github.com/tree-sitter/tree-sitter/blob/master/CHANGELOG.md#breaking-1
-
-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
-;; Primitive types
-(string) @string
-(char) @constant
-(integer) @number
-(var) @variable
-(atom) @string.special.symbol
-
-;;; Comments
-((var) @comment.discard
- (#match? @comment.discard "^_"))
-
-(dotdotdot) @comment.discard
-(comment) @comment
-
-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
-;; 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) @constant)
-(macro_call_expr name: (var) @keyword.directive args: (_) )
-(macro_call_expr name: (atom) @keyword.directive)
-(record_field_name name: (atom) @property)
-(record_name name: (atom) @type)
-
-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
-;; 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)
-
-;; wild attribute
-(wild_attribute name: (attr_name name: (atom) @keyword))
-
-;; fun decl
-
-;; include/include_lib
-
-;; ifdef/ifndef
-(pp_ifdef name: (_) @keyword.directive)
-(pp_ifndef name: (_) @keyword.directive)
-
-;; define
-(pp_define
-    lhs: (macro_lhs
-      name: (var) @constant))
-(pp_define
-    lhs: (macro_lhs
-      name: (_) @keyword.directive
-      args: (var_args args: (var))))
-
-
-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
-;; 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

extensions/erlang/languages/erlang/outline.scm πŸ”—

@@ -1,31 +0,0 @@
-(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

extensions/erlang/src/erlang.rs πŸ”—

@@ -1,46 +0,0 @@
-mod language_servers;
-
-use zed_extension_api::{self as zed, Result};
-
-use crate::language_servers::{ErlangLanguagePlatform, ErlangLs};
-
-struct ErlangExtension {
-    erlang_ls: Option<ErlangLs>,
-    erlang_language_platform: Option<ErlangLanguagePlatform>,
-}
-
-impl zed::Extension for ErlangExtension {
-    fn new() -> Self {
-        Self {
-            erlang_ls: None,
-            erlang_language_platform: None,
-        }
-    }
-
-    fn language_server_command(
-        &mut self,
-        language_server_id: &zed::LanguageServerId,
-        worktree: &zed::Worktree,
-    ) -> Result<zed::Command> {
-        match language_server_id.as_ref() {
-            ErlangLs::LANGUAGE_SERVER_ID => {
-                let erlang_ls = self.erlang_ls.get_or_insert_with(ErlangLs::new);
-
-                Ok(zed::Command {
-                    command: erlang_ls.language_server_binary_path(language_server_id, worktree)?,
-                    args: vec![],
-                    env: Default::default(),
-                })
-            }
-            ErlangLanguagePlatform::LANGUAGE_SERVER_ID => {
-                let erlang_language_platform = self
-                    .erlang_language_platform
-                    .get_or_insert_with(ErlangLanguagePlatform::new);
-                erlang_language_platform.language_server_command(language_server_id, worktree)
-            }
-            language_server_id => Err(format!("unknown language server: {language_server_id}")),
-        }
-    }
-}
-
-zed::register_extension!(ErlangExtension);

extensions/erlang/src/language_servers/erlang_language_platform.rs πŸ”—

@@ -1,112 +0,0 @@
-use std::fs;
-
-use zed_extension_api::{self as zed, LanguageServerId, Result};
-
-pub struct ErlangLanguagePlatform {
-    cached_binary_path: Option<String>,
-}
-
-impl ErlangLanguagePlatform {
-    pub const LANGUAGE_SERVER_ID: &'static str = "elp";
-
-    pub fn new() -> Self {
-        Self {
-            cached_binary_path: None,
-        }
-    }
-
-    pub fn language_server_command(
-        &mut self,
-        language_server_id: &LanguageServerId,
-        worktree: &zed::Worktree,
-    ) -> Result<zed::Command> {
-        Ok(zed::Command {
-            command: self.language_server_binary_path(language_server_id, worktree)?,
-            args: vec!["server".to_string()],
-            env: Default::default(),
-        })
-    }
-
-    fn language_server_binary_path(
-        &mut self,
-        language_server_id: &LanguageServerId,
-        worktree: &zed::Worktree,
-    ) -> Result<String> {
-        if let Some(path) = worktree.which("elp") {
-            return Ok(path);
-        }
-
-        if let Some(path) = &self.cached_binary_path {
-            if fs::metadata(path).map_or(false, |stat| stat.is_file()) {
-                return Ok(path.clone());
-            }
-        }
-
-        zed::set_language_server_installation_status(
-            language_server_id,
-            &zed::LanguageServerInstallationStatus::CheckingForUpdate,
-        );
-        let release = zed::latest_github_release(
-            "WhatsApp/erlang-language-platform",
-            zed::GithubReleaseOptions {
-                require_assets: true,
-                pre_release: false,
-            },
-        )?;
-
-        let (platform, arch) = zed::current_platform();
-        let asset_name = {
-            let otp_version = "26.2";
-            let (os, os_target) = match platform {
-                zed::Os::Mac => ("macos", "apple-darwin"),
-                zed::Os::Linux => ("linux", "unknown-linux-gnu"),
-                zed::Os::Windows => return Err(format!("unsupported platform: {platform:?}")),
-            };
-
-            format!(
-                "elp-{os}-{arch}-{os_target}-otp-{otp_version}.tar.gz",
-                arch = match arch {
-                    zed::Architecture::Aarch64 => "aarch64",
-                    zed::Architecture::X8664 => "x86_64",
-                    zed::Architecture::X86 =>
-                        return Err(format!("unsupported architecture: {arch:?}")),
-                },
-            )
-        };
-
-        let asset = release
-            .assets
-            .iter()
-            .find(|asset| asset.name == asset_name)
-            .ok_or_else(|| format!("no asset found matching {:?}", asset_name))?;
-
-        let version_dir = format!("elp-{}", release.version);
-        let binary_path = format!("{version_dir}/elp");
-
-        if !fs::metadata(&binary_path).map_or(false, |stat| stat.is_file()) {
-            zed::set_language_server_installation_status(
-                language_server_id,
-                &zed::LanguageServerInstallationStatus::Downloading,
-            );
-
-            zed::download_file(
-                &asset.download_url,
-                &version_dir,
-                zed::DownloadedFileType::GzipTar,
-            )
-            .map_err(|e| format!("failed to download file: {e}"))?;
-
-            let entries =
-                fs::read_dir(".").map_err(|e| format!("failed to list working directory {e}"))?;
-            for entry in entries {
-                let entry = entry.map_err(|e| format!("failed to load directory entry {e}"))?;
-                if entry.file_name().to_str() != Some(&version_dir) {
-                    fs::remove_dir_all(entry.path()).ok();
-                }
-            }
-        }
-
-        self.cached_binary_path = Some(binary_path.clone());
-        Ok(binary_path)
-    }
-}

extensions/erlang/src/language_servers/erlang_ls.rs πŸ”—

@@ -1,21 +0,0 @@
-use zed_extension_api::{self as zed, LanguageServerId, Result};
-
-pub struct ErlangLs;
-
-impl ErlangLs {
-    pub const LANGUAGE_SERVER_ID: &'static str = "erlang-ls";
-
-    pub fn new() -> Self {
-        Self
-    }
-
-    pub fn language_server_binary_path(
-        &mut self,
-        _language_server_id: &LanguageServerId,
-        worktree: &zed::Worktree,
-    ) -> Result<String> {
-        worktree
-            .which("erlang_ls")
-            .ok_or_else(|| "erlang_ls must be installed and available on your $PATH".to_string())
-    }
-}