chore: Extract languages from zed
crate (#8270)
- Moves languages module from `zed` into a separate crate. That way we have less of a long pole at the end of compilation. - Removes moot dependencies on editor/picker. This is totally harmless and might help in the future if we decide to decouple picker from editor. Before: ``` Number of crates that depend on 'picker' but not on 'editor': 1 Total number of crates that depend on 'picker': 13 Total number of crates that depend on 'editor': 30 ``` After: ``` Number of crates that depend on 'picker' but not on 'editor': 5 Total number of crates that depend on 'picker': 12 Total number of crates that depend on 'editor': 26 ``` The more crates depend on just picker but not editor, the better in that case. Release Notes: - N/A
This commit is contained in:
parent
7cf0696c89
commit
0f584cb353
309 changed files with 221 additions and 154 deletions
89
crates/languages/Cargo.toml
Normal file
89
crates/languages/Cargo.toml
Normal file
|
@ -0,0 +1,89 @@
|
|||
[package]
|
||||
name = "languages"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
publish = false
|
||||
license = "GPL-3.0-or-later"
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
anyhow.workspace = true
|
||||
gpui.workspace = true
|
||||
language.workspace = true
|
||||
node_runtime.workspace = true
|
||||
rust-embed = "8.2.0"
|
||||
settings.workspace = true
|
||||
|
||||
tree-sitter-astro.workspace = true
|
||||
tree-sitter-bash.workspace = true
|
||||
tree-sitter-c-sharp.workspace = true
|
||||
tree-sitter-c.workspace = true
|
||||
tree-sitter-clojure.workspace = true
|
||||
tree-sitter-cpp.workspace = true
|
||||
tree-sitter-css.workspace = true
|
||||
tree-sitter-dockerfile.workspace = true
|
||||
tree-sitter-dart.workspace = true
|
||||
tree-sitter-elixir.workspace = true
|
||||
tree-sitter-elm.workspace = true
|
||||
tree-sitter-embedded-template.workspace = true
|
||||
tree-sitter-erlang.workspace = true
|
||||
tree-sitter-gitcommit.workspace = true
|
||||
tree-sitter-gleam.workspace = true
|
||||
tree-sitter-glsl.workspace = true
|
||||
tree-sitter-go.workspace = true
|
||||
tree-sitter-gomod.workspace = true
|
||||
tree-sitter-gowork.workspace = true
|
||||
tree-sitter-haskell.workspace = true
|
||||
tree-sitter-hcl.workspace = true
|
||||
tree-sitter-heex.workspace = true
|
||||
tree-sitter-html.workspace = true
|
||||
tree-sitter-json.workspace = true
|
||||
tree-sitter-lua.workspace = true
|
||||
tree-sitter-markdown.workspace = true
|
||||
tree-sitter-nix.workspace = true
|
||||
tree-sitter-nu.workspace = true
|
||||
tree-sitter-ocaml.workspace = true
|
||||
tree-sitter-php.workspace = true
|
||||
tree-sitter-prisma-io.workspace = true
|
||||
tree-sitter-proto.workspace = true
|
||||
tree-sitter-purescript.workspace = true
|
||||
tree-sitter-python.workspace = true
|
||||
tree-sitter-racket.workspace = true
|
||||
tree-sitter-ruby.workspace = true
|
||||
tree-sitter-rust.workspace = true
|
||||
tree-sitter-scheme.workspace = true
|
||||
tree-sitter-svelte.workspace = true
|
||||
tree-sitter-toml.workspace = true
|
||||
tree-sitter-typescript.workspace = true
|
||||
tree-sitter-uiua.workspace = true
|
||||
tree-sitter-vue.workspace = true
|
||||
tree-sitter-yaml.workspace = true
|
||||
tree-sitter-zig.workspace = true
|
||||
tree-sitter.workspace = true
|
||||
util.workspace = true
|
||||
lsp.workspace = true
|
||||
async-trait = "0.1.77"
|
||||
shellexpand = "3.1.0"
|
||||
serde_json.workspace = true
|
||||
serde_derive.workspace = true
|
||||
futures.workspace = true
|
||||
smol.workspace = true
|
||||
toml.workspace = true
|
||||
lazy_static.workspace = true
|
||||
schemars.workspace = true
|
||||
log.workspace = true
|
||||
task.workspace = true
|
||||
parking_lot.workspace = true
|
||||
async-compression = "0.4.6"
|
||||
collections.workspace = true
|
||||
async-tar = "0.4.2"
|
||||
regex.workspace = true
|
||||
feature_flags.workspace = true
|
||||
project.workspace = true
|
||||
serde.workspace = true
|
||||
rope.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
text.workspace = true
|
||||
theme.workspace = true
|
||||
unindent.workspace = true
|
1
crates/languages/LICENSE-GPL
Symbolic link
1
crates/languages/LICENSE-GPL
Symbolic link
|
@ -0,0 +1 @@
|
|||
../../LICENSE-GPL
|
138
crates/languages/src/astro.rs
Normal file
138
crates/languages/src/astro.rs
Normal file
|
@ -0,0 +1,138 @@
|
|||
use anyhow::{anyhow, Result};
|
||||
use async_trait::async_trait;
|
||||
use futures::StreamExt;
|
||||
use language::{LanguageServerName, LspAdapter, LspAdapterDelegate};
|
||||
use lsp::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/@astrojs/language-server/bin/nodeServer.js";
|
||||
|
||||
fn server_binary_arguments(server_path: &Path) -> Vec<OsString> {
|
||||
vec![server_path.into(), "--stdio".into()]
|
||||
}
|
||||
|
||||
pub struct AstroLspAdapter {
|
||||
node: Arc<dyn NodeRuntime>,
|
||||
}
|
||||
|
||||
impl AstroLspAdapter {
|
||||
pub fn new(node: Arc<dyn NodeRuntime>) -> Self {
|
||||
AstroLspAdapter { node }
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl LspAdapter for AstroLspAdapter {
|
||||
fn name(&self) -> LanguageServerName {
|
||||
LanguageServerName("astro-language-server".into())
|
||||
}
|
||||
|
||||
fn short_name(&self) -> &'static str {
|
||||
"astro"
|
||||
}
|
||||
|
||||
async fn fetch_latest_server_version(
|
||||
&self,
|
||||
_: &dyn LspAdapterDelegate,
|
||||
) -> Result<Box<dyn 'static + Any + Send>> {
|
||||
Ok(Box::new(
|
||||
self.node
|
||||
.npm_package_latest_version("@astrojs/language-server")
|
||||
.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,
|
||||
&[("@astrojs/language-server", version.as_str())],
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
|
||||
Ok(LanguageServerBinary {
|
||||
path: self.node.binary_path().await?,
|
||||
env: None,
|
||||
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
|
||||
}
|
||||
|
||||
fn initialization_options(&self) -> Option<serde_json::Value> {
|
||||
Some(json!({
|
||||
"provideFormatter": true,
|
||||
"typescript": {
|
||||
"tsdk": "node_modules/typescript/lib",
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
fn prettier_plugins(&self) -> &[&'static str] {
|
||||
&["prettier-plugin-astro"]
|
||||
}
|
||||
}
|
||||
|
||||
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?,
|
||||
env: None,
|
||||
arguments: server_binary_arguments(&server_path),
|
||||
})
|
||||
} else {
|
||||
Err(anyhow!(
|
||||
"missing executable in directory {:?}",
|
||||
last_version_dir
|
||||
))
|
||||
}
|
||||
})()
|
||||
.await
|
||||
.log_err()
|
||||
}
|
3
crates/languages/src/astro/brackets.scm
Normal file
3
crates/languages/src/astro/brackets.scm
Normal file
|
@ -0,0 +1,3 @@
|
|||
("{" @open "}" @close)
|
||||
("<" @open ">" @close)
|
||||
("\"" @open "\"" @close)
|
22
crates/languages/src/astro/config.toml
Normal file
22
crates/languages/src/astro/config.toml
Normal file
|
@ -0,0 +1,22 @@
|
|||
name = "Astro"
|
||||
grammar = "astro"
|
||||
path_suffixes = ["astro"]
|
||||
block_comment = ["<!-- ", " -->"]
|
||||
autoclose_before = ";:.,=}])>"
|
||||
brackets = [
|
||||
{ start = "{", end = "}", close = true, newline = true },
|
||||
{ start = "[", end = "]", close = true, newline = true },
|
||||
{ start = "(", end = ")", close = true, newline = true },
|
||||
{ start = "<", end = ">", close = false, newline = true, not_in = ["string", "comment"] },
|
||||
{ start = "\"", end = "\"", close = true, newline = false, not_in = ["string", "comment"] },
|
||||
{ start = "'", end = "'", close = true, newline = false, not_in = ["string", "comment"] },
|
||||
{ start = "`", end = "`", close = true, newline = false, not_in = ["string"] },
|
||||
{ start = "/*", end = " */", close = true, newline = false, not_in = ["string", "comment"] },
|
||||
]
|
||||
word_characters = ["#", "$", "-"]
|
||||
scope_opt_in_language_servers = ["tailwindcss-language-server"]
|
||||
prettier_parser_name = "astro"
|
||||
|
||||
[overrides.string]
|
||||
word_characters = ["-"]
|
||||
opt_into_language_servers = ["tailwindcss-language-server"]
|
25
crates/languages/src/astro/highlights.scm
Normal file
25
crates/languages/src/astro/highlights.scm
Normal file
|
@ -0,0 +1,25 @@
|
|||
(tag_name) @tag
|
||||
(erroneous_end_tag_name) @keyword
|
||||
(doctype) @constant
|
||||
(attribute_name) @property
|
||||
(attribute_value) @string
|
||||
(comment) @comment
|
||||
|
||||
[
|
||||
(attribute_value)
|
||||
(quoted_attribute_value)
|
||||
] @string
|
||||
|
||||
"=" @operator
|
||||
|
||||
[
|
||||
"{"
|
||||
"}"
|
||||
] @punctuation.bracket
|
||||
|
||||
[
|
||||
"<"
|
||||
">"
|
||||
"</"
|
||||
"/>"
|
||||
] @tag.delimiter
|
16
crates/languages/src/astro/injections.scm
Normal file
16
crates/languages/src/astro/injections.scm
Normal file
|
@ -0,0 +1,16 @@
|
|||
; inherits: html_tags
|
||||
(frontmatter
|
||||
(raw_text) @content
|
||||
(#set! "language" "typescript"))
|
||||
|
||||
(interpolation
|
||||
(raw_text) @content
|
||||
(#set! "language" "tsx"))
|
||||
|
||||
(script_element
|
||||
(raw_text) @content
|
||||
(#set! "language" "typescript"))
|
||||
|
||||
(style_element
|
||||
(raw_text) @content
|
||||
(#set! "language" "css"))
|
3
crates/languages/src/bash/brackets.scm
Normal file
3
crates/languages/src/bash/brackets.scm
Normal file
|
@ -0,0 +1,3 @@
|
|||
("(" @open ")" @close)
|
||||
("[" @open "]" @close)
|
||||
("{" @open "}" @close)
|
10
crates/languages/src/bash/config.toml
Normal file
10
crates/languages/src/bash/config.toml
Normal file
|
@ -0,0 +1,10 @@
|
|||
name = "Shell Script"
|
||||
grammar = "bash"
|
||||
path_suffixes = ["sh", "bash", "bashrc", "bash_profile", "bash_aliases", "bash_logout", "profile", "zsh", "zshrc", "zshenv", "zsh_profile", "zsh_aliases", "zsh_histfile", "zlogin", "zprofile", ".env"]
|
||||
line_comments = ["# "]
|
||||
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"] },
|
||||
]
|
59
crates/languages/src/bash/highlights.scm
Normal file
59
crates/languages/src/bash/highlights.scm
Normal 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 "^-")
|
||||
)
|
2
crates/languages/src/bash/redactions.scm
Normal file
2
crates/languages/src/bash/redactions.scm
Normal file
|
@ -0,0 +1,2 @@
|
|||
(variable_assignment
|
||||
value: (_) @redact)
|
326
crates/languages/src/c.rs
Normal file
326
crates/languages/src/c.rs
Normal file
|
@ -0,0 +1,326 @@
|
|||
use anyhow::{anyhow, Context, Result};
|
||||
use async_trait::async_trait;
|
||||
use futures::StreamExt;
|
||||
pub use language::*;
|
||||
use lsp::LanguageServerBinary;
|
||||
use smol::fs::{self, File};
|
||||
use std::{any::Any, path::PathBuf, sync::Arc};
|
||||
use util::{
|
||||
async_maybe,
|
||||
fs::remove_matching,
|
||||
github::{latest_github_release, GitHubLspBinaryVersion},
|
||||
ResultExt,
|
||||
};
|
||||
|
||||
pub struct CLspAdapter;
|
||||
|
||||
#[async_trait]
|
||||
impl super::LspAdapter for CLspAdapter {
|
||||
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", true, false, delegate.http_client()).await?;
|
||||
let asset_name = format!("clangd-mac-{}.zip", release.tag_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.tag_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,
|
||||
env: None,
|
||||
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: &lsp::CompletionItem,
|
||||
language: &Arc<Language>,
|
||||
) -> Option<CodeLabel> {
|
||||
let label = completion
|
||||
.label
|
||||
.strip_prefix('•')
|
||||
.unwrap_or(&completion.label)
|
||||
.trim();
|
||||
|
||||
match completion.kind {
|
||||
Some(lsp::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(lsp::CompletionItemKind::CONSTANT | lsp::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(lsp::CompletionItemKind::FUNCTION | lsp::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 {
|
||||
lsp::CompletionItemKind::STRUCT
|
||||
| lsp::CompletionItemKind::INTERFACE
|
||||
| lsp::CompletionItemKind::CLASS
|
||||
| lsp::CompletionItemKind::ENUM => Some("type"),
|
||||
lsp::CompletionItemKind::ENUM_MEMBER => Some("variant"),
|
||||
lsp::CompletionItemKind::KEYWORD => Some("keyword"),
|
||||
lsp::CompletionItemKind::VALUE | lsp::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: lsp::SymbolKind,
|
||||
language: &Arc<Language>,
|
||||
) -> Option<CodeLabel> {
|
||||
let (text, filter_range, display_range) = match kind {
|
||||
lsp::SymbolKind::METHOD | lsp::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)
|
||||
}
|
||||
lsp::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)
|
||||
}
|
||||
lsp::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)
|
||||
}
|
||||
lsp::SymbolKind::INTERFACE | lsp::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)
|
||||
}
|
||||
lsp::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)
|
||||
}
|
||||
lsp::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)
|
||||
}
|
||||
lsp::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_maybe!({
|
||||
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,
|
||||
env: None,
|
||||
arguments: vec![],
|
||||
})
|
||||
} else {
|
||||
Err(anyhow!(
|
||||
"missing clangd binary in directory {:?}",
|
||||
clangd_dir
|
||||
))
|
||||
}
|
||||
})
|
||||
.await
|
||||
.log_err()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use gpui::{Context, TestAppContext};
|
||||
use language::{language_settings::AllLanguageSettings, AutoindentMode, Buffer};
|
||||
use settings::SettingsStore;
|
||||
use std::num::NonZeroU32;
|
||||
use text::BufferId;
|
||||
|
||||
#[gpui::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);
|
||||
language::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::language("c", tree_sitter_c::language(), None).await;
|
||||
|
||||
cx.new_model(|cx| {
|
||||
let mut buffer = Buffer::new(0, BufferId::new(cx.entity_id().as_u64()).unwrap(), "")
|
||||
.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
|
||||
});
|
||||
}
|
||||
}
|
4
crates/languages/src/c/brackets.scm
Normal file
4
crates/languages/src/c/brackets.scm
Normal file
|
@ -0,0 +1,4 @@
|
|||
("(" @open ")" @close)
|
||||
("[" @open "]" @close)
|
||||
("{" @open "}" @close)
|
||||
("\"" @open "\"" @close)
|
13
crates/languages/src/c/config.toml
Normal file
13
crates/languages/src/c/config.toml
Normal file
|
@ -0,0 +1,13 @@
|
|||
name = "C"
|
||||
grammar = "c"
|
||||
path_suffixes = ["c"]
|
||||
line_comments = ["// "]
|
||||
autoclose_before = ";:.,=}])>"
|
||||
brackets = [
|
||||
{ start = "{", end = "}", close = true, newline = true },
|
||||
{ start = "[", end = "]", close = true, newline = true },
|
||||
{ start = "(", end = ")", close = true, newline = true },
|
||||
{ start = "\"", end = "\"", close = true, newline = false, not_in = ["string"] },
|
||||
{ start = "'", end = "'", close = true, newline = false, not_in = ["string", "comment"] },
|
||||
{ start = "/*", end = " */", close = true, newline = false, not_in = ["string", "comment"] },
|
||||
]
|
43
crates/languages/src/c/embedding.scm
Normal file
43
crates/languages/src/c/embedding.scm
Normal 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
|
||||
)
|
109
crates/languages/src/c/highlights.scm
Normal file
109
crates/languages/src/c/highlights.scm
Normal 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
|
||||
|
9
crates/languages/src/c/indents.scm
Normal file
9
crates/languages/src/c/indents.scm
Normal file
|
@ -0,0 +1,9 @@
|
|||
[
|
||||
(field_expression)
|
||||
(assignment_expression)
|
||||
(if_statement)
|
||||
(for_statement)
|
||||
] @indent
|
||||
|
||||
(_ "{" "}" @end) @indent
|
||||
(_ "(" ")" @end) @indent
|
7
crates/languages/src/c/injections.scm
Normal file
7
crates/languages/src/c/injections.scm
Normal file
|
@ -0,0 +1,7 @@
|
|||
(preproc_def
|
||||
value: (preproc_arg) @content
|
||||
(#set! "language" "c"))
|
||||
|
||||
(preproc_function_def
|
||||
value: (preproc_arg) @content
|
||||
(#set! "language" "c"))
|
70
crates/languages/src/c/outline.scm
Normal file
70
crates/languages/src/c/outline.scm
Normal 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
|
2
crates/languages/src/c/overrides.scm
Normal file
2
crates/languages/src/c/overrides.scm
Normal file
|
@ -0,0 +1,2 @@
|
|||
(comment) @comment
|
||||
(string_literal) @string
|
145
crates/languages/src/clojure.rs
Normal file
145
crates/languages/src/clojure.rs
Normal file
|
@ -0,0 +1,145 @@
|
|||
use anyhow::{anyhow, bail, Context, Result};
|
||||
use async_trait::async_trait;
|
||||
pub use language::*;
|
||||
use lsp::LanguageServerBinary;
|
||||
use smol::fs::{self, File};
|
||||
use std::{any::Any, env::consts, path::PathBuf};
|
||||
use util::{
|
||||
fs::remove_matching,
|
||||
github::{latest_github_release, GitHubLspBinaryVersion},
|
||||
};
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct ClojureLspAdapter;
|
||||
|
||||
#[async_trait]
|
||||
impl super::LspAdapter for ClojureLspAdapter {
|
||||
fn name(&self) -> LanguageServerName {
|
||||
LanguageServerName("clojure-lsp".into())
|
||||
}
|
||||
|
||||
fn short_name(&self) -> &'static str {
|
||||
"clojure"
|
||||
}
|
||||
|
||||
async fn fetch_latest_server_version(
|
||||
&self,
|
||||
delegate: &dyn LspAdapterDelegate,
|
||||
) -> Result<Box<dyn 'static + Send + Any>> {
|
||||
let release = latest_github_release(
|
||||
"clojure-lsp/clojure-lsp",
|
||||
true,
|
||||
false,
|
||||
delegate.http_client(),
|
||||
)
|
||||
.await?;
|
||||
let os = match consts::OS {
|
||||
"macos" => "macos",
|
||||
"linux" => "linux",
|
||||
"windows" => "windows",
|
||||
other => bail!("Running on unsupported os: {other}"),
|
||||
};
|
||||
let platform = match consts::ARCH {
|
||||
"x86_64" => "amd64",
|
||||
"aarch64" => "aarch64",
|
||||
other => bail!("Running on unsupported platform: {other}"),
|
||||
};
|
||||
let asset_name = format!("clojure-lsp-native-{os}-{platform}.zip");
|
||||
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.tag_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 zip_path = container_dir.join(format!("clojure-lsp_{}.zip", version.name));
|
||||
let folder_path = container_dir.join("bin");
|
||||
let binary_path = folder_path.join("clojure-lsp");
|
||||
|
||||
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() {
|
||||
return Err(anyhow!(
|
||||
"download failed with status {}",
|
||||
response.status().to_string()
|
||||
))?;
|
||||
}
|
||||
futures::io::copy(response.body_mut(), &mut file).await?;
|
||||
|
||||
fs::create_dir_all(&folder_path)
|
||||
.await
|
||||
.with_context(|| format!("failed to create directory {}", folder_path.display()))?;
|
||||
|
||||
let unzip_status = smol::process::Command::new("unzip")
|
||||
.arg(&zip_path)
|
||||
.arg("-d")
|
||||
.arg(&folder_path)
|
||||
.output()
|
||||
.await?
|
||||
.status;
|
||||
if !unzip_status.success() {
|
||||
return Err(anyhow!("failed to unzip elixir-ls archive"))?;
|
||||
}
|
||||
|
||||
remove_matching(&container_dir, |entry| entry != folder_path).await;
|
||||
}
|
||||
|
||||
Ok(LanguageServerBinary {
|
||||
path: binary_path,
|
||||
env: None,
|
||||
arguments: vec![],
|
||||
})
|
||||
}
|
||||
|
||||
async fn cached_server_binary(
|
||||
&self,
|
||||
container_dir: PathBuf,
|
||||
_: &dyn LspAdapterDelegate,
|
||||
) -> Option<LanguageServerBinary> {
|
||||
let binary_path = container_dir.join("bin").join("clojure-lsp");
|
||||
if binary_path.exists() {
|
||||
Some(LanguageServerBinary {
|
||||
path: binary_path,
|
||||
env: None,
|
||||
arguments: vec![],
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
async fn installation_test_binary(
|
||||
&self,
|
||||
container_dir: PathBuf,
|
||||
) -> Option<LanguageServerBinary> {
|
||||
let binary_path = container_dir.join("bin").join("clojure-lsp");
|
||||
if binary_path.exists() {
|
||||
Some(LanguageServerBinary {
|
||||
path: binary_path,
|
||||
env: None,
|
||||
arguments: vec!["--version".into()],
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
3
crates/languages/src/clojure/brackets.scm
Normal file
3
crates/languages/src/clojure/brackets.scm
Normal file
|
@ -0,0 +1,3 @@
|
|||
("(" @open ")" @close)
|
||||
("[" @open "]" @close)
|
||||
("{" @open "}" @close)
|
12
crates/languages/src/clojure/config.toml
Normal file
12
crates/languages/src/clojure/config.toml
Normal file
|
@ -0,0 +1,12 @@
|
|||
name = "Clojure"
|
||||
grammar = "clojure"
|
||||
path_suffixes = ["clj", "cljs"]
|
||||
line_comments = [";; "]
|
||||
autoclose_before = "}])"
|
||||
brackets = [
|
||||
{ start = "{", end = "}", close = true, newline = true },
|
||||
{ start = "[", end = "]", close = true, newline = true },
|
||||
{ start = "(", end = ")", close = true, newline = true },
|
||||
{ start = "\"", end = "\"", close = true, newline = false, not_in = ["string"] },
|
||||
]
|
||||
word_characters = ["-"]
|
41
crates/languages/src/clojure/highlights.scm
Normal file
41
crates/languages/src/clojure/highlights.scm
Normal file
|
@ -0,0 +1,41 @@
|
|||
;; Literals
|
||||
|
||||
(num_lit) @number
|
||||
|
||||
[
|
||||
(char_lit)
|
||||
(str_lit)
|
||||
] @string
|
||||
|
||||
[
|
||||
(bool_lit)
|
||||
(nil_lit)
|
||||
] @constant.builtin
|
||||
|
||||
(kwd_lit) @constant
|
||||
|
||||
;; Comments
|
||||
|
||||
(comment) @comment
|
||||
|
||||
;; Treat quasiquotation as operators for the purpose of highlighting.
|
||||
|
||||
[
|
||||
"'"
|
||||
"`"
|
||||
"~"
|
||||
"@"
|
||||
"~@"
|
||||
] @operator
|
||||
|
||||
|
||||
(list_lit
|
||||
.
|
||||
(sym_lit) @function)
|
||||
|
||||
(list_lit
|
||||
.
|
||||
(sym_lit) @keyword
|
||||
(#match? @keyword
|
||||
"^(do|if|let|var|fn|fn*|loop*|recur|throw|try|catch|finally|set!|new|quote|->|->>)$"
|
||||
))
|
3
crates/languages/src/clojure/indents.scm
Normal file
3
crates/languages/src/clojure/indents.scm
Normal file
|
@ -0,0 +1,3 @@
|
|||
(_ "[" "]") @indent
|
||||
(_ "{" "}") @indent
|
||||
(_ "(" ")") @indent
|
1
crates/languages/src/clojure/outline.scm
Normal file
1
crates/languages/src/clojure/outline.scm
Normal file
|
@ -0,0 +1 @@
|
|||
|
4
crates/languages/src/cpp/brackets.scm
Normal file
4
crates/languages/src/cpp/brackets.scm
Normal file
|
@ -0,0 +1,4 @@
|
|||
("(" @open ")" @close)
|
||||
("[" @open "]" @close)
|
||||
("{" @open "}" @close)
|
||||
("\"" @open "\"" @close)
|
13
crates/languages/src/cpp/config.toml
Normal file
13
crates/languages/src/cpp/config.toml
Normal file
|
@ -0,0 +1,13 @@
|
|||
name = "C++"
|
||||
grammar = "cpp"
|
||||
path_suffixes = ["cc", "cpp", "h", "hpp", "cxx", "hxx", "inl"]
|
||||
line_comments = ["// "]
|
||||
autoclose_before = ";:.,=}])>"
|
||||
brackets = [
|
||||
{ start = "{", end = "}", close = true, newline = true },
|
||||
{ start = "[", end = "]", close = true, newline = true },
|
||||
{ start = "(", end = ")", close = true, newline = true },
|
||||
{ start = "\"", end = "\"", close = true, newline = false, not_in = ["string"] },
|
||||
{ start = "'", end = "'", close = true, newline = false, not_in = ["string", "comment"] },
|
||||
{ start = "/*", end = " */", close = true, newline = false, not_in = ["string", "comment"] },
|
||||
]
|
61
crates/languages/src/cpp/embedding.scm
Normal file
61
crates/languages/src/cpp/embedding.scm
Normal 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
|
||||
)
|
158
crates/languages/src/cpp/highlights.scm
Normal file
158
crates/languages/src/cpp/highlights.scm
Normal 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
|
7
crates/languages/src/cpp/indents.scm
Normal file
7
crates/languages/src/cpp/indents.scm
Normal file
|
@ -0,0 +1,7 @@
|
|||
[
|
||||
(field_expression)
|
||||
(assignment_expression)
|
||||
] @indent
|
||||
|
||||
(_ "{" "}" @end) @indent
|
||||
(_ "(" ")" @end) @indent
|
7
crates/languages/src/cpp/injections.scm
Normal file
7
crates/languages/src/cpp/injections.scm
Normal file
|
@ -0,0 +1,7 @@
|
|||
(preproc_def
|
||||
value: (preproc_arg) @content
|
||||
(#set! "language" "c++"))
|
||||
|
||||
(preproc_function_def
|
||||
value: (preproc_arg) @content
|
||||
(#set! "language" "c++"))
|
149
crates/languages/src/cpp/outline.scm
Normal file
149
crates/languages/src/cpp/outline.scm
Normal 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
|
2
crates/languages/src/cpp/overrides.scm
Normal file
2
crates/languages/src/cpp/overrides.scm
Normal file
|
@ -0,0 +1,2 @@
|
|||
(comment) @comment
|
||||
(string_literal) @string
|
153
crates/languages/src/csharp.rs
Normal file
153
crates/languages/src/csharp.rs
Normal file
|
@ -0,0 +1,153 @@
|
|||
use anyhow::{anyhow, Context, Result};
|
||||
use async_compression::futures::bufread::GzipDecoder;
|
||||
use async_tar::Archive;
|
||||
use async_trait::async_trait;
|
||||
use futures::{io::BufReader, StreamExt};
|
||||
use language::{LanguageServerName, LspAdapterDelegate};
|
||||
use lsp::LanguageServerBinary;
|
||||
use smol::fs;
|
||||
use std::env::consts::ARCH;
|
||||
use std::ffi::OsString;
|
||||
use std::{any::Any, path::PathBuf};
|
||||
use util::async_maybe;
|
||||
use util::github::latest_github_release;
|
||||
use util::{github::GitHubLspBinaryVersion, ResultExt};
|
||||
|
||||
pub struct OmniSharpAdapter;
|
||||
|
||||
#[async_trait]
|
||||
impl super::LspAdapter for OmniSharpAdapter {
|
||||
fn name(&self) -> LanguageServerName {
|
||||
LanguageServerName("OmniSharp".into())
|
||||
}
|
||||
|
||||
fn short_name(&self) -> &'static str {
|
||||
"OmniSharp"
|
||||
}
|
||||
|
||||
async fn fetch_latest_server_version(
|
||||
&self,
|
||||
delegate: &dyn LspAdapterDelegate,
|
||||
) -> Result<Box<dyn 'static + Send + Any>> {
|
||||
let mapped_arch = match ARCH {
|
||||
"aarch64" => Some("arm64"),
|
||||
"x86_64" => Some("x64"),
|
||||
_ => None,
|
||||
};
|
||||
|
||||
match mapped_arch {
|
||||
None => Ok(Box::new(())),
|
||||
Some(arch) => {
|
||||
let release = latest_github_release(
|
||||
"OmniSharp/omnisharp-roslyn",
|
||||
true,
|
||||
false,
|
||||
delegate.http_client(),
|
||||
)
|
||||
.await?;
|
||||
let asset_name = format!("omnisharp-osx-{}-net6.0.tar.gz", arch);
|
||||
let asset = release
|
||||
.assets
|
||||
.iter()
|
||||
.find(|asset| asset.name == asset_name)
|
||||
.ok_or_else(|| anyhow!("no asset found matching {:?}", asset_name))?;
|
||||
let version = GitHubLspBinaryVersion {
|
||||
name: release.tag_name,
|
||||
url: asset.browser_download_url.clone(),
|
||||
};
|
||||
|
||||
Ok(Box::new(version) as Box<_>)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn fetch_server_binary(
|
||||
&self,
|
||||
version: Box<dyn 'static + Send + Any>,
|
||||
container_dir: PathBuf,
|
||||
delegate: &dyn LspAdapterDelegate,
|
||||
) -> Result<LanguageServerBinary> {
|
||||
let version = version.downcast::<GitHubLspBinaryVersion>().unwrap();
|
||||
let binary_path = container_dir.join("omnisharp");
|
||||
|
||||
if fs::metadata(&binary_path).await.is_err() {
|
||||
let mut response = delegate
|
||||
.http_client()
|
||||
.get(&version.url, Default::default(), true)
|
||||
.await
|
||||
.context("error downloading release")?;
|
||||
let decompressed_bytes = GzipDecoder::new(BufReader::new(response.body_mut()));
|
||||
let archive = Archive::new(decompressed_bytes);
|
||||
archive.unpack(container_dir).await?;
|
||||
}
|
||||
|
||||
// todo!("windows")
|
||||
#[cfg(not(windows))]
|
||||
{
|
||||
fs::set_permissions(
|
||||
&binary_path,
|
||||
<fs::Permissions as fs::unix::PermissionsExt>::from_mode(0o755),
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
Ok(LanguageServerBinary {
|
||||
path: binary_path,
|
||||
env: None,
|
||||
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 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 == "omnisharp")
|
||||
{
|
||||
last_binary_path = Some(entry.path());
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(path) = last_binary_path {
|
||||
Ok(LanguageServerBinary {
|
||||
path,
|
||||
env: None,
|
||||
arguments: server_binary_arguments(),
|
||||
})
|
||||
} else {
|
||||
Err(anyhow!("no cached binary"))
|
||||
}
|
||||
})
|
||||
.await
|
||||
.log_err()
|
||||
}
|
||||
|
||||
fn server_binary_arguments() -> Vec<OsString> {
|
||||
vec!["-lsp".into()]
|
||||
}
|
13
crates/languages/src/csharp/config.toml
Normal file
13
crates/languages/src/csharp/config.toml
Normal file
|
@ -0,0 +1,13 @@
|
|||
name = "CSharp"
|
||||
grammar = "c_sharp"
|
||||
path_suffixes = ["cs"]
|
||||
line_comments = ["// "]
|
||||
autoclose_before = ";:.,=}])>"
|
||||
brackets = [
|
||||
{ start = "{", end = "}", close = true, newline = true },
|
||||
{ start = "[", end = "]", close = true, newline = true },
|
||||
{ start = "(", end = ")", close = true, newline = true },
|
||||
{ start = "\"", end = "\"", close = true, newline = false, not_in = ["string"] },
|
||||
{ start = "'", end = "'", close = true, newline = false, not_in = ["string", "comment"] },
|
||||
{ start = "/*", end = " */", close = true, newline = false, not_in = ["string", "comment"] },
|
||||
]
|
254
crates/languages/src/csharp/highlights.scm
Normal file
254
crates/languages/src/csharp/highlights.scm
Normal file
|
@ -0,0 +1,254 @@
|
|||
;; Methods
|
||||
(method_declaration name: (identifier) @function)
|
||||
(local_function_statement name: (identifier) @function)
|
||||
|
||||
;; Types
|
||||
(interface_declaration name: (identifier) @type)
|
||||
(class_declaration name: (identifier) @type)
|
||||
(enum_declaration name: (identifier) @type)
|
||||
(struct_declaration (identifier) @type)
|
||||
(record_declaration (identifier) @type)
|
||||
(record_struct_declaration (identifier) @type)
|
||||
(namespace_declaration name: (identifier) @type)
|
||||
|
||||
(constructor_declaration name: (identifier) @constructor)
|
||||
(destructor_declaration name: (identifier) @constructor)
|
||||
|
||||
[
|
||||
(implicit_type)
|
||||
(predefined_type)
|
||||
] @type.builtin
|
||||
|
||||
(_ type: (identifier) @type)
|
||||
|
||||
;; Enum
|
||||
(enum_member_declaration (identifier) @property)
|
||||
|
||||
;; Literals
|
||||
[
|
||||
(real_literal)
|
||||
(integer_literal)
|
||||
] @number
|
||||
|
||||
[
|
||||
(character_literal)
|
||||
(string_literal)
|
||||
(verbatim_string_literal)
|
||||
(interpolated_string_text)
|
||||
(interpolated_verbatim_string_text)
|
||||
"\""
|
||||
"$\""
|
||||
"@$\""
|
||||
"$@\""
|
||||
] @string
|
||||
|
||||
[
|
||||
(boolean_literal)
|
||||
(null_literal)
|
||||
] @constant
|
||||
|
||||
;; Comments
|
||||
(comment) @comment
|
||||
|
||||
;; Tokens
|
||||
[
|
||||
";"
|
||||
"."
|
||||
","
|
||||
] @punctuation.delimiter
|
||||
|
||||
[
|
||||
"--"
|
||||
"-"
|
||||
"-="
|
||||
"&"
|
||||
"&="
|
||||
"&&"
|
||||
"+"
|
||||
"++"
|
||||
"+="
|
||||
"<"
|
||||
"<="
|
||||
"<<"
|
||||
"<<="
|
||||
"="
|
||||
"=="
|
||||
"!"
|
||||
"!="
|
||||
"=>"
|
||||
">"
|
||||
">="
|
||||
">>"
|
||||
">>="
|
||||
">>>"
|
||||
">>>="
|
||||
"|"
|
||||
"|="
|
||||
"||"
|
||||
"?"
|
||||
"??"
|
||||
"??="
|
||||
"^"
|
||||
"^="
|
||||
"~"
|
||||
"*"
|
||||
"*="
|
||||
"/"
|
||||
"/="
|
||||
"%"
|
||||
"%="
|
||||
":"
|
||||
] @operator
|
||||
|
||||
[
|
||||
"("
|
||||
")"
|
||||
"["
|
||||
"]"
|
||||
"{"
|
||||
"}"
|
||||
] @punctuation.bracket
|
||||
|
||||
;; Keywords
|
||||
(modifier) @keyword
|
||||
(this_expression) @keyword
|
||||
(escape_sequence) @keyword
|
||||
|
||||
[
|
||||
"add"
|
||||
"alias"
|
||||
"as"
|
||||
"base"
|
||||
"break"
|
||||
"case"
|
||||
"catch"
|
||||
"checked"
|
||||
"class"
|
||||
"continue"
|
||||
"default"
|
||||
"delegate"
|
||||
"do"
|
||||
"else"
|
||||
"enum"
|
||||
"event"
|
||||
"explicit"
|
||||
"extern"
|
||||
"finally"
|
||||
"for"
|
||||
"foreach"
|
||||
"global"
|
||||
"goto"
|
||||
"if"
|
||||
"implicit"
|
||||
"interface"
|
||||
"is"
|
||||
"lock"
|
||||
"namespace"
|
||||
"notnull"
|
||||
"operator"
|
||||
"params"
|
||||
"return"
|
||||
"remove"
|
||||
"sizeof"
|
||||
"stackalloc"
|
||||
"static"
|
||||
"struct"
|
||||
"switch"
|
||||
"throw"
|
||||
"try"
|
||||
"typeof"
|
||||
"unchecked"
|
||||
"using"
|
||||
"while"
|
||||
"new"
|
||||
"await"
|
||||
"in"
|
||||
"yield"
|
||||
"get"
|
||||
"set"
|
||||
"when"
|
||||
"out"
|
||||
"ref"
|
||||
"from"
|
||||
"where"
|
||||
"select"
|
||||
"record"
|
||||
"init"
|
||||
"with"
|
||||
"let"
|
||||
] @keyword
|
||||
|
||||
|
||||
;; Linq
|
||||
(from_clause (identifier) @variable)
|
||||
(group_clause (identifier) @variable)
|
||||
(order_by_clause (identifier) @variable)
|
||||
(join_clause (identifier) @variable)
|
||||
(select_clause (identifier) @variable)
|
||||
(query_continuation (identifier) @variable) @keyword
|
||||
|
||||
;; Record
|
||||
(with_expression
|
||||
(with_initializer_expression
|
||||
(simple_assignment_expression
|
||||
(identifier) @variable)))
|
||||
|
||||
;; Exprs
|
||||
(binary_expression (identifier) @variable (identifier) @variable)
|
||||
(binary_expression (identifier)* @variable)
|
||||
(conditional_expression (identifier) @variable)
|
||||
(prefix_unary_expression (identifier) @variable)
|
||||
(postfix_unary_expression (identifier)* @variable)
|
||||
(assignment_expression (identifier) @variable)
|
||||
(cast_expression (_) (identifier) @variable)
|
||||
|
||||
;; Class
|
||||
(base_list (identifier) @type) ;; applies to record_base too
|
||||
(property_declaration (generic_name))
|
||||
(property_declaration
|
||||
name: (identifier) @variable)
|
||||
(property_declaration
|
||||
name: (identifier) @variable)
|
||||
(property_declaration
|
||||
name: (identifier) @variable)
|
||||
|
||||
;; Lambda
|
||||
(lambda_expression) @variable
|
||||
|
||||
;; Attribute
|
||||
(attribute) @attribute
|
||||
|
||||
;; Parameter
|
||||
(parameter
|
||||
name: (identifier) @variable)
|
||||
(parameter (identifier) @variable)
|
||||
(parameter_modifier) @keyword
|
||||
|
||||
;; Variable declarations
|
||||
(variable_declarator (identifier) @variable)
|
||||
(for_each_statement left: (identifier) @variable)
|
||||
(catch_declaration (_) (identifier) @variable)
|
||||
|
||||
;; Return
|
||||
(return_statement (identifier) @variable)
|
||||
(yield_statement (identifier) @variable)
|
||||
|
||||
;; Type
|
||||
(generic_name (identifier) @type)
|
||||
(type_parameter (identifier) @property)
|
||||
(type_argument_list (identifier) @type)
|
||||
(as_expression right: (identifier) @type)
|
||||
(is_expression right: (identifier) @type)
|
||||
|
||||
;; Type constraints
|
||||
(type_parameter_constraints_clause (identifier) @property)
|
||||
|
||||
;; Switch
|
||||
(switch_statement (identifier) @variable)
|
||||
(switch_expression (identifier) @variable)
|
||||
|
||||
;; Lock statement
|
||||
(lock_statement (identifier) @variable)
|
||||
|
||||
;; Method calls
|
||||
(invocation_expression (member_access_expression name: (identifier) @function))
|
2
crates/languages/src/csharp/injections.scm
Normal file
2
crates/languages/src/csharp/injections.scm
Normal file
|
@ -0,0 +1,2 @@
|
|||
((comment) @injection.content
|
||||
(#set! injection.language "comment"))
|
38
crates/languages/src/csharp/outline.scm
Normal file
38
crates/languages/src/csharp/outline.scm
Normal file
|
@ -0,0 +1,38 @@
|
|||
(class_declaration
|
||||
"class" @context
|
||||
name: (identifier) @name
|
||||
) @item
|
||||
|
||||
(constructor_declaration
|
||||
name: (identifier) @name
|
||||
) @item
|
||||
|
||||
(property_declaration
|
||||
type: (identifier)? @context
|
||||
type: (predefined_type)? @context
|
||||
name: (identifier) @name
|
||||
) @item
|
||||
|
||||
(field_declaration
|
||||
(variable_declaration) @context
|
||||
) @item
|
||||
|
||||
(method_declaration
|
||||
name: (identifier) @name
|
||||
parameters: (parameter_list) @context
|
||||
) @item
|
||||
|
||||
(enum_declaration
|
||||
"enum" @context
|
||||
name: (identifier) @name
|
||||
) @item
|
||||
|
||||
(namespace_declaration
|
||||
"namespace" @context
|
||||
name: (qualified_name) @name
|
||||
) @item
|
||||
|
||||
(interface_declaration
|
||||
"interface" @context
|
||||
name: (identifier) @name
|
||||
) @item
|
132
crates/languages/src/css.rs
Normal file
132
crates/languages/src/css.rs
Normal file
|
@ -0,0 +1,132 @@
|
|||
use anyhow::{anyhow, Result};
|
||||
use async_trait::async_trait;
|
||||
use futures::StreamExt;
|
||||
use language::{LanguageServerName, LspAdapter, LspAdapterDelegate};
|
||||
use lsp::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::{async_maybe, 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 {
|
||||
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?,
|
||||
env: None,
|
||||
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
|
||||
}
|
||||
|
||||
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_maybe!({
|
||||
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?,
|
||||
env: None,
|
||||
arguments: server_binary_arguments(&server_path),
|
||||
})
|
||||
} else {
|
||||
Err(anyhow!(
|
||||
"missing executable in directory {:?}",
|
||||
last_version_dir
|
||||
))
|
||||
}
|
||||
})
|
||||
.await
|
||||
.log_err()
|
||||
}
|
3
crates/languages/src/css/brackets.scm
Normal file
3
crates/languages/src/css/brackets.scm
Normal file
|
@ -0,0 +1,3 @@
|
|||
("(" @open ")" @close)
|
||||
("[" @open "]" @close)
|
||||
("{" @open "}" @close)
|
14
crates/languages/src/css/config.toml
Normal file
14
crates/languages/src/css/config.toml
Normal file
|
@ -0,0 +1,14 @@
|
|||
name = "CSS"
|
||||
grammar = "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"
|
78
crates/languages/src/css/highlights.scm
Normal file
78
crates/languages/src/css/highlights.scm
Normal 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
|
1
crates/languages/src/css/indents.scm
Normal file
1
crates/languages/src/css/indents.scm
Normal file
|
@ -0,0 +1 @@
|
|||
(_ "{" "}" @end) @indent
|
2
crates/languages/src/css/overrides.scm
Normal file
2
crates/languages/src/css/overrides.scm
Normal file
|
@ -0,0 +1,2 @@
|
|||
(comment) @comment
|
||||
(string_value) @string
|
54
crates/languages/src/dart.rs
Normal file
54
crates/languages/src/dart.rs
Normal file
|
@ -0,0 +1,54 @@
|
|||
use anyhow::{anyhow, Result};
|
||||
use async_trait::async_trait;
|
||||
use language::{LanguageServerName, LspAdapter, LspAdapterDelegate};
|
||||
use lsp::LanguageServerBinary;
|
||||
use std::{any::Any, path::PathBuf};
|
||||
|
||||
pub struct DartLanguageServer;
|
||||
|
||||
#[async_trait]
|
||||
impl LspAdapter for DartLanguageServer {
|
||||
fn name(&self) -> LanguageServerName {
|
||||
LanguageServerName("dart".into())
|
||||
}
|
||||
|
||||
fn short_name(&self) -> &'static str {
|
||||
"dart"
|
||||
}
|
||||
|
||||
async fn fetch_latest_server_version(
|
||||
&self,
|
||||
_: &dyn LspAdapterDelegate,
|
||||
) -> Result<Box<dyn 'static + Send + Any>> {
|
||||
Ok(Box::new(()))
|
||||
}
|
||||
|
||||
async fn fetch_server_binary(
|
||||
&self,
|
||||
_: Box<dyn 'static + Send + Any>,
|
||||
_: PathBuf,
|
||||
_: &dyn LspAdapterDelegate,
|
||||
) -> Result<LanguageServerBinary> {
|
||||
Err(anyhow!("dart must me installed from dart.dev/get-dart"))
|
||||
}
|
||||
|
||||
async fn cached_server_binary(
|
||||
&self,
|
||||
_: PathBuf,
|
||||
_: &dyn LspAdapterDelegate,
|
||||
) -> Option<LanguageServerBinary> {
|
||||
Some(LanguageServerBinary {
|
||||
path: "dart".into(),
|
||||
env: None,
|
||||
arguments: vec!["language-server".into(), "--protocol=lsp".into()],
|
||||
})
|
||||
}
|
||||
|
||||
fn can_be_reinstalled(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
async fn installation_test_binary(&self, _: PathBuf) -> Option<LanguageServerBinary> {
|
||||
None
|
||||
}
|
||||
}
|
6
crates/languages/src/dart/brackets.scm
Normal file
6
crates/languages/src/dart/brackets.scm
Normal file
|
@ -0,0 +1,6 @@
|
|||
("(" @open ")" @close)
|
||||
("[" @open "]" @close)
|
||||
("{" @open "}" @close)
|
||||
("<" @open ">" @close)
|
||||
("\"" @open "\"" @close)
|
||||
("'" @open "'" @close)
|
13
crates/languages/src/dart/config.toml
Normal file
13
crates/languages/src/dart/config.toml
Normal file
|
@ -0,0 +1,13 @@
|
|||
name = "Dart"
|
||||
grammar = "dart"
|
||||
path_suffixes = ["dart"]
|
||||
line_comments = ["// "]
|
||||
autoclose_before = ";:.,=}])>"
|
||||
brackets = [
|
||||
{ start = "{", end = "}", close = true, newline = true },
|
||||
{ start = "[", end = "]", close = true, newline = true },
|
||||
{ start = "(", end = ")", close = true, newline = true },
|
||||
{ start = "\"", end = "\"", close = true, newline = false, not_in = ["string"] },
|
||||
{ start = "'", end = "'", close = true, newline = false, not_in = ["string"] },
|
||||
{ start = "/*", end = " */", close = true, newline = false, not_in = ["string", "comment"] },
|
||||
]
|
209
crates/languages/src/dart/highlights.scm
Normal file
209
crates/languages/src/dart/highlights.scm
Normal file
|
@ -0,0 +1,209 @@
|
|||
(dotted_identifier_list) @string
|
||||
|
||||
; Methods
|
||||
; --------------------
|
||||
(function_type
|
||||
name: (identifier) @function)
|
||||
(super) @function
|
||||
|
||||
; Annotations
|
||||
; --------------------
|
||||
(annotation
|
||||
name: (identifier) @attribute)
|
||||
|
||||
; Operators and Tokens
|
||||
; --------------------
|
||||
(template_substitution
|
||||
"$" @punctuation.special
|
||||
"{" @punctuation.special
|
||||
"}" @punctuation.special
|
||||
) @none
|
||||
|
||||
(template_substitution
|
||||
"$" @punctuation.special
|
||||
(identifier_dollar_escaped) @variable
|
||||
) @none
|
||||
|
||||
(escape_sequence) @string.escape
|
||||
|
||||
[
|
||||
"@"
|
||||
"=>"
|
||||
".."
|
||||
"??"
|
||||
"=="
|
||||
"?"
|
||||
":"
|
||||
"&&"
|
||||
"%"
|
||||
"<"
|
||||
">"
|
||||
"="
|
||||
">="
|
||||
"<="
|
||||
"||"
|
||||
(increment_operator)
|
||||
(is_operator)
|
||||
(prefix_operator)
|
||||
(equality_operator)
|
||||
(additive_operator)
|
||||
] @operator
|
||||
|
||||
[
|
||||
"("
|
||||
")"
|
||||
"["
|
||||
"]"
|
||||
"{"
|
||||
"}"
|
||||
"<"
|
||||
">"
|
||||
] @punctuation.bracket
|
||||
|
||||
; Delimiters
|
||||
; --------------------
|
||||
[
|
||||
";"
|
||||
"."
|
||||
","
|
||||
] @punctuation.delimiter
|
||||
|
||||
; Types
|
||||
; --------------------
|
||||
(class_definition
|
||||
name: (identifier) @type)
|
||||
(constructor_signature
|
||||
name: (identifier) @type)
|
||||
(scoped_identifier
|
||||
scope: (identifier) @type)
|
||||
(function_signature
|
||||
name: (identifier) @function)
|
||||
(getter_signature
|
||||
(identifier) @function)
|
||||
(setter_signature
|
||||
name: (identifier) @function)
|
||||
(enum_declaration
|
||||
name: (identifier) @type)
|
||||
(enum_constant
|
||||
name: (identifier) @type)
|
||||
(type_identifier) @type
|
||||
(void_type) @type
|
||||
|
||||
((scoped_identifier
|
||||
scope: (identifier) @type
|
||||
name: (identifier) @type)
|
||||
(#match? @type "^[a-zA-Z]"))
|
||||
|
||||
(type_identifier) @type
|
||||
|
||||
; Variables
|
||||
; --------------------
|
||||
; var keyword
|
||||
(inferred_type) @keyword
|
||||
|
||||
(const_builtin) @constant.builtin
|
||||
(final_builtin) @constant.builtin
|
||||
|
||||
((identifier) @type
|
||||
(#match? @type "^_?[A-Z]"))
|
||||
|
||||
("Function" @type)
|
||||
|
||||
; properties
|
||||
; TODO: add method/call_expression to grammar and
|
||||
; distinguish method call from variable access
|
||||
(unconditional_assignable_selector
|
||||
(identifier) @property)
|
||||
|
||||
; assignments
|
||||
(assignment_expression
|
||||
left: (assignable_expression) @variable)
|
||||
|
||||
(this) @variable.builtin
|
||||
|
||||
; Literals
|
||||
; --------------------
|
||||
[
|
||||
(hex_integer_literal)
|
||||
(decimal_integer_literal)
|
||||
(decimal_floating_point_literal)
|
||||
; TODO: inaccessible nodes
|
||||
; (octal_integer_literal)
|
||||
; (hex_floating_point_literal)
|
||||
] @number
|
||||
|
||||
(symbol_literal) @symbol
|
||||
(string_literal) @string
|
||||
(true) @boolean
|
||||
(false) @boolean
|
||||
(null_literal) @constant.builtin
|
||||
|
||||
(documentation_comment) @comment
|
||||
(comment) @comment
|
||||
|
||||
; Keywords
|
||||
; --------------------
|
||||
["import" "library" "export"] @keyword.include
|
||||
|
||||
; Reserved words (cannot be used as identifiers)
|
||||
; TODO: "rethrow" @keyword
|
||||
[
|
||||
; "assert"
|
||||
(case_builtin)
|
||||
"extension"
|
||||
"on"
|
||||
"class"
|
||||
"enum"
|
||||
"extends"
|
||||
"in"
|
||||
"is"
|
||||
"new"
|
||||
"return"
|
||||
"super"
|
||||
"with"
|
||||
] @keyword
|
||||
|
||||
|
||||
; Built in identifiers:
|
||||
; alone these are marked as keywords
|
||||
[
|
||||
"abstract"
|
||||
"as"
|
||||
"async"
|
||||
"async*"
|
||||
"yield"
|
||||
"sync*"
|
||||
"await"
|
||||
"covariant"
|
||||
"deferred"
|
||||
"dynamic"
|
||||
"external"
|
||||
"factory"
|
||||
"get"
|
||||
"implements"
|
||||
"interface"
|
||||
"library"
|
||||
"operator"
|
||||
"mixin"
|
||||
"part"
|
||||
"set"
|
||||
"show"
|
||||
"static"
|
||||
"typedef"
|
||||
] @keyword
|
||||
|
||||
; when used as an identifier:
|
||||
((identifier) @variable.builtin
|
||||
(#vim-match? @variable.builtin "^(abstract|as|covariant|deferred|dynamic|export|external|factory|Function|get|implements|import|interface|library|operator|mixin|part|set|static|typedef)$"))
|
||||
|
||||
["if" "else" "switch" "default"] @keyword
|
||||
|
||||
[
|
||||
"try"
|
||||
"throw"
|
||||
"catch"
|
||||
"finally"
|
||||
(break_statement)
|
||||
] @keyword
|
||||
|
||||
["do" "while" "continue" "for"] @keyword
|
7
crates/languages/src/dart/indents.scm
Normal file
7
crates/languages/src/dart/indents.scm
Normal file
|
@ -0,0 +1,7 @@
|
|||
[
|
||||
(if_statement)
|
||||
(for_statement)
|
||||
] @indent
|
||||
|
||||
(_ "{" "}" @end) @indent
|
||||
(_ "(" ")" @end) @indent
|
18
crates/languages/src/dart/outline.scm
Normal file
18
crates/languages/src/dart/outline.scm
Normal file
|
@ -0,0 +1,18 @@
|
|||
(class_definition
|
||||
"class" @context
|
||||
name: (_) @name) @item
|
||||
|
||||
(function_signature
|
||||
name: (_) @name) @item
|
||||
|
||||
(getter_signature
|
||||
"get" @context
|
||||
name: (_) @name) @item
|
||||
|
||||
(setter_signature
|
||||
"set" @context
|
||||
name: (_) @name) @item
|
||||
|
||||
(enum_declaration
|
||||
"enum" @context
|
||||
name: (_) @name) @item
|
236
crates/languages/src/deno.rs
Normal file
236
crates/languages/src/deno.rs
Normal file
|
@ -0,0 +1,236 @@
|
|||
use anyhow::{anyhow, bail, Context, Result};
|
||||
use async_trait::async_trait;
|
||||
use collections::HashMap;
|
||||
use futures::StreamExt;
|
||||
use language::{LanguageServerName, LspAdapter, LspAdapterDelegate};
|
||||
use lsp::{CodeActionKind, LanguageServerBinary};
|
||||
use schemars::JsonSchema;
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use serde_json::json;
|
||||
use settings::Settings;
|
||||
use smol::{fs, fs::File};
|
||||
use std::{any::Any, env::consts, ffi::OsString, path::PathBuf, sync::Arc};
|
||||
use util::{
|
||||
async_maybe,
|
||||
fs::remove_matching,
|
||||
github::{latest_github_release, GitHubLspBinaryVersion},
|
||||
ResultExt,
|
||||
};
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize, JsonSchema)]
|
||||
pub struct DenoSettings {
|
||||
pub enable: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize, Default, Deserialize, JsonSchema)]
|
||||
pub struct DenoSettingsContent {
|
||||
enable: Option<bool>,
|
||||
}
|
||||
|
||||
impl Settings for DenoSettings {
|
||||
const KEY: Option<&'static str> = Some("deno");
|
||||
|
||||
type FileContent = DenoSettingsContent;
|
||||
|
||||
fn load(
|
||||
default_value: &Self::FileContent,
|
||||
user_values: &[&Self::FileContent],
|
||||
_: &mut gpui::AppContext,
|
||||
) -> Result<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
Self::load_via_json_merge(default_value, user_values)
|
||||
}
|
||||
}
|
||||
|
||||
fn deno_server_binary_arguments() -> Vec<OsString> {
|
||||
vec!["lsp".into()]
|
||||
}
|
||||
|
||||
pub struct DenoLspAdapter {}
|
||||
|
||||
impl DenoLspAdapter {
|
||||
pub fn new() -> Self {
|
||||
DenoLspAdapter {}
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl LspAdapter for DenoLspAdapter {
|
||||
fn name(&self) -> LanguageServerName {
|
||||
LanguageServerName("deno-language-server".into())
|
||||
}
|
||||
|
||||
fn short_name(&self) -> &'static str {
|
||||
"deno-ts"
|
||||
}
|
||||
|
||||
async fn fetch_latest_server_version(
|
||||
&self,
|
||||
delegate: &dyn LspAdapterDelegate,
|
||||
) -> Result<Box<dyn 'static + Send + Any>> {
|
||||
let release =
|
||||
latest_github_release("denoland/deno", true, false, delegate.http_client()).await?;
|
||||
let os = match consts::OS {
|
||||
"macos" => "apple-darwin",
|
||||
"linux" => "unknown-linux-gnu",
|
||||
"windows" => "pc-windows-msvc",
|
||||
other => bail!("Running on unsupported os: {other}"),
|
||||
};
|
||||
let asset_name = format!("deno-{}-{os}.zip", consts::ARCH);
|
||||
let asset = release
|
||||
.assets
|
||||
.iter()
|
||||
.find(|asset| asset.name == asset_name)
|
||||
.ok_or_else(|| anyhow!("no asset found matching {:?}", asset_name))?;
|
||||
let version = GitHubLspBinaryVersion {
|
||||
name: release.tag_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!("deno_{}.zip", version.name));
|
||||
let version_dir = container_dir.join(format!("deno_{}", version.name));
|
||||
let binary_path = version_dir.join("deno");
|
||||
|
||||
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)
|
||||
.arg("-d")
|
||||
.arg(&version_dir)
|
||||
.output()
|
||||
.await?
|
||||
.status;
|
||||
if !unzip_status.success() {
|
||||
Err(anyhow!("failed to unzip deno archive"))?;
|
||||
}
|
||||
|
||||
remove_matching(&container_dir, |entry| entry != version_dir).await;
|
||||
}
|
||||
|
||||
Ok(LanguageServerBinary {
|
||||
path: binary_path,
|
||||
env: None,
|
||||
arguments: deno_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
|
||||
}
|
||||
|
||||
fn code_action_kinds(&self) -> Option<Vec<CodeActionKind>> {
|
||||
Some(vec![
|
||||
CodeActionKind::QUICKFIX,
|
||||
CodeActionKind::REFACTOR,
|
||||
CodeActionKind::REFACTOR_EXTRACT,
|
||||
CodeActionKind::SOURCE,
|
||||
])
|
||||
}
|
||||
|
||||
async fn label_for_completion(
|
||||
&self,
|
||||
item: &lsp::CompletionItem,
|
||||
language: &Arc<language::Language>,
|
||||
) -> Option<language::CodeLabel> {
|
||||
use lsp::CompletionItemKind as Kind;
|
||||
let len = item.label.len();
|
||||
let grammar = language.grammar()?;
|
||||
let highlight_id = match item.kind? {
|
||||
Kind::CLASS | Kind::INTERFACE => grammar.highlight_id_for_name("type"),
|
||||
Kind::CONSTRUCTOR => grammar.highlight_id_for_name("type"),
|
||||
Kind::CONSTANT => grammar.highlight_id_for_name("constant"),
|
||||
Kind::FUNCTION | Kind::METHOD => grammar.highlight_id_for_name("function"),
|
||||
Kind::PROPERTY | Kind::FIELD => grammar.highlight_id_for_name("property"),
|
||||
_ => None,
|
||||
}?;
|
||||
|
||||
let text = match &item.detail {
|
||||
Some(detail) => format!("{} {}", item.label, detail),
|
||||
None => item.label.clone(),
|
||||
};
|
||||
|
||||
Some(language::CodeLabel {
|
||||
text,
|
||||
runs: vec![(0..len, highlight_id)],
|
||||
filter_range: 0..len,
|
||||
})
|
||||
}
|
||||
|
||||
fn initialization_options(&self) -> Option<serde_json::Value> {
|
||||
Some(json!({
|
||||
"provideFormatter": true,
|
||||
}))
|
||||
}
|
||||
|
||||
fn language_ids(&self) -> HashMap<String, String> {
|
||||
HashMap::from_iter([
|
||||
("TypeScript".into(), "typescript".into()),
|
||||
("JavaScript".into(), "javascript".into()),
|
||||
("TSX".into(), "typescriptreact".into()),
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
async fn get_cached_server_binary(container_dir: PathBuf) -> Option<LanguageServerBinary> {
|
||||
async_maybe!({
|
||||
let mut last = None;
|
||||
let mut entries = fs::read_dir(&container_dir).await?;
|
||||
while let Some(entry) = entries.next().await {
|
||||
last = Some(entry?.path());
|
||||
}
|
||||
|
||||
match last {
|
||||
Some(path) if path.is_dir() => {
|
||||
let binary = path.join("deno");
|
||||
if fs::metadata(&binary).await.is_ok() {
|
||||
return Ok(LanguageServerBinary {
|
||||
path: binary,
|
||||
env: None,
|
||||
arguments: deno_server_binary_arguments(),
|
||||
});
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
Err(anyhow!("no cached binary"))
|
||||
})
|
||||
.await
|
||||
.log_err()
|
||||
}
|
126
crates/languages/src/dockerfile.rs
Normal file
126
crates/languages/src/dockerfile.rs
Normal file
|
@ -0,0 +1,126 @@
|
|||
use anyhow::{anyhow, Result};
|
||||
use async_trait::async_trait;
|
||||
use futures::StreamExt;
|
||||
use language::{LanguageServerName, LspAdapter, LspAdapterDelegate};
|
||||
use lsp::LanguageServerBinary;
|
||||
use node_runtime::NodeRuntime;
|
||||
use smol::fs;
|
||||
use std::{
|
||||
any::Any,
|
||||
ffi::OsString,
|
||||
path::{Path, PathBuf},
|
||||
sync::Arc,
|
||||
};
|
||||
use util::{async_maybe, ResultExt};
|
||||
|
||||
const SERVER_PATH: &'static str =
|
||||
"node_modules/dockerfile-language-server-nodejs/bin/docker-langserver";
|
||||
|
||||
fn server_binary_arguments(server_path: &Path) -> Vec<OsString> {
|
||||
vec![server_path.into(), "--stdio".into()]
|
||||
}
|
||||
|
||||
pub struct DockerfileLspAdapter {
|
||||
node: Arc<dyn NodeRuntime>,
|
||||
}
|
||||
|
||||
impl DockerfileLspAdapter {
|
||||
pub fn new(node: Arc<dyn NodeRuntime>) -> Self {
|
||||
Self { node }
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl LspAdapter for DockerfileLspAdapter {
|
||||
fn name(&self) -> LanguageServerName {
|
||||
LanguageServerName("docker-langserver".into())
|
||||
}
|
||||
|
||||
fn short_name(&self) -> &'static str {
|
||||
"dockerfile"
|
||||
}
|
||||
|
||||
async fn fetch_latest_server_version(
|
||||
&self,
|
||||
_: &dyn LspAdapterDelegate,
|
||||
) -> Result<Box<dyn 'static + Send + Any>> {
|
||||
Ok(Box::new(
|
||||
self.node
|
||||
.npm_package_latest_version("dockerfile-language-server-nodejs")
|
||||
.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,
|
||||
&[("dockerfile-language-server-nodejs", version.as_str())],
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
|
||||
Ok(LanguageServerBinary {
|
||||
path: self.node.binary_path().await?,
|
||||
env: None,
|
||||
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 get_cached_server_binary(
|
||||
container_dir: PathBuf,
|
||||
node: &dyn NodeRuntime,
|
||||
) -> Option<LanguageServerBinary> {
|
||||
async_maybe!({
|
||||
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?,
|
||||
env: None,
|
||||
arguments: server_binary_arguments(&server_path),
|
||||
})
|
||||
} else {
|
||||
Err(anyhow!(
|
||||
"missing executable in directory {:?}",
|
||||
last_version_dir
|
||||
))
|
||||
}
|
||||
})
|
||||
.await
|
||||
.log_err()
|
||||
}
|
9
crates/languages/src/dockerfile/config.toml
Normal file
9
crates/languages/src/dockerfile/config.toml
Normal file
|
@ -0,0 +1,9 @@
|
|||
name = "Dockerfile"
|
||||
grammar = "dockerfile"
|
||||
path_suffixes = ["Dockerfile"]
|
||||
line_comments = ["# "]
|
||||
brackets = [
|
||||
{ start = "{", end = "}", close = true, newline = true },
|
||||
{ start = "[", end = "]", close = true, newline = true },
|
||||
{ start = "\"", end = "\"", close = true, newline = false, not_in = ["string"] },
|
||||
]
|
63
crates/languages/src/dockerfile/highlights.scm
Normal file
63
crates/languages/src/dockerfile/highlights.scm
Normal file
|
@ -0,0 +1,63 @@
|
|||
; Dockerfile instructions set taken from:
|
||||
; https://docs.docker.com/engine/reference/builder/#overview
|
||||
; https://github.com/helix-editor/helix/blob/78c34194b5c83beb26ca04f12bf9d53fd5aba801/runtime/queries/dockerfile/highlights.scm
|
||||
[
|
||||
"ADD"
|
||||
"ARG"
|
||||
"CMD"
|
||||
"COPY"
|
||||
"ENTRYPOINT"
|
||||
"ENV"
|
||||
"EXPOSE"
|
||||
"FROM"
|
||||
"HEALTHCHECK"
|
||||
"LABEL"
|
||||
"MAINTAINER"
|
||||
"ONBUILD"
|
||||
"RUN"
|
||||
"SHELL"
|
||||
"STOPSIGNAL"
|
||||
"USER"
|
||||
"VOLUME"
|
||||
"WORKDIR"
|
||||
|
||||
; "as" for multi-stage builds
|
||||
"AS"
|
||||
] @keyword
|
||||
|
||||
[
|
||||
":"
|
||||
"@"
|
||||
] @operator
|
||||
|
||||
(comment) @comment
|
||||
|
||||
(image_spec
|
||||
(image_tag
|
||||
":" @punctuation.special)
|
||||
(image_digest
|
||||
"@" @punctuation.special))
|
||||
|
||||
[
|
||||
(double_quoted_string)
|
||||
(single_quoted_string)
|
||||
(json_string)
|
||||
] @string
|
||||
|
||||
[
|
||||
(env_pair)
|
||||
(label_pair)
|
||||
] @constant
|
||||
|
||||
[
|
||||
(param)
|
||||
(mount_param)
|
||||
] @function
|
||||
|
||||
(expansion
|
||||
[
|
||||
"$"
|
||||
"{"
|
||||
"}"
|
||||
] @punctuation.special
|
||||
) @constant
|
6
crates/languages/src/dockerfile/injections.scm
Normal file
6
crates/languages/src/dockerfile/injections.scm
Normal file
|
@ -0,0 +1,6 @@
|
|||
; We need impl this
|
||||
; ((comment) @injection.content
|
||||
; (#set! injection.language "comment"))
|
||||
|
||||
((shell_command) @content
|
||||
(#set! "language" "bash"))
|
549
crates/languages/src/elixir.rs
Normal file
549
crates/languages/src/elixir.rs
Normal file
|
@ -0,0 +1,549 @@
|
|||
use anyhow::{anyhow, bail, Context, Result};
|
||||
use async_trait::async_trait;
|
||||
use futures::StreamExt;
|
||||
use gpui::{AsyncAppContext, Task};
|
||||
pub use language::*;
|
||||
use lsp::{CompletionItemKind, LanguageServerBinary, SymbolKind};
|
||||
use schemars::JsonSchema;
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use settings::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 gpui::AppContext,
|
||||
) -> Result<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
Self::load_via_json_merge(default_value, user_values)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ElixirLspAdapter;
|
||||
|
||||
#[async_trait]
|
||||
impl LspAdapter for ElixirLspAdapter {
|
||||
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", true, false, http).await?;
|
||||
|
||||
let asset_name = format!("elixir-ls-{}.zip", &release.tag_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.tag_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 zip_path = container_dir.join(format!("elixir-ls_{}.zip", version.name));
|
||||
let folder_path = container_dir.join("elixir-ls");
|
||||
let binary_path = folder_path.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(&folder_path)
|
||||
.await
|
||||
.with_context(|| format!("failed to create directory {}", folder_path.display()))?;
|
||||
let unzip_status = smol::process::Command::new("unzip")
|
||||
.arg(&zip_path)
|
||||
.arg("-d")
|
||||
.arg(&folder_path)
|
||||
.output()
|
||||
.await?
|
||||
.status;
|
||||
if !unzip_status.success() {
|
||||
Err(anyhow!("failed to unzip elixir-ls archive"))?;
|
||||
}
|
||||
|
||||
remove_matching(&container_dir, |entry| entry != folder_path).await;
|
||||
}
|
||||
|
||||
Ok(LanguageServerBinary {
|
||||
path: binary_path,
|
||||
env: None,
|
||||
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: &lsp::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> {
|
||||
let server_path = container_dir.join("elixir-ls/language_server.sh");
|
||||
if server_path.exists() {
|
||||
Some(LanguageServerBinary {
|
||||
path: server_path,
|
||||
env: None,
|
||||
arguments: vec![],
|
||||
})
|
||||
} else {
|
||||
log::error!("missing executable in directory {:?}", server_path);
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub struct NextLspAdapter;
|
||||
|
||||
#[async_trait]
|
||||
impl LspAdapter for NextLspAdapter {
|
||||
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 platform = match consts::ARCH {
|
||||
"x86_64" => "darwin_amd64",
|
||||
"aarch64" => "darwin_arm64",
|
||||
other => bail!("Running on unsupported platform: {other}"),
|
||||
};
|
||||
let release =
|
||||
latest_github_release("elixir-tools/next-ls", true, false, delegate.http_client())
|
||||
.await?;
|
||||
let version = release.tag_name;
|
||||
let asset_name = format!("next_ls_{platform}");
|
||||
let asset = release
|
||||
.assets
|
||||
.iter()
|
||||
.find(|asset| asset.name == asset_name)
|
||||
.with_context(|| format!("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?;
|
||||
|
||||
// todo!("windows")
|
||||
#[cfg(not(windows))]
|
||||
{
|
||||
fs::set_permissions(
|
||||
&binary_path,
|
||||
<fs::Permissions as fs::unix::PermissionsExt>::from_mode(0o755),
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(LanguageServerBinary {
|
||||
path: binary_path,
|
||||
env: None,
|
||||
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: &lsp::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,
|
||||
env: None,
|
||||
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 {
|
||||
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()),
|
||||
env: None,
|
||||
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()),
|
||||
env: None,
|
||||
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()),
|
||||
env: None,
|
||||
arguments: self.arguments.iter().map(|arg| arg.into()).collect(),
|
||||
})
|
||||
}
|
||||
|
||||
async fn label_for_completion(
|
||||
&self,
|
||||
completion: &lsp::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: &lsp::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(),
|
||||
})
|
||||
}
|
5
crates/languages/src/elixir/brackets.scm
Normal file
5
crates/languages/src/elixir/brackets.scm
Normal file
|
@ -0,0 +1,5 @@
|
|||
("(" @open ")" @close)
|
||||
("[" @open "]" @close)
|
||||
("{" @open "}" @close)
|
||||
("\"" @open "\"" @close)
|
||||
("do" @open "end" @close)
|
17
crates/languages/src/elixir/config.toml
Normal file
17
crates/languages/src/elixir/config.toml
Normal file
|
@ -0,0 +1,17 @@
|
|||
name = "Elixir"
|
||||
grammar = "elixir"
|
||||
path_suffixes = ["ex", "exs"]
|
||||
line_comments = ["# "]
|
||||
autoclose_before = ";:.,=}])>"
|
||||
brackets = [
|
||||
{ start = "{", end = "}", close = true, newline = true },
|
||||
{ start = "[", end = "]", close = true, newline = true },
|
||||
{ start = "(", end = ")", close = true, newline = true },
|
||||
{ start = "\"", end = "\"", close = true, newline = false, not_in = ["string", "comment"] },
|
||||
{ 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"]
|
27
crates/languages/src/elixir/embedding.scm
Normal file
27
crates/languages/src/elixir/embedding.scm
Normal 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
|
153
crates/languages/src/elixir/highlights.scm
Normal file
153
crates/languages/src/elixir/highlights.scm
Normal 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"))
|
6
crates/languages/src/elixir/indents.scm
Normal file
6
crates/languages/src/elixir/indents.scm
Normal file
|
@ -0,0 +1,6 @@
|
|||
(call) @indent
|
||||
|
||||
(_ "[" "]" @end) @indent
|
||||
(_ "{" "}" @end) @indent
|
||||
(_ "(" ")" @end) @indent
|
||||
(_ "do" "end" @end) @indent
|
7
crates/languages/src/elixir/injections.scm
Normal file
7
crates/languages/src/elixir/injections.scm
Normal file
|
@ -0,0 +1,7 @@
|
|||
; Phoenix HTML template
|
||||
|
||||
((sigil
|
||||
(sigil_name) @_sigil_name
|
||||
(quoted_content) @content)
|
||||
(#eq? @_sigil_name "H")
|
||||
(#set! language "heex"))
|
26
crates/languages/src/elixir/outline.scm
Normal file
26
crates/languages/src/elixir/outline.scm
Normal 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
|
2
crates/languages/src/elixir/overrides.scm
Normal file
2
crates/languages/src/elixir/overrides.scm
Normal file
|
@ -0,0 +1,2 @@
|
|||
(comment) @comment
|
||||
[(string) (charlist)] @string
|
150
crates/languages/src/elm.rs
Normal file
150
crates/languages/src/elm.rs
Normal file
|
@ -0,0 +1,150 @@
|
|||
use anyhow::{anyhow, Result};
|
||||
use async_trait::async_trait;
|
||||
use futures::StreamExt;
|
||||
use gpui::AppContext;
|
||||
use language::{LanguageServerName, LspAdapter, LspAdapterDelegate};
|
||||
use lsp::LanguageServerBinary;
|
||||
use node_runtime::NodeRuntime;
|
||||
use project::project_settings::ProjectSettings;
|
||||
use serde_json::Value;
|
||||
use settings::Settings;
|
||||
use smol::fs;
|
||||
use std::{
|
||||
any::Any,
|
||||
ffi::OsString,
|
||||
path::{Path, PathBuf},
|
||||
sync::Arc,
|
||||
};
|
||||
use util::ResultExt;
|
||||
|
||||
const SERVER_NAME: &'static str = "elm-language-server";
|
||||
const SERVER_PATH: &'static str = "node_modules/@elm-tooling/elm-language-server/out/node/index.js";
|
||||
|
||||
fn server_binary_arguments(server_path: &Path) -> Vec<OsString> {
|
||||
vec![server_path.into(), "--stdio".into()]
|
||||
}
|
||||
|
||||
pub struct ElmLspAdapter {
|
||||
node: Arc<dyn NodeRuntime>,
|
||||
}
|
||||
|
||||
impl ElmLspAdapter {
|
||||
pub fn new(node: Arc<dyn NodeRuntime>) -> Self {
|
||||
ElmLspAdapter { node }
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl LspAdapter for ElmLspAdapter {
|
||||
fn name(&self) -> LanguageServerName {
|
||||
LanguageServerName(SERVER_NAME.into())
|
||||
}
|
||||
|
||||
fn short_name(&self) -> &'static str {
|
||||
"elmLS"
|
||||
}
|
||||
|
||||
async fn fetch_latest_server_version(
|
||||
&self,
|
||||
_: &dyn LspAdapterDelegate,
|
||||
) -> Result<Box<dyn 'static + Any + Send>> {
|
||||
Ok(Box::new(
|
||||
self.node
|
||||
.npm_package_latest_version("@elm-tooling/elm-language-server")
|
||||
.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,
|
||||
&[("@elm-tooling/elm-language-server", version.as_str())],
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
|
||||
Ok(LanguageServerBinary {
|
||||
path: self.node.binary_path().await?,
|
||||
env: None,
|
||||
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
|
||||
}
|
||||
|
||||
fn workspace_configuration(&self, _workspace_root: &Path, cx: &mut AppContext) -> Value {
|
||||
// elm-language-server expects workspace didChangeConfiguration notification
|
||||
// params to be the same as lsp initialization_options
|
||||
let override_options = ProjectSettings::get_global(cx)
|
||||
.lsp
|
||||
.get(SERVER_NAME)
|
||||
.and_then(|s| s.initialization_options.clone())
|
||||
.unwrap_or_default();
|
||||
|
||||
match override_options.clone().as_object_mut() {
|
||||
Some(op) => {
|
||||
// elm-language-server requests workspace configuration
|
||||
// for the `elmLS` section, so we have to nest
|
||||
// another copy of initialization_options there
|
||||
op.insert("elmLS".into(), override_options);
|
||||
serde_json::to_value(op).unwrap_or_default()
|
||||
}
|
||||
None => override_options,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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?,
|
||||
env: None,
|
||||
arguments: server_binary_arguments(&server_path),
|
||||
})
|
||||
} else {
|
||||
Err(anyhow!(
|
||||
"missing executable in directory {:?}",
|
||||
last_version_dir
|
||||
))
|
||||
}
|
||||
})()
|
||||
.await
|
||||
.log_err()
|
||||
}
|
12
crates/languages/src/elm/config.toml
Normal file
12
crates/languages/src/elm/config.toml
Normal file
|
@ -0,0 +1,12 @@
|
|||
name = "Elm"
|
||||
grammar = "elm"
|
||||
path_suffixes = ["elm"]
|
||||
line_comments = ["-- "]
|
||||
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"] },
|
||||
]
|
72
crates/languages/src/elm/highlights.scm
Normal file
72
crates/languages/src/elm/highlights.scm
Normal 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
|
2
crates/languages/src/elm/injections.scm
Normal file
2
crates/languages/src/elm/injections.scm
Normal file
|
@ -0,0 +1,2 @@
|
|||
((glsl_content) @content
|
||||
(#set! "language" "glsl"))
|
22
crates/languages/src/elm/outline.scm
Normal file
22
crates/languages/src/elm/outline.scm
Normal 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
|
9
crates/languages/src/erb/config.toml
Normal file
9
crates/languages/src/erb/config.toml
Normal file
|
@ -0,0 +1,9 @@
|
|||
name = "ERB"
|
||||
grammar = "embedded_template"
|
||||
path_suffixes = ["erb"]
|
||||
autoclose_before = ">})"
|
||||
brackets = [
|
||||
{ start = "<", end = ">", close = true, newline = true },
|
||||
]
|
||||
block_comment = ["<%#", "%>"]
|
||||
scope_opt_in_language_servers = ["tailwindcss-language-server"]
|
12
crates/languages/src/erb/highlights.scm
Normal file
12
crates/languages/src/erb/highlights.scm
Normal file
|
@ -0,0 +1,12 @@
|
|||
(comment_directive) @comment
|
||||
|
||||
[
|
||||
"<%#"
|
||||
"<%"
|
||||
"<%="
|
||||
"<%_"
|
||||
"<%-"
|
||||
"%>"
|
||||
"-%>"
|
||||
"_%>"
|
||||
] @keyword
|
7
crates/languages/src/erb/injections.scm
Normal file
7
crates/languages/src/erb/injections.scm
Normal file
|
@ -0,0 +1,7 @@
|
|||
((code) @content
|
||||
(#set! "language" "ruby")
|
||||
(#set! "combined"))
|
||||
|
||||
((content) @content
|
||||
(#set! "language" "html")
|
||||
(#set! "combined"))
|
60
crates/languages/src/erlang.rs
Normal file
60
crates/languages/src/erlang.rs
Normal file
|
@ -0,0 +1,60 @@
|
|||
use anyhow::{anyhow, Result};
|
||||
use async_trait::async_trait;
|
||||
use language::{LanguageServerName, LspAdapter, LspAdapterDelegate};
|
||||
use lsp::LanguageServerBinary;
|
||||
use std::{any::Any, path::PathBuf};
|
||||
|
||||
pub struct ErlangLspAdapter;
|
||||
|
||||
#[async_trait]
|
||||
impl LspAdapter for ErlangLspAdapter {
|
||||
fn name(&self) -> LanguageServerName {
|
||||
LanguageServerName("erlang_ls".into())
|
||||
}
|
||||
|
||||
fn short_name(&self) -> &'static str {
|
||||
"erlang_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,
|
||||
_version: Box<dyn 'static + Send + Any>,
|
||||
_container_dir: PathBuf,
|
||||
_: &dyn LspAdapterDelegate,
|
||||
) -> Result<LanguageServerBinary> {
|
||||
Err(anyhow!(
|
||||
"erlang_ls must be installed and available in your $PATH"
|
||||
))
|
||||
}
|
||||
|
||||
async fn cached_server_binary(
|
||||
&self,
|
||||
_: PathBuf,
|
||||
_: &dyn LspAdapterDelegate,
|
||||
) -> Option<LanguageServerBinary> {
|
||||
Some(LanguageServerBinary {
|
||||
path: "erlang_ls".into(),
|
||||
env: None,
|
||||
arguments: vec![],
|
||||
})
|
||||
}
|
||||
|
||||
fn can_be_reinstalled(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
async fn installation_test_binary(&self, _: PathBuf) -> Option<LanguageServerBinary> {
|
||||
Some(LanguageServerBinary {
|
||||
path: "erlang_ls".into(),
|
||||
env: None,
|
||||
arguments: vec!["--version".into()],
|
||||
})
|
||||
}
|
||||
}
|
3
crates/languages/src/erlang/brackets.scm
Normal file
3
crates/languages/src/erlang/brackets.scm
Normal file
|
@ -0,0 +1,3 @@
|
|||
("(" @open ")" @close)
|
||||
("[" @open "]" @close)
|
||||
("{" @open "}" @close)
|
24
crates/languages/src/erlang/config.toml
Normal file
24
crates/languages/src/erlang/config.toml
Normal file
|
@ -0,0 +1,24 @@
|
|||
name = "Erlang"
|
||||
grammar = "erlang"
|
||||
# TODO: support parsing rebar.config files
|
||||
# # https://github.com/WhatsApp/tree-sitter-erlang/issues/3
|
||||
path_suffixes = ["erl", "hrl", "app.src", "escript", "xrl", "yrl", "Emakefile", "rebar.config"]
|
||||
line_comments = ["% ", "%% ", "%%% "]
|
||||
autoclose_before = ";:.,=}])>"
|
||||
brackets = [
|
||||
{ start = "{", end = "}", close = true, newline = true },
|
||||
{ start = "[", end = "]", close = true, newline = true },
|
||||
{ start = "(", end = ")", close = true, newline = true },
|
||||
{ start = "<<", end = ">>", close = true, newline = false, not_in = ["string"] },
|
||||
{ start = "\"", end = "\"", close = true, newline = false, not_in = ["string"] },
|
||||
{ start = "'", end = "'", close = true, newline = false, not_in = ["string", "comment"] },
|
||||
]
|
||||
# Indent if a line ends brackets, "->" or most keywords. Also if prefixed
|
||||
# with "||". This should work with most formatting models.
|
||||
# The ([^%]).* is to ensure this doesn't match inside comments.
|
||||
increase_indent_pattern = "^([^%]).*([{(\\[]]|\\->|after|begin|case|catch|fun|if|of|try|when|maybe|else|(\\|\\|.*))\\s*$"
|
||||
|
||||
# Dedent after brackets, end or lone "->". The latter happens in a spec
|
||||
# with indented types, typically after "when". Only do this if it's _only_
|
||||
# preceded by whitespace.
|
||||
decrease_indent_pattern = "^\\s*([)}\\]]|end|else|\\->\\s*$)"
|
9
crates/languages/src/erlang/folds.scm
Normal file
9
crates/languages/src/erlang/folds.scm
Normal file
|
@ -0,0 +1,9 @@
|
|||
[
|
||||
(fun_decl)
|
||||
(anonymous_fun)
|
||||
(case_expr)
|
||||
(maybe_expr)
|
||||
(map_expr)
|
||||
(export_attribute)
|
||||
(export_type_attribute)
|
||||
] @fold
|
231
crates/languages/src/erlang/highlights.scm
Normal file
231
crates/languages/src/erlang/highlights.scm
Normal file
|
@ -0,0 +1,231 @@
|
|||
;; Copyright (c) Facebook, Inc. and its affiliates.
|
||||
;;
|
||||
;; Licensed under the Apache License, Version 2.0 (the "License");
|
||||
;; you may not use this file except in compliance with the License.
|
||||
;; You may obtain a copy of the License at
|
||||
;;
|
||||
;; http://www.apache.org/licenses/LICENSE-2.0
|
||||
;;
|
||||
;; Unless required by applicable law or agreed to in writing, software
|
||||
;; distributed under the License is distributed on an "AS IS" BASIS,
|
||||
;; WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
;; See the License for the specific language governing permissions and
|
||||
;; limitations under the License.
|
||||
;; ---------------------------------------------------------------------
|
||||
|
||||
;; Based initially on the contents of https://github.com/WhatsApp/tree-sitter-erlang/issues/2 by @Wilfred
|
||||
;; and https://github.com/the-mikedavis/tree-sitter-erlang/blob/main/queries/highlights.scm
|
||||
;;
|
||||
;; The tests are also based on those in
|
||||
;; https://github.com/the-mikedavis/tree-sitter-erlang/tree/main/test/highlight
|
||||
;;
|
||||
|
||||
|
||||
;; First match wins in this file
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
;; Attributes
|
||||
|
||||
;; module attribute
|
||||
(module_attribute
|
||||
name: (atom) @module)
|
||||
|
||||
;; behaviour
|
||||
(behaviour_attribute name: (atom) @module)
|
||||
|
||||
;; export
|
||||
|
||||
;; Import attribute
|
||||
(import_attribute
|
||||
module: (atom) @module)
|
||||
|
||||
;; export_type
|
||||
|
||||
;; optional_callbacks
|
||||
|
||||
;; compile
|
||||
(compile_options_attribute
|
||||
options: (tuple
|
||||
expr: (atom)
|
||||
expr: (list
|
||||
exprs: (binary_op_expr
|
||||
lhs: (atom)
|
||||
rhs: (integer)))))
|
||||
|
||||
;; file attribute
|
||||
|
||||
;; record
|
||||
(record_decl name: (atom) @type)
|
||||
(record_decl name: (macro_call_expr name: (var) @constant))
|
||||
(record_field name: (atom) @property)
|
||||
|
||||
;; type alias
|
||||
|
||||
;; opaque
|
||||
|
||||
;; Spec attribute
|
||||
(spec fun: (atom) @function)
|
||||
(spec
|
||||
module: (module name: (atom) @module)
|
||||
fun: (atom) @function)
|
||||
|
||||
;; callback
|
||||
(callback fun: (atom) @function)
|
||||
|
||||
;; fun decl
|
||||
|
||||
;; include/include_lib
|
||||
|
||||
;; ifdef/ifndef
|
||||
(pp_ifdef name: (_) @keyword.directive)
|
||||
(pp_ifndef name: (_) @keyword.directive)
|
||||
|
||||
;; define
|
||||
(pp_define
|
||||
lhs: (macro_lhs
|
||||
name: (_) @keyword.directive
|
||||
args: (var_args args: (var))))
|
||||
(pp_define
|
||||
lhs: (macro_lhs
|
||||
name: (var) @constant))
|
||||
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; Functions
|
||||
(fa fun: (atom) @function)
|
||||
(type_name name: (atom) @function)
|
||||
(call expr: (atom) @function)
|
||||
(function_clause name: (atom) @function)
|
||||
(internal_fun fun: (atom) @function)
|
||||
|
||||
;; This is a fudge, we should check that the operator is '/'
|
||||
;; But our grammar does not (currently) provide it
|
||||
(binary_op_expr lhs: (atom) @function rhs: (integer))
|
||||
|
||||
;; Others
|
||||
(remote_module module: (atom) @module)
|
||||
(remote fun: (atom) @function)
|
||||
(macro_call_expr name: (var) @keyword.directive args: (_) )
|
||||
(macro_call_expr name: (var) @constant)
|
||||
(macro_call_expr name: (atom) @keyword.directive)
|
||||
(record_field_name name: (atom) @property)
|
||||
(record_name name: (atom) @type)
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; Reserved words
|
||||
[ "after"
|
||||
"and"
|
||||
"band"
|
||||
"begin"
|
||||
"behavior"
|
||||
"behaviour"
|
||||
"bnot"
|
||||
"bor"
|
||||
"bsl"
|
||||
"bsr"
|
||||
"bxor"
|
||||
"callback"
|
||||
"case"
|
||||
"catch"
|
||||
"compile"
|
||||
"define"
|
||||
"deprecated"
|
||||
"div"
|
||||
"elif"
|
||||
"else"
|
||||
"end"
|
||||
"endif"
|
||||
"export"
|
||||
"export_type"
|
||||
"file"
|
||||
"fun"
|
||||
"if"
|
||||
"ifdef"
|
||||
"ifndef"
|
||||
"import"
|
||||
"include"
|
||||
"include_lib"
|
||||
"maybe"
|
||||
"module"
|
||||
"of"
|
||||
"opaque"
|
||||
"optional_callbacks"
|
||||
"or"
|
||||
"receive"
|
||||
"record"
|
||||
"spec"
|
||||
"try"
|
||||
"type"
|
||||
"undef"
|
||||
"unit"
|
||||
"when"
|
||||
"xor"] @keyword
|
||||
|
||||
["andalso" "orelse"] @keyword.operator
|
||||
|
||||
;; Punctuation
|
||||
["," "." ";"] @punctuation.delimiter
|
||||
["(" ")" "{" "}" "[" "]" "<<" ">>"] @punctuation.bracket
|
||||
|
||||
;; Operators
|
||||
["!"
|
||||
"->"
|
||||
"<-"
|
||||
"#"
|
||||
"::"
|
||||
"|"
|
||||
":"
|
||||
"="
|
||||
"||"
|
||||
|
||||
"+"
|
||||
"-"
|
||||
"bnot"
|
||||
"not"
|
||||
|
||||
"/"
|
||||
"*"
|
||||
"div"
|
||||
"rem"
|
||||
"band"
|
||||
"and"
|
||||
|
||||
"+"
|
||||
"-"
|
||||
"bor"
|
||||
"bxor"
|
||||
"bsl"
|
||||
"bsr"
|
||||
"or"
|
||||
"xor"
|
||||
|
||||
"++"
|
||||
"--"
|
||||
|
||||
"=="
|
||||
"/="
|
||||
"=<"
|
||||
"<"
|
||||
">="
|
||||
">"
|
||||
"=:="
|
||||
"=/="
|
||||
] @operator
|
||||
|
||||
;;; Comments
|
||||
((var) @comment.discard
|
||||
(#match? @comment.discard "^_"))
|
||||
|
||||
(dotdotdot) @comment.discard
|
||||
(comment) @comment
|
||||
|
||||
;; Primitive types
|
||||
(string) @string
|
||||
(char) @constant
|
||||
(integer) @number
|
||||
(var) @variable
|
||||
(atom) @string.special.symbol
|
||||
|
||||
;; wild attribute (Should take precedence over atoms, otherwise they are highlighted as atoms)
|
||||
(wild_attribute name: (attr_name name: (_) @keyword))
|
3
crates/languages/src/erlang/indents.scm
Normal file
3
crates/languages/src/erlang/indents.scm
Normal file
|
@ -0,0 +1,3 @@
|
|||
(_ "[" "]" @end) @indent
|
||||
(_ "{" "}" @end) @indent
|
||||
(_ "(" ")" @end) @indent
|
31
crates/languages/src/erlang/outline.scm
Normal file
31
crates/languages/src/erlang/outline.scm
Normal file
|
@ -0,0 +1,31 @@
|
|||
(module_attribute
|
||||
"module" @context
|
||||
name: (_) @name) @item
|
||||
|
||||
(behaviour_attribute
|
||||
"behaviour" @context
|
||||
(atom) @name) @item
|
||||
|
||||
(type_alias
|
||||
"type" @context
|
||||
name: (_) @name) @item
|
||||
|
||||
(opaque
|
||||
"opaque" @context
|
||||
name: (_) @name) @item
|
||||
|
||||
(pp_define
|
||||
"define" @context
|
||||
lhs: (_) @name) @item
|
||||
|
||||
(record_decl
|
||||
"record" @context
|
||||
name: (_) @name) @item
|
||||
|
||||
(callback
|
||||
"callback" @context
|
||||
fun: (_) @function ( (_) @name)) @item
|
||||
|
||||
(fun_decl (function_clause
|
||||
name: (_) @name
|
||||
args: (_) @context)) @item
|
12
crates/languages/src/gitcommit/config.toml
Normal file
12
crates/languages/src/gitcommit/config.toml
Normal file
|
@ -0,0 +1,12 @@
|
|||
name = "Git Commit"
|
||||
grammar = "git_commit"
|
||||
path_suffixes = [
|
||||
# Refer to https://github.com/neovim/neovim/blob/master/runtime/lua/vim/filetype.lua#L1286-L1290
|
||||
"TAG_EDITMSG",
|
||||
"MERGE_MSG",
|
||||
"COMMIT_EDITMSG",
|
||||
"NOTES_EDITMSG",
|
||||
"EDIT_DESCRIPTION",
|
||||
]
|
||||
line_comments = ["#"]
|
||||
brackets = []
|
34
crates/languages/src/gitcommit/highlights.scm
Normal file
34
crates/languages/src/gitcommit/highlights.scm
Normal file
|
@ -0,0 +1,34 @@
|
|||
(comment) @comment
|
||||
(generated_comment) @comment
|
||||
(title) @text.title
|
||||
(text) @text
|
||||
(branch) @text.reference
|
||||
(change) @keyword
|
||||
(filepath) @text.uri
|
||||
(arrow) @punctuation.delimiter
|
||||
|
||||
(subject) @text.title
|
||||
(subject (overflow) @text)
|
||||
(prefix (type) @keyword)
|
||||
(prefix (scope) @parameter)
|
||||
(prefix [
|
||||
"("
|
||||
")"
|
||||
":"
|
||||
] @punctuation.delimiter)
|
||||
(prefix [
|
||||
"!"
|
||||
] @punctuation.special)
|
||||
|
||||
(message) @text
|
||||
|
||||
(trailer (token) @keyword)
|
||||
(trailer (value) @text)
|
||||
|
||||
(breaking_change (token) @text.warning)
|
||||
(breaking_change (value) @text)
|
||||
|
||||
(scissor) @comment
|
||||
(subject_prefix) @keyword
|
||||
|
||||
(ERROR) @error
|
8
crates/languages/src/gitcommit/injections.scm
Normal file
8
crates/languages/src/gitcommit/injections.scm
Normal file
|
@ -0,0 +1,8 @@
|
|||
((diff) @injection.content
|
||||
(#set! injection.combined)
|
||||
(#set! injection.language "diff"))
|
||||
|
||||
((rebase_command) @injection.content
|
||||
(#set! injection.combined)
|
||||
(#set! injection.language "git_rebase"))
|
||||
|
126
crates/languages/src/gleam.rs
Normal file
126
crates/languages/src/gleam.rs
Normal file
|
@ -0,0 +1,126 @@
|
|||
use std::any::Any;
|
||||
use std::env::consts;
|
||||
use std::ffi::OsString;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use anyhow::{anyhow, bail, Result};
|
||||
use async_compression::futures::bufread::GzipDecoder;
|
||||
use async_tar::Archive;
|
||||
use async_trait::async_trait;
|
||||
use futures::io::BufReader;
|
||||
use futures::StreamExt;
|
||||
use language::{LanguageServerName, LspAdapter, LspAdapterDelegate};
|
||||
use lsp::LanguageServerBinary;
|
||||
use smol::fs;
|
||||
use util::github::{latest_github_release, GitHubLspBinaryVersion};
|
||||
use util::{async_maybe, ResultExt};
|
||||
|
||||
fn server_binary_arguments() -> Vec<OsString> {
|
||||
vec!["lsp".into()]
|
||||
}
|
||||
|
||||
pub struct GleamLspAdapter;
|
||||
|
||||
#[async_trait]
|
||||
impl LspAdapter for GleamLspAdapter {
|
||||
fn name(&self) -> LanguageServerName {
|
||||
LanguageServerName("gleam".into())
|
||||
}
|
||||
|
||||
fn short_name(&self) -> &'static str {
|
||||
"gleam"
|
||||
}
|
||||
|
||||
async fn fetch_latest_server_version(
|
||||
&self,
|
||||
delegate: &dyn LspAdapterDelegate,
|
||||
) -> Result<Box<dyn 'static + Send + Any>> {
|
||||
let release =
|
||||
latest_github_release("gleam-lang/gleam", true, false, delegate.http_client()).await?;
|
||||
let asset_name = format!(
|
||||
"gleam-{version}-{arch}-{os}.tar.gz",
|
||||
version = release.tag_name,
|
||||
arch = std::env::consts::ARCH,
|
||||
os = match consts::OS {
|
||||
"macos" => "apple-darwin",
|
||||
"linux" => "unknown-linux-musl",
|
||||
"windows" => "pc-windows-msvc",
|
||||
other => bail!("Running on unsupported os: {other}"),
|
||||
},
|
||||
);
|
||||
let asset = release
|
||||
.assets
|
||||
.iter()
|
||||
.find(|asset| asset.name == asset_name)
|
||||
.ok_or_else(|| anyhow!("no asset found matching {:?}", asset_name))?;
|
||||
Ok(Box::new(GitHubLspBinaryVersion {
|
||||
name: release.tag_name,
|
||||
url: asset.browser_download_url.clone(),
|
||||
}))
|
||||
}
|
||||
|
||||
async fn fetch_server_binary(
|
||||
&self,
|
||||
version: Box<dyn 'static + Send + Any>,
|
||||
container_dir: PathBuf,
|
||||
delegate: &dyn LspAdapterDelegate,
|
||||
) -> Result<LanguageServerBinary> {
|
||||
let version = version.downcast::<GitHubLspBinaryVersion>().unwrap();
|
||||
let binary_path = container_dir.join("gleam");
|
||||
|
||||
if fs::metadata(&binary_path).await.is_err() {
|
||||
let mut response = delegate
|
||||
.http_client()
|
||||
.get(&version.url, Default::default(), true)
|
||||
.await
|
||||
.map_err(|err| anyhow!("error downloading release: {}", err))?;
|
||||
let decompressed_bytes = GzipDecoder::new(BufReader::new(response.body_mut()));
|
||||
let archive = Archive::new(decompressed_bytes);
|
||||
archive.unpack(container_dir).await?;
|
||||
}
|
||||
|
||||
Ok(LanguageServerBinary {
|
||||
path: binary_path,
|
||||
env: None,
|
||||
arguments: server_binary_arguments(),
|
||||
})
|
||||
}
|
||||
|
||||
async fn cached_server_binary(
|
||||
&self,
|
||||
container_dir: PathBuf,
|
||||
_: &dyn LspAdapterDelegate,
|
||||
) -> Option<LanguageServerBinary> {
|
||||
get_cached_server_binary(container_dir).await
|
||||
}
|
||||
|
||||
async fn installation_test_binary(
|
||||
&self,
|
||||
container_dir: PathBuf,
|
||||
) -> Option<LanguageServerBinary> {
|
||||
get_cached_server_binary(container_dir)
|
||||
.await
|
||||
.map(|mut binary| {
|
||||
binary.arguments = vec!["--version".into()];
|
||||
binary
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
async fn get_cached_server_binary(container_dir: PathBuf) -> Option<LanguageServerBinary> {
|
||||
async_maybe!({
|
||||
let mut last = None;
|
||||
let mut entries = fs::read_dir(&container_dir).await?;
|
||||
while let Some(entry) = entries.next().await {
|
||||
last = Some(entry?.path());
|
||||
}
|
||||
|
||||
anyhow::Ok(LanguageServerBinary {
|
||||
path: last.ok_or_else(|| anyhow!("no cached binary"))?,
|
||||
env: None,
|
||||
arguments: server_binary_arguments(),
|
||||
})
|
||||
})
|
||||
.await
|
||||
.log_err()
|
||||
}
|
11
crates/languages/src/gleam/config.toml
Normal file
11
crates/languages/src/gleam/config.toml
Normal file
|
@ -0,0 +1,11 @@
|
|||
name = "Gleam"
|
||||
grammar = "gleam"
|
||||
path_suffixes = ["gleam"]
|
||||
line_comments = ["// ", "/// "]
|
||||
autoclose_before = ";:.,=}])>"
|
||||
brackets = [
|
||||
{ start = "{", end = "}", close = true, newline = true },
|
||||
{ start = "[", end = "]", close = true, newline = true },
|
||||
{ start = "(", end = ")", close = true, newline = true },
|
||||
{ start = "\"", end = "\"", close = true, newline = false, not_in = ["string", "comment"] },
|
||||
]
|
130
crates/languages/src/gleam/highlights.scm
Normal file
130
crates/languages/src/gleam/highlights.scm
Normal file
|
@ -0,0 +1,130 @@
|
|||
; Comments
|
||||
(module_comment) @comment
|
||||
(statement_comment) @comment
|
||||
(comment) @comment
|
||||
|
||||
; Constants
|
||||
(constant
|
||||
name: (identifier) @constant)
|
||||
|
||||
; Modules
|
||||
(module) @module
|
||||
(import alias: (identifier) @module)
|
||||
(remote_type_identifier
|
||||
module: (identifier) @module)
|
||||
(remote_constructor_name
|
||||
module: (identifier) @module)
|
||||
((field_access
|
||||
record: (identifier) @module
|
||||
field: (label) @function)
|
||||
(#is-not? local))
|
||||
|
||||
; Functions
|
||||
(unqualified_import (identifier) @function)
|
||||
(unqualified_import "type" (type_identifier) @type)
|
||||
(unqualified_import (type_identifier) @constructor)
|
||||
(function
|
||||
name: (identifier) @function)
|
||||
(external_function
|
||||
name: (identifier) @function)
|
||||
(function_parameter
|
||||
name: (identifier) @variable.parameter)
|
||||
((function_call
|
||||
function: (identifier) @function)
|
||||
(#is-not? local))
|
||||
((binary_expression
|
||||
operator: "|>"
|
||||
right: (identifier) @function)
|
||||
(#is-not? local))
|
||||
|
||||
; "Properties"
|
||||
; Assumed to be intended to refer to a name for a field; something that comes
|
||||
; before ":" or after "."
|
||||
; e.g. record field names, tuple indices, names for named arguments, etc
|
||||
(label) @property
|
||||
(tuple_access
|
||||
index: (integer) @property)
|
||||
|
||||
; Attributes
|
||||
(attribute
|
||||
"@" @attribute
|
||||
name: (identifier) @attribute)
|
||||
|
||||
(attribute_value (identifier) @constant)
|
||||
|
||||
; Type names
|
||||
(remote_type_identifier) @type
|
||||
(type_identifier) @type
|
||||
|
||||
; Data constructors
|
||||
(constructor_name) @constructor
|
||||
|
||||
; Literals
|
||||
(string) @string
|
||||
((escape_sequence) @warning
|
||||
; Deprecated in v0.33.0-rc2:
|
||||
(#eq? @warning "\\e"))
|
||||
(escape_sequence) @string.escape
|
||||
(bit_string_segment_option) @function.builtin
|
||||
(integer) @number
|
||||
(float) @number
|
||||
|
||||
; Reserved identifiers
|
||||
; TODO: when tree-sitter supports `#any-of?` in the Rust bindings,
|
||||
; refactor this to use `#any-of?` rather than `#match?`
|
||||
((identifier) @warning
|
||||
(#match? @warning "^(auto|delegate|derive|else|implement|macro|test|echo)$"))
|
||||
|
||||
; Variables
|
||||
(identifier) @variable
|
||||
(discard) @comment.unused
|
||||
|
||||
; Keywords
|
||||
[
|
||||
(visibility_modifier) ; "pub"
|
||||
(opacity_modifier) ; "opaque"
|
||||
"as"
|
||||
"assert"
|
||||
"case"
|
||||
"const"
|
||||
; DEPRECATED: 'external' was removed in v0.30.
|
||||
"external"
|
||||
"fn"
|
||||
"if"
|
||||
"import"
|
||||
"let"
|
||||
"panic"
|
||||
"todo"
|
||||
"type"
|
||||
"use"
|
||||
] @keyword
|
||||
|
||||
; Operators
|
||||
(binary_expression
|
||||
operator: _ @operator)
|
||||
(boolean_negation "!" @operator)
|
||||
(integer_negation "-" @operator)
|
||||
|
||||
; Punctuation
|
||||
[
|
||||
"("
|
||||
")"
|
||||
"["
|
||||
"]"
|
||||
"{"
|
||||
"}"
|
||||
"<<"
|
||||
">>"
|
||||
] @punctuation.bracket
|
||||
[
|
||||
"."
|
||||
","
|
||||
;; Controversial -- maybe some are operators?
|
||||
":"
|
||||
"#"
|
||||
"="
|
||||
"->"
|
||||
".."
|
||||
"-"
|
||||
"<-"
|
||||
] @punctuation.delimiter
|
3
crates/languages/src/gleam/indents.scm
Normal file
3
crates/languages/src/gleam/indents.scm
Normal file
|
@ -0,0 +1,3 @@
|
|||
(_ "[" "]" @end) @indent
|
||||
(_ "{" "}" @end) @indent
|
||||
(_ "(" ")" @end) @indent
|
31
crates/languages/src/gleam/outline.scm
Normal file
31
crates/languages/src/gleam/outline.scm
Normal file
|
@ -0,0 +1,31 @@
|
|||
(external_type
|
||||
(visibility_modifier)? @context
|
||||
"type" @context
|
||||
(type_name) @name) @item
|
||||
|
||||
(type_definition
|
||||
(visibility_modifier)? @context
|
||||
(opacity_modifier)? @context
|
||||
"type" @context
|
||||
(type_name) @name) @item
|
||||
|
||||
(data_constructor
|
||||
(constructor_name) @name) @item
|
||||
|
||||
(data_constructor_argument
|
||||
(label) @name) @item
|
||||
|
||||
(type_alias
|
||||
(visibility_modifier)? @context
|
||||
"type" @context
|
||||
(type_name) @name) @item
|
||||
|
||||
(function
|
||||
(visibility_modifier)? @context
|
||||
"fn" @context
|
||||
name: (_) @name) @item
|
||||
|
||||
(constant
|
||||
(visibility_modifier)? @context
|
||||
"const" @context
|
||||
name: (_) @name) @item
|
10
crates/languages/src/glsl/config.toml
Normal file
10
crates/languages/src/glsl/config.toml
Normal file
|
@ -0,0 +1,10 @@
|
|||
name = "GLSL"
|
||||
grammar = "glsl"
|
||||
path_suffixes = ["vert", "frag", "tesc", "tese", "geom", "comp"]
|
||||
line_comments = ["// "]
|
||||
block_comment = ["/* ", " */"]
|
||||
brackets = [
|
||||
{ start = "{", end = "}", close = true, newline = true },
|
||||
{ start = "[", end = "]", close = true, newline = true },
|
||||
{ start = "(", end = ")", close = true, newline = true },
|
||||
]
|
118
crates/languages/src/glsl/highlights.scm
Normal file
118
crates/languages/src/glsl/highlights.scm
Normal 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_")
|
||||
)
|
506
crates/languages/src/go.rs
Normal file
506
crates/languages/src/go.rs
Normal file
|
@ -0,0 +1,506 @@
|
|||
use anyhow::{anyhow, Context, Result};
|
||||
use async_trait::async_trait;
|
||||
use futures::StreamExt;
|
||||
use gpui::{AsyncAppContext, Task};
|
||||
pub use language::*;
|
||||
use lazy_static::lazy_static;
|
||||
use lsp::LanguageServerBinary;
|
||||
use regex::Regex;
|
||||
use serde_json::json;
|
||||
use smol::{fs, process};
|
||||
use std::{
|
||||
any::Any,
|
||||
ffi::{OsStr, OsString},
|
||||
ops::Range,
|
||||
path::PathBuf,
|
||||
str,
|
||||
sync::{
|
||||
atomic::{AtomicBool, Ordering::SeqCst},
|
||||
Arc,
|
||||
},
|
||||
};
|
||||
use util::{async_maybe, 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 {
|
||||
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, false, delegate.http_client()).await?;
|
||||
let version: Option<String> = release.tag_name.strip_prefix("gopls/v").map(str::to_string);
|
||||
if version.is_none() {
|
||||
log::warn!(
|
||||
"couldn't infer gopls version from GitHub release tag name '{}'",
|
||||
release.tag_name
|
||||
);
|
||||
}
|
||||
Ok(Box::new(version) as Box<_>)
|
||||
}
|
||||
|
||||
fn check_if_user_installed(
|
||||
&self,
|
||||
delegate: &Arc<dyn LspAdapterDelegate>,
|
||||
cx: &mut AsyncAppContext,
|
||||
) -> Option<Task<Option<LanguageServerBinary>>> {
|
||||
let delegate = delegate.clone();
|
||||
|
||||
Some(cx.spawn(|cx| async move {
|
||||
match cx.update(|cx| delegate.which_command(OsString::from("gopls"), cx)) {
|
||||
Ok(task) => task.await.map(|(path, env)| LanguageServerBinary {
|
||||
path,
|
||||
arguments: server_binary_arguments(),
|
||||
env: Some(env),
|
||||
}),
|
||||
Err(_) => None,
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
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(),
|
||||
env: None,
|
||||
});
|
||||
}
|
||||
}
|
||||
} 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() {
|
||||
log::error!(
|
||||
"failed to install gopls via `go install`. stdout: {:?}, stderr: {:?}",
|
||||
String::from_utf8_lossy(&install_output.stdout),
|
||||
String::from_utf8_lossy(&install_output.stderr)
|
||||
);
|
||||
|
||||
return Err(anyhow!("failed to install gopls with `go install`. Is `go` installed and in the PATH? Check logs for more information."));
|
||||
}
|
||||
|
||||
let installed_binary_path = gobin_dir.join("gopls");
|
||||
let version_output = process::Command::new(&installed_binary_path)
|
||||
.arg("version")
|
||||
.output()
|
||||
.await
|
||||
.context("failed to run installed gopls binary")?;
|
||||
let version_stdout = str::from_utf8(&version_output.stdout)
|
||||
.context("gopls version produced invalid utf8 output")?;
|
||||
let version = GOPLS_VERSION_REGEX
|
||||
.find(version_stdout)
|
||||
.with_context(|| format!("failed to parse golps version output '{version_stdout}'"))?
|
||||
.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(),
|
||||
env: None,
|
||||
})
|
||||
}
|
||||
|
||||
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
|
||||
})
|
||||
}
|
||||
|
||||
fn initialization_options(&self) -> Option<serde_json::Value> {
|
||||
Some(json!({
|
||||
"usePlaceholders": true,
|
||||
"hints": {
|
||||
"assignVariableTypes": true,
|
||||
"compositeLiteralFields": true,
|
||||
"compositeLiteralTypes": true,
|
||||
"constantValues": true,
|
||||
"functionTypeParameters": true,
|
||||
"parameterNames": true,
|
||||
"rangeVariableTypes": true
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
async fn label_for_completion(
|
||||
&self,
|
||||
completion: &lsp::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((lsp::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((
|
||||
lsp::CompletionItemKind::CONSTANT | lsp::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((lsp::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((lsp::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((lsp::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((lsp::CompletionItemKind::FUNCTION | lsp::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: lsp::SymbolKind,
|
||||
language: &Arc<Language>,
|
||||
) -> Option<CodeLabel> {
|
||||
let (text, filter_range, display_range) = match kind {
|
||||
lsp::SymbolKind::METHOD | lsp::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)
|
||||
}
|
||||
lsp::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)
|
||||
}
|
||||
lsp::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)
|
||||
}
|
||||
lsp::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)
|
||||
}
|
||||
lsp::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)
|
||||
}
|
||||
lsp::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)
|
||||
}
|
||||
lsp::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_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.starts_with("gopls_"))
|
||||
{
|
||||
last_binary_path = Some(entry.path());
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(path) = last_binary_path {
|
||||
Ok(LanguageServerBinary {
|
||||
path,
|
||||
arguments: server_binary_arguments(),
|
||||
env: None,
|
||||
})
|
||||
} 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::language;
|
||||
use gpui::Hsla;
|
||||
use theme::SyntaxTheme;
|
||||
|
||||
#[gpui::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();
|
||||
|
||||
assert_eq!(
|
||||
language
|
||||
.label_for_completion(&lsp::CompletionItem {
|
||||
kind: Some(lsp::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(&lsp::CompletionItem {
|
||||
kind: Some(lsp::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(&lsp::CompletionItem {
|
||||
kind: Some(lsp::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![(12..15, highlight_type)],
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
4
crates/languages/src/go/brackets.scm
Normal file
4
crates/languages/src/go/brackets.scm
Normal file
|
@ -0,0 +1,4 @@
|
|||
("(" @open ")" @close)
|
||||
("[" @open "]" @close)
|
||||
("{" @open "}" @close)
|
||||
("\"" @open "\"" @close)
|
13
crates/languages/src/go/config.toml
Normal file
13
crates/languages/src/go/config.toml
Normal file
|
@ -0,0 +1,13 @@
|
|||
name = "Go"
|
||||
grammar = "go"
|
||||
path_suffixes = ["go"]
|
||||
line_comments = ["// "]
|
||||
autoclose_before = ";:.,=}])>"
|
||||
brackets = [
|
||||
{ start = "{", end = "}", close = true, newline = true },
|
||||
{ start = "[", end = "]", close = true, newline = true },
|
||||
{ start = "(", end = ")", close = true, newline = true },
|
||||
{ start = "\"", end = "\"", close = true, newline = false, not_in = ["comment", "string"] },
|
||||
{ start = "'", end = "'", close = true, newline = false, not_in = ["comment", "string"] },
|
||||
{ start = "/*", end = " */", close = true, newline = false, not_in = ["comment", "string"] },
|
||||
]
|
24
crates/languages/src/go/embedding.scm
Normal file
24
crates/languages/src/go/embedding.scm
Normal 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
|
||||
)
|
120
crates/languages/src/go/highlights.scm
Normal file
120
crates/languages/src/go/highlights.scm
Normal file
|
@ -0,0 +1,120 @@
|
|||
(type_identifier) @type
|
||||
(field_identifier) @variable.member
|
||||
|
||||
(keyed_element
|
||||
.
|
||||
(literal_element
|
||||
(identifier) @variable.member))
|
||||
|
||||
(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)
|
||||
|
||||
[
|
||||
"("
|
||||
")"
|
||||
"{"
|
||||
"}"
|
||||
"["
|
||||
"]"
|
||||
] @punctuation.bracket
|
||||
|
||||
[
|
||||
"--"
|
||||
"-"
|
||||
"-="
|
||||
":="
|
||||
"!"
|
||||
"!="
|
||||
"..."
|
||||
"*"
|
||||
"*"
|
||||
"*="
|
||||
"/"
|
||||
"/="
|
||||
"&"
|
||||
"&&"
|
||||
"&="
|
||||
"%"
|
||||
"%="
|
||||
"^"
|
||||
"^="
|
||||
"+"
|
||||
"++"
|
||||
"+="
|
||||
"<-"
|
||||
"<"
|
||||
"<<"
|
||||
"<<="
|
||||
"<="
|
||||
"="
|
||||
"=="
|
||||
">"
|
||||
">="
|
||||
">>"
|
||||
">>="
|
||||
"|"
|
||||
"|="
|
||||
"||"
|
||||
"~"
|
||||
] @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
|
9
crates/languages/src/go/indents.scm
Normal file
9
crates/languages/src/go/indents.scm
Normal file
|
@ -0,0 +1,9 @@
|
|||
[
|
||||
(assignment_statement)
|
||||
(call_expression)
|
||||
(selector_expression)
|
||||
] @indent
|
||||
|
||||
(_ "[" "]" @end) @indent
|
||||
(_ "{" "}" @end) @indent
|
||||
(_ "(" ")" @end) @indent
|
44
crates/languages/src/go/outline.scm
Normal file
44
crates/languages/src/go/outline.scm
Normal file
|
@ -0,0 +1,44 @@
|
|||
(type_declaration
|
||||
"type" @context
|
||||
(type_spec
|
||||
name: (_) @name)) @item
|
||||
|
||||
(function_declaration
|
||||
"func" @context
|
||||
name: (identifier) @name
|
||||
parameters: (parameter_list
|
||||
"("
|
||||
")")) @item
|
||||
|
||||
(method_declaration
|
||||
"func" @context
|
||||
receiver: (parameter_list
|
||||
"(" @context
|
||||
(parameter_declaration
|
||||
name: (_) @name
|
||||
type: (_) @context)
|
||||
")" @context)
|
||||
name: (field_identifier) @name
|
||||
parameters: (parameter_list
|
||||
"("
|
||||
")")) @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
|
6
crates/languages/src/go/overrides.scm
Normal file
6
crates/languages/src/go/overrides.scm
Normal file
|
@ -0,0 +1,6 @@
|
|||
(comment) @comment
|
||||
[
|
||||
(interpreted_string_literal)
|
||||
(raw_string_literal)
|
||||
(rune_literal)
|
||||
] @string
|
8
crates/languages/src/gomod/config.toml
Normal file
8
crates/languages/src/gomod/config.toml
Normal file
|
@ -0,0 +1,8 @@
|
|||
name = "Go Mod"
|
||||
grammar = "gomod"
|
||||
path_suffixes = ["mod"]
|
||||
line_comments = ["//"]
|
||||
autoclose_before = ")"
|
||||
brackets = [
|
||||
{ start = "(", end = ")", close = true, newline = true}
|
||||
]
|
18
crates/languages/src/gomod/highlights.scm
Normal file
18
crates/languages/src/gomod/highlights.scm
Normal file
|
@ -0,0 +1,18 @@
|
|||
[
|
||||
"require"
|
||||
"replace"
|
||||
"go"
|
||||
"toolchain"
|
||||
"exclude"
|
||||
"retract"
|
||||
"module"
|
||||
] @keyword
|
||||
|
||||
"=>" @operator
|
||||
|
||||
(comment) @comment
|
||||
|
||||
[
|
||||
(version)
|
||||
(go_version)
|
||||
] @string
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue