mirror of
https://github.com/zed-industries/zed.git
synced 2024-12-29 04:20:46 +00:00
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",
|
||||
]
|
||||
|
||||
[[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]]
|
||||
name = "tree-sitter-glsl"
|
||||
version = "0.1.4"
|
||||
|
@ -9781,6 +9790,7 @@ dependencies = [
|
|||
"tree-sitter-elixir",
|
||||
"tree-sitter-elm",
|
||||
"tree-sitter-embedded-template",
|
||||
"tree-sitter-gleam",
|
||||
"tree-sitter-glsl",
|
||||
"tree-sitter-go",
|
||||
"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-embedded-template = "0.20.0"
|
||||
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-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" }
|
||||
|
|
|
@ -121,6 +121,7 @@ tree-sitter-elixir.workspace = true
|
|||
tree-sitter-elm.workspace = true
|
||||
tree-sitter-embedded-template.workspace = true
|
||||
tree-sitter-glsl.workspace = true
|
||||
tree-sitter-gleam.workspace = true
|
||||
tree-sitter-go.workspace = true
|
||||
tree-sitter-heex.workspace = true
|
||||
tree-sitter-json.workspace = true
|
||||
|
|
|
@ -12,6 +12,7 @@ use self::elixir::ElixirSettings;
|
|||
mod c;
|
||||
mod css;
|
||||
mod elixir;
|
||||
mod gleam;
|
||||
mod go;
|
||||
mod html;
|
||||
mod json;
|
||||
|
@ -99,6 +100,11 @@ pub fn init(
|
|||
),
|
||||
}
|
||||
|
||||
language(
|
||||
"gleam",
|
||||
tree_sitter_gleam::language(),
|
||||
vec![Arc::new(gleam::GleamLspAdapter)],
|
||||
);
|
||||
language(
|
||||
"go",
|
||||
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…
Reference in a new issue