Add Zig support (#6690)
This PR adds support for Zig with the official LSP (ZLS) and the treesitter grammar listed on the [website](https://tree-sitter.github.io/tree-sitter/). I've tested this on a few large codebases and go to definition, completion, and hover overlays all seem to work fine with no error logs.  The highlights are adapted from the tree sitter repository which target neovim I think, I've fixed a handful of issues and the highlighting is looking pretty good but need to do more testing. Release Notes: - Added Zig support with zls [#5300](https://github.com/zed-industries/zed/issues/5300).
This commit is contained in:
commit
1734403aa3
10 changed files with 426 additions and 0 deletions
10
Cargo.lock
generated
10
Cargo.lock
generated
|
@ -8716,6 +8716,15 @@ dependencies = [
|
|||
"tree-sitter",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tree-sitter-zig"
|
||||
version = "0.0.1"
|
||||
source = "git+https://github.com/maxxnino/tree-sitter-zig?rev=0d08703e4c3f426ec61695d7617415fff97029bd#0d08703e4c3f426ec61695d7617415fff97029bd"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"tree-sitter",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "try-lock"
|
||||
version = "0.2.4"
|
||||
|
@ -9812,6 +9821,7 @@ dependencies = [
|
|||
"tree-sitter-uiua",
|
||||
"tree-sitter-vue",
|
||||
"tree-sitter-yaml",
|
||||
"tree-sitter-zig",
|
||||
"unindent",
|
||||
"url",
|
||||
"urlencoding",
|
||||
|
|
|
@ -161,6 +161,7 @@ tree-sitter-nix = { git = "https://github.com/nix-community/tree-sitter-nix", re
|
|||
tree-sitter-nu = { git = "https://github.com/nushell/tree-sitter-nu", rev = "26bbaecda0039df4067861ab38ea8ea169f7f5aa"}
|
||||
tree-sitter-vue = {git = "https://github.com/zed-industries/tree-sitter-vue", rev = "6608d9d60c386f19d80af7d8132322fa11199c42"}
|
||||
tree-sitter-uiua = {git = "https://github.com/shnarazk/tree-sitter-uiua", rev = "9260f11be5900beda4ee6d1a24ab8ddfaf5a19b2"}
|
||||
tree-sitter-zig = { git = "https://github.com/maxxnino/tree-sitter-zig", rev = "0d08703e4c3f426ec61695d7617415fff97029bd" }
|
||||
|
||||
[patch.crates-io]
|
||||
tree-sitter = { git = "https://github.com/tree-sitter/tree-sitter", rev = "31c40449749c4263a91a43593831b82229049a4c" }
|
||||
|
|
|
@ -142,6 +142,7 @@ tree-sitter-nix.workspace = true
|
|||
tree-sitter-nu.workspace = true
|
||||
tree-sitter-vue.workspace = true
|
||||
tree-sitter-uiua.workspace = true
|
||||
tree-sitter-zig.workspace = true
|
||||
|
||||
url = "2.2"
|
||||
urlencoding = "2.1.2"
|
||||
|
|
|
@ -31,6 +31,7 @@ mod typescript;
|
|||
mod uiua;
|
||||
mod vue;
|
||||
mod yaml;
|
||||
mod zig;
|
||||
|
||||
// 1. Add tree-sitter-{language} parser to zed crate
|
||||
// 2. Create a language directory in zed/crates/zed/src/languages and add the language to init function below
|
||||
|
@ -112,6 +113,11 @@ pub fn init(
|
|||
tree_sitter_go::language(),
|
||||
vec![Arc::new(go::GoLspAdapter)],
|
||||
);
|
||||
language(
|
||||
"zig",
|
||||
tree_sitter_zig::language(),
|
||||
vec![Arc::new(zig::ZlsAdapter)],
|
||||
);
|
||||
language(
|
||||
"heex",
|
||||
tree_sitter_heex::language(),
|
||||
|
|
125
crates/zed/src/languages/zig.rs
Normal file
125
crates/zed/src/languages/zig.rs
Normal file
|
@ -0,0 +1,125 @@
|
|||
use anyhow::{anyhow, Context, Result};
|
||||
use async_compression::futures::bufread::GzipDecoder;
|
||||
use async_tar::Archive;
|
||||
use async_trait::async_trait;
|
||||
use futures::{io::BufReader, StreamExt};
|
||||
use language::{LanguageServerName, LspAdapter, LspAdapterDelegate};
|
||||
use lsp::LanguageServerBinary;
|
||||
use smol::fs;
|
||||
use std::env::consts::ARCH;
|
||||
use std::{any::Any, path::PathBuf};
|
||||
use util::async_maybe;
|
||||
use util::github::latest_github_release;
|
||||
use util::{github::GitHubLspBinaryVersion, ResultExt};
|
||||
|
||||
pub struct ZlsAdapter;
|
||||
|
||||
#[async_trait]
|
||||
impl LspAdapter for ZlsAdapter {
|
||||
fn name(&self) -> LanguageServerName {
|
||||
LanguageServerName("zls".into())
|
||||
}
|
||||
|
||||
fn short_name(&self) -> &'static str {
|
||||
"zls"
|
||||
}
|
||||
|
||||
async fn fetch_latest_server_version(
|
||||
&self,
|
||||
delegate: &dyn LspAdapterDelegate,
|
||||
) -> Result<Box<dyn 'static + Send + Any>> {
|
||||
let release = latest_github_release("zigtools/zls", false, delegate.http_client()).await?;
|
||||
let asset_name = format!("zls-{}-macos.tar.gz", ARCH);
|
||||
let asset = release
|
||||
.assets
|
||||
.iter()
|
||||
.find(|asset| asset.name == asset_name)
|
||||
.ok_or_else(|| anyhow!("no asset found matching {:?}", asset_name))?;
|
||||
let version = GitHubLspBinaryVersion {
|
||||
name: release.name,
|
||||
url: asset.browser_download_url.clone(),
|
||||
};
|
||||
|
||||
Ok(Box::new(version) as Box<_>)
|
||||
}
|
||||
|
||||
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("bin/zls");
|
||||
|
||||
if fs::metadata(&binary_path).await.is_err() {
|
||||
let mut response = delegate
|
||||
.http_client()
|
||||
.get(&version.url, Default::default(), true)
|
||||
.await
|
||||
.context("error downloading release")?;
|
||||
let decompressed_bytes = GzipDecoder::new(BufReader::new(response.body_mut()));
|
||||
let archive = Archive::new(decompressed_bytes);
|
||||
archive.unpack(container_dir).await?;
|
||||
}
|
||||
|
||||
fs::set_permissions(
|
||||
&binary_path,
|
||||
<fs::Permissions as fs::unix::PermissionsExt>::from_mode(0o755),
|
||||
)
|
||||
.await?;
|
||||
Ok(LanguageServerBinary {
|
||||
path: binary_path,
|
||||
arguments: vec![],
|
||||
})
|
||||
}
|
||||
|
||||
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!["--help".into()];
|
||||
binary
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
async fn get_cached_server_binary(container_dir: PathBuf) -> Option<LanguageServerBinary> {
|
||||
async_maybe!({
|
||||
let mut last_binary_path = None;
|
||||
let mut entries = fs::read_dir(&container_dir).await?;
|
||||
while let Some(entry) = entries.next().await {
|
||||
let entry = entry?;
|
||||
if entry.file_type().await?.is_file()
|
||||
&& entry
|
||||
.file_name()
|
||||
.to_str()
|
||||
.map_or(false, |name| name == "zls")
|
||||
{
|
||||
last_binary_path = Some(entry.path());
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(path) = last_binary_path {
|
||||
Ok(LanguageServerBinary {
|
||||
path,
|
||||
arguments: Vec::new(),
|
||||
})
|
||||
} else {
|
||||
Err(anyhow!("no cached binary"))
|
||||
}
|
||||
})
|
||||
.await
|
||||
.log_err()
|
||||
}
|
10
crates/zed/src/languages/zig/config.toml
Normal file
10
crates/zed/src/languages/zig/config.toml
Normal file
|
@ -0,0 +1,10 @@
|
|||
name = "Zig"
|
||||
path_suffixes = ["zig"]
|
||||
line_comment = "// "
|
||||
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 = true },
|
||||
]
|
16
crates/zed/src/languages/zig/folds.scm
Normal file
16
crates/zed/src/languages/zig/folds.scm
Normal file
|
@ -0,0 +1,16 @@
|
|||
[
|
||||
(Block)
|
||||
(ContainerDecl)
|
||||
(SwitchExpr)
|
||||
(InitList)
|
||||
(AsmExpr)
|
||||
(ErrorSetDecl)
|
||||
(LINESTRING)
|
||||
(
|
||||
[
|
||||
(IfPrefix)
|
||||
(WhilePrefix)
|
||||
(ForPrefix)
|
||||
]
|
||||
)
|
||||
] @fold
|
230
crates/zed/src/languages/zig/highlights.scm
Normal file
230
crates/zed/src/languages/zig/highlights.scm
Normal file
|
@ -0,0 +1,230 @@
|
|||
[
|
||||
(container_doc_comment)
|
||||
(doc_comment)
|
||||
(line_comment)
|
||||
] @comment
|
||||
|
||||
[
|
||||
variable: (IDENTIFIER)
|
||||
variable_type_function: (IDENTIFIER)
|
||||
] @variable
|
||||
|
||||
parameter: (IDENTIFIER) @parameter
|
||||
|
||||
[
|
||||
field_member: (IDENTIFIER)
|
||||
field_access: (IDENTIFIER)
|
||||
] @field
|
||||
|
||||
;; assume TitleCase is a type
|
||||
(
|
||||
[
|
||||
variable_type_function: (IDENTIFIER)
|
||||
field_access: (IDENTIFIER)
|
||||
parameter: (IDENTIFIER)
|
||||
] @type
|
||||
(#match? @type "^[A-Z]([a-z]+[A-Za-z0-9]*)*$")
|
||||
)
|
||||
|
||||
(
|
||||
(_
|
||||
variable_type_function: (IDENTIFIER) @function
|
||||
(FnCallArguments))
|
||||
)
|
||||
|
||||
;; assume all CAPS_1 is a constant
|
||||
(
|
||||
[
|
||||
variable_type_function: (IDENTIFIER)
|
||||
field_access: (IDENTIFIER)
|
||||
] @constant
|
||||
(#match? @constant "^[A-Z][A-Z_0-9]+$")
|
||||
)
|
||||
|
||||
[
|
||||
function_call: (IDENTIFIER)
|
||||
function: (IDENTIFIER)
|
||||
] @function
|
||||
|
||||
exception: "!" @keyword.exception
|
||||
|
||||
(
|
||||
(IDENTIFIER) @variable.builtin
|
||||
(#eq? @variable.builtin "_")
|
||||
)
|
||||
|
||||
(PtrTypeStart "c" @variable.builtin)
|
||||
|
||||
(
|
||||
(ContainerDeclType
|
||||
[
|
||||
(ErrorUnionExpr)
|
||||
"enum"
|
||||
]
|
||||
)
|
||||
(ContainerField (IDENTIFIER) @constant)
|
||||
)
|
||||
|
||||
field_constant: (IDENTIFIER) @constant
|
||||
|
||||
(BUILTINIDENTIFIER) @keyword
|
||||
|
||||
((BUILTINIDENTIFIER) @keyword.import
|
||||
(#any-of? @keyword.import "@import" "@cImport"))
|
||||
|
||||
(INTEGER) @number
|
||||
|
||||
(FLOAT) @number.float
|
||||
|
||||
[
|
||||
"true"
|
||||
"false"
|
||||
] @boolean
|
||||
|
||||
[
|
||||
(LINESTRING)
|
||||
(STRINGLITERALSINGLE)
|
||||
] @string
|
||||
|
||||
(CHAR_LITERAL) @character
|
||||
(EscapeSequence) @string.escape
|
||||
(FormatSequence) @string.special
|
||||
|
||||
(BreakLabel (IDENTIFIER) @label)
|
||||
(BlockLabel (IDENTIFIER) @label)
|
||||
|
||||
[
|
||||
"asm"
|
||||
"defer"
|
||||
"errdefer"
|
||||
"test"
|
||||
"struct"
|
||||
"union"
|
||||
"enum"
|
||||
"opaque"
|
||||
"error"
|
||||
] @keyword
|
||||
|
||||
[
|
||||
"async"
|
||||
"await"
|
||||
"suspend"
|
||||
"nosuspend"
|
||||
"resume"
|
||||
] @keyword.coroutine
|
||||
|
||||
[
|
||||
"fn"
|
||||
] @keyword.function
|
||||
|
||||
[
|
||||
"and"
|
||||
"or"
|
||||
"orelse"
|
||||
] @keyword.operator
|
||||
|
||||
[
|
||||
"return"
|
||||
] @keyword.return
|
||||
|
||||
[
|
||||
"if"
|
||||
"else"
|
||||
"switch"
|
||||
] @keyword
|
||||
|
||||
[
|
||||
"for"
|
||||
"while"
|
||||
"break"
|
||||
"continue"
|
||||
] @keyword
|
||||
|
||||
[
|
||||
"usingnamespace"
|
||||
] @keyword.import
|
||||
|
||||
[
|
||||
"try"
|
||||
"catch"
|
||||
] @keyword
|
||||
|
||||
[
|
||||
"anytype"
|
||||
(BuildinTypeExpr)
|
||||
] @type.builtin
|
||||
|
||||
[
|
||||
"const"
|
||||
"var"
|
||||
"volatile"
|
||||
"allowzero"
|
||||
"noalias"
|
||||
] @type.qualifier
|
||||
|
||||
[
|
||||
"addrspace"
|
||||
"align"
|
||||
"callconv"
|
||||
"linksection"
|
||||
] @keyword.storage
|
||||
|
||||
[
|
||||
"comptime"
|
||||
"export"
|
||||
"extern"
|
||||
"inline"
|
||||
"noinline"
|
||||
"packed"
|
||||
"pub"
|
||||
"threadlocal"
|
||||
] @attribute
|
||||
|
||||
[
|
||||
"null"
|
||||
"unreachable"
|
||||
"undefined"
|
||||
] @constant.builtin
|
||||
|
||||
[
|
||||
(CompareOp)
|
||||
(BitwiseOp)
|
||||
(BitShiftOp)
|
||||
(AdditionOp)
|
||||
(AssignOp)
|
||||
(MultiplyOp)
|
||||
(PrefixOp)
|
||||
"*"
|
||||
"**"
|
||||
"->"
|
||||
".?"
|
||||
".*"
|
||||
"?"
|
||||
] @operator
|
||||
|
||||
[
|
||||
";"
|
||||
"."
|
||||
","
|
||||
":"
|
||||
] @punctuation.delimiter
|
||||
|
||||
[
|
||||
".."
|
||||
"..."
|
||||
] @punctuation.special
|
||||
|
||||
[
|
||||
"["
|
||||
"]"
|
||||
"("
|
||||
")"
|
||||
"{"
|
||||
"}"
|
||||
(Payload "|")
|
||||
(PtrPayload "|")
|
||||
(PtrIndexPayload "|")
|
||||
] @punctuation.bracket
|
||||
|
||||
; Error
|
||||
(ERROR) @error
|
22
crates/zed/src/languages/zig/indents.scm
Normal file
22
crates/zed/src/languages/zig/indents.scm
Normal file
|
@ -0,0 +1,22 @@
|
|||
[
|
||||
(Block)
|
||||
(ContainerDecl)
|
||||
(SwitchExpr)
|
||||
(InitList)
|
||||
] @indent.begin
|
||||
|
||||
[
|
||||
"("
|
||||
")"
|
||||
"["
|
||||
"]"
|
||||
"{"
|
||||
"}"
|
||||
] @indent.branch
|
||||
|
||||
[
|
||||
(line_comment)
|
||||
(container_doc_comment)
|
||||
(doc_comment)
|
||||
(LINESTRING)
|
||||
] @indent.ignore
|
5
crates/zed/src/languages/zig/injections.scm
Normal file
5
crates/zed/src/languages/zig/injections.scm
Normal file
|
@ -0,0 +1,5 @@
|
|||
[
|
||||
(container_doc_comment)
|
||||
(doc_comment)
|
||||
(line_comment)
|
||||
] @comment
|
Loading…
Add table
Add a link
Reference in a new issue