Extract Terraform extension (#10479)

This PR extracts Terraform support into an extension and removes the
built-in Terraform support from Zed.

Release Notes:

- Removed built-in support for Terraform, in favor of making it
available as
an extension. The Terraform extension will be suggested for download
when you
open a `.tf`, `.tfvars`, or `.hcl` file.
This commit is contained in:
Marshall Bowers 2024-04-12 11:49:49 -04:00 committed by GitHub
parent f4d9a97195
commit b5b872656b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
24 changed files with 162 additions and 208 deletions

View file

@ -45,7 +45,6 @@ tree-sitter-embedded-template.workspace = true
tree-sitter-go.workspace = true
tree-sitter-gomod.workspace = true
tree-sitter-gowork.workspace = true
tree-sitter-hcl.workspace = true
tree-sitter-heex.workspace = true
tree-sitter-jsdoc.workspace = true
tree-sitter-json.workspace = true

View file

@ -1,14 +0,0 @@
name = "HCL"
grammar = "hcl"
path_suffixes = ["hcl"]
line_comments = ["# ", "// "]
block_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 = false, not_in = ["comment", "string"] },
{ start = "'", end = "'", close = true, newline = false, not_in = ["comment", "string"] },
{ start = "/*", end = " */", close = true, newline = false, not_in = ["comment", "string"] },
]

View file

@ -1,117 +0,0 @@
; https://github.com/nvim-treesitter/nvim-treesitter/blob/cb79d2446196d25607eb1d982c96939abdf67b8e/queries/hcl/highlights.scm
; highlights.scm
[
"!"
"\*"
"/"
"%"
"\+"
"-"
">"
">="
"<"
"<="
"=="
"!="
"&&"
"||"
] @operator
[
"{"
"}"
"["
"]"
"("
")"
] @punctuation.bracket
[
"."
".*"
","
"[*]"
] @punctuation.delimiter
[
(ellipsis)
"\?"
"=>"
] @punctuation.special
[
":"
"="
] @punctuation
[
"for"
"endfor"
"in"
"if"
"else"
"endif"
] @keyword
[
(quoted_template_start) ; "
(quoted_template_end) ; "
(template_literal) ; non-interpolation/directive content
] @string
[
(heredoc_identifier) ; END
(heredoc_start) ; << or <<-
] @punctuation.delimiter
[
(template_interpolation_start) ; ${
(template_interpolation_end) ; }
(template_directive_start) ; %{
(template_directive_end) ; }
(strip_marker) ; ~
] @punctuation.special
(numeric_lit) @number
(bool_lit) @boolean
(null_lit) @constant
(comment) @comment
(identifier) @variable
(body
(block
(identifier) @keyword))
(body
(block
(body
(block
(identifier) @type))))
(function_call
(identifier) @function)
(attribute
(identifier) @variable)
; { key: val }
;
; highlight identifier keys as though they were block attributes
(object_elem
key:
(expression
(variable_expr
(identifier) @variable)))
; var.foo, data.bar
;
; first element in get_attr is a variable.builtin or a reference to a variable.builtin
(expression
(variable_expr
(identifier) @variable)
(get_attr
(identifier) @variable))

View file

@ -1,11 +0,0 @@
; https://github.com/nvim-treesitter/nvim-treesitter/blob/ce4adf11cfe36fc5b0e5bcdce0c7c6e8fbc9798a/queries/hcl/indents.scm
[
(block)
(object)
(tuple)
(function_call)
] @indent
(_ "[" "]" @end) @indent
(_ "(" ")" @end) @indent
(_ "{" "}" @end) @indent

View file

@ -1,6 +0,0 @@
; https://github.com/nvim-treesitter/nvim-treesitter/blob/ce4adf11cfe36fc5b0e5bcdce0c7c6e8fbc9798a/queries/hcl/injections.scm
(heredoc_template
(template_literal) @content
(heredoc_identifier) @language
(#downcase! @language))

View file

@ -23,7 +23,6 @@ mod python;
mod ruby;
mod rust;
mod tailwind;
mod terraform;
mod typescript;
mod vue;
mod yaml;
@ -63,7 +62,6 @@ pub fn init(
("go", tree_sitter_go::language()),
("gomod", tree_sitter_gomod::language()),
("gowork", tree_sitter_gowork::language()),
("hcl", tree_sitter_hcl::language()),
("heex", tree_sitter_heex::language()),
("jsdoc", tree_sitter_jsdoc::language()),
("json", tree_sitter_json::language()),
@ -280,12 +278,6 @@ pub fn init(
]
);
language!("proto");
language!("terraform", vec![Arc::new(terraform::TerraformLspAdapter)]);
language!(
"terraform-vars",
vec![Arc::new(terraform::TerraformLspAdapter)]
);
language!("hcl", vec![]);
languages.register_secondary_lsp_adapter(
"Astro".into(),

View file

@ -1,14 +0,0 @@
name = "Terraform Vars"
grammar = "hcl"
path_suffixes = ["tfvars"]
line_comments = ["# ", "// "]
block_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 = false, not_in = ["comment", "string"] },
{ start = "'", end = "'", close = true, newline = false, not_in = ["comment", "string"] },
{ start = "/*", end = " */", close = true, newline = false, not_in = ["comment", "string"] },
]

View file

@ -1,159 +0,0 @@
; https://github.com/nvim-treesitter/nvim-treesitter/blob/cb79d2446196d25607eb1d982c96939abdf67b8e/queries/hcl/highlights.scm
; highlights.scm
[
"!"
"\*"
"/"
"%"
"\+"
"-"
">"
">="
"<"
"<="
"=="
"!="
"&&"
"||"
] @operator
[
"{"
"}"
"["
"]"
"("
")"
] @punctuation.bracket
[
"."
".*"
","
"[*]"
] @punctuation.delimiter
[
(ellipsis)
"\?"
"=>"
] @punctuation.special
[
":"
"="
] @punctuation
[
"for"
"endfor"
"in"
"if"
"else"
"endif"
] @keyword
[
(quoted_template_start) ; "
(quoted_template_end) ; "
(template_literal) ; non-interpolation/directive content
] @string
[
(heredoc_identifier) ; END
(heredoc_start) ; << or <<-
] @punctuation.delimiter
[
(template_interpolation_start) ; ${
(template_interpolation_end) ; }
(template_directive_start) ; %{
(template_directive_end) ; }
(strip_marker) ; ~
] @punctuation.special
(numeric_lit) @number
(bool_lit) @boolean
(null_lit) @constant
(comment) @comment
(identifier) @variable
(body
(block
(identifier) @keyword))
(body
(block
(body
(block
(identifier) @type))))
(function_call
(identifier) @function)
(attribute
(identifier) @variable)
; { key: val }
;
; highlight identifier keys as though they were block attributes
(object_elem
key:
(expression
(variable_expr
(identifier) @variable)))
; var.foo, data.bar
;
; first element in get_attr is a variable.builtin or a reference to a variable.builtin
(expression
(variable_expr
(identifier) @variable)
(get_attr
(identifier) @variable))
; https://github.com/nvim-treesitter/nvim-treesitter/blob/cb79d2446196d25607eb1d982c96939abdf67b8e/queries/terraform/highlights.scm
; Terraform specific references
;
;
; local/module/data/var/output
(expression
(variable_expr
(identifier) @variable
(#any-of? @variable "data" "var" "local" "module" "output"))
(get_attr
(identifier) @variable))
; path.root/cwd/module
(expression
(variable_expr
(identifier) @type
(#eq? @type "path"))
(get_attr
(identifier) @variable
(#any-of? @variable "root" "cwd" "module")))
; terraform.workspace
(expression
(variable_expr
(identifier) @type
(#eq? @type "terraform"))
(get_attr
(identifier) @variable
(#any-of? @variable "workspace")))
; Terraform specific keywords
; FIXME: ideally only for identifiers under a `variable` block to minimize false positives
((identifier) @type
(#any-of? @type "bool" "string" "number" "object" "tuple" "list" "map" "set" "any"))
(object_elem
val:
(expression
(variable_expr
(identifier) @type
(#any-of? @type "bool" "string" "number" "object" "tuple" "list" "map" "set" "any"))))

View file

@ -1,14 +0,0 @@
; https://github.com/nvim-treesitter/nvim-treesitter/blob/ce4adf11cfe36fc5b0e5bcdce0c7c6e8fbc9798a/queries/hcl/indents.scm
[
(block)
(object)
(tuple)
(function_call)
] @indent
(_ "[" "]" @end) @indent
(_ "(" ")" @end) @indent
(_ "{" "}" @end) @indent
; https://github.com/nvim-treesitter/nvim-treesitter/blob/ce4adf11cfe36fc5b0e5bcdce0c7c6e8fbc9798a/queries/terraform/indents.scm
; inherits: hcl

View file

@ -1,9 +0,0 @@
; https://github.com/nvim-treesitter/nvim-treesitter/blob/ce4adf11cfe36fc5b0e5bcdce0c7c6e8fbc9798a/queries/hcl/injections.scm
(heredoc_template
(template_literal) @content
(heredoc_identifier) @language
(#downcase! @language))
; https://github.com/nvim-treesitter/nvim-treesitter/blob/ce4adf11cfe36fc5b0e5bcdce0c7c6e8fbc9798a/queries/terraform/injections.scm
; inherits: hcl

View file

@ -1,181 +0,0 @@
use anyhow::{anyhow, Context, Result};
use async_trait::async_trait;
use collections::HashMap;
use futures::StreamExt;
pub use language::*;
use lsp::{CodeActionKind, LanguageServerBinary};
use smol::fs::{self, File};
use std::{any::Any, ffi::OsString, path::PathBuf};
use util::{
fs::remove_matching,
github::{latest_github_release, GitHubLspBinaryVersion},
maybe, ResultExt,
};
fn terraform_ls_binary_arguments() -> Vec<OsString> {
vec!["serve".into()]
}
pub struct TerraformLspAdapter;
#[async_trait(?Send)]
impl LspAdapter for TerraformLspAdapter {
fn name(&self) -> LanguageServerName {
LanguageServerName("terraform-ls".into())
}
async fn fetch_latest_server_version(
&self,
delegate: &dyn LspAdapterDelegate,
) -> Result<Box<dyn 'static + Send + Any>> {
// TODO: maybe use release API instead
// https://api.releases.hashicorp.com/v1/releases/terraform-ls?limit=1
let release = latest_github_release(
"hashicorp/terraform-ls",
false,
false,
delegate.http_client(),
)
.await?;
Ok(Box::new(GitHubLspBinaryVersion {
name: release.tag_name,
url: Default::default(),
}))
}
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 zip_path = container_dir.join(format!("terraform-ls_{}.zip", version.name));
let version_dir = container_dir.join(format!("terraform-ls_{}", version.name));
let binary_path = version_dir.join("terraform-ls");
let url = build_download_url(version.name)?;
if fs::metadata(&binary_path).await.is_err() {
let mut response = delegate
.http_client()
.get(&url, Default::default(), true)
.await
.context("error downloading release")?;
let mut file = File::create(&zip_path).await?;
if !response.status().is_success() {
Err(anyhow!(
"download failed with status {}",
response.status().to_string()
))?;
}
futures::io::copy(response.body_mut(), &mut file).await?;
let unzip_status = smol::process::Command::new("unzip")
.current_dir(&container_dir)
.arg(&zip_path)
.arg("-d")
.arg(&version_dir)
.output()
.await?
.status;
if !unzip_status.success() {
Err(anyhow!("failed to unzip Terraform LS archive"))?;
}
remove_matching(&container_dir, |entry| entry != version_dir).await;
}
Ok(LanguageServerBinary {
path: binary_path,
env: None,
arguments: terraform_ls_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
})
}
fn code_action_kinds(&self) -> Option<Vec<CodeActionKind>> {
// TODO: file issue for server supported code actions
// TODO: reenable default actions / delete override
Some(vec![])
}
fn language_ids(&self) -> HashMap<String, String> {
HashMap::from_iter([
("Terraform".into(), "terraform".into()),
("Terraform Vars".into(), "terraform-vars".into()),
])
}
}
fn build_download_url(version: String) -> Result<String> {
let v = version.strip_prefix('v').unwrap_or(&version);
let os = match std::env::consts::OS {
"linux" => "linux",
"macos" => "darwin",
"win" => "windows",
_ => Err(anyhow!("unsupported OS {}", std::env::consts::OS))?,
}
.to_string();
let arch = match std::env::consts::ARCH {
"x86" => "386",
"x86_64" => "amd64",
"arm" => "arm",
"aarch64" => "arm64",
_ => Err(anyhow!("unsupported ARCH {}", std::env::consts::ARCH))?,
}
.to_string();
let url = format!(
"https://releases.hashicorp.com/terraform-ls/{v}/terraform-ls_{v}_{os}_{arch}.zip",
);
Ok(url)
}
async fn get_cached_server_binary(container_dir: PathBuf) -> Option<LanguageServerBinary> {
maybe!(async {
let mut last = None;
let mut entries = fs::read_dir(&container_dir).await?;
while let Some(entry) = entries.next().await {
last = Some(entry?.path());
}
match last {
Some(path) if path.is_dir() => {
let binary = path.join("terraform-ls");
if fs::metadata(&binary).await.is_ok() {
return Ok(LanguageServerBinary {
path: binary,
env: None,
arguments: terraform_ls_binary_arguments(),
});
}
}
_ => {}
}
Err(anyhow!("no cached binary"))
})
.await
.log_err()
}

View file

@ -1,15 +0,0 @@
name = "Terraform"
grammar = "hcl"
path_suffixes = ["tf"]
line_comments = ["# ", "// "]
block_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 = false, not_in = ["comment", "string"] },
{ start = "'", end = "'", close = true, newline = false, not_in = ["comment", "string"] },
{ start = "/*", end = " */", close = true, newline = false, not_in = ["comment", "string"] },
]
tab_size = 2

View file

@ -1,159 +0,0 @@
; https://github.com/nvim-treesitter/nvim-treesitter/blob/cb79d2446196d25607eb1d982c96939abdf67b8e/queries/hcl/highlights.scm
; highlights.scm
[
"!"
"\*"
"/"
"%"
"\+"
"-"
">"
">="
"<"
"<="
"=="
"!="
"&&"
"||"
] @operator
[
"{"
"}"
"["
"]"
"("
")"
] @punctuation.bracket
[
"."
".*"
","
"[*]"
] @punctuation.delimiter
[
(ellipsis)
"\?"
"=>"
] @punctuation.special
[
":"
"="
] @punctuation
[
"for"
"endfor"
"in"
"if"
"else"
"endif"
] @keyword
[
(quoted_template_start) ; "
(quoted_template_end) ; "
(template_literal) ; non-interpolation/directive content
] @string
[
(heredoc_identifier) ; END
(heredoc_start) ; << or <<-
] @punctuation.delimiter
[
(template_interpolation_start) ; ${
(template_interpolation_end) ; }
(template_directive_start) ; %{
(template_directive_end) ; }
(strip_marker) ; ~
] @punctuation.special
(numeric_lit) @number
(bool_lit) @boolean
(null_lit) @constant
(comment) @comment
(identifier) @variable
(body
(block
(identifier) @keyword))
(body
(block
(body
(block
(identifier) @type))))
(function_call
(identifier) @function)
(attribute
(identifier) @variable)
; { key: val }
;
; highlight identifier keys as though they were block attributes
(object_elem
key:
(expression
(variable_expr
(identifier) @variable)))
; var.foo, data.bar
;
; first element in get_attr is a variable.builtin or a reference to a variable.builtin
(expression
(variable_expr
(identifier) @variable)
(get_attr
(identifier) @variable))
; https://github.com/nvim-treesitter/nvim-treesitter/blob/cb79d2446196d25607eb1d982c96939abdf67b8e/queries/terraform/highlights.scm
; Terraform specific references
;
;
; local/module/data/var/output
(expression
(variable_expr
(identifier) @variable
(#any-of? @variable "data" "var" "local" "module" "output"))
(get_attr
(identifier) @variable))
; path.root/cwd/module
(expression
(variable_expr
(identifier) @type
(#eq? @type "path"))
(get_attr
(identifier) @variable
(#any-of? @variable "root" "cwd" "module")))
; terraform.workspace
(expression
(variable_expr
(identifier) @type
(#eq? @type "terraform"))
(get_attr
(identifier) @variable
(#any-of? @variable "workspace")))
; Terraform specific keywords
; FIXME: ideally only for identifiers under a `variable` block to minimize false positives
((identifier) @type
(#any-of? @type "bool" "string" "number" "object" "tuple" "list" "map" "set" "any"))
(object_elem
val:
(expression
(variable_expr
(identifier) @type
(#any-of? @type "bool" "string" "number" "object" "tuple" "list" "map" "set" "any"))))

View file

@ -1,14 +0,0 @@
; https://github.com/nvim-treesitter/nvim-treesitter/blob/ce4adf11cfe36fc5b0e5bcdce0c7c6e8fbc9798a/queries/hcl/indents.scm
[
(block)
(object)
(tuple)
(function_call)
] @indent
(_ "[" "]" @end) @indent
(_ "(" ")" @end) @indent
(_ "{" "}" @end) @indent
; https://github.com/nvim-treesitter/nvim-treesitter/blob/ce4adf11cfe36fc5b0e5bcdce0c7c6e8fbc9798a/queries/terraform/indents.scm
; inherits: hcl

View file

@ -1,9 +0,0 @@
; https://github.com/nvim-treesitter/nvim-treesitter/blob/ce4adf11cfe36fc5b0e5bcdce0c7c6e8fbc9798a/queries/hcl/injections.scm
(heredoc_template
(template_literal) @content
(heredoc_identifier) @language
(#downcase! @language))
; https://github.com/nvim-treesitter/nvim-treesitter/blob/ce4adf11cfe36fc5b0e5bcdce0c7c6e8fbc9798a/queries/terraform/injections.scm
; inherits: hcl