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:
Piotr Osiewicz 2024-02-23 15:56:08 +01:00 committed by GitHub
parent 7cf0696c89
commit 0f584cb353
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
309 changed files with 221 additions and 154 deletions

View file

@ -113,52 +113,6 @@ theme_selector.workspace = true
thiserror.workspace = true
tiny_http = "0.8"
toml.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
url.workspace = true
urlencoding = "2.1.2"
util.workspace = true
@ -167,6 +121,8 @@ vim.workspace = true
welcome.workspace = true
workspace.workspace = true
zed_actions.workspace = true
languages = { version = "0.1.0", path = "../languages" }
[dev-dependencies]
call = { workspace = true, features = ["test-support"] }
@ -175,6 +131,7 @@ gpui = { workspace = true, features = ["test-support"] }
language = { workspace = true, features = ["test-support"] }
project = { workspace = true, features = ["test-support"] }
text = { workspace = true, features = ["test-support"] }
tree-sitter-rust.workspace = true
unindent.workspace = true
workspace = { workspace = true, features = ["test-support"] }

View file

@ -1,377 +0,0 @@
use anyhow::Context;
use gpui::AppContext;
pub use language::*;
use node_runtime::NodeRuntime;
use rust_embed::RustEmbed;
use settings::Settings;
use std::{str, sync::Arc};
use util::asset_str;
use self::{deno::DenoSettings, elixir::ElixirSettings};
mod astro;
mod c;
mod clojure;
mod csharp;
mod css;
mod dart;
mod deno;
mod dockerfile;
mod elixir;
mod elm;
mod erlang;
mod gleam;
mod go;
mod haskell;
mod html;
mod json;
#[cfg(feature = "plugin_runtime")]
mod language_plugin;
mod lua;
mod nu;
mod ocaml;
mod php;
mod prisma;
mod purescript;
mod python;
mod ruby;
mod rust;
mod svelte;
mod tailwind;
mod toml;
mod typescript;
mod uiua;
mod vue;
mod yaml;
mod zig;
// 1. Add tree-sitter-{language} parser to zed crate
// 2. Create a language directory in zed/crates/zed/src/languages and add the language to init function below
// 3. Add config.toml to the newly created language directory using existing languages as a template
// 4. Copy highlights from tree sitter repo for the language into a highlights.scm file.
// Note: github highlights take the last match while zed takes the first
// 5. Add indents.scm, outline.scm, and brackets.scm to implement indent on newline, outline/breadcrumbs,
// and autoclosing brackets respectively
// 6. If the language has injections add an injections.scm query file
#[derive(RustEmbed)]
#[folder = "src/languages"]
#[exclude = "*.rs"]
struct LanguageDir;
pub fn init(
languages: Arc<LanguageRegistry>,
node_runtime: Arc<dyn NodeRuntime>,
cx: &mut AppContext,
) {
ElixirSettings::register(cx);
DenoSettings::register(cx);
languages.register_native_grammars([
("astro", tree_sitter_astro::language()),
("bash", tree_sitter_bash::language()),
("c", tree_sitter_c::language()),
("c_sharp", tree_sitter_c_sharp::language()),
("clojure", tree_sitter_clojure::language()),
("cpp", tree_sitter_cpp::language()),
("css", tree_sitter_css::language()),
("dockerfile", tree_sitter_dockerfile::language()),
("elixir", tree_sitter_elixir::language()),
("elm", tree_sitter_elm::language()),
(
"embedded_template",
tree_sitter_embedded_template::language(),
),
("erlang", tree_sitter_erlang::language()),
("git_commit", tree_sitter_gitcommit::language()),
("gleam", tree_sitter_gleam::language()),
("glsl", tree_sitter_glsl::language()),
("go", tree_sitter_go::language()),
("gomod", tree_sitter_gomod::language()),
("gowork", tree_sitter_gowork::language()),
("haskell", tree_sitter_haskell::language()),
("hcl", tree_sitter_hcl::language()),
("heex", tree_sitter_heex::language()),
("html", tree_sitter_html::language()),
("json", tree_sitter_json::language()),
("lua", tree_sitter_lua::language()),
("markdown", tree_sitter_markdown::language()),
("nix", tree_sitter_nix::language()),
("nu", tree_sitter_nu::language()),
("ocaml", tree_sitter_ocaml::language_ocaml()),
(
"ocaml_interface",
tree_sitter_ocaml::language_ocaml_interface(),
),
("php", tree_sitter_php::language_php()),
("prisma", tree_sitter_prisma_io::language()),
("proto", tree_sitter_proto::language()),
("purescript", tree_sitter_purescript::language()),
("python", tree_sitter_python::language()),
("racket", tree_sitter_racket::language()),
("ruby", tree_sitter_ruby::language()),
("rust", tree_sitter_rust::language()),
("scheme", tree_sitter_scheme::language()),
("svelte", tree_sitter_svelte::language()),
("toml", tree_sitter_toml::language()),
("tsx", tree_sitter_typescript::language_tsx()),
("typescript", tree_sitter_typescript::language_typescript()),
("uiua", tree_sitter_uiua::language()),
("vue", tree_sitter_vue::language()),
("yaml", tree_sitter_yaml::language()),
("zig", tree_sitter_zig::language()),
("dart", tree_sitter_dart::language()),
]);
let language = |asset_dir_name: &'static str, adapters| {
let config = load_config(asset_dir_name);
languages.register_language(
config.name.clone(),
config.grammar.clone(),
config.matcher.clone(),
adapters,
move || Ok((config.clone(), load_queries(asset_dir_name))),
)
};
language(
"astro",
vec![
Arc::new(astro::AstroLspAdapter::new(node_runtime.clone())),
Arc::new(tailwind::TailwindLspAdapter::new(node_runtime.clone())),
],
);
language("bash", vec![]);
language("c", vec![Arc::new(c::CLspAdapter) as Arc<dyn LspAdapter>]);
language("clojure", vec![Arc::new(clojure::ClojureLspAdapter)]);
language("cpp", vec![Arc::new(c::CLspAdapter)]);
language("csharp", vec![Arc::new(csharp::OmniSharpAdapter {})]);
language(
"css",
vec![
Arc::new(css::CssLspAdapter::new(node_runtime.clone())),
Arc::new(tailwind::TailwindLspAdapter::new(node_runtime.clone())),
],
);
language(
"dockerfile",
vec![Arc::new(dockerfile::DockerfileLspAdapter::new(
node_runtime.clone(),
))],
);
match &ElixirSettings::get(None, cx).lsp {
elixir::ElixirLspSetting::ElixirLs => language(
"elixir",
vec![
Arc::new(elixir::ElixirLspAdapter),
Arc::new(tailwind::TailwindLspAdapter::new(node_runtime.clone())),
],
),
elixir::ElixirLspSetting::NextLs => {
language("elixir", vec![Arc::new(elixir::NextLspAdapter)])
}
elixir::ElixirLspSetting::Local { path, arguments } => language(
"elixir",
vec![Arc::new(elixir::LocalLspAdapter {
path: path.clone(),
arguments: arguments.clone(),
})],
),
}
language("gitcommit", vec![]);
language("erlang", vec![Arc::new(erlang::ErlangLspAdapter)]);
language("gleam", vec![Arc::new(gleam::GleamLspAdapter)]);
language("go", vec![Arc::new(go::GoLspAdapter)]);
language("gomod", vec![]);
language("gowork", vec![]);
language("zig", vec![Arc::new(zig::ZlsAdapter)]);
language(
"heex",
vec![
Arc::new(elixir::ElixirLspAdapter),
Arc::new(tailwind::TailwindLspAdapter::new(node_runtime.clone())),
],
);
language(
"json",
vec![Arc::new(json::JsonLspAdapter::new(
node_runtime.clone(),
languages.clone(),
))],
);
language("markdown", vec![]);
language(
"python",
vec![Arc::new(python::PythonLspAdapter::new(
node_runtime.clone(),
))],
);
language("rust", vec![Arc::new(rust::RustLspAdapter)]);
language("toml", vec![Arc::new(toml::TaploLspAdapter)]);
match &DenoSettings::get(None, cx).enable {
true => {
language(
"tsx",
vec![
Arc::new(deno::DenoLspAdapter::new()),
Arc::new(tailwind::TailwindLspAdapter::new(node_runtime.clone())),
],
);
language("typescript", vec![Arc::new(deno::DenoLspAdapter::new())]);
language(
"javascript",
vec![
Arc::new(deno::DenoLspAdapter::new()),
Arc::new(tailwind::TailwindLspAdapter::new(node_runtime.clone())),
],
);
}
false => {
language(
"tsx",
vec![
Arc::new(typescript::TypeScriptLspAdapter::new(node_runtime.clone())),
Arc::new(typescript::EsLintLspAdapter::new(node_runtime.clone())),
Arc::new(tailwind::TailwindLspAdapter::new(node_runtime.clone())),
],
);
language(
"typescript",
vec![
Arc::new(typescript::TypeScriptLspAdapter::new(node_runtime.clone())),
Arc::new(typescript::EsLintLspAdapter::new(node_runtime.clone())),
],
);
language(
"javascript",
vec![
Arc::new(typescript::TypeScriptLspAdapter::new(node_runtime.clone())),
Arc::new(typescript::EsLintLspAdapter::new(node_runtime.clone())),
Arc::new(tailwind::TailwindLspAdapter::new(node_runtime.clone())),
],
);
}
}
language("haskell", vec![Arc::new(haskell::HaskellLanguageServer {})]);
language(
"html",
vec![
Arc::new(html::HtmlLspAdapter::new(node_runtime.clone())),
Arc::new(tailwind::TailwindLspAdapter::new(node_runtime.clone())),
],
);
language("ruby", vec![Arc::new(ruby::RubyLanguageServer)]);
language(
"erb",
vec![
Arc::new(ruby::RubyLanguageServer),
Arc::new(tailwind::TailwindLspAdapter::new(node_runtime.clone())),
],
);
language("scheme", vec![]);
language("racket", vec![]);
language("lua", vec![Arc::new(lua::LuaLspAdapter)]);
language(
"yaml",
vec![Arc::new(yaml::YamlLspAdapter::new(node_runtime.clone()))],
);
language(
"svelte",
vec![
Arc::new(svelte::SvelteLspAdapter::new(node_runtime.clone())),
Arc::new(tailwind::TailwindLspAdapter::new(node_runtime.clone())),
],
);
language(
"php",
vec![
Arc::new(php::IntelephenseLspAdapter::new(node_runtime.clone())),
Arc::new(tailwind::TailwindLspAdapter::new(node_runtime.clone())),
],
);
language(
"purescript",
vec![Arc::new(purescript::PurescriptLspAdapter::new(
node_runtime.clone(),
))],
);
language(
"elm",
vec![Arc::new(elm::ElmLspAdapter::new(node_runtime.clone()))],
);
language("glsl", vec![]);
language("nix", vec![]);
language("nu", vec![Arc::new(nu::NuLanguageServer {})]);
language("ocaml", vec![Arc::new(ocaml::OCamlLspAdapter)]);
language("ocaml-interface", vec![Arc::new(ocaml::OCamlLspAdapter)]);
language(
"vue",
vec![Arc::new(vue::VueLspAdapter::new(node_runtime.clone()))],
);
language("uiua", vec![Arc::new(uiua::UiuaLanguageServer {})]);
language("proto", vec![]);
language("terraform", vec![]);
language("terraform-vars", vec![]);
language("hcl", vec![]);
language(
"prisma",
vec![Arc::new(prisma::PrismaLspAdapter::new(
node_runtime.clone(),
))],
);
language("dart", vec![Arc::new(dart::DartLanguageServer {})]);
}
#[cfg(any(test, feature = "test-support"))]
pub async fn language(
name: &str,
grammar: tree_sitter::Language,
lsp_adapter: Option<Arc<dyn LspAdapter>>,
) -> Arc<Language> {
Arc::new(
Language::new(load_config(name), Some(grammar))
.with_lsp_adapters(lsp_adapter.into_iter().collect())
.await
.with_queries(load_queries(name))
.unwrap(),
)
}
fn load_config(name: &str) -> LanguageConfig {
let config_toml = String::from_utf8(
LanguageDir::get(&format!("{}/config.toml", name))
.unwrap()
.data
.to_vec(),
)
.unwrap();
::toml::from_str(&config_toml)
.with_context(|| format!("failed to load config.toml for language {name:?}"))
.unwrap()
}
fn load_queries(name: &str) -> LanguageQueries {
let mut result = LanguageQueries::default();
for path in LanguageDir::iter() {
if let Some(remainder) = path.strip_prefix(name).and_then(|p| p.strip_prefix('/')) {
if !remainder.ends_with(".scm") {
continue;
}
for (name, query) in QUERY_FILENAME_PREFIXES {
if remainder.starts_with(name) {
let contents = asset_str::<LanguageDir>(path.as_ref());
match query(&mut result) {
None => *query(&mut result) = Some(contents),
Some(r) => r.to_mut().push_str(contents.as_ref()),
}
}
}
}
}
result
}

