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

@ -0,0 +1,16 @@
[package]
name = "zed_terraform"
version = "0.0.1"
edition = "2021"
publish = false
license = "Apache-2.0"
[lints]
workspace = true
[lib]
path = "src/terraform.rs"
crate-type = ["cdylib"]
[dependencies]
zed_extension_api = "0.0.6"

View file

@ -0,0 +1 @@
../../LICENSE-APACHE

View file

@ -0,0 +1,16 @@
id = "terraform"
name = "Terraform"
description = "Terraform support."
version = "0.0.1"
schema_version = 1
authors = ["Caius Durling <dev@caius.name>", "Daniel Banck <dbanck@users.noreply.github.com>"]
repository = "https://github.com/zed-industries/zed"
[language_servers.terraform-ls]
name = "Terraform Language Server"
languages = ["Terraform", "Terraform Vars"]
language_ids = { Terraform = "terraform", "Terraform Vars" = "terraform-vars" }
[grammars.hcl]
repository = "https://github.com/MichaHoffmann/tree-sitter-hcl"
commit = "e936d3fef8bac884661472dce71ad82284761eb1"

View file

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

@ -0,0 +1,117 @@
; 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

@ -0,0 +1,11 @@
; 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

@ -0,0 +1,6 @@
; 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

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

@ -0,0 +1,159 @@
; 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

@ -0,0 +1,14 @@
; 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

@ -0,0 +1,9 @@
; 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

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

@ -0,0 +1,159 @@
; 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

@ -0,0 +1,14 @@
; 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

@ -0,0 +1,9 @@
; 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

@ -0,0 +1,101 @@
use std::fs;
use zed::LanguageServerId;
use zed_extension_api::{self as zed, Result};
struct TerraformExtension {
cached_binary_path: Option<String>,
}
impl TerraformExtension {
fn language_server_binary_path(
&mut self,
language_server_id: &LanguageServerId,
worktree: &zed::Worktree,
) -> Result<String> {
if let Some(path) = &self.cached_binary_path {
if fs::metadata(path).map_or(false, |stat| stat.is_file()) {
return Ok(path.clone());
}
}
if let Some(path) = worktree.which("terraform-ls") {
self.cached_binary_path = Some(path.clone());
return Ok(path);
}
zed::set_language_server_installation_status(
&language_server_id,
&zed::LanguageServerInstallationStatus::CheckingForUpdate,
);
let release = zed::latest_github_release(
"hashicorp/terraform-ls",
zed::GithubReleaseOptions {
require_assets: false,
pre_release: false,
},
)?;
let (platform, arch) = zed::current_platform();
let download_url = format!(
"https://releases.hashicorp.com/terraform-ls/{version}/terraform-ls_{version}_{os}_{arch}.zip",
version = release.version.strip_prefix('v').unwrap_or(&release.version),
os = match platform {
zed::Os::Mac => "darwin",
zed::Os::Linux => "linux",
zed::Os::Windows => "windows",
},
arch = match arch {
zed::Architecture::Aarch64 => "arm64",
zed::Architecture::X86 => "386",
zed::Architecture::X8664 => "amd64",
},
);
let version_dir = format!("terraform-ls-{}", release.version);
let binary_path = format!("{version_dir}/terraform-ls");
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(&download_url, &version_dir, zed::DownloadedFileType::Zip)
.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)
}
}
impl zed::Extension for TerraformExtension {
fn new() -> Self {
Self {
cached_binary_path: None,
}
}
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!["serve".to_string()],
env: Default::default(),
})
}
}
zed::register_extension!(TerraformExtension);