Add Gleam support (#6733)
This PR adds support for [Gleam](https://gleam.run/). <img width="1320" alt="Screenshot 2024-01-25 at 6 39 18 PM" src="https://github.com/zed-industries/zed/assets/1486634/7891b6e9-d7dc-46a0-b7c5-8aa7854c1f35"> <img width="757" alt="Screenshot 2024-01-25 at 6 39 37 PM" src="https://github.com/zed-industries/zed/assets/1486634/f7ce6b3f-6175-45cb-8547-cfd286d918c6"> <img width="694" alt="Screenshot 2024-01-25 at 6 39 55 PM" src="https://github.com/zed-industries/zed/assets/1486634/b0838027-c377-47e6-bdd1-bdc9b67a8672"> There are still some areas of improvement, like extending what constructs we support in the outline view, but this is a good start. Release Notes: - Added Gleam support ([#5162](https://github.com/zed-industries/zed/issues/5162)).
This commit is contained in:
parent
20c90f07e1
commit
50b9e5d8d2
8 changed files with 280 additions and 0 deletions
10
Cargo.lock
generated
10
Cargo.lock
generated
|
@ -8503,6 +8503,15 @@ dependencies = [
|
||||||
"tree-sitter",
|
"tree-sitter",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tree-sitter-gleam"
|
||||||
|
version = "0.34.0"
|
||||||
|
source = "git+https://github.com/gleam-lang/tree-sitter-gleam?rev=58b7cac8fc14c92b0677c542610d8738c373fa81#58b7cac8fc14c92b0677c542610d8738c373fa81"
|
||||||
|
dependencies = [
|
||||||
|
"cc",
|
||||||
|
"tree-sitter",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tree-sitter-glsl"
|
name = "tree-sitter-glsl"
|
||||||
version = "0.1.4"
|
version = "0.1.4"
|
||||||
|
@ -9781,6 +9790,7 @@ dependencies = [
|
||||||
"tree-sitter-elixir",
|
"tree-sitter-elixir",
|
||||||
"tree-sitter-elm",
|
"tree-sitter-elm",
|
||||||
"tree-sitter-embedded-template",
|
"tree-sitter-embedded-template",
|
||||||
|
"tree-sitter-gleam",
|
||||||
"tree-sitter-glsl",
|
"tree-sitter-glsl",
|
||||||
"tree-sitter-go",
|
"tree-sitter-go",
|
||||||
"tree-sitter-heex",
|
"tree-sitter-heex",
|
||||||
|
|
|
@ -140,6 +140,7 @@ tree-sitter-elixir = { git = "https://github.com/elixir-lang/tree-sitter-elixir"
|
||||||
tree-sitter-elm = { git = "https://github.com/elm-tooling/tree-sitter-elm", rev = "692c50c0b961364c40299e73c1306aecb5d20f40"}
|
tree-sitter-elm = { git = "https://github.com/elm-tooling/tree-sitter-elm", rev = "692c50c0b961364c40299e73c1306aecb5d20f40"}
|
||||||
tree-sitter-embedded-template = "0.20.0"
|
tree-sitter-embedded-template = "0.20.0"
|
||||||
tree-sitter-glsl = { git = "https://github.com/theHamsta/tree-sitter-glsl", rev = "2a56fb7bc8bb03a1892b4741279dd0a8758b7fb3" }
|
tree-sitter-glsl = { git = "https://github.com/theHamsta/tree-sitter-glsl", rev = "2a56fb7bc8bb03a1892b4741279dd0a8758b7fb3" }
|
||||||
|
tree-sitter-gleam = { git = "https://github.com/gleam-lang/tree-sitter-gleam", rev = "58b7cac8fc14c92b0677c542610d8738c373fa81" }
|
||||||
tree-sitter-go = { git = "https://github.com/tree-sitter/tree-sitter-go", rev = "aeb2f33b366fd78d5789ff104956ce23508b85db" }
|
tree-sitter-go = { git = "https://github.com/tree-sitter/tree-sitter-go", rev = "aeb2f33b366fd78d5789ff104956ce23508b85db" }
|
||||||
tree-sitter-heex = { git = "https://github.com/phoenixframework/tree-sitter-heex", rev = "2e1348c3cf2c9323e87c2744796cf3f3868aa82a" }
|
tree-sitter-heex = { git = "https://github.com/phoenixframework/tree-sitter-heex", rev = "2e1348c3cf2c9323e87c2744796cf3f3868aa82a" }
|
||||||
tree-sitter-json = { git = "https://github.com/tree-sitter/tree-sitter-json", rev = "40a81c01a40ac48744e0c8ccabbaba1920441199" }
|
tree-sitter-json = { git = "https://github.com/tree-sitter/tree-sitter-json", rev = "40a81c01a40ac48744e0c8ccabbaba1920441199" }
|
||||||
|
|
|
@ -121,6 +121,7 @@ tree-sitter-elixir.workspace = true
|
||||||
tree-sitter-elm.workspace = true
|
tree-sitter-elm.workspace = true
|
||||||
tree-sitter-embedded-template.workspace = true
|
tree-sitter-embedded-template.workspace = true
|
||||||
tree-sitter-glsl.workspace = true
|
tree-sitter-glsl.workspace = true
|
||||||
|
tree-sitter-gleam.workspace = true
|
||||||
tree-sitter-go.workspace = true
|
tree-sitter-go.workspace = true
|
||||||
tree-sitter-heex.workspace = true
|
tree-sitter-heex.workspace = true
|
||||||
tree-sitter-json.workspace = true
|
tree-sitter-json.workspace = true
|
||||||
|
|
|
@ -12,6 +12,7 @@ use self::elixir::ElixirSettings;
|
||||||
mod c;
|
mod c;
|
||||||
mod css;
|
mod css;
|
||||||
mod elixir;
|
mod elixir;
|
||||||
|
mod gleam;
|
||||||
mod go;
|
mod go;
|
||||||
mod html;
|
mod html;
|
||||||
mod json;
|
mod json;
|
||||||
|
@ -99,6 +100,11 @@ pub fn init(
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
language(
|
||||||
|
"gleam",
|
||||||
|
tree_sitter_gleam::language(),
|
||||||
|
vec![Arc::new(gleam::GleamLspAdapter)],
|
||||||
|
);
|
||||||
language(
|
language(
|
||||||
"go",
|
"go",
|
||||||
tree_sitter_go::language(),
|
tree_sitter_go::language(),
|
||||||
|
|
118
crates/zed/src/languages/gleam.rs
Normal file
118
crates/zed/src/languages/gleam.rs
Normal file
|
@ -0,0 +1,118 @@
|
||||||
|
use std::any::Any;
|
||||||
|
use std::ffi::OsString;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
use anyhow::{anyhow, Result};
|
||||||
|
use async_compression::futures::bufread::GzipDecoder;
|
||||||
|
use async_tar::Archive;
|
||||||
|
use async_trait::async_trait;
|
||||||
|
use futures::io::BufReader;
|
||||||
|
use futures::StreamExt;
|
||||||
|
use language::{LanguageServerName, LspAdapter, LspAdapterDelegate};
|
||||||
|
use lsp::LanguageServerBinary;
|
||||||
|
use smol::fs;
|
||||||
|
use util::github::{latest_github_release, GitHubLspBinaryVersion};
|
||||||
|
use util::{async_maybe, ResultExt};
|
||||||
|
|
||||||
|
fn server_binary_arguments() -> Vec<OsString> {
|
||||||
|
vec!["lsp".into()]
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct GleamLspAdapter;
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl LspAdapter for GleamLspAdapter {
|
||||||
|
fn name(&self) -> LanguageServerName {
|
||||||
|
LanguageServerName("gleam".into())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn short_name(&self) -> &'static str {
|
||||||
|
"gleam"
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn fetch_latest_server_version(
|
||||||
|
&self,
|
||||||
|
delegate: &dyn LspAdapterDelegate,
|
||||||
|
) -> Result<Box<dyn 'static + Send + Any>> {
|
||||||
|
let release =
|
||||||
|
latest_github_release("gleam-lang/gleam", false, delegate.http_client()).await?;
|
||||||
|
|
||||||
|
let asset_name = format!(
|
||||||
|
"gleam-{version}-{arch}-apple-darwin.tar.gz",
|
||||||
|
version = release.name,
|
||||||
|
arch = std::env::consts::ARCH
|
||||||
|
);
|
||||||
|
let asset = release
|
||||||
|
.assets
|
||||||
|
.iter()
|
||||||
|
.find(|asset| asset.name == asset_name)
|
||||||
|
.ok_or_else(|| anyhow!("no asset found matching {:?}", asset_name))?;
|
||||||
|
Ok(Box::new(GitHubLspBinaryVersion {
|
||||||
|
name: release.name,
|
||||||
|
url: asset.browser_download_url.clone(),
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn fetch_server_binary(
|
||||||
|
&self,
|
||||||
|
version: Box<dyn 'static + Send + Any>,
|
||||||
|
container_dir: PathBuf,
|
||||||
|
delegate: &dyn LspAdapterDelegate,
|
||||||
|
) -> Result<LanguageServerBinary> {
|
||||||
|
let version = version.downcast::<GitHubLspBinaryVersion>().unwrap();
|
||||||
|
let binary_path = container_dir.join("gleam");
|
||||||
|
|
||||||
|
if fs::metadata(&binary_path).await.is_err() {
|
||||||
|
let mut response = delegate
|
||||||
|
.http_client()
|
||||||
|
.get(&version.url, Default::default(), true)
|
||||||
|
.await
|
||||||
|
.map_err(|err| anyhow!("error downloading release: {}", err))?;
|
||||||
|
let decompressed_bytes = GzipDecoder::new(BufReader::new(response.body_mut()));
|
||||||
|
let archive = Archive::new(decompressed_bytes);
|
||||||
|
archive.unpack(container_dir).await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(LanguageServerBinary {
|
||||||
|
path: binary_path,
|
||||||
|
arguments: server_binary_arguments(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn cached_server_binary(
|
||||||
|
&self,
|
||||||
|
container_dir: PathBuf,
|
||||||
|
_: &dyn LspAdapterDelegate,
|
||||||
|
) -> Option<LanguageServerBinary> {
|
||||||
|
get_cached_server_binary(container_dir).await
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn installation_test_binary(
|
||||||
|
&self,
|
||||||
|
container_dir: PathBuf,
|
||||||
|
) -> Option<LanguageServerBinary> {
|
||||||
|
get_cached_server_binary(container_dir)
|
||||||
|
.await
|
||||||
|
.map(|mut binary| {
|
||||||
|
binary.arguments = vec!["--version".into()];
|
||||||
|
binary
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_cached_server_binary(container_dir: PathBuf) -> Option<LanguageServerBinary> {
|
||||||
|
async_maybe!({
|
||||||
|
let mut last = None;
|
||||||
|
let mut entries = fs::read_dir(&container_dir).await?;
|
||||||
|
while let Some(entry) = entries.next().await {
|
||||||
|
last = Some(entry?.path());
|
||||||
|
}
|
||||||
|
|
||||||
|
anyhow::Ok(LanguageServerBinary {
|
||||||
|
path: last.ok_or_else(|| anyhow!("no cached binary"))?,
|
||||||
|
arguments: server_binary_arguments(),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.log_err()
|
||||||
|
}
|
10
crates/zed/src/languages/gleam/config.toml
Normal file
10
crates/zed/src/languages/gleam/config.toml
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
name = "Gleam"
|
||||||
|
path_suffixes = ["gleam"]
|
||||||
|
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", "comment"] },
|
||||||
|
]
|
130
crates/zed/src/languages/gleam/highlights.scm
Normal file
130
crates/zed/src/languages/gleam/highlights.scm
Normal file
|
@ -0,0 +1,130 @@
|
||||||
|
; Comments
|
||||||
|
(module_comment) @comment
|
||||||
|
(statement_comment) @comment
|
||||||
|
(comment) @comment
|
||||||
|
|
||||||
|
; Constants
|
||||||
|
(constant
|
||||||
|
name: (identifier) @constant)
|
||||||
|
|
||||||
|
; Modules
|
||||||
|
(module) @module
|
||||||
|
(import alias: (identifier) @module)
|
||||||
|
(remote_type_identifier
|
||||||
|
module: (identifier) @module)
|
||||||
|
(remote_constructor_name
|
||||||
|
module: (identifier) @module)
|
||||||
|
((field_access
|
||||||
|
record: (identifier) @module
|
||||||
|
field: (label) @function)
|
||||||
|
(#is-not? local))
|
||||||
|
|
||||||
|
; Functions
|
||||||
|
(unqualified_import (identifier) @function)
|
||||||
|
(unqualified_import "type" (type_identifier) @type)
|
||||||
|
(unqualified_import (type_identifier) @constructor)
|
||||||
|
(function
|
||||||
|
name: (identifier) @function)
|
||||||
|
(external_function
|
||||||
|
name: (identifier) @function)
|
||||||
|
(function_parameter
|
||||||
|
name: (identifier) @variable.parameter)
|
||||||
|
((function_call
|
||||||
|
function: (identifier) @function)
|
||||||
|
(#is-not? local))
|
||||||
|
((binary_expression
|
||||||
|
operator: "|>"
|
||||||
|
right: (identifier) @function)
|
||||||
|
(#is-not? local))
|
||||||
|
|
||||||
|
; "Properties"
|
||||||
|
; Assumed to be intended to refer to a name for a field; something that comes
|
||||||
|
; before ":" or after "."
|
||||||
|
; e.g. record field names, tuple indices, names for named arguments, etc
|
||||||
|
(label) @property
|
||||||
|
(tuple_access
|
||||||
|
index: (integer) @property)
|
||||||
|
|
||||||
|
; Attributes
|
||||||
|
(attribute
|
||||||
|
"@" @attribute
|
||||||
|
name: (identifier) @attribute)
|
||||||
|
|
||||||
|
(attribute_value (identifier) @constant)
|
||||||
|
|
||||||
|
; Type names
|
||||||
|
(remote_type_identifier) @type
|
||||||
|
(type_identifier) @type
|
||||||
|
|
||||||
|
; Data constructors
|
||||||
|
(constructor_name) @constructor
|
||||||
|
|
||||||
|
; Literals
|
||||||
|
(string) @string
|
||||||
|
((escape_sequence) @warning
|
||||||
|
; Deprecated in v0.33.0-rc2:
|
||||||
|
(#eq? @warning "\\e"))
|
||||||
|
(escape_sequence) @string.escape
|
||||||
|
(bit_string_segment_option) @function.builtin
|
||||||
|
(integer) @number
|
||||||
|
(float) @number
|
||||||
|
|
||||||
|
; Reserved identifiers
|
||||||
|
; TODO: when tree-sitter supports `#any-of?` in the Rust bindings,
|
||||||
|
; refactor this to use `#any-of?` rather than `#match?`
|
||||||
|
((identifier) @warning
|
||||||
|
(#match? @warning "^(auto|delegate|derive|else|implement|macro|test|echo)$"))
|
||||||
|
|
||||||
|
; Variables
|
||||||
|
(identifier) @variable
|
||||||
|
(discard) @comment.unused
|
||||||
|
|
||||||
|
; Keywords
|
||||||
|
[
|
||||||
|
(visibility_modifier) ; "pub"
|
||||||
|
(opacity_modifier) ; "opaque"
|
||||||
|
"as"
|
||||||
|
"assert"
|
||||||
|
"case"
|
||||||
|
"const"
|
||||||
|
; DEPRECATED: 'external' was removed in v0.30.
|
||||||
|
"external"
|
||||||
|
"fn"
|
||||||
|
"if"
|
||||||
|
"import"
|
||||||
|
"let"
|
||||||
|
"panic"
|
||||||
|
"todo"
|
||||||
|
"type"
|
||||||
|
"use"
|
||||||
|
] @keyword
|
||||||
|
|
||||||
|
; Operators
|
||||||
|
(binary_expression
|
||||||
|
operator: _ @operator)
|
||||||
|
(boolean_negation "!" @operator)
|
||||||
|
(integer_negation "-" @operator)
|
||||||
|
|
||||||
|
; Punctuation
|
||||||
|
[
|
||||||
|
"("
|
||||||
|
")"
|
||||||
|
"["
|
||||||
|
"]"
|
||||||
|
"{"
|
||||||
|
"}"
|
||||||
|
"<<"
|
||||||
|
">>"
|
||||||
|
] @punctuation.bracket
|
||||||
|
[
|
||||||
|
"."
|
||||||
|
","
|
||||||
|
;; Controversial -- maybe some are operators?
|
||||||
|
":"
|
||||||
|
"#"
|
||||||
|
"="
|
||||||
|
"->"
|
||||||
|
".."
|
||||||
|
"-"
|
||||||
|
"<-"
|
||||||
|
] @punctuation.delimiter
|
4
crates/zed/src/languages/gleam/outline.scm
Normal file
4
crates/zed/src/languages/gleam/outline.scm
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
(function
|
||||||
|
(visibility_modifier)? @context
|
||||||
|
"fn" @context
|
||||||
|
name: (_) @name) @item
|
Loading…
Add table
Add a link
Reference in a new issue