View file

@ -1,138 +0,0 @@
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()
}

View file

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

View file

@ -1,22 +0,0 @@
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"]

View file

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

View file

@ -1,16 +0,0 @@
; 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"))

View file

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

View file

@ -1,10 +0,0 @@
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"] },
]

View file

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

View file

@ -1,2 +0,0 @@
(variable_assignment
value: (_) @redact)

View file

@ -1,326 +0,0 @@
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::languages::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
});
}
}

View file

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

View file

@ -1,13 +0,0 @@
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"] },
]

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,12 +0,0 @@
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 = ["-"]

View file

@ -1,41 +0,0 @@
;; 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|->|->>)$"
))

View file

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

View file

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

View file

@ -1,13 +0,0 @@
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"] },
]

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,153 +0,0 @@
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()]
}

View file

@ -1,13 +0,0 @@
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"] },
]

View file

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

View file

@ -1,2 +0,0 @@
((comment) @injection.content
(#set! injection.language "comment"))

View file

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

View file

@ -1,132 +0,0 @@
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()
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,13 +0,0 @@
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"] },
]

View file

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

View file

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

View file

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

View file

@ -1,236 +0,0 @@
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()
}

View file

@ -1,126 +0,0 @@
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()
}

View file

@ -1,9 +0,0 @@
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"] },
]

View file

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

View file

@ -1,6 +0,0 @@
; We need impl this
; ((comment) @injection.content
; (#set! injection.language "comment"))
((shell_command) @content
(#set! "language" "bash"))

View file

@ -1,549 +0,0 @@
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(),
})
}

View file

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

View file

@ -1,17 +0,0 @@
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"]

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,150 +0,0 @@
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()
}

View file

@ -1,12 +0,0 @@
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"] },
]

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,60 +0,0 @@
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()],
})
}
}

View file

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

View file

@ -1,24 +0,0 @@
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*$)"

View file

@ -1,9 +0,0 @@
[
(fun_decl)
(anonymous_fun)
(case_expr)
(maybe_expr)
(map_expr)
(export_attribute)
(export_type_attribute)
] @fold

View file

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

View file

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

View file

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

View file

@ -1,12 +0,0 @@
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 = []

View file

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

View file

@ -1,8 +0,0 @@
((diff) @injection.content
(#set! injection.combined)
(#set! injection.language "diff"))
((rebase_command) @injection.content
(#set! injection.combined)
(#set! injection.language "git_rebase"))

View file

@ -1,126 +0,0 @@
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()
}

View file

@ -1,11 +0,0 @@
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"] },
]

View file

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

View file

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

View file

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

View file

@ -1,10 +0,0 @@
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 },
]

View file

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

View file

@ -1,506 +0,0 @@
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::languages::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)],
})
);
}
}

View file

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

View file

@ -1,13 +0,0 @@
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"] },
]

View file

@ -1,24 +0,0 @@
(
(comment)* @context
.
(type_declaration
(type_spec
name: (_) @name)
) @item
)
(
(comment)* @context
.
(function_declaration
name: (_) @name
) @item
)
(
(comment)* @context
.
(method_declaration
name: (_) @name
) @item
)

View file

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

View file

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

View file

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

View file

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

View file

@ -1,8 +0,0 @@
name = "Go Mod"
grammar = "gomod"
path_suffixes = ["mod"]
line_comments = ["//"]
autoclose_before = ")"
brackets = [
{ start = "(", end = ")", close = true, newline = true}
]

View file

@ -1,18 +0,0 @@
[
"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