Port zed/src/languages to zed2

This commit is contained in:
Julia 2023-10-30 21:41:17 -04:00
parent 58446c2715
commit db34de6be4
191 changed files with 9448 additions and 182 deletions

View file

@ -0,0 +1,3 @@
("(" @open ")" @close)
("[" @open "]" @close)
("{" @open "}" @close)

View file

@ -0,0 +1,9 @@
name = "Shell Script"
path_suffixes = ["sh", "bash", "bashrc", "bash_profile", "bash_aliases", "bash_logout", "profile", "zsh", "zshrc", "zshenv", "zsh_profile", "zsh_aliases", "zsh_histfile", "zlogin", "zprofile"]
line_comment = "# "
first_line_pattern = "^#!.*\\b(?:ba|z)?sh\\b"
brackets = [
{ start = "[", end = "]", close = true, newline = false },
{ start = "(", end = ")", close = true, newline = false },
{ start = "\"", end = "\"", close = true, newline = false, not_in = ["comment", "string"] },
]

View file

@ -0,0 +1,59 @@
[
(string)
(raw_string)
(heredoc_body)
(heredoc_start)
(ansi_c_string)
] @string
(command_name) @function
(variable_name) @property
[
"case"
"do"
"done"
"elif"
"else"
"esac"
"export"
"fi"
"for"
"function"
"if"
"in"
"select"
"then"
"unset"
"until"
"while"
"local"
"declare"
] @keyword
(comment) @comment
(function_definition name: (word) @function)
(file_descriptor) @number
[
(command_substitution)
(process_substitution)
(expansion)
]@embedded
[
"$"
"&&"
">"
">>"
"<"
"|"
] @operator
(
(command (_) @constant)
(#match? @constant "^-")
)

View file

@ -0,0 +1,321 @@
use anyhow::{anyhow, Context, Result};
use async_trait::async_trait;
use futures::StreamExt;
pub use language2::*;
use lsp2::LanguageServerBinary;
use smol::fs::{self, File};
use std::{any::Any, path::PathBuf, sync::Arc};
use util::{
fs::remove_matching,
github::{latest_github_release, GitHubLspBinaryVersion},
ResultExt,
};
pub struct CLspAdapter;
#[async_trait]
impl super::LspAdapter for CLspAdapter {
async fn name(&self) -> LanguageServerName {
LanguageServerName("clangd".into())
}
fn short_name(&self) -> &'static str {
"clangd"
}
async fn fetch_latest_server_version(
&self,
delegate: &dyn LspAdapterDelegate,
) -> Result<Box<dyn 'static + Send + Any>> {
let release = latest_github_release("clangd/clangd", false, delegate.http_client()).await?;
let asset_name = format!("clangd-mac-{}.zip", release.name);
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 zip_path = container_dir.join(format!("clangd_{}.zip", version.name));
let version_dir = container_dir.join(format!("clangd_{}", version.name));
let binary_path = version_dir.join("bin/clangd");
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 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)
.output()
.await?
.status;
if !unzip_status.success() {
Err(anyhow!("failed to unzip clangd archive"))?;
}
remove_matching(&container_dir, |entry| entry != version_dir).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 label_for_completion(
&self,
completion: &lsp2::CompletionItem,
language: &Arc<Language>,
) -> Option<CodeLabel> {
let label = completion
.label
.strip_prefix('•')
.unwrap_or(&completion.label)
.trim();
match completion.kind {
Some(lsp2::CompletionItemKind::FIELD) if completion.detail.is_some() => {
let detail = completion.detail.as_ref().unwrap();
let text = format!("{} {}", detail, label);
let source = Rope::from(format!("struct S {{ {} }}", text).as_str());
let runs = language.highlight_text(&source, 11..11 + text.len());
return Some(CodeLabel {
filter_range: detail.len() + 1..text.len(),
text,
runs,
});
}
Some(lsp2::CompletionItemKind::CONSTANT | lsp2::CompletionItemKind::VARIABLE)
if completion.detail.is_some() =>
{
let detail = completion.detail.as_ref().unwrap();
let text = format!("{} {}", detail, label);
let runs = language.highlight_text(&Rope::from(text.as_str()), 0..text.len());
return Some(CodeLabel {
filter_range: detail.len() + 1..text.len(),
text,
runs,
});
}
Some(lsp2::CompletionItemKind::FUNCTION | lsp2::CompletionItemKind::METHOD)
if completion.detail.is_some() =>
{
let detail = completion.detail.as_ref().unwrap();
let text = format!("{} {}", detail, label);
let runs = language.highlight_text(&Rope::from(text.as_str()), 0..text.len());
return Some(CodeLabel {
filter_range: detail.len() + 1..text.rfind('(').unwrap_or(text.len()),
text,
runs,
});
}
Some(kind) => {
let highlight_name = match kind {
lsp2::CompletionItemKind::STRUCT
| lsp2::CompletionItemKind::INTERFACE
| lsp2::CompletionItemKind::CLASS
| lsp2::CompletionItemKind::ENUM => Some("type"),
lsp2::CompletionItemKind::ENUM_MEMBER => Some("variant"),
lsp2::CompletionItemKind::KEYWORD => Some("keyword"),
lsp2::CompletionItemKind::VALUE | lsp2::CompletionItemKind::CONSTANT => {
Some("constant")
}
_ => None,
};
if let Some(highlight_id) = language
.grammar()
.and_then(|g| g.highlight_id_for_name(highlight_name?))
{
let mut label = CodeLabel::plain(label.to_string(), None);
label.runs.push((
0..label.text.rfind('(').unwrap_or(label.text.len()),
highlight_id,
));
return Some(label);
}
}
_ => {}
}
Some(CodeLabel::plain(label.to_string(), None))
}
async fn label_for_symbol(
&self,
name: &str,
kind: lsp2::SymbolKind,
language: &Arc<Language>,
) -> Option<CodeLabel> {
let (text, filter_range, display_range) = match kind {
lsp2::SymbolKind::METHOD | lsp2::SymbolKind::FUNCTION => {
let text = format!("void {} () {{}}", name);
let filter_range = 0..name.len();
let display_range = 5..5 + name.len();
(text, filter_range, display_range)
}
lsp2::SymbolKind::STRUCT => {
let text = format!("struct {} {{}}", name);
let filter_range = 7..7 + name.len();
let display_range = 0..filter_range.end;
(text, filter_range, display_range)
}
lsp2::SymbolKind::ENUM => {
let text = format!("enum {} {{}}", name);
let filter_range = 5..5 + name.len();
let display_range = 0..filter_range.end;
(text, filter_range, display_range)
}
lsp2::SymbolKind::INTERFACE | lsp2::SymbolKind::CLASS => {
let text = format!("class {} {{}}", name);
let filter_range = 6..6 + name.len();
let display_range = 0..filter_range.end;
(text, filter_range, display_range)
}
lsp2::SymbolKind::CONSTANT => {
let text = format!("const int {} = 0;", name);
let filter_range = 10..10 + name.len();
let display_range = 0..filter_range.end;
(text, filter_range, display_range)
}
lsp2::SymbolKind::MODULE => {
let text = format!("namespace {} {{}}", name);
let filter_range = 10..10 + name.len();
let display_range = 0..filter_range.end;
(text, filter_range, display_range)
}
lsp2::SymbolKind::TYPE_PARAMETER => {
let text = format!("typename {} {{}};", name);
let filter_range = 9..9 + name.len();
let display_range = 0..filter_range.end;
(text, filter_range, display_range)
}
_ => return None,
};
Some(CodeLabel {
runs: language.highlight_text(&text.as_str().into(), display_range.clone()),
text: text[display_range].to_string(),
filter_range,
})
}
}
async fn get_cached_server_binary(container_dir: PathBuf) -> Option<LanguageServerBinary> {
(|| async move {
let mut last_clangd_dir = 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_dir() {
last_clangd_dir = Some(entry.path());
}
}
let clangd_dir = last_clangd_dir.ok_or_else(|| anyhow!("no cached binary"))?;
let clangd_bin = clangd_dir.join("bin/clangd");
if clangd_bin.exists() {
Ok(LanguageServerBinary {
path: clangd_bin,
arguments: vec![],
})
} else {
Err(anyhow!(
"missing clangd binary in directory {:?}",
clangd_dir
))
}
})()
.await
.log_err()
}
#[cfg(test)]
mod tests {
use gpui2::{Context, TestAppContext};
use language2::{language_settings::AllLanguageSettings, AutoindentMode, Buffer};
use settings2::SettingsStore;
use std::num::NonZeroU32;
#[gpui2::test]
async fn test_c_autoindent(cx: &mut TestAppContext) {
// cx.executor().set_block_on_ticks(usize::MAX..=usize::MAX);
cx.update(|cx| {
let test_settings = SettingsStore::test(cx);
cx.set_global(test_settings);
language2::init(cx);
cx.update_global::<SettingsStore, _>(|store, cx| {
store.update_user_settings::<AllLanguageSettings>(cx, |s| {
s.defaults.tab_size = NonZeroU32::new(2);
});
});
});
let language = crate::languages::language("c", tree_sitter_c::language(), None).await;
cx.build_model(|cx| {
let mut buffer =
Buffer::new(0, cx.entity_id().as_u64(), "").with_language(language, cx);
// empty function
buffer.edit([(0..0, "int main() {}")], None, cx);
// indent inside braces
let ix = buffer.len() - 1;
buffer.edit([(ix..ix, "\n\n")], Some(AutoindentMode::EachLine), cx);
assert_eq!(buffer.text(), "int main() {\n \n}");
// indent body of single-statement if statement
let ix = buffer.len() - 2;
buffer.edit([(ix..ix, "if (a)\nb;")], Some(AutoindentMode::EachLine), cx);
assert_eq!(buffer.text(), "int main() {\n if (a)\n b;\n}");
// indent inside field expression
let ix = buffer.len() - 3;
buffer.edit([(ix..ix, "\n.c")], Some(AutoindentMode::EachLine), cx);
assert_eq!(buffer.text(), "int main() {\n if (a)\n b\n .c;\n}");
buffer
});
}
}

View file

@ -0,0 +1,3 @@
("[" @open "]" @close)
("{" @open "}" @close)
("\"" @open "\"" @close)

View file

@ -0,0 +1,12 @@
name = "C"
path_suffixes = ["c"]
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 = false, not_in = ["string"] },
{ start = "'", end = "'", close = true, newline = false, not_in = ["string", "comment"] },
{ start = "/*", end = " */", close = true, newline = false, not_in = ["string", "comment"] },
]

View file

@ -0,0 +1,43 @@
(
(comment)* @context
.
(declaration
declarator: [
(function_declarator
declarator: (_) @name)
(pointer_declarator
"*" @name
declarator: (function_declarator
declarator: (_) @name))
(pointer_declarator
"*" @name
declarator: (pointer_declarator
"*" @name
declarator: (function_declarator
declarator: (_) @name)))
]
) @item
)
(
(comment)* @context
.
(function_definition
declarator: [
(function_declarator
declarator: (_) @name
)
(pointer_declarator
"*" @name
declarator: (function_declarator
declarator: (_) @name
))
(pointer_declarator
"*" @name
declarator: (pointer_declarator
"*" @name
declarator: (function_declarator
declarator: (_) @name)))
]
) @item
)

View file

@ -0,0 +1,109 @@
[
"break"
"case"
"const"
"continue"
"default"
"do"
"else"
"enum"
"extern"
"for"
"if"
"inline"
"return"
"sizeof"
"static"
"struct"
"switch"
"typedef"
"union"
"volatile"
"while"
] @keyword
[
"#define"
"#elif"
"#else"
"#endif"
"#if"
"#ifdef"
"#ifndef"
"#include"
(preproc_directive)
] @keyword
[
"--"
"-"
"-="
"->"
"="
"!="
"*"
"&"
"&&"
"+"
"++"
"+="
"<"
"=="
">"
"||"
] @operator
[
"."
";"
] @punctuation.delimiter
[
"{"
"}"
"("
")"
"["
"]"
] @punctuation.bracket
[
(string_literal)
(system_lib_string)
(char_literal)
] @string
(comment) @comment
(number_literal) @number
[
(true)
(false)
(null)
] @constant
(identifier) @variable
((identifier) @constant
(#match? @constant "^_*[A-Z][A-Z\\d_]*$"))
(call_expression
function: (identifier) @function)
(call_expression
function: (field_expression
field: (field_identifier) @function))
(function_declarator
declarator: (identifier) @function)
(preproc_function_def
name: (identifier) @function.special)
(field_identifier) @property
(statement_identifier) @label
[
(type_identifier)
(primitive_type)
(sized_type_specifier)
] @type

View file

@ -0,0 +1,9 @@
[
(field_expression)
(assignment_expression)
(if_statement)
(for_statement)
] @indent
(_ "{" "}" @end) @indent
(_ "(" ")" @end) @indent

View file

@ -0,0 +1,7 @@
(preproc_def
value: (preproc_arg) @content
(#set! "language" "c"))
(preproc_function_def
value: (preproc_arg) @content
(#set! "language" "c"))

View file

@ -0,0 +1,70 @@
(preproc_def
"#define" @context
name: (_) @name) @item
(preproc_function_def
"#define" @context
name: (_) @name
parameters: (preproc_params
"(" @context
")" @context)) @item
(type_definition
"typedef" @context
declarator: (_) @name) @item
(declaration
(type_qualifier)? @context
type: (_)? @context
declarator: [
(function_declarator
declarator: (_) @name
parameters: (parameter_list
"(" @context
")" @context))
(pointer_declarator
"*" @context
declarator: (function_declarator
declarator: (_) @name
parameters: (parameter_list
"(" @context
")" @context)))
(pointer_declarator
"*" @context
declarator: (pointer_declarator
"*" @context
declarator: (function_declarator
declarator: (_) @name
parameters: (parameter_list
"(" @context
")" @context))))
]
) @item
(function_definition
(type_qualifier)? @context
type: (_)? @context
declarator: [
(function_declarator
declarator: (_) @name
parameters: (parameter_list
"(" @context
")" @context))
(pointer_declarator
"*" @context
declarator: (function_declarator
declarator: (_) @name
parameters: (parameter_list
"(" @context
")" @context)))
(pointer_declarator
"*" @context
declarator: (pointer_declarator
"*" @context
declarator: (function_declarator
declarator: (_) @name
parameters: (parameter_list
"(" @context
")" @context))))
]
) @item

View file

@ -0,0 +1,2 @@
(comment) @comment
(string_literal) @string

View file

@ -0,0 +1,3 @@
("[" @open "]" @close)
("{" @open "}" @close)
("\"" @open "\"" @close)

View file

@ -0,0 +1,12 @@
name = "C++"
path_suffixes = ["cc", "cpp", "h", "hpp", "cxx", "hxx", "inl"]
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 = false, not_in = ["string"] },
{ start = "'", end = "'", close = true, newline = false, not_in = ["string", "comment"] },
{ start = "/*", end = " */", close = true, newline = false, not_in = ["string", "comment"] },
]

View file

@ -0,0 +1,61 @@
(
(comment)* @context
.
(function_definition
(type_qualifier)? @name
type: (_)? @name
declarator: [
(function_declarator
declarator: (_) @name)
(pointer_declarator
"*" @name
declarator: (function_declarator
declarator: (_) @name))
(pointer_declarator
"*" @name
declarator: (pointer_declarator
"*" @name
declarator: (function_declarator
declarator: (_) @name)))
(reference_declarator
["&" "&&"] @name
(function_declarator
declarator: (_) @name))
]
(type_qualifier)? @name) @item
)
(
(comment)* @context
.
(template_declaration
(class_specifier
"class" @name
name: (_) @name)
) @item
)
(
(comment)* @context
.
(class_specifier
"class" @name
name: (_) @name) @item
)
(
(comment)* @context
.
(enum_specifier
"enum" @name
name: (_) @name) @item
)
(
(comment)* @context
.
(declaration
type: (struct_specifier
"struct" @name)
declarator: (_) @name) @item
)

View file

@ -0,0 +1,158 @@
(identifier) @variable
(call_expression
function: (qualified_identifier
name: (identifier) @function))
(call_expression
function: (identifier) @function)
(call_expression
function: (field_expression
field: (field_identifier) @function))
(preproc_function_def
name: (identifier) @function.special)
(template_function
name: (identifier) @function)
(template_method
name: (field_identifier) @function)
(function_declarator
declarator: (identifier) @function)
(function_declarator
declarator: (qualified_identifier
name: (identifier) @function))
(function_declarator
declarator: (field_identifier) @function)
((namespace_identifier) @type
(#match? @type "^[A-Z]"))
(auto) @type
(type_identifier) @type
((identifier) @constant
(#match? @constant "^_*[A-Z][A-Z\\d_]*$"))
(field_identifier) @property
(statement_identifier) @label
(this) @variable.special
[
"break"
"case"
"catch"
"class"
"co_await"
"co_return"
"co_yield"
"const"
"constexpr"
"continue"
"default"
"delete"
"do"
"else"
"enum"
"explicit"
"extern"
"final"
"for"
"friend"
"if"
"if"
"inline"
"mutable"
"namespace"
"new"
"noexcept"
"override"
"private"
"protected"
"public"
"return"
"sizeof"
"static"
"struct"
"switch"
"template"
"throw"
"try"
"typedef"
"typename"
"union"
"using"
"virtual"
"volatile"
"while"
(primitive_type)
(type_qualifier)
] @keyword
[
"#define"
"#elif"
"#else"
"#endif"
"#if"
"#ifdef"
"#ifndef"
"#include"
(preproc_directive)
] @keyword
(comment) @comment
[
(true)
(false)
(null)
(nullptr)
] @constant
(number_literal) @number
[
(string_literal)
(system_lib_string)
(char_literal)
(raw_string_literal)
] @string
[
"."
";"
] @punctuation.delimiter
[
"{"
"}"
"("
")"
"["
"]"
] @punctuation.bracket
[
"--"
"-"
"-="
"->"
"="
"!="
"*"
"&"
"&&"
"+"
"++"
"+="
"<"
"=="
">"
"||"
] @operator

View file

@ -0,0 +1,7 @@
[
(field_expression)
(assignment_expression)
] @indent
(_ "{" "}" @end) @indent
(_ "(" ")" @end) @indent

View file

@ -0,0 +1,7 @@
(preproc_def
value: (preproc_arg) @content
(#set! "language" "c++"))
(preproc_function_def
value: (preproc_arg) @content
(#set! "language" "c++"))

View file

@ -0,0 +1,149 @@
(preproc_def
"#define" @context
name: (_) @name) @item
(preproc_function_def
"#define" @context
name: (_) @name
parameters: (preproc_params
"(" @context
")" @context)) @item
(type_definition
"typedef" @context
declarator: (_) @name) @item
(struct_specifier
"struct" @context
name: (_) @name) @item
(class_specifier
"class" @context
name: (_) @name) @item
(enum_specifier
"enum" @context
name: (_) @name) @item
(enumerator
name: (_) @name) @item
(declaration
(storage_class_specifier) @context
(type_qualifier)? @context
type: (_) @context
declarator: (init_declarator
declarator: (_) @name)) @item
(function_definition
(type_qualifier)? @context
type: (_)? @context
declarator: [
(function_declarator
declarator: (_) @name
parameters: (parameter_list
"(" @context
")" @context))
(pointer_declarator
"*" @context
declarator: (function_declarator
declarator: (_) @name
parameters: (parameter_list
"(" @context
")" @context)))
(pointer_declarator
"*" @context
declarator: (pointer_declarator
"*" @context
declarator: (function_declarator
declarator: (_) @name
parameters: (parameter_list
"(" @context
")" @context))))
(reference_declarator
["&" "&&"] @context
(function_declarator
declarator: (_) @name
parameters: (parameter_list
"(" @context
")" @context)))
]
(type_qualifier)? @context) @item
(declaration
(type_qualifier)? @context
type: (_)? @context
declarator: [
(field_identifier) @name
(pointer_declarator
"*" @context
declarator: (field_identifier) @name)
(function_declarator
declarator: (_) @name
parameters: (parameter_list
"(" @context
")" @context))
(pointer_declarator
"*" @context
declarator: (function_declarator
declarator: (_) @name
parameters: (parameter_list
"(" @context
")" @context)))
(pointer_declarator
"*" @context
declarator: (pointer_declarator
"*" @context
declarator: (function_declarator
declarator: (_) @name
parameters: (parameter_list
"(" @context
")" @context))))
(reference_declarator
["&" "&&"] @context
(function_declarator
declarator: (_) @name
parameters: (parameter_list
"(" @context
")" @context)))
]
(type_qualifier)? @context) @item
(field_declaration
(type_qualifier)? @context
type: (_) @context
declarator: [
(field_identifier) @name
(pointer_declarator
"*" @context
declarator: (field_identifier) @name)
(function_declarator
declarator: (_) @name
parameters: (parameter_list
"(" @context
")" @context))
(pointer_declarator
"*" @context
declarator: (function_declarator
declarator: (_) @name
parameters: (parameter_list
"(" @context
")" @context)))
(pointer_declarator
"*" @context
declarator: (pointer_declarator
"*" @context
declarator: (function_declarator
declarator: (_) @name
parameters: (parameter_list
"(" @context
")" @context))))
(reference_declarator
["&" "&&"] @context
(function_declarator
declarator: (_) @name
parameters: (parameter_list
"(" @context
")" @context)))
]
(type_qualifier)? @context) @item

View file

@ -0,0 +1,2 @@
(comment) @comment
(string_literal) @string

View file

@ -0,0 +1,130 @@
use anyhow::{anyhow, Result};
use async_trait::async_trait;
use futures::StreamExt;
use language2::{LanguageServerName, LspAdapter, LspAdapterDelegate};
use lsp2::LanguageServerBinary;
use node_runtime::NodeRuntime;
use serde_json::json;
use smol::fs;
use std::{
any::Any,
ffi::OsString,
path::{Path, PathBuf},
sync::Arc,
};
use util::ResultExt;
const SERVER_PATH: &'static str =
"node_modules/vscode-langservers-extracted/bin/vscode-css-language-server";
fn server_binary_arguments(server_path: &Path) -> Vec<OsString> {
vec![server_path.into(), "--stdio".into()]
}
pub struct CssLspAdapter {
node: Arc<dyn NodeRuntime>,
}
impl CssLspAdapter {
pub fn new(node: Arc<dyn NodeRuntime>) -> Self {
CssLspAdapter { node }
}
}
#[async_trait]
impl LspAdapter for CssLspAdapter {
async fn name(&self) -> LanguageServerName {
LanguageServerName("vscode-css-language-server".into())
}
fn short_name(&self) -> &'static str {
"css"
}
async fn fetch_latest_server_version(
&self,
_: &dyn LspAdapterDelegate,
) -> Result<Box<dyn 'static + Any + Send>> {
Ok(Box::new(
self.node
.npm_package_latest_version("vscode-langservers-extracted")
.await?,
) as Box<_>)
}
async fn fetch_server_binary(
&self,
version: Box<dyn 'static + Send + Any>,
container_dir: PathBuf,
_: &dyn LspAdapterDelegate,
) -> Result<LanguageServerBinary> {
let version = version.downcast::<String>().unwrap();
let server_path = container_dir.join(SERVER_PATH);
if fs::metadata(&server_path).await.is_err() {
self.node
.npm_install_packages(
&container_dir,
&[("vscode-langservers-extracted", version.as_str())],
)
.await?;
}
Ok(LanguageServerBinary {
path: self.node.binary_path().await?,
arguments: server_binary_arguments(&server_path),
})
}
async fn cached_server_binary(
&self,
container_dir: PathBuf,
_: &dyn LspAdapterDelegate,
) -> Option<LanguageServerBinary> {
get_cached_server_binary(container_dir, &*self.node).await
}
async fn installation_test_binary(
&self,
container_dir: PathBuf,
) -> Option<LanguageServerBinary> {
get_cached_server_binary(container_dir, &*self.node).await
}
async fn initialization_options(&self) -> Option<serde_json::Value> {
Some(json!({
"provideFormatter": true
}))
}
}
async fn get_cached_server_binary(
container_dir: PathBuf,
node: &dyn NodeRuntime,
) -> Option<LanguageServerBinary> {
(|| async move {
let mut last_version_dir = 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_dir() {
last_version_dir = Some(entry.path());
}
}
let last_version_dir = last_version_dir.ok_or_else(|| anyhow!("no cached binary"))?;
let server_path = last_version_dir.join(SERVER_PATH);
if server_path.exists() {
Ok(LanguageServerBinary {
path: node.binary_path().await?,
arguments: server_binary_arguments(&server_path),
})
} else {
Err(anyhow!(
"missing executable in directory {:?}",
last_version_dir
))
}
})()
.await
.log_err()
}

View file

@ -0,0 +1,3 @@
("(" @open ")" @close)
("[" @open "]" @close)
("{" @open "}" @close)

View file

@ -0,0 +1,13 @@
name = "CSS"
path_suffixes = ["css"]
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"] },
{ start = "'", end = "'", close = true, newline = false, not_in = ["string", "comment"] },
]
word_characters = ["-"]
block_comment = ["/* ", " */"]
prettier_parser_name = "css"

View file

@ -0,0 +1,78 @@
(comment) @comment
[
(tag_name)
(nesting_selector)
(universal_selector)
] @tag
[
"~"
">"
"+"
"-"
"*"
"/"
"="
"^="
"|="
"~="
"$="
"*="
"and"
"or"
"not"
"only"
] @operator
(attribute_selector (plain_value) @string)
(attribute_name) @attribute
(pseudo_element_selector (tag_name) @attribute)
(pseudo_class_selector (class_name) @attribute)
[
(class_name)
(id_name)
(namespace_name)
(property_name)
(feature_name)
] @property
(function_name) @function
(
[
(property_name)
(plain_value)
] @variable.special
(#match? @variable.special "^--")
)
[
"@media"
"@import"
"@charset"
"@namespace"
"@supports"
"@keyframes"
(at_keyword)
(to)
(from)
(important)
] @keyword
(string_value) @string
(color_value) @string.special
[
(integer_value)
(float_value)
] @number
(unit) @type
[
","
":"
] @punctuation.delimiter

View file

@ -0,0 +1 @@
(_ "{" "}" @end) @indent

View file

@ -0,0 +1,2 @@
(comment) @comment
(string_value) @string

View file

@ -0,0 +1,546 @@
use anyhow::{anyhow, bail, Context, Result};
use async_trait::async_trait;
use futures::StreamExt;
use gpui2::{AsyncAppContext, Task};
pub use language2::*;
use lsp2::{CompletionItemKind, LanguageServerBinary, SymbolKind};
use schemars::JsonSchema;
use serde_derive::{Deserialize, Serialize};
use settings2::Settings;
use smol::fs::{self, File};
use std::{
any::Any,
env::consts,
ops::Deref,
path::PathBuf,
sync::{
atomic::{AtomicBool, Ordering::SeqCst},
Arc,
},
};
use util::{
async_maybe,
fs::remove_matching,
github::{latest_github_release, GitHubLspBinaryVersion},
ResultExt,
};
#[derive(Clone, Serialize, Deserialize, JsonSchema)]
pub struct ElixirSettings {
pub lsp: ElixirLspSetting,
}
#[derive(Clone, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum ElixirLspSetting {
ElixirLs,
NextLs,
Local {
path: String,
arguments: Vec<String>,
},
}
#[derive(Clone, Serialize, Default, Deserialize, JsonSchema)]
pub struct ElixirSettingsContent {
lsp: Option<ElixirLspSetting>,
}
impl Settings for ElixirSettings {
const KEY: Option<&'static str> = Some("elixir");
type FileContent = ElixirSettingsContent;
fn load(
default_value: &Self::FileContent,
user_values: &[&Self::FileContent],
_: &mut gpui2::AppContext,
) -> Result<Self>
where
Self: Sized,
{
Self::load_via_json_merge(default_value, user_values)
}
}
pub struct ElixirLspAdapter;
#[async_trait]
impl LspAdapter for ElixirLspAdapter {
async fn name(&self) -> LanguageServerName {
LanguageServerName("elixir-ls".into())
}
fn short_name(&self) -> &'static str {
"elixir-ls"
}
fn will_start_server(
&self,
delegate: &Arc<dyn LspAdapterDelegate>,
cx: &mut AsyncAppContext,
) -> Option<Task<Result<()>>> {
static DID_SHOW_NOTIFICATION: AtomicBool = AtomicBool::new(false);
const NOTIFICATION_MESSAGE: &str = "Could not run the elixir language server, `elixir-ls`, because `elixir` was not found.";
let delegate = delegate.clone();
Some(cx.spawn(|cx| async move {
let elixir_output = smol::process::Command::new("elixir")
.args(["--version"])
.output()
.await;
if elixir_output.is_err() {
if DID_SHOW_NOTIFICATION
.compare_exchange(false, true, SeqCst, SeqCst)
.is_ok()
{
cx.update(|cx| {
delegate.show_notification(NOTIFICATION_MESSAGE, cx);
})?
}
return Err(anyhow!("cannot run elixir-ls"));
}
Ok(())
}))
}
async fn fetch_latest_server_version(
&self,
delegate: &dyn LspAdapterDelegate,
) -> Result<Box<dyn 'static + Send + Any>> {
let http = delegate.http_client();
let release = latest_github_release("elixir-lsp/elixir-ls", false, http).await?;
let version_name = release
.name
.strip_prefix("Release ")
.context("Elixir-ls release name does not start with prefix")?
.to_owned();
let asset_name = format!("elixir-ls-{}.zip", &version_name);
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: version_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 zip_path = container_dir.join(format!("elixir-ls_{}.zip", version.name));
let version_dir = container_dir.join(format!("elixir-ls_{}", version.name));
let binary_path = version_dir.join("language_server.sh");
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 mut file = File::create(&zip_path)
.await
.with_context(|| format!("failed to create file {}", zip_path.display()))?;
if !response.status().is_success() {
Err(anyhow!(
"download failed with status {}",
response.status().to_string()
))?;
}
futures::io::copy(response.body_mut(), &mut file).await?;
fs::create_dir_all(&version_dir)
.await
.with_context(|| format!("failed to create directory {}", version_dir.display()))?;
let unzip_status = smol::process::Command::new("unzip")
.arg(&zip_path)
.arg("-d")
.arg(&version_dir)
.output()
.await?
.status;
if !unzip_status.success() {
Err(anyhow!("failed to unzip elixir-ls archive"))?;
}
remove_matching(&container_dir, |entry| entry != version_dir).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_elixir_ls(container_dir).await
}
async fn installation_test_binary(
&self,
container_dir: PathBuf,
) -> Option<LanguageServerBinary> {
get_cached_server_binary_elixir_ls(container_dir).await
}
async fn label_for_completion(
&self,
completion: &lsp2::CompletionItem,
language: &Arc<Language>,
) -> Option<CodeLabel> {
match completion.kind.zip(completion.detail.as_ref()) {
Some((_, detail)) if detail.starts_with("(function)") => {
let text = detail.strip_prefix("(function) ")?;
let filter_range = 0..text.find('(').unwrap_or(text.len());
let source = Rope::from(format!("def {text}").as_str());
let runs = language.highlight_text(&source, 4..4 + text.len());
return Some(CodeLabel {
text: text.to_string(),
runs,
filter_range,
});
}
Some((_, detail)) if detail.starts_with("(macro)") => {
let text = detail.strip_prefix("(macro) ")?;
let filter_range = 0..text.find('(').unwrap_or(text.len());
let source = Rope::from(format!("defmacro {text}").as_str());
let runs = language.highlight_text(&source, 9..9 + text.len());
return Some(CodeLabel {
text: text.to_string(),
runs,
filter_range,
});
}
Some((
CompletionItemKind::CLASS
| CompletionItemKind::MODULE
| CompletionItemKind::INTERFACE
| CompletionItemKind::STRUCT,
_,
)) => {
let filter_range = 0..completion
.label
.find(" (")
.unwrap_or(completion.label.len());
let text = &completion.label[filter_range.clone()];
let source = Rope::from(format!("defmodule {text}").as_str());
let runs = language.highlight_text(&source, 10..10 + text.len());
return Some(CodeLabel {
text: completion.label.clone(),
runs,
filter_range,
});
}
_ => {}
}
None
}
async fn label_for_symbol(
&self,
name: &str,
kind: SymbolKind,
language: &Arc<Language>,
) -> Option<CodeLabel> {
let (text, filter_range, display_range) = match kind {
SymbolKind::METHOD | SymbolKind::FUNCTION => {
let text = format!("def {}", name);
let filter_range = 4..4 + name.len();
let display_range = 0..filter_range.end;
(text, filter_range, display_range)
}
SymbolKind::CLASS | SymbolKind::MODULE | SymbolKind::INTERFACE | SymbolKind::STRUCT => {
let text = format!("defmodule {}", name);
let filter_range = 10..10 + name.len();
let display_range = 0..filter_range.end;
(text, filter_range, display_range)
}
_ => return None,
};
Some(CodeLabel {
runs: language.highlight_text(&text.as_str().into(), display_range.clone()),
text: text[display_range].to_string(),
filter_range,
})
}
}
async fn get_cached_server_binary_elixir_ls(
container_dir: PathBuf,
) -> Option<LanguageServerBinary> {
(|| async move {
let mut last = None;
let mut entries = fs::read_dir(&container_dir).await?;
while let Some(entry) = entries.next().await {
last = Some(entry?.path());
}
last.map(|path| LanguageServerBinary {
path,
arguments: vec![],
})
.ok_or_else(|| anyhow!("no cached binary"))
})()
.await
.log_err()
}
pub struct NextLspAdapter;
#[async_trait]
impl LspAdapter for NextLspAdapter {
async fn name(&self) -> LanguageServerName {
LanguageServerName("next-ls".into())
}
fn short_name(&self) -> &'static str {
"next-ls"
}
async fn fetch_latest_server_version(
&self,
delegate: &dyn LspAdapterDelegate,
) -> Result<Box<dyn 'static + Send + Any>> {
let release =
latest_github_release("elixir-tools/next-ls", false, delegate.http_client()).await?;
let version = release.name.clone();
let platform = match consts::ARCH {
"x86_64" => "darwin_amd64",
"aarch64" => "darwin_arm64",
other => bail!("Running on unsupported platform: {other}"),
};
let asset_name = format!("next_ls_{}", platform);
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: version,
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("next-ls");
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 mut file = smol::fs::File::create(&binary_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?;
fs::set_permissions(
&binary_path,
<fs::Permissions as fs::unix::PermissionsExt>::from_mode(0o755),
)
.await?;
}
Ok(LanguageServerBinary {
path: binary_path,
arguments: vec!["--stdio".into()],
})
}
async fn cached_server_binary(
&self,
container_dir: PathBuf,
_: &dyn LspAdapterDelegate,
) -> Option<LanguageServerBinary> {
get_cached_server_binary_next(container_dir)
.await
.map(|mut binary| {
binary.arguments = vec!["--stdio".into()];
binary
})
}
async fn installation_test_binary(
&self,
container_dir: PathBuf,
) -> Option<LanguageServerBinary> {
get_cached_server_binary_next(container_dir)
.await
.map(|mut binary| {
binary.arguments = vec!["--help".into()];
binary
})
}
async fn label_for_completion(
&self,
completion: &lsp2::CompletionItem,
language: &Arc<Language>,
) -> Option<CodeLabel> {
label_for_completion_elixir(completion, language)
}
async fn label_for_symbol(
&self,
name: &str,
symbol_kind: SymbolKind,
language: &Arc<Language>,
) -> Option<CodeLabel> {
label_for_symbol_elixir(name, symbol_kind, language)
}
}
async fn get_cached_server_binary_next(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 == "next-ls")
{
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()
}
pub struct LocalLspAdapter {
pub path: String,
pub arguments: Vec<String>,
}
#[async_trait]
impl LspAdapter for LocalLspAdapter {
async fn name(&self) -> LanguageServerName {
LanguageServerName("local-ls".into())
}
fn short_name(&self) -> &'static str {
"local-ls"
}
async fn fetch_latest_server_version(
&self,
_: &dyn LspAdapterDelegate,
) -> Result<Box<dyn 'static + Send + Any>> {
Ok(Box::new(()) as Box<_>)
}
async fn fetch_server_binary(
&self,
_: Box<dyn 'static + Send + Any>,
_: PathBuf,
_: &dyn LspAdapterDelegate,
) -> Result<LanguageServerBinary> {
let path = shellexpand::full(&self.path)?;
Ok(LanguageServerBinary {
path: PathBuf::from(path.deref()),
arguments: self.arguments.iter().map(|arg| arg.into()).collect(),
})
}
async fn cached_server_binary(
&self,
_: PathBuf,
_: &dyn LspAdapterDelegate,
) -> Option<LanguageServerBinary> {
let path = shellexpand::full(&self.path).ok()?;
Some(LanguageServerBinary {
path: PathBuf::from(path.deref()),
arguments: self.arguments.iter().map(|arg| arg.into()).collect(),
})
}
async fn installation_test_binary(&self, _: PathBuf) -> Option<LanguageServerBinary> {
let path = shellexpand::full(&self.path).ok()?;
Some(LanguageServerBinary {
path: PathBuf::from(path.deref()),
arguments: self.arguments.iter().map(|arg| arg.into()).collect(),
})
}
async fn label_for_completion(
&self,
completion: &lsp2::CompletionItem,
language: &Arc<Language>,
) -> Option<CodeLabel> {
label_for_completion_elixir(completion, language)
}
async fn label_for_symbol(
&self,
name: &str,
symbol: SymbolKind,
language: &Arc<Language>,
) -> Option<CodeLabel> {
label_for_symbol_elixir(name, symbol, language)
}
}
fn label_for_completion_elixir(
completion: &lsp2::CompletionItem,
language: &Arc<Language>,
) -> Option<CodeLabel> {
return Some(CodeLabel {
runs: language.highlight_text(&completion.label.clone().into(), 0..completion.label.len()),
text: completion.label.clone(),
filter_range: 0..completion.label.len(),
});
}
fn label_for_symbol_elixir(
name: &str,
_: SymbolKind,
language: &Arc<Language>,
) -> Option<CodeLabel> {
Some(CodeLabel {
runs: language.highlight_text(&name.into(), 0..name.len()),
text: name.to_string(),
filter_range: 0..name.len(),
})
}

View file

@ -0,0 +1,5 @@
("(" @open ")" @close)
("[" @open "]" @close)
("{" @open "}" @close)
("\"" @open "\"" @close)
("do" @open "end" @close)

View file

@ -0,0 +1,16 @@
name = "Elixir"
path_suffixes = ["ex", "exs"]
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 = false, not_in = ["string", "comment"] },
{ start = "'", end = "'", close = true, newline = false, not_in = ["string", "comment"] },
]
scope_opt_in_language_servers = ["tailwindcss-language-server"]
[overrides.string]
word_characters = ["-"]
opt_into_language_servers = ["tailwindcss-language-server"]

View file

@ -0,0 +1,27 @@
(
(unary_operator
operator: "@"
operand: (call
target: (identifier) @unary
(#match? @unary "^(doc)$"))
) @context
.
(call
target: (identifier) @name
(arguments
[
(identifier) @name
(call
target: (identifier) @name)
(binary_operator
left: (call
target: (identifier) @name)
operator: "when")
])
(#match? @name "^(def|defp|defdelegate|defguard|defguardp|defmacro|defmacrop|defn|defnp)$")) @item
)
(call
target: (identifier) @name
(arguments (alias) @name)
(#match? @name "^(defmodule|defprotocol)$")) @item

View file

@ -0,0 +1,153 @@
["when" "and" "or" "not" "in" "not in" "fn" "do" "end" "catch" "rescue" "after" "else"] @keyword
(unary_operator
operator: "&"
operand: (integer) @operator)
(operator_identifier) @operator
(unary_operator
operator: _ @operator)
(binary_operator
operator: _ @operator)
(dot
operator: _ @operator)
(stab_clause
operator: _ @operator)
[
(boolean)
(nil)
] @constant
[
(integer)
(float)
] @number
(alias) @type
(call
target: (dot
left: (atom) @type))
(char) @constant
(escape_sequence) @string.escape
[
(atom)
(quoted_atom)
(keyword)
(quoted_keyword)
] @string.special.symbol
[
(string)
(charlist)
] @string
(sigil
(sigil_name) @__name__
quoted_start: _ @string
quoted_end: _ @string
(#match? @__name__ "^[sS]$")) @string
(sigil
(sigil_name) @__name__
quoted_start: _ @string.regex
quoted_end: _ @string.regex
(#match? @__name__ "^[rR]$")) @string.regex
(sigil
(sigil_name) @__name__
quoted_start: _ @string.special
quoted_end: _ @string.special) @string.special
(
(identifier) @comment.unused
(#match? @comment.unused "^_")
)
(call
target: [
(identifier) @function
(dot
right: (identifier) @function)
])
(call
target: (identifier) @keyword
(arguments
[
(identifier) @function
(binary_operator
left: (identifier) @function
operator: "when")
(binary_operator
operator: "|>"
right: (identifier))
])
(#match? @keyword "^(def|defdelegate|defguard|defguardp|defmacro|defmacrop|defn|defnp|defp)$"))
(binary_operator
operator: "|>"
right: (identifier) @function)
(call
target: (identifier) @keyword
(#match? @keyword "^(def|defdelegate|defexception|defguard|defguardp|defimpl|defmacro|defmacrop|defmodule|defn|defnp|defoverridable|defp|defprotocol|defstruct)$"))
(call
target: (identifier) @keyword
(#match? @keyword "^(alias|case|cond|else|for|if|import|quote|raise|receive|require|reraise|super|throw|try|unless|unquote|unquote_splicing|use|with)$"))
(
(identifier) @constant.builtin
(#match? @constant.builtin "^(__MODULE__|__DIR__|__ENV__|__CALLER__|__STACKTRACE__)$")
)
(unary_operator
operator: "@" @comment.doc
operand: (call
target: (identifier) @__attribute__ @comment.doc
(arguments
[
(string)
(charlist)
(sigil)
(boolean)
] @comment.doc))
(#match? @__attribute__ "^(moduledoc|typedoc|doc)$"))
(comment) @comment
[
"%"
] @punctuation
[
","
";"
] @punctuation.delimiter
[
"("
")"
"["
"]"
"{"
"}"
"<<"
">>"
] @punctuation.bracket
(interpolation "#{" @punctuation.special "}" @punctuation.special) @embedded
((sigil
(sigil_name) @_sigil_name
(quoted_content) @embedded)
(#eq? @_sigil_name "H"))

View file

@ -0,0 +1,6 @@
(call) @indent
(_ "[" "]" @end) @indent
(_ "{" "}" @end) @indent
(_ "(" ")" @end) @indent
(_ "do" "end" @end) @indent

View file

@ -0,0 +1,7 @@
; Phoenix HTML template
((sigil
(sigil_name) @_sigil_name
(quoted_content) @content)
(#eq? @_sigil_name "H")
(#set! language "heex"))

View file

@ -0,0 +1,26 @@
(call
target: (identifier) @context
(arguments (alias) @name)
(#match? @context "^(defmodule|defprotocol)$")) @item
(call
target: (identifier) @context
(arguments
[
(identifier) @name
(call
target: (identifier) @name
(arguments
"(" @context.extra
_* @context.extra
")" @context.extra))
(binary_operator
left: (call
target: (identifier) @name
(arguments
"(" @context.extra
_* @context.extra
")" @context.extra))
operator: "when")
])
(#match? @context "^(def|defp|defdelegate|defguard|defguardp|defmacro|defmacrop|defn|defnp)$")) @item

View file

@ -0,0 +1,2 @@
(comment) @comment
[(string) (charlist)] @string

View file

@ -0,0 +1,11 @@
name = "Elm"
path_suffixes = ["elm"]
line_comment = "-- "
block_comment = ["{- ", " -}"]
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"] },
{ start = "'", end = "'", close = true, newline = false, not_in = ["string", "comment"] },
]

View file

@ -0,0 +1,72 @@
[
"if"
"then"
"else"
"let"
"in"
(case)
(of)
(backslash)
(as)
(port)
(exposing)
(alias)
(import)
(module)
(type)
(arrow)
] @keyword
[
(eq)
(operator_identifier)
(colon)
] @operator
(type_annotation(lower_case_identifier) @function)
(port_annotation(lower_case_identifier) @function)
(function_declaration_left(lower_case_identifier) @function.definition)
(function_call_expr
target: (value_expr
name: (value_qid (lower_case_identifier) @function)))
(exposed_value(lower_case_identifier) @function)
(exposed_type(upper_case_identifier) @type)
(field_access_expr(value_expr(value_qid)) @identifier)
(lower_pattern) @variable
(record_base_identifier) @identifier
[
"("
")"
] @punctuation.bracket
[
"|"
","
] @punctuation.delimiter
(number_constant_expr) @constant
(type_declaration(upper_case_identifier) @type)
(type_ref) @type
(type_alias_declaration name: (upper_case_identifier) @type)
(value_expr(upper_case_qid(upper_case_identifier)) @type)
[
(line_comment)
(block_comment)
] @comment
(string_escape) @string.escape
[
(open_quote)
(close_quote)
(regular_string_part)
(open_char)
(close_char)
] @string

View file

@ -0,0 +1,2 @@
((glsl_content) @content
(#set! "language" "glsl"))

View file

@ -0,0 +1,22 @@
(type_declaration
(type) @context
(upper_case_identifier) @name) @item
(type_alias_declaration
(type) @context
(alias) @context
name: (upper_case_identifier) @name) @item
(type_alias_declaration
typeExpression:
(type_expression
part: (record_type
(field_type
name: (lower_case_identifier) @name) @item)))
(union_variant
name: (upper_case_identifier) @name) @item
(value_declaration
functionDeclarationLeft:
(function_declaration_left(lower_case_identifier) @name)) @item

View file

@ -0,0 +1,8 @@
name = "ERB"
path_suffixes = ["erb"]
autoclose_before = ">})"
brackets = [
{ start = "<", end = ">", close = true, newline = true },
]
block_comment = ["<%#", "%>"]
scope_opt_in_language_servers = ["tailwindcss-language-server"]

View file

@ -0,0 +1,12 @@
(comment_directive) @comment
[
"<%#"
"<%"
"<%="
"<%_"
"<%-"
"%>"
"-%>"
"_%>"
] @keyword

View file

@ -0,0 +1,7 @@
((code) @content
(#set! "language" "ruby")
(#set! "combined"))
((content) @content
(#set! "language" "html")
(#set! "combined"))

View file

@ -0,0 +1,9 @@
name = "GLSL"
path_suffixes = ["vert", "frag", "tesc", "tese", "geom", "comp"]
line_comment = "// "
block_comment = ["/* ", " */"]
brackets = [
{ start = "{", end = "}", close = true, newline = true },
{ start = "[", end = "]", close = true, newline = true },
{ start = "(", end = ")", close = true, newline = true },
]

View file

@ -0,0 +1,118 @@
"break" @keyword
"case" @keyword
"const" @keyword
"continue" @keyword
"default" @keyword
"do" @keyword
"else" @keyword
"enum" @keyword
"extern" @keyword
"for" @keyword
"if" @keyword
"inline" @keyword
"return" @keyword
"sizeof" @keyword
"static" @keyword
"struct" @keyword
"switch" @keyword
"typedef" @keyword
"union" @keyword
"volatile" @keyword
"while" @keyword
"#define" @keyword
"#elif" @keyword
"#else" @keyword
"#endif" @keyword
"#if" @keyword
"#ifdef" @keyword
"#ifndef" @keyword
"#include" @keyword
(preproc_directive) @keyword
"--" @operator
"-" @operator
"-=" @operator
"->" @operator
"=" @operator
"!=" @operator
"*" @operator
"&" @operator
"&&" @operator
"+" @operator
"++" @operator
"+=" @operator
"<" @operator
"==" @operator
">" @operator
"||" @operator
"." @delimiter
";" @delimiter
(string_literal) @string
(system_lib_string) @string
(null) @constant
(number_literal) @number
(char_literal) @number
(call_expression
function: (identifier) @function)
(call_expression
function: (field_expression
field: (field_identifier) @function))
(function_declarator
declarator: (identifier) @function)
(preproc_function_def
name: (identifier) @function.special)
(field_identifier) @property
(statement_identifier) @label
(type_identifier) @type
(primitive_type) @type
(sized_type_specifier) @type
((identifier) @constant
(#match? @constant "^[A-Z][A-Z\\d_]*$"))
(identifier) @variable
(comment) @comment
; inherits: c
[
"in"
"out"
"inout"
"uniform"
"shared"
"layout"
"attribute"
"varying"
"buffer"
"coherent"
"readonly"
"writeonly"
"precision"
"highp"
"mediump"
"lowp"
"centroid"
"sample"
"patch"
"smooth"
"flat"
"noperspective"
"invariant"
"precise"
] @type.qualifier
"subroutine" @keyword.function
(extension_storage_class) @storageclass
(
(identifier) @variable.builtin
(#match? @variable.builtin "^gl_")
)

View file

@ -0,0 +1,464 @@
use anyhow::{anyhow, Result};
use async_trait::async_trait;
use futures::StreamExt;
use gpui2::{AsyncAppContext, Task};
pub use language2::*;
use lazy_static::lazy_static;
use lsp2::LanguageServerBinary;
use regex::Regex;
use smol::{fs, process};
use std::{
any::Any,
ffi::{OsStr, OsString},
ops::Range,
path::PathBuf,
str,
sync::{
atomic::{AtomicBool, Ordering::SeqCst},
Arc,
},
};
use util::{fs::remove_matching, github::latest_github_release, ResultExt};
fn server_binary_arguments() -> Vec<OsString> {
vec!["-mode=stdio".into()]
}
#[derive(Copy, Clone)]
pub struct GoLspAdapter;
lazy_static! {
static ref GOPLS_VERSION_REGEX: Regex = Regex::new(r"\d+\.\d+\.\d+").unwrap();
}
#[async_trait]
impl super::LspAdapter for GoLspAdapter {
async fn name(&self) -> LanguageServerName {
LanguageServerName("gopls".into())
}
fn short_name(&self) -> &'static str {
"gopls"
}
async fn fetch_latest_server_version(
&self,
delegate: &dyn LspAdapterDelegate,
) -> Result<Box<dyn 'static + Send + Any>> {
let release = latest_github_release("golang/tools", false, delegate.http_client()).await?;
let version: Option<String> = release.name.strip_prefix("gopls/v").map(str::to_string);
if version.is_none() {
log::warn!(
"couldn't infer gopls version from github release name '{}'",
release.name
);
}
Ok(Box::new(version) as Box<_>)
}
fn will_fetch_server(
&self,
delegate: &Arc<dyn LspAdapterDelegate>,
cx: &mut AsyncAppContext,
) -> Option<Task<Result<()>>> {
static DID_SHOW_NOTIFICATION: AtomicBool = AtomicBool::new(false);
const NOTIFICATION_MESSAGE: &str =
"Could not install the Go language server `gopls`, because `go` was not found.";
let delegate = delegate.clone();
Some(cx.spawn(|cx| async move {
let install_output = process::Command::new("go").args(["version"]).output().await;
if install_output.is_err() {
if DID_SHOW_NOTIFICATION
.compare_exchange(false, true, SeqCst, SeqCst)
.is_ok()
{
cx.update(|cx| {
delegate.show_notification(NOTIFICATION_MESSAGE, cx);
})?
}
return Err(anyhow!("cannot install gopls"));
}
Ok(())
}))
}
async fn fetch_server_binary(
&self,
version: Box<dyn 'static + Send + Any>,
container_dir: PathBuf,
delegate: &dyn LspAdapterDelegate,
) -> Result<LanguageServerBinary> {
let version = version.downcast::<Option<String>>().unwrap();
let this = *self;
if let Some(version) = *version {
let binary_path = container_dir.join(&format!("gopls_{version}"));
if let Ok(metadata) = fs::metadata(&binary_path).await {
if metadata.is_file() {
remove_matching(&container_dir, |entry| {
entry != binary_path && entry.file_name() != Some(OsStr::new("gobin"))
})
.await;
return Ok(LanguageServerBinary {
path: binary_path.to_path_buf(),
arguments: server_binary_arguments(),
});
}
}
} else if let Some(path) = this
.cached_server_binary(container_dir.clone(), delegate)
.await
{
return Ok(path);
}
let gobin_dir = container_dir.join("gobin");
fs::create_dir_all(&gobin_dir).await?;
let install_output = process::Command::new("go")
.env("GO111MODULE", "on")
.env("GOBIN", &gobin_dir)
.args(["install", "golang.org/x/tools/gopls@latest"])
.output()
.await?;
if !install_output.status.success() {
Err(anyhow!("failed to install gopls. Is go installed?"))?;
}
let installed_binary_path = gobin_dir.join("gopls");
let version_output = process::Command::new(&installed_binary_path)
.arg("version")
.output()
.await
.map_err(|e| anyhow!("failed to run installed gopls binary {:?}", e))?;
let version_stdout = str::from_utf8(&version_output.stdout)
.map_err(|_| anyhow!("gopls version produced invalid utf8"))?;
let version = GOPLS_VERSION_REGEX
.find(version_stdout)
.ok_or_else(|| anyhow!("failed to parse gopls version output"))?
.as_str();
let binary_path = container_dir.join(&format!("gopls_{version}"));
fs::rename(&installed_binary_path, &binary_path).await?;
Ok(LanguageServerBinary {
path: binary_path.to_path_buf(),
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!["--help".into()];
binary
})
}
async fn label_for_completion(
&self,
completion: &lsp2::CompletionItem,
language: &Arc<Language>,
) -> Option<CodeLabel> {
let label = &completion.label;
// Gopls returns nested fields and methods as completions.
// To syntax highlight these, combine their final component
// with their detail.
let name_offset = label.rfind('.').unwrap_or(0);
match completion.kind.zip(completion.detail.as_ref()) {
Some((lsp2::CompletionItemKind::MODULE, detail)) => {
let text = format!("{label} {detail}");
let source = Rope::from(format!("import {text}").as_str());
let runs = language.highlight_text(&source, 7..7 + text.len());
return Some(CodeLabel {
text,
runs,
filter_range: 0..label.len(),
});
}
Some((
lsp2::CompletionItemKind::CONSTANT | lsp2::CompletionItemKind::VARIABLE,
detail,
)) => {
let text = format!("{label} {detail}");
let source =
Rope::from(format!("var {} {}", &text[name_offset..], detail).as_str());
let runs = adjust_runs(
name_offset,
language.highlight_text(&source, 4..4 + text.len()),
);
return Some(CodeLabel {
text,
runs,
filter_range: 0..label.len(),
});
}
Some((lsp2::CompletionItemKind::STRUCT, _)) => {
let text = format!("{label} struct {{}}");
let source = Rope::from(format!("type {}", &text[name_offset..]).as_str());
let runs = adjust_runs(
name_offset,
language.highlight_text(&source, 5..5 + text.len()),
);
return Some(CodeLabel {
text,
runs,
filter_range: 0..label.len(),
});
}
Some((lsp2::CompletionItemKind::INTERFACE, _)) => {
let text = format!("{label} interface {{}}");
let source = Rope::from(format!("type {}", &text[name_offset..]).as_str());
let runs = adjust_runs(
name_offset,
language.highlight_text(&source, 5..5 + text.len()),
);
return Some(CodeLabel {
text,
runs,
filter_range: 0..label.len(),
});
}
Some((lsp2::CompletionItemKind::FIELD, detail)) => {
let text = format!("{label} {detail}");
let source =
Rope::from(format!("type T struct {{ {} }}", &text[name_offset..]).as_str());
let runs = adjust_runs(
name_offset,
language.highlight_text(&source, 16..16 + text.len()),
);
return Some(CodeLabel {
text,
runs,
filter_range: 0..label.len(),
});
}
Some((
lsp2::CompletionItemKind::FUNCTION | lsp2::CompletionItemKind::METHOD,
detail,
)) => {
if let Some(signature) = detail.strip_prefix("func") {
let text = format!("{label}{signature}");
let source = Rope::from(format!("func {} {{}}", &text[name_offset..]).as_str());
let runs = adjust_runs(
name_offset,
language.highlight_text(&source, 5..5 + text.len()),
);
return Some(CodeLabel {
filter_range: 0..label.len(),
text,
runs,
});
}
}
_ => {}
}
None
}
async fn label_for_symbol(
&self,
name: &str,
kind: lsp2::SymbolKind,
language: &Arc<Language>,
) -> Option<CodeLabel> {
let (text, filter_range, display_range) = match kind {
lsp2::SymbolKind::METHOD | lsp2::SymbolKind::FUNCTION => {
let text = format!("func {} () {{}}", name);
let filter_range = 5..5 + name.len();
let display_range = 0..filter_range.end;
(text, filter_range, display_range)
}
lsp2::SymbolKind::STRUCT => {
let text = format!("type {} struct {{}}", name);
let filter_range = 5..5 + name.len();
let display_range = 0..text.len();
(text, filter_range, display_range)
}
lsp2::SymbolKind::INTERFACE => {
let text = format!("type {} interface {{}}", name);
let filter_range = 5..5 + name.len();
let display_range = 0..text.len();
(text, filter_range, display_range)
}
lsp2::SymbolKind::CLASS => {
let text = format!("type {} T", name);
let filter_range = 5..5 + name.len();
let display_range = 0..filter_range.end;
(text, filter_range, display_range)
}
lsp2::SymbolKind::CONSTANT => {
let text = format!("const {} = nil", name);
let filter_range = 6..6 + name.len();
let display_range = 0..filter_range.end;
(text, filter_range, display_range)
}
lsp2::SymbolKind::VARIABLE => {
let text = format!("var {} = nil", name);
let filter_range = 4..4 + name.len();
let display_range = 0..filter_range.end;
(text, filter_range, display_range)
}
lsp2::SymbolKind::MODULE => {
let text = format!("package {}", name);
let filter_range = 8..8 + name.len();
let display_range = 0..filter_range.end;
(text, filter_range, display_range)
}
_ => return None,
};
Some(CodeLabel {
runs: language.highlight_text(&text.as_str().into(), display_range.clone()),
text: text[display_range].to_string(),
filter_range,
})
}
}
async fn get_cached_server_binary(container_dir: PathBuf) -> Option<LanguageServerBinary> {
(|| async move {
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.starts_with("gopls_"))
{
last_binary_path = Some(entry.path());
}
}
if let Some(path) = last_binary_path {
Ok(LanguageServerBinary {
path,
arguments: server_binary_arguments(),
})
} else {
Err(anyhow!("no cached binary"))
}
})()
.await
.log_err()
}
fn adjust_runs(
delta: usize,
mut runs: Vec<(Range<usize>, HighlightId)>,
) -> Vec<(Range<usize>, HighlightId)> {
for (range, _) in &mut runs {
range.start += delta;
range.end += delta;
}
runs
}
#[cfg(test)]
mod tests {
use super::*;
use crate::languages::language;
use gpui2::Hsla;
use theme2::SyntaxTheme;
#[gpui2::test]
async fn test_go_label_for_completion() {
let language = language(
"go",
tree_sitter_go::language(),
Some(Arc::new(GoLspAdapter)),
)
.await;
let theme = SyntaxTheme::new_test([
("type", Hsla::default()),
("keyword", Hsla::default()),
("function", Hsla::default()),
("number", Hsla::default()),
("property", Hsla::default()),
]);
language.set_theme(&theme);
let grammar = language.grammar().unwrap();
let highlight_function = grammar.highlight_id_for_name("function").unwrap();
let highlight_type = grammar.highlight_id_for_name("type").unwrap();
let highlight_keyword = grammar.highlight_id_for_name("keyword").unwrap();
let highlight_number = grammar.highlight_id_for_name("number").unwrap();
let highlight_field = grammar.highlight_id_for_name("property").unwrap();
assert_eq!(
language
.label_for_completion(&lsp2::CompletionItem {
kind: Some(lsp2::CompletionItemKind::FUNCTION),
label: "Hello".to_string(),
detail: Some("func(a B) c.D".to_string()),
..Default::default()
})
.await,
Some(CodeLabel {
text: "Hello(a B) c.D".to_string(),
filter_range: 0..5,
runs: vec![
(0..5, highlight_function),
(8..9, highlight_type),
(13..14, highlight_type),
],
})
);
// Nested methods
assert_eq!(
language
.label_for_completion(&lsp2::CompletionItem {
kind: Some(lsp2::CompletionItemKind::METHOD),
label: "one.two.Three".to_string(),
detail: Some("func() [3]interface{}".to_string()),
..Default::default()
})
.await,
Some(CodeLabel {
text: "one.two.Three() [3]interface{}".to_string(),
filter_range: 0..13,
runs: vec![
(8..13, highlight_function),
(17..18, highlight_number),
(19..28, highlight_keyword),
],
})
);
// Nested fields
assert_eq!(
language
.label_for_completion(&lsp2::CompletionItem {
kind: Some(lsp2::CompletionItemKind::FIELD),
label: "two.Three".to_string(),
detail: Some("a.Bcd".to_string()),
..Default::default()
})
.await,
Some(CodeLabel {
text: "two.Three a.Bcd".to_string(),
filter_range: 0..9,
runs: vec![(4..9, highlight_field), (12..15, highlight_type)],
})
);
}
}

View file

@ -0,0 +1,3 @@
("[" @open "]" @close)
("{" @open "}" @close)
("\"" @open "\"" @close)

View file

@ -0,0 +1,12 @@
name = "Go"
path_suffixes = ["go"]
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 = 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,24 @@
(
(comment)* @context
.
(type_declaration
(type_spec
name: (_) @name)
) @item
)
(
(comment)* @context
.
(function_declaration
name: (_) @name
) @item
)
(
(comment)* @context
.
(method_declaration
name: (_) @name
) @item
)

View file

@ -0,0 +1,107 @@
(identifier) @variable
(type_identifier) @type
(field_identifier) @property
(call_expression
function: (identifier) @function)
(call_expression
function: (selector_expression
field: (field_identifier) @function.method))
(function_declaration
name: (identifier) @function)
(method_declaration
name: (field_identifier) @function.method)
[
"--"
"-"
"-="
":="
"!"
"!="
"..."
"*"
"*"
"*="
"/"
"/="
"&"
"&&"
"&="
"%"
"%="
"^"
"^="
"+"
"++"
"+="
"<-"
"<"
"<<"
"<<="
"<="
"="
"=="
">"
">="
">>"
">>="
"|"
"|="
"||"
"~"
] @operator
[
"break"
"case"
"chan"
"const"
"continue"
"default"
"defer"
"else"
"fallthrough"
"for"
"func"
"go"
"goto"
"if"
"import"
"interface"
"map"
"package"
"range"
"return"
"select"
"struct"
"switch"
"type"
"var"
] @keyword
[
(interpreted_string_literal)
(raw_string_literal)
(rune_literal)
] @string
(escape_sequence) @escape
[
(int_literal)
(float_literal)
(imaginary_literal)
] @number
[
(true)
(false)
(nil)
(iota)
] @constant.builtin
(comment) @comment

View file

@ -0,0 +1,9 @@
[
(assignment_statement)
(call_expression)
(selector_expression)
] @indent
(_ "[" "]" @end) @indent
(_ "{" "}" @end) @indent
(_ "(" ")" @end) @indent

View file

@ -0,0 +1,43 @@
(type_declaration
"type" @context
(type_spec
name: (_) @name)) @item
(function_declaration
"func" @context
name: (identifier) @name
parameters: (parameter_list
"(" @context
")" @context)) @item
(method_declaration
"func" @context
receiver: (parameter_list
"(" @context
(parameter_declaration
type: (_) @context)
")" @context)
name: (field_identifier) @name
parameters: (parameter_list
"(" @context
")" @context)) @item
(const_declaration
"const" @context
(const_spec
name: (identifier) @name) @item)
(source_file
(var_declaration
"var" @context
(var_spec
name: (identifier) @name) @item))
(method_spec
name: (_) @name
parameters: (parameter_list
"(" @context
")" @context)) @item
(field_declaration
name: (_) @name) @item

View file

@ -0,0 +1,6 @@
(comment) @comment
[
(interpreted_string_literal)
(raw_string_literal)
(rune_literal)
] @string

View file

@ -0,0 +1,12 @@
name = "HEEX"
path_suffixes = ["heex"]
autoclose_before = ">})"
brackets = [
{ start = "<", end = ">", close = true, newline = true },
]
block_comment = ["<%!-- ", " --%>"]
scope_opt_in_language_servers = ["tailwindcss-language-server"]
[overrides.string]
word_characters = ["-"]
opt_into_language_servers = ["tailwindcss-language-server"]

View file

@ -0,0 +1,57 @@
; HEEx delimiters
[
"/>"
"<!"
"<"
"</"
"</:"
"<:"
">"
"{"
"}"
] @punctuation.bracket
[
"<%!--"
"<%"
"<%#"
"<%%="
"<%="
"%>"
"--%>"
"-->"
"<!--"
] @keyword
; HEEx operators are highlighted as such
"=" @operator
; HEEx inherits the DOCTYPE tag from HTML
(doctype) @constant
(comment) @comment
; HEEx tags and slots are highlighted as HTML
[
(tag_name)
(slot_name)
] @tag
; HEEx attributes are highlighted as HTML attributes
(attribute_name) @attribute
; HEEx special attributes are highlighted as keywords
(special_attribute_name) @keyword
[
(attribute_value)
(quoted_attribute_value)
] @string
; HEEx components are highlighted as Elixir modules and functions
(component_name
[
(module) @module
(function) @function
"." @punctuation.delimiter
])

View file

@ -0,0 +1,13 @@
(
(directive
[
(partial_expression_value)
(expression_value)
(ending_expression_value)
] @content)
(#set! language "elixir")
(#set! combined)
)
((expression (expression_value) @content)
(#set! language "elixir"))

View file

@ -0,0 +1,4 @@
[
(attribute_value)
(quoted_attribute_value)
] @string

View file

@ -0,0 +1,130 @@
use anyhow::{anyhow, Result};
use async_trait::async_trait;
use futures::StreamExt;
use language2::{LanguageServerName, LspAdapter, LspAdapterDelegate};
use lsp2::LanguageServerBinary;
use node_runtime::NodeRuntime;
use serde_json::json;
use smol::fs;
use std::{
any::Any,
ffi::OsString,
path::{Path, PathBuf},
sync::Arc,
};
use util::ResultExt;
const SERVER_PATH: &'static str =
"node_modules/vscode-langservers-extracted/bin/vscode-html-language-server";
fn server_binary_arguments(server_path: &Path) -> Vec<OsString> {
vec![server_path.into(), "--stdio".into()]
}
pub struct HtmlLspAdapter {
node: Arc<dyn NodeRuntime>,
}
impl HtmlLspAdapter {
pub fn new(node: Arc<dyn NodeRuntime>) -> Self {
HtmlLspAdapter { node }
}
}
#[async_trait]
impl LspAdapter for HtmlLspAdapter {
async fn name(&self) -> LanguageServerName {
LanguageServerName("vscode-html-language-server".into())
}
fn short_name(&self) -> &'static str {
"html"
}
async fn fetch_latest_server_version(
&self,
_: &dyn LspAdapterDelegate,
) -> Result<Box<dyn 'static + Any + Send>> {
Ok(Box::new(
self.node
.npm_package_latest_version("vscode-langservers-extracted")
.await?,
) as Box<_>)
}
async fn fetch_server_binary(
&self,
version: Box<dyn 'static + Send + Any>,
container_dir: PathBuf,
_: &dyn LspAdapterDelegate,
) -> Result<LanguageServerBinary> {
let version = version.downcast::<String>().unwrap();
let server_path = container_dir.join(SERVER_PATH);
if fs::metadata(&server_path).await.is_err() {
self.node
.npm_install_packages(
&container_dir,
&[("vscode-langservers-extracted", version.as_str())],
)
.await?;
}
Ok(LanguageServerBinary {
path: self.node.binary_path().await?,
arguments: server_binary_arguments(&server_path),
})
}
async fn cached_server_binary(
&self,
container_dir: PathBuf,
_: &dyn LspAdapterDelegate,
) -> Option<LanguageServerBinary> {
get_cached_server_binary(container_dir, &*self.node).await
}
async fn installation_test_binary(
&self,
container_dir: PathBuf,
) -> Option<LanguageServerBinary> {
get_cached_server_binary(container_dir, &*self.node).await
}
async fn initialization_options(&self) -> Option<serde_json::Value> {
Some(json!({
"provideFormatter": true
}))
}
}
async fn get_cached_server_binary(
container_dir: PathBuf,
node: &dyn NodeRuntime,
) -> Option<LanguageServerBinary> {
(|| async move {
let mut last_version_dir = 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_dir() {
last_version_dir = Some(entry.path());
}
}
let last_version_dir = last_version_dir.ok_or_else(|| anyhow!("no cached binary"))?;
let server_path = last_version_dir.join(SERVER_PATH);
if server_path.exists() {
Ok(LanguageServerBinary {
path: node.binary_path().await?,
arguments: server_binary_arguments(&server_path),
})
} else {
Err(anyhow!(
"missing executable in directory {:?}",
last_version_dir
))
}
})()
.await
.log_err()
}

View file

@ -0,0 +1,2 @@
("<" @open ">" @close)
("\"" @open "\"" @close)

View file

@ -0,0 +1,14 @@
name = "HTML"
path_suffixes = ["html"]
autoclose_before = ">})"
block_comment = ["<!-- ", " -->"]
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 = true, not_in = ["comment", "string"] },
{ start = "!--", end = " --", close = true, newline = false, not_in = ["comment", "string"] },
]
word_characters = ["-"]
prettier_parser_name = "html"

View file

@ -0,0 +1,15 @@
(tag_name) @keyword
(erroneous_end_tag_name) @keyword
(doctype) @constant
(attribute_name) @property
(attribute_value) @string
(comment) @comment
"=" @operator
[
"<"
">"
"</"
"/>"
] @punctuation.bracket

View file

@ -0,0 +1,6 @@
(start_tag ">" @end) @indent
(self_closing_tag "/>" @end) @indent
(element
(start_tag) @start
(end_tag)? @end) @indent

View file

@ -0,0 +1,7 @@
(script_element
(raw_text) @content
(#set! "language" "javascript"))
(style_element
(raw_text) @content
(#set! "language" "css"))

View file

@ -0,0 +1,2 @@
(comment) @comment
(quoted_attribute_value) @string

View file

@ -0,0 +1,5 @@
("(" @open ")" @close)
("[" @open "]" @close)
("{" @open "}" @close)
("<" @open ">" @close)
("\"" @open "\"" @close)

View file

@ -0,0 +1,26 @@
name = "JavaScript"
path_suffixes = ["js", "jsx", "mjs", "cjs"]
first_line_pattern = '^#!.*\bnode\b'
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 = false, newline = true, not_in = ["comment", "string"] },
{ 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"] },
{ start = "/*", end = " */", close = true, newline = false, not_in = ["comment", "string"] },
]
word_characters = ["$", "#"]
scope_opt_in_language_servers = ["tailwindcss-language-server"]
prettier_parser_name = "babel"
[overrides.element]
line_comment = { remove = true }
block_comment = ["{/* ", " */}"]
[overrides.string]
word_characters = ["-"]
opt_into_language_servers = ["tailwindcss-language-server"]

View file

@ -0,0 +1,71 @@
(
(comment)* @context
.
[
(export_statement
(function_declaration
"async"? @name
"function" @name
name: (_) @name))
(function_declaration
"async"? @name
"function" @name
name: (_) @name)
] @item
)
(
(comment)* @context
.
[
(export_statement
(class_declaration
"class" @name
name: (_) @name))
(class_declaration
"class" @name
name: (_) @name)
] @item
)
(
(comment)* @context
.
[
(export_statement
(interface_declaration
"interface" @name
name: (_) @name))
(interface_declaration
"interface" @name
name: (_) @name)
] @item
)
(
(comment)* @context
.
[
(export_statement
(enum_declaration
"enum" @name
name: (_) @name))
(enum_declaration
"enum" @name
name: (_) @name)
] @item
)
(
(comment)* @context
.
(method_definition
[
"get"
"set"
"async"
"*"
"static"
]* @name
name: (_) @name) @item
)

View file

@ -0,0 +1,217 @@
; Variables
(identifier) @variable
; Properties
(property_identifier) @property
; Function and method calls
(call_expression
function: (identifier) @function)
(call_expression
function: (member_expression
property: (property_identifier) @function.method))
; Function and method definitions
(function
name: (identifier) @function)
(function_declaration
name: (identifier) @function)
(method_definition
name: (property_identifier) @function.method)
(pair
key: (property_identifier) @function.method
value: [(function) (arrow_function)])
(assignment_expression
left: (member_expression
property: (property_identifier) @function.method)
right: [(function) (arrow_function)])
(variable_declarator
name: (identifier) @function
value: [(function) (arrow_function)])
(assignment_expression
left: (identifier) @function
right: [(function) (arrow_function)])
; Special identifiers
((identifier) @type
(#match? @type "^[A-Z]"))
(type_identifier) @type
(predefined_type) @type.builtin
([
(identifier)
(shorthand_property_identifier)
(shorthand_property_identifier_pattern)
] @constant
(#match? @constant "^_*[A-Z_][A-Z\\d_]*$"))
; Literals
(this) @variable.special
(super) @variable.special
[
(null)
(undefined)
] @constant.builtin
[
(true)
(false)
] @boolean
(comment) @comment
[
(string)
(template_string)
] @string
(regex) @string.regex
(number) @number
; Tokens
[
";"
"?."
"."
","
":"
] @punctuation.delimiter
[
"-"
"--"
"-="
"+"
"++"
"+="
"*"
"*="
"**"
"**="
"/"
"/="
"%"
"%="
"<"
"<="
"<<"
"<<="
"="
"=="
"==="
"!"
"!="
"!=="
"=>"
">"
">="
">>"
">>="
">>>"
">>>="
"~"
"^"
"&"
"|"
"^="
"&="
"|="
"&&"
"||"
"??"
"&&="
"||="
"??="
] @operator
[
"("
")"
"["
"]"
"{"
"}"
] @punctuation.bracket
[
"as"
"async"
"await"
"break"
"case"
"catch"
"class"
"const"
"continue"
"debugger"
"default"
"delete"
"do"
"else"
"export"
"extends"
"finally"
"for"
"from"
"function"
"get"
"if"
"import"
"in"
"instanceof"
"let"
"new"
"of"
"return"
"set"
"static"
"switch"
"target"
"throw"
"try"
"typeof"
"var"
"void"
"while"
"with"
"yield"
] @keyword
(template_substitution
"${" @punctuation.special
"}" @punctuation.special) @embedded
(type_arguments
"<" @punctuation.bracket
">" @punctuation.bracket)
; Keywords
[ "abstract"
"declare"
"enum"
"export"
"implements"
"interface"
"keyof"
"namespace"
"private"
"protected"
"public"
"type"
"readonly"
"override"
] @keyword

View file

@ -0,0 +1,15 @@
[
(call_expression)
(assignment_expression)
(member_expression)
(lexical_declaration)
(variable_declaration)
(assignment_expression)
(if_statement)
(for_statement)
] @indent
(_ "[" "]" @end) @indent
(_ "<" ">" @end) @indent
(_ "{" "}" @end) @indent
(_ "(" ")" @end) @indent

View file

@ -0,0 +1,62 @@
(internal_module
"namespace" @context
name: (_) @name) @item
(enum_declaration
"enum" @context
name: (_) @name) @item
(function_declaration
"async"? @context
"function" @context
name: (_) @name
parameters: (formal_parameters
"(" @context
")" @context)) @item
(interface_declaration
"interface" @context
name: (_) @name) @item
(program
(export_statement
(lexical_declaration
["let" "const"] @context
(variable_declarator
name: (_) @name) @item)))
(program
(lexical_declaration
["let" "const"] @context
(variable_declarator
name: (_) @name) @item))
(class_declaration
"class" @context
name: (_) @name) @item
(method_definition
[
"get"
"set"
"async"
"*"
"readonly"
"static"
(override_modifier)
(accessibility_modifier)
]* @context
name: (_) @name
parameters: (formal_parameters
"(" @context
")" @context)) @item
(public_field_definition
[
"declare"
"readonly"
"abstract"
"static"
(accessibility_modifier)
]* @context
name: (_) @name) @item

View file

@ -0,0 +1,13 @@
(comment) @comment
[
(string)
(template_string)
] @string
[
(jsx_element)
(jsx_fragment)
(jsx_self_closing_element)
(jsx_expression)
] @element

View file

@ -0,0 +1,184 @@
use anyhow::{anyhow, Result};
use async_trait::async_trait;
use collections::HashMap;
use feature_flags2::FeatureFlagAppExt;
use futures::{future::BoxFuture, FutureExt, StreamExt};
use gpui2::AppContext;
use language2::{LanguageRegistry, LanguageServerName, LspAdapter, LspAdapterDelegate};
use lsp2::LanguageServerBinary;
use node_runtime::NodeRuntime;
use serde_json::json;
use settings2::{KeymapFile, SettingsJsonSchemaParams, SettingsStore};
use smol::fs;
use std::{
any::Any,
ffi::OsString,
future,
path::{Path, PathBuf},
sync::Arc,
};
use util::{paths, ResultExt};
const SERVER_PATH: &'static str =
"node_modules/vscode-json-languageserver/bin/vscode-json-languageserver";
fn server_binary_arguments(server_path: &Path) -> Vec<OsString> {
vec![server_path.into(), "--stdio".into()]
}
pub struct JsonLspAdapter {
node: Arc<dyn NodeRuntime>,
languages: Arc<LanguageRegistry>,
}
impl JsonLspAdapter {
pub fn new(node: Arc<dyn NodeRuntime>, languages: Arc<LanguageRegistry>) -> Self {
JsonLspAdapter { node, languages }
}
}
#[async_trait]
impl LspAdapter for JsonLspAdapter {
async fn name(&self) -> LanguageServerName {
LanguageServerName("json-language-server".into())
}
fn short_name(&self) -> &'static str {
"json"
}
async fn fetch_latest_server_version(
&self,
_: &dyn LspAdapterDelegate,
) -> Result<Box<dyn 'static + Send + Any>> {
Ok(Box::new(
self.node
.npm_package_latest_version("vscode-json-languageserver")
.await?,
) as Box<_>)
}
async fn fetch_server_binary(
&self,
version: Box<dyn 'static + Send + Any>,
container_dir: PathBuf,
_: &dyn LspAdapterDelegate,
) -> Result<LanguageServerBinary> {
let version = version.downcast::<String>().unwrap();
let server_path = container_dir.join(SERVER_PATH);
if fs::metadata(&server_path).await.is_err() {
self.node
.npm_install_packages(
&container_dir,
&[("vscode-json-languageserver", version.as_str())],
)
.await?;
}
Ok(LanguageServerBinary {
path: self.node.binary_path().await?,
arguments: server_binary_arguments(&server_path),
})
}
async fn cached_server_binary(
&self,
container_dir: PathBuf,
_: &dyn LspAdapterDelegate,
) -> Option<LanguageServerBinary> {
get_cached_server_binary(container_dir, &*self.node).await
}
async fn installation_test_binary(
&self,
container_dir: PathBuf,
) -> Option<LanguageServerBinary> {
get_cached_server_binary(container_dir, &*self.node).await
}
async fn initialization_options(&self) -> Option<serde_json::Value> {
Some(json!({
"provideFormatter": true
}))
}
fn workspace_configuration(
&self,
cx: &mut AppContext,
) -> BoxFuture<'static, serde_json::Value> {
let action_names = cx.all_action_names().collect::<Vec<_>>();
let staff_mode = cx.is_staff();
let language_names = &self.languages.language_names();
let settings_schema = cx.global::<SettingsStore>().json_schema(
&SettingsJsonSchemaParams {
language_names,
staff_mode,
},
cx,
);
future::ready(serde_json::json!({
"json": {
"format": {
"enable": true,
},
"schemas": [
{
"fileMatch": [
schema_file_match(&paths::SETTINGS),
&*paths::LOCAL_SETTINGS_RELATIVE_PATH,
],
"schema": settings_schema,
},
{
"fileMatch": [schema_file_match(&paths::KEYMAP)],
"schema": KeymapFile::generate_json_schema(&action_names),
}
]
}
}))
.boxed()
}
async fn language_ids(&self) -> HashMap<String, String> {
[("JSON".into(), "jsonc".into())].into_iter().collect()
}
}
async fn get_cached_server_binary(
container_dir: PathBuf,
node: &dyn NodeRuntime,
) -> Option<LanguageServerBinary> {
(|| async move {
let mut last_version_dir = 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_dir() {
last_version_dir = Some(entry.path());
}
}
let last_version_dir = last_version_dir.ok_or_else(|| anyhow!("no cached binary"))?;
let server_path = last_version_dir.join(SERVER_PATH);
if server_path.exists() {
Ok(LanguageServerBinary {
path: node.binary_path().await?,
arguments: server_binary_arguments(&server_path),
})
} else {
Err(anyhow!(
"missing executable in directory {:?}",
last_version_dir
))
}
})()
.await
.log_err()
}
fn schema_file_match(path: &Path) -> &Path {
path.strip_prefix(path.parent().unwrap().parent().unwrap())
.unwrap()
}

View file

@ -0,0 +1,3 @@
("[" @open "]" @close)
("{" @open "}" @close)
("\"" @open "\"" @close)

View file

@ -0,0 +1,10 @@
name = "JSON"
path_suffixes = ["json"]
line_comment = "// "
autoclose_before = ",]}"
brackets = [
{ start = "{", end = "}", close = true, newline = true },
{ start = "[", end = "]", close = true, newline = true },
{ start = "\"", end = "\"", close = true, newline = false, not_in = ["string"] },
]
prettier_parser_name = "json"

View file

@ -0,0 +1,14 @@
; Only produce one embedding for the entire file.
(document) @item
; Collapse arrays, except for the first object.
(array
"[" @keep
.
(object)? @keep
"]" @keep) @collapse
; Collapse string values (but not keys).
(pair value: (string
"\"" @keep
"\"" @keep) @collapse)

View file

@ -0,0 +1,21 @@
(comment) @comment
(string) @string
(pair
key: (string) @property)
(number) @number
[
(true)
(false)
(null)
] @constant
[
"{"
"}"
"["
"]"
] @punctuation.bracket

View file

@ -0,0 +1,2 @@
(array "]" @end) @indent
(object "}" @end) @indent

View file

@ -0,0 +1,2 @@
(pair
key: (string (string_content) @name)) @item

View file

@ -0,0 +1 @@
(string) @string

View file

@ -0,0 +1,168 @@
use anyhow::{anyhow, Result};
use async_trait::async_trait;
use collections::HashMap;
use futures::lock::Mutex;
use gpui2::executor::Background;
use language2::{LanguageServerName, LspAdapter, LspAdapterDelegate};
use lsp2::LanguageServerBinary;
use plugin_runtime::{Plugin, PluginBinary, PluginBuilder, WasiFn};
use std::{any::Any, path::PathBuf, sync::Arc};
use util::ResultExt;
#[allow(dead_code)]
pub async fn new_json(executor: Arc<Background>) -> Result<PluginLspAdapter> {
let plugin = PluginBuilder::new_default()?
.host_function_async("command", |command: String| async move {
let mut args = command.split(' ');
let command = args.next().unwrap();
smol::process::Command::new(command)
.args(args)
.output()
.await
.log_err()
.map(|output| output.stdout)
})?
.init(PluginBinary::Precompiled(include_bytes!(
"../../../../plugins/bin/json_language.wasm.pre",
)))
.await?;
PluginLspAdapter::new(plugin, executor).await
}
pub struct PluginLspAdapter {
name: WasiFn<(), String>,
fetch_latest_server_version: WasiFn<(), Option<String>>,
fetch_server_binary: WasiFn<(PathBuf, String), Result<LanguageServerBinary, String>>,
cached_server_binary: WasiFn<PathBuf, Option<LanguageServerBinary>>,
initialization_options: WasiFn<(), String>,
language_ids: WasiFn<(), Vec<(String, String)>>,
executor: Arc<Background>,
runtime: Arc<Mutex<Plugin>>,
}
impl PluginLspAdapter {
#[allow(unused)]
pub async fn new(mut plugin: Plugin, executor: Arc<Background>) -> Result<Self> {
Ok(Self {
name: plugin.function("name")?,
fetch_latest_server_version: plugin.function("fetch_latest_server_version")?,
fetch_server_binary: plugin.function("fetch_server_binary")?,
cached_server_binary: plugin.function("cached_server_binary")?,
initialization_options: plugin.function("initialization_options")?,
language_ids: plugin.function("language_ids")?,
executor,
runtime: Arc::new(Mutex::new(plugin)),
})
}
}
#[async_trait]
impl LspAdapter for PluginLspAdapter {
async fn name(&self) -> LanguageServerName {
let name: String = self
.runtime
.lock()
.await
.call(&self.name, ())
.await
.unwrap();
LanguageServerName(name.into())
}
fn short_name(&self) -> &'static str {
"PluginLspAdapter"
}
async fn fetch_latest_server_version(
&self,
_: &dyn LspAdapterDelegate,
) -> Result<Box<dyn 'static + Send + Any>> {
let runtime = self.runtime.clone();
let function = self.fetch_latest_server_version;
self.executor
.spawn(async move {
let mut runtime = runtime.lock().await;
let versions: Result<Option<String>> =
runtime.call::<_, Option<String>>(&function, ()).await;
versions
.map_err(|e| anyhow!("{}", e))?
.ok_or_else(|| anyhow!("Could not fetch latest server version"))
.map(|v| Box::new(v) as Box<_>)
})
.await
}
async fn fetch_server_binary(
&self,
version: Box<dyn 'static + Send + Any>,
container_dir: PathBuf,
_: &dyn LspAdapterDelegate,
) -> Result<LanguageServerBinary> {
let version = *version.downcast::<String>().unwrap();
let runtime = self.runtime.clone();
let function = self.fetch_server_binary;
self.executor
.spawn(async move {
let mut runtime = runtime.lock().await;
let handle = runtime.attach_path(&container_dir)?;
let result: Result<LanguageServerBinary, String> =
runtime.call(&function, (container_dir, version)).await?;
runtime.remove_resource(handle)?;
result.map_err(|e| anyhow!("{}", e))
})
.await
}
async fn cached_server_binary(
&self,
container_dir: PathBuf,
_: &dyn LspAdapterDelegate,
) -> Option<LanguageServerBinary> {
let runtime = self.runtime.clone();
let function = self.cached_server_binary;
self.executor
.spawn(async move {
let mut runtime = runtime.lock().await;
let handle = runtime.attach_path(&container_dir).ok()?;
let result: Option<LanguageServerBinary> =
runtime.call(&function, container_dir).await.ok()?;
runtime.remove_resource(handle).ok()?;
result
})
.await
}
fn can_be_reinstalled(&self) -> bool {
false
}
async fn installation_test_binary(&self, _: PathBuf) -> Option<LanguageServerBinary> {
None
}
async fn initialization_options(&self) -> Option<serde_json::Value> {
let string: String = self
.runtime
.lock()
.await
.call(&self.initialization_options, ())
.await
.log_err()?;
serde_json::from_str(&string).ok()
}
async fn language_ids(&self) -> HashMap<String, String> {
self.runtime
.lock()
.await
.call(&self.language_ids, ())
.await
.log_err()
.unwrap_or_default()
.into_iter()
.collect()
}
}

View file

@ -0,0 +1,135 @@
use anyhow::{anyhow, bail, Result};
use async_compression::futures::bufread::GzipDecoder;
use async_tar::Archive;
use async_trait::async_trait;
use futures::{io::BufReader, StreamExt};
use language2::{LanguageServerName, LspAdapterDelegate};
use lsp2::LanguageServerBinary;
use smol::fs;
use std::{any::Any, env::consts, path::PathBuf};
use util::{
async_maybe,
github::{latest_github_release, GitHubLspBinaryVersion},
ResultExt,
};
#[derive(Copy, Clone)]
pub struct LuaLspAdapter;
#[async_trait]
impl super::LspAdapter for LuaLspAdapter {
async fn name(&self) -> LanguageServerName {
LanguageServerName("lua-language-server".into())
}
fn short_name(&self) -> &'static str {
"lua"
}
async fn fetch_latest_server_version(
&self,
delegate: &dyn LspAdapterDelegate,
) -> Result<Box<dyn 'static + Send + Any>> {
let release =
latest_github_release("LuaLS/lua-language-server", false, delegate.http_client())
.await?;
let version = release.name.clone();
let platform = match consts::ARCH {
"x86_64" => "x64",
"aarch64" => "arm64",
other => bail!("Running on unsupported platform: {other}"),
};
let asset_name = format!("lua-language-server-{version}-darwin-{platform}.tar.gz");
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.clone(),
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/lua-language-server");
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?;
}
fs::set_permissions(
&binary_path,
<fs::Permissions as fs::unix::PermissionsExt>::from_mode(0o755),
)
.await?;
Ok(LanguageServerBinary {
path: binary_path,
arguments: Vec::new(),
})
}
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_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 == "lua-language-server")
{
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()
}

View file

@ -0,0 +1,3 @@
("[" @open "]" @close)
("{" @open "}" @close)
("(" @open ")" @close)

View file

@ -0,0 +1,10 @@
name = "Lua"
path_suffixes = ["lua"]
line_comment = "-- "
autoclose_before = ",]}"
brackets = [
{ start = "{", end = "}", close = true, newline = true },
{ start = "[", end = "]", close = true, newline = true },
{ start = "\"", end = "\"", close = true, newline = false, not_in = ["string"] },
]
collapsed_placeholder = "--[ ... ]--"

View file

@ -0,0 +1,10 @@
(
(comment)* @context
.
(function_declaration
"function" @name
name: (_) @name
(comment)* @collapse
body: (block) @collapse
) @item
)

View file

@ -0,0 +1,198 @@
;; Keywords
"return" @keyword
[
"goto"
"in"
"local"
] @keyword
(break_statement) @keyword
(do_statement
[
"do"
"end"
] @keyword)
(while_statement
[
"while"
"do"
"end"
] @keyword)
(repeat_statement
[
"repeat"
"until"
] @keyword)
(if_statement
[
"if"
"elseif"
"else"
"then"
"end"
] @keyword)
(elseif_statement
[
"elseif"
"then"
"end"
] @keyword)
(else_statement
[
"else"
"end"
] @keyword)
(for_statement
[
"for"
"do"
"end"
] @keyword)
(function_declaration
[
"function"
"end"
] @keyword)
(function_definition
[
"function"
"end"
] @keyword)
;; Operators
[
"and"
"not"
"or"
] @operator
[
"+"
"-"
"*"
"/"
"%"
"^"
"#"
"=="
"~="
"<="
">="
"<"
">"
"="
"&"
"~"
"|"
"<<"
">>"
"//"
".."
] @operator
;; Punctuations
[
";"
":"
","
"."
] @punctuation.delimiter
;; Brackets
[
"("
")"
"["
"]"
"{"
"}"
] @punctuation.bracket
;; Variables
(identifier) @variable
((identifier) @variable.special
(#eq? @variable.special "self"))
(variable_list
attribute: (attribute
(["<" ">"] @punctuation.bracket
(identifier) @attribute)))
;; Constants
((identifier) @constant
(#match? @constant "^[A-Z][A-Z_0-9]*$"))
(vararg_expression) @constant
(nil) @constant.builtin
[
(false)
(true)
] @boolean
;; Tables
(field name: (identifier) @field)
(dot_index_expression field: (identifier) @field)
(table_constructor
[
"{"
"}"
] @constructor)
;; Functions
(parameters (identifier) @parameter)
(function_call
name: [
(identifier) @function
(dot_index_expression field: (identifier) @function)
])
(function_declaration
name: [
(identifier) @function.definition
(dot_index_expression field: (identifier) @function.definition)
])
(method_index_expression method: (identifier) @method)
(function_call
(identifier) @function.builtin
(#any-of? @function.builtin
;; built-in functions in Lua 5.1
"assert" "collectgarbage" "dofile" "error" "getfenv" "getmetatable" "ipairs"
"load" "loadfile" "loadstring" "module" "next" "pairs" "pcall" "print"
"rawequal" "rawget" "rawset" "require" "select" "setfenv" "setmetatable"
"tonumber" "tostring" "type" "unpack" "xpcall"))
;; Others
(comment) @comment
(hash_bang_line) @preproc
(number) @number
(string) @string

View file

@ -0,0 +1,10 @@
(if_statement "end" @end) @indent
(do_statement "end" @end) @indent
(while_statement "end" @end) @indent
(for_statement "end" @end) @indent
(repeat_statement "until" @end) @indent
(function_declaration "end" @end) @indent
(_ "[" "]" @end) @indent
(_ "{" "}" @end) @indent
(_ "(" ")" @end) @indent

View file

@ -0,0 +1,3 @@
(function_declaration
"function" @context
name: (_) @name) @item

View file

@ -0,0 +1,11 @@
name = "Markdown"
path_suffixes = ["md", "mdx"]
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 },
{ start = "\"", end = "\"", close = false, newline = false },
{ start = "'", end = "'", close = false, newline = false },
{ start = "`", end = "`", close = false, newline = false },
]

View file

@ -0,0 +1,24 @@
(emphasis) @emphasis
(strong_emphasis) @emphasis.strong
[
(atx_heading)
(setext_heading)
] @title
[
(list_marker_plus)
(list_marker_minus)
(list_marker_star)
(list_marker_dot)
(list_marker_parenthesis)
] @punctuation.list_marker
(code_span) @text.literal
(fenced_code_block
(info_string
(language) @text.literal))
(link_destination) @link_uri
(link_text) @link_text

View file

@ -0,0 +1,4 @@
(fenced_code_block
(info_string
(language) @language)
(code_fence_content) @content)

View file

@ -0,0 +1,11 @@
name = "Nix"
path_suffixes = ["nix"]
line_comment = "# "
block_comment = ["/* ", " */"]
autoclose_before = ";:.,=}])>` \n\t\""
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 },
]

View file

@ -0,0 +1,95 @@
(comment) @comment
[
"if"
"then"
"else"
"let"
"inherit"
"in"
"rec"
"with"
"assert"
"or"
] @keyword
[
(string_expression)
(indented_string_expression)
] @string
[
(path_expression)
(hpath_expression)
(spath_expression)
] @string.special.path
(uri_expression) @link_uri
[
(integer_expression)
(float_expression)
] @number
(interpolation
"${" @punctuation.special
"}" @punctuation.special) @embedded
(escape_sequence) @escape
(dollar_escape) @escape
(function_expression
universal: (identifier) @parameter
)
(formal
name: (identifier) @parameter
"?"? @punctuation.delimiter)
(select_expression
attrpath: (attrpath (identifier)) @property)
(apply_expression
function: [
(variable_expression (identifier)) @function
(select_expression
attrpath: (attrpath
attr: (identifier) @function .))])
(unary_expression
operator: _ @operator)
(binary_expression
operator: _ @operator)
(variable_expression (identifier) @variable)
(binding
attrpath: (attrpath (identifier)) @property)
"=" @operator
[
";"
"."
","
] @punctuation.delimiter
[
"("
")"
"["
"]"
"{"
"}"
] @punctuation.bracket
(identifier) @variable
((identifier) @function.builtin
(#match? @function.builtin "^(__add|__addErrorContext|__all|__any|__appendContext|__attrNames|__attrValues|__bitAnd|__bitOr|__bitXor|__catAttrs|__compareVersions|__concatLists|__concatMap|__concatStringsSep|__deepSeq|__div|__elem|__elemAt|__fetchurl|__filter|__filterSource|__findFile|__foldl'|__fromJSON|__functionArgs|__genList|__genericClosure|__getAttr|__getContext|__getEnv|__hasAttr|__hasContext|__hashFile|__hashString|__head|__intersectAttrs|__isAttrs|__isBool|__isFloat|__isFunction|__isInt|__isList|__isPath|__isString|__langVersion|__length|__lessThan|__listToAttrs|__mapAttrs|__match|__mul|__parseDrvName|__partition|__path|__pathExists|__readDir|__readFile|__replaceStrings|__seq|__sort|__split|__splitVersion|__storePath|__stringLength|__sub|__substring|__tail|__toFile|__toJSON|__toPath|__toXML|__trace|__tryEval|__typeOf|__unsafeDiscardOutputDependency|__unsafeDiscardStringContext|__unsafeGetAttrPos|__valueSize|abort|baseNameOf|derivation|derivationStrict|dirOf|fetchGit|fetchMercurial|fetchTarball|fromTOML|import|isNull|map|placeholder|removeAttrs|scopedImport|throw|toString)$")
(#is-not? local))
((identifier) @variable.builtin
(#match? @variable.builtin "^(__currentSystem|__currentTime|__nixPath|__nixVersion|__storeDir|builtins|false|null|true)$")
(#is-not? local))

View file

@ -0,0 +1,4 @@
("(" @open ")" @close)
("[" @open "]" @close)
("{" @open "}" @close)
(parameter_pipes "|" @open "|" @close)

View file

@ -0,0 +1,9 @@
name = "Nu"
path_suffixes = ["nu"]
line_comment = "# "
autoclose_before = ";:.,=}])>` \n\t\""
brackets = [
{ start = "{", end = "}", close = true, newline = true },
{ start = "[", end = "]", close = true, newline = true },
{ start = "(", end = ")", close = true, newline = true },
]

View file

@ -0,0 +1,302 @@
;;; ---
;;; keywords
[
"def"
"def-env"
"alias"
"export-env"
"export"
"extern"
"module"
"let"
"let-env"
"mut"
"const"
"hide-env"
"source"
"source-env"
"overlay"
"register"
"loop"
"while"
"error"
"do"
"if"
"else"
"try"
"catch"
"match"
"break"
"continue"
"return"
] @keyword
(hide_mod "hide" @keyword)
(decl_use "use" @keyword)
(ctrl_for
"for" @keyword
"in" @keyword
)
(overlay_list "list" @keyword)
(overlay_hide "hide" @keyword)
(overlay_new "new" @keyword)
(overlay_use
"use" @keyword
"as" @keyword
)
(ctrl_error "make" @keyword)
;;; ---
;;; literals
(val_number) @constant
(val_duration
unit: [
"ns" "µs" "us" "ms" "sec" "min" "hr" "day" "wk"
] @variable
)
(val_filesize
unit: [
"b" "B"
"kb" "kB" "Kb" "KB"
"mb" "mB" "Mb" "MB"
"gb" "gB" "Gb" "GB"
"tb" "tB" "Tb" "TB"
"pb" "pB" "Pb" "PB"
"eb" "eB" "Eb" "EB"
"zb" "zB" "Zb" "ZB"
"kib" "kiB" "kIB" "kIb" "Kib" "KIb" "KIB"
"mib" "miB" "mIB" "mIb" "Mib" "MIb" "MIB"
"gib" "giB" "gIB" "gIb" "Gib" "GIb" "GIB"
"tib" "tiB" "tIB" "tIb" "Tib" "TIb" "TIB"
"pib" "piB" "pIB" "pIb" "Pib" "PIb" "PIB"
"eib" "eiB" "eIB" "eIb" "Eib" "EIb" "EIB"
"zib" "ziB" "zIB" "zIb" "Zib" "ZIb" "ZIB"
] @variable
)
(val_binary
[
"0b"
"0o"
"0x"
] @constant
"[" @punctuation.bracket
digit: [
"," @punctuation.delimiter
(hex_digit) @constant
]
"]" @punctuation.bracket
) @constant
(val_bool) @constant.builtin
(val_nothing) @constant.builtin
(val_string) @string
(val_date) @constant
(inter_escape_sequence) @constant
(escape_sequence) @constant
(val_interpolated [
"$\""
"$\'"
"\""
"\'"
] @string)
(unescaped_interpolated_content) @string
(escaped_interpolated_content) @string
(expr_interpolated ["(" ")"] @variable)
;;; ---
;;; operators
(expr_binary [
"+"
"-"
"*"
"/"
"mod"
"//"
"++"
"**"
"=="
"!="
"<"
"<="
">"
">="
"=~"
"!~"
"and"
"or"
"xor"
"bit-or"
"bit-xor"
"bit-and"
"bit-shl"
"bit-shr"
"in"
"not-in"
"starts-with"
"ends-with"
] @operator)
(expr_binary opr: ([
"and"
"or"
"xor"
"bit-or"
"bit-xor"
"bit-and"
"bit-shl"
"bit-shr"
"in"
"not-in"
"starts-with"
"ends-with"
]) @keyword)
(where_command [
"+"
"-"
"*"
"/"
"mod"
"//"
"++"
"**"
"=="
"!="
"<"
"<="
">"
">="
"=~"
"!~"
"and"
"or"
"xor"
"bit-or"
"bit-xor"
"bit-and"
"bit-shl"
"bit-shr"
"in"
"not-in"
"starts-with"
"ends-with"
] @operator)
(assignment [
"="
"+="
"-="
"*="
"/="
"++="
] @operator)
(expr_unary ["not" "-"] @operator)
(val_range [
".."
"..="
"..<"
] @operator)
["=>" "=" "|"] @operator
[
"o>" "out>"
"e>" "err>"
"e+o>" "err+out>"
"o+e>" "out+err>"
] @special
;;; ---
;;; punctuation
[
","
";"
] @punctuation.delimiter
(param_short_flag "-" @punctuation.delimiter)
(param_long_flag ["--"] @punctuation.delimiter)
(long_flag ["--"] @punctuation.delimiter)
(param_rest "..." @punctuation.delimiter)
(param_type [":"] @punctuation.special)
(param_value ["="] @punctuation.special)
(param_cmd ["@"] @punctuation.special)
(param_opt ["?"] @punctuation.special)
[
"(" ")"
"{" "}"
"[" "]"
] @punctuation.bracket
(val_record
(record_entry ":" @punctuation.delimiter))
;;; ---
;;; identifiers
(param_rest
name: (_) @variable)
(param_opt
name: (_) @variable)
(parameter
param_name: (_) @variable)
(param_cmd
(cmd_identifier) @string)
(param_long_flag) @variable
(param_short_flag) @variable
(short_flag) @variable
(long_flag) @variable
(scope_pattern [(wild_card) @function])
(cmd_identifier) @function
(command
"^" @punctuation.delimiter
head: (_) @function
)
"where" @function
(path
["." "?"] @punctuation.delimiter
) @variable
(val_variable
"$" @operator
[
(identifier) @variable
"in" @type.builtin
"nu" @type.builtin
"env" @type.builtin
"nothing" @type.builtin
] ; If we have a special styling, use it here
)
;;; ---
;;; types
(flat_type) @type.builtin
(list_type
"list" @type
["<" ">"] @punctuation.bracket
)
(collection_type
["record" "table"] @type
"<" @punctuation.bracket
key: (_) @variable
["," ":"] @punctuation.delimiter
">" @punctuation.bracket
)
(shebang) @comment
(comment) @comment

View file

@ -0,0 +1,3 @@
(_ "[" "]" @end) @indent
(_ "{" "}" @end) @indent
(_ "(" ")" @end) @indent

View file

@ -0,0 +1,137 @@
use anyhow::{anyhow, Result};
use async_trait::async_trait;
use collections::HashMap;
use language2::{LanguageServerName, LspAdapter, LspAdapterDelegate};
use lsp2::LanguageServerBinary;
use node_runtime::NodeRuntime;
use smol::{fs, stream::StreamExt};
use std::{
any::Any,
ffi::OsString,
path::{Path, PathBuf},
sync::Arc,
};
use util::ResultExt;
fn intelephense_server_binary_arguments(server_path: &Path) -> Vec<OsString> {
vec![server_path.into(), "--stdio".into()]
}
pub struct IntelephenseVersion(String);
pub struct IntelephenseLspAdapter {
node: Arc<dyn NodeRuntime>,
}
impl IntelephenseLspAdapter {
const SERVER_PATH: &'static str = "node_modules/intelephense/lib/intelephense.js";
#[allow(unused)]
pub fn new(node: Arc<dyn NodeRuntime>) -> Self {
Self { node }
}
}
#[async_trait]
impl LspAdapter for IntelephenseLspAdapter {
async fn name(&self) -> LanguageServerName {
LanguageServerName("intelephense".into())
}
fn short_name(&self) -> &'static str {
"php"
}
async fn fetch_latest_server_version(
&self,
_delegate: &dyn LspAdapterDelegate,
) -> Result<Box<dyn 'static + Send + Any>> {
Ok(Box::new(IntelephenseVersion(
self.node.npm_package_latest_version("intelephense").await?,
)) 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::<IntelephenseVersion>().unwrap();
let server_path = container_dir.join(Self::SERVER_PATH);
if fs::metadata(&server_path).await.is_err() {
self.node
.npm_install_packages(&container_dir, &[("intelephense", version.0.as_str())])
.await?;
}
Ok(LanguageServerBinary {
path: self.node.binary_path().await?,
arguments: intelephense_server_binary_arguments(&server_path),
})
}
async fn cached_server_binary(
&self,
container_dir: PathBuf,
_: &dyn LspAdapterDelegate,
) -> Option<LanguageServerBinary> {
get_cached_server_binary(container_dir, &*self.node).await
}
async fn installation_test_binary(
&self,
container_dir: PathBuf,
) -> Option<LanguageServerBinary> {
get_cached_server_binary(container_dir, &*self.node).await
}
async fn label_for_completion(
&self,
_item: &lsp2::CompletionItem,
_language: &Arc<language2::Language>,
) -> Option<language2::CodeLabel> {
None
}
async fn initialization_options(&self) -> Option<serde_json::Value> {
None
}
async fn language_ids(&self) -> HashMap<String, String> {
HashMap::from_iter([("PHP".into(), "php".into())])
}
}
async fn get_cached_server_binary(
container_dir: PathBuf,
node: &dyn NodeRuntime,
) -> Option<LanguageServerBinary> {
(|| async move {
let mut last_version_dir = 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_dir() {
last_version_dir = Some(entry.path());
}
}
let last_version_dir = last_version_dir.ok_or_else(|| anyhow!("no cached binary"))?;
let server_path = last_version_dir.join(IntelephenseLspAdapter::SERVER_PATH);
if server_path.exists() {
Ok(LanguageServerBinary {
path: node.binary_path().await?,
arguments: intelephense_server_binary_arguments(&server_path),
})
} else {
Err(anyhow!(
"missing executable in directory {:?}",
last_version_dir
))
}
})()
.await
.log_err()
}

View file

@ -0,0 +1,14 @@
name = "PHP"
path_suffixes = ["php"]
first_line_pattern = '^#!.*php'
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 = false, not_in = ["string"] },
]
collapsed_placeholder = "/* ... */"
word_characters = ["$"]
scope_opt_in_language_servers = ["tailwindcss-language-server"]

View file

@ -0,0 +1,36 @@
(
(comment)* @context
.
[
(function_definition
"function" @name
name: (_) @name
body: (_
"{" @keep
"}" @keep) @collapse
)
(trait_declaration
"trait" @name
name: (_) @name)
(method_declaration
"function" @name
name: (_) @name
body: (_
"{" @keep
"}" @keep) @collapse
)
(interface_declaration
"interface" @name
name: (_) @name
)
(enum_declaration
"enum" @name
name: (_) @name
)
] @item
)

Some files were not shown because too many files have changed in this diff Show more