Port zed/src/languages to zed2

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

5
Cargo.lock generated
View file

@ -10946,7 +10946,7 @@ dependencies = [
"ctor",
"db2",
"env_logger 0.9.3",
"feature_flags",
"feature_flags2",
"fs2",
"fsevent",
"futures 0.3.28",
@ -10963,7 +10963,7 @@ dependencies = [
"lazy_static",
"libc",
"log",
"lsp",
"lsp2",
"node_runtime",
"num_cpus",
"parking_lot 0.11.2",
@ -11016,6 +11016,7 @@ dependencies = [
"tree-sitter-svelte",
"tree-sitter-toml",
"tree-sitter-typescript",
"tree-sitter-vue",
"tree-sitter-yaml",
"unindent",
"url",

View file

@ -637,6 +637,10 @@ impl AppContext {
)
}
pub fn all_action_names<'a>(&'a self) -> impl Iterator<Item = SharedString> + 'a {
self.action_builders.keys().cloned()
}
/// Move the global of the given type to the stack.
pub(crate) fn lease_global<G: 'static>(&mut self) -> GlobalLease<G> {
GlobalLease::new(

View file

@ -230,8 +230,8 @@ impl CachedLspAdapter {
self.adapter.label_for_symbol(name, kind, language).await
}
pub fn enabled_formatters(&self) -> Vec<BundledFormatter> {
self.adapter.enabled_formatters()
pub fn prettier_plugins(&self) -> &[&'static str] {
self.adapter.prettier_plugins()
}
}
@ -340,31 +340,8 @@ pub trait LspAdapter: 'static + Send + Sync {
Default::default()
}
fn enabled_formatters(&self) -> Vec<BundledFormatter> {
Vec::new()
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum BundledFormatter {
Prettier {
// See https://prettier.io/docs/en/options.html#parser for a list of valid values.
// Usually, every language has a single parser (standard or plugin-provided), hence `Some("parser_name")` can be used.
// There can not be multiple parsers for a single language, in case of a conflict, we would attempt to select the one with most plugins.
//
// But exceptions like Tailwind CSS exist, which uses standard parsers for CSS/JS/HTML/etc. but require an extra plugin to be installed.
// For those cases, `None` will install the plugin but apply other, regular parser defined for the language, and this would not be a conflict.
parser_name: Option<&'static str>,
plugin_names: Vec<&'static str>,
},
}
impl BundledFormatter {
pub fn prettier(parser_name: &'static str) -> Self {
Self::Prettier {
parser_name: Some(parser_name),
plugin_names: Vec::new(),
}
fn prettier_plugins(&self) -> &[&'static str] {
&[]
}
}
@ -402,6 +379,8 @@ pub struct LanguageConfig {
pub overrides: HashMap<String, LanguageConfigOverride>,
#[serde(default)]
pub word_characters: HashSet<char>,
#[serde(default)]
pub prettier_parser_name: Option<String>,
}
#[derive(Debug, Default)]
@ -475,6 +454,7 @@ impl Default for LanguageConfig {
overrides: Default::default(),
collapsed_placeholder: Default::default(),
word_characters: Default::default(),
prettier_parser_name: None,
}
}
}
@ -500,7 +480,7 @@ pub struct FakeLspAdapter {
pub initializer: Option<Box<dyn 'static + Send + Sync + Fn(&mut lsp2::FakeLanguageServer)>>,
pub disk_based_diagnostics_progress_token: Option<String>,
pub disk_based_diagnostics_sources: Vec<String>,
pub enabled_formatters: Vec<BundledFormatter>,
pub prettier_plugins: Vec<&'static str>,
}
#[derive(Clone, Debug, Default)]
@ -1604,6 +1584,10 @@ impl Language {
override_id: None,
}
}
pub fn prettier_parser_name(&self) -> Option<&str> {
self.config.prettier_parser_name.as_deref()
}
}
impl LanguageScope {
@ -1766,7 +1750,7 @@ impl Default for FakeLspAdapter {
disk_based_diagnostics_progress_token: None,
initialization_options: None,
disk_based_diagnostics_sources: Vec::new(),
enabled_formatters: Vec::new(),
prettier_plugins: Vec::new(),
}
}
}
@ -1824,8 +1808,8 @@ impl LspAdapter for Arc<FakeLspAdapter> {
self.initialization_options.clone()
}
fn enabled_formatters(&self) -> Vec<BundledFormatter> {
self.enabled_formatters.clone()
fn prettier_plugins(&self) -> &[&'static str] {
&self.prettier_plugins
}
}

View file

@ -1,8 +1,8 @@
use anyhow::Context;
use collections::{HashMap, HashSet};
use collections::HashMap;
use fs2::Fs;
use gpui2::{AsyncAppContext, Model};
use language2::{language_settings::language_settings, Buffer, BundledFormatter, Diff};
use language2::{language_settings::language_settings, Buffer, Diff};
use lsp2::{LanguageServer, LanguageServerId};
use node_runtime::NodeRuntime;
use serde::{Deserialize, Serialize};
@ -189,128 +189,134 @@ impl Prettier {
) -> anyhow::Result<Diff> {
match self {
Self::Real(local) => {
let params = buffer.update(cx, |buffer, cx| {
let buffer_language = buffer.language();
let parsers_with_plugins = buffer_language
.into_iter()
.flat_map(|language| {
language
let params = buffer
.update(cx, |buffer, cx| {
let buffer_language = buffer.language();
let parser_with_plugins = buffer_language.and_then(|l| {
let prettier_parser = l.prettier_parser_name()?;
let mut prettier_plugins = l
.lsp_adapters()
.iter()
.flat_map(|adapter| adapter.enabled_formatters())
.filter_map(|formatter| match formatter {
BundledFormatter::Prettier {
parser_name,
plugin_names,
} => Some((parser_name, plugin_names)),
})
})
.fold(
HashMap::default(),
|mut parsers_with_plugins, (parser_name, plugins)| {
match parser_name {
Some(parser_name) => parsers_with_plugins
.entry(parser_name)
.or_insert_with(HashSet::default)
.extend(plugins),
None => parsers_with_plugins.values_mut().for_each(|existing_plugins| {
existing_plugins.extend(plugins.iter());
}),
.flat_map(|adapter| adapter.prettier_plugins())
.collect::<Vec<_>>();
prettier_plugins.dedup();
Some((prettier_parser, prettier_plugins))
});
let prettier_node_modules = self.prettier_dir().join("node_modules");
anyhow::ensure!(
prettier_node_modules.is_dir(),
"Prettier node_modules dir does not exist: {prettier_node_modules:?}"
);
let plugin_name_into_path = |plugin_name: &str| {
let prettier_plugin_dir = prettier_node_modules.join(plugin_name);
for possible_plugin_path in [
prettier_plugin_dir.join("dist").join("index.mjs"),
prettier_plugin_dir.join("dist").join("index.js"),
prettier_plugin_dir.join("dist").join("plugin.js"),
prettier_plugin_dir.join("index.mjs"),
prettier_plugin_dir.join("index.js"),
prettier_plugin_dir.join("plugin.js"),
prettier_plugin_dir,
] {
if possible_plugin_path.is_file() {
return Some(possible_plugin_path);
}
parsers_with_plugins
},
}
None
};
let (parser, located_plugins) = match parser_with_plugins {
Some((parser, plugins)) => {
// Tailwind plugin requires being added last
// https://github.com/tailwindlabs/prettier-plugin-tailwindcss#compatibility-with-other-prettier-plugins
let mut add_tailwind_back = false;
let mut plugins = plugins
.into_iter()
.filter(|&&plugin_name| {
if plugin_name == TAILWIND_PRETTIER_PLUGIN_PACKAGE_NAME {
add_tailwind_back = true;
false
} else {
true
}
})
.map(|plugin_name| {
(plugin_name, plugin_name_into_path(plugin_name))
})
.collect::<Vec<_>>();
if add_tailwind_back {
plugins.push((
&TAILWIND_PRETTIER_PLUGIN_PACKAGE_NAME,
plugin_name_into_path(
TAILWIND_PRETTIER_PLUGIN_PACKAGE_NAME,
),
));
}
(Some(parser.to_string()), plugins)
}
None => (None, Vec::new()),
};
let prettier_options = if self.is_default() {
let language_settings =
language_settings(buffer_language, buffer.file(), cx);
let mut options = language_settings.prettier.clone();
if !options.contains_key("tabWidth") {
options.insert(
"tabWidth".to_string(),
serde_json::Value::Number(serde_json::Number::from(
language_settings.tab_size.get(),
)),
);
}
if !options.contains_key("printWidth") {
options.insert(
"printWidth".to_string(),
serde_json::Value::Number(serde_json::Number::from(
language_settings.preferred_line_length,
)),
);
}
Some(options)
} else {
None
};
let plugins = located_plugins
.into_iter()
.filter_map(|(plugin_name, located_plugin_path)| {
match located_plugin_path {
Some(path) => Some(path),
None => {
log::error!(
"Have not found plugin path for {:?} inside {:?}",
plugin_name,
prettier_node_modules
);
None
}
}
})
.collect();
log::debug!(
"Formatting file {:?} with prettier, plugins :{:?}, options: {:?}",
plugins,
prettier_options,
buffer.file().map(|f| f.full_path(cx))
);
let selected_parser_with_plugins = parsers_with_plugins.iter().max_by_key(|(_, plugins)| plugins.len());
if parsers_with_plugins.len() > 1 {
log::warn!("Found multiple parsers with plugins {parsers_with_plugins:?}, will select only one: {selected_parser_with_plugins:?}");
}
let prettier_node_modules = self.prettier_dir().join("node_modules");
anyhow::ensure!(prettier_node_modules.is_dir(), "Prettier node_modules dir does not exist: {prettier_node_modules:?}");
let plugin_name_into_path = |plugin_name: &str| {
let prettier_plugin_dir = prettier_node_modules.join(plugin_name);
for possible_plugin_path in [
prettier_plugin_dir.join("dist").join("index.mjs"),
prettier_plugin_dir.join("dist").join("index.js"),
prettier_plugin_dir.join("dist").join("plugin.js"),
prettier_plugin_dir.join("index.mjs"),
prettier_plugin_dir.join("index.js"),
prettier_plugin_dir.join("plugin.js"),
prettier_plugin_dir,
] {
if possible_plugin_path.is_file() {
return Some(possible_plugin_path);
}
}
None
};
let (parser, located_plugins) = match selected_parser_with_plugins {
Some((parser, plugins)) => {
// Tailwind plugin requires being added last
// https://github.com/tailwindlabs/prettier-plugin-tailwindcss#compatibility-with-other-prettier-plugins
let mut add_tailwind_back = false;
let mut plugins = plugins.into_iter().filter(|&&plugin_name| {
if plugin_name == TAILWIND_PRETTIER_PLUGIN_PACKAGE_NAME {
add_tailwind_back = true;
false
} else {
true
}
}).map(|plugin_name| (plugin_name, plugin_name_into_path(plugin_name))).collect::<Vec<_>>();
if add_tailwind_back {
plugins.push((&TAILWIND_PRETTIER_PLUGIN_PACKAGE_NAME, plugin_name_into_path(TAILWIND_PRETTIER_PLUGIN_PACKAGE_NAME)));
}
(Some(parser.to_string()), plugins)
},
None => (None, Vec::new()),
};
let prettier_options = if self.is_default() {
let language_settings = language_settings(buffer_language, buffer.file(), cx);
let mut options = language_settings.prettier.clone();
if !options.contains_key("tabWidth") {
options.insert(
"tabWidth".to_string(),
serde_json::Value::Number(serde_json::Number::from(
language_settings.tab_size.get(),
)),
);
}
if !options.contains_key("printWidth") {
options.insert(
"printWidth".to_string(),
serde_json::Value::Number(serde_json::Number::from(
language_settings.preferred_line_length,
)),
);
}
Some(options)
} else {
None
};
let plugins = located_plugins.into_iter().filter_map(|(plugin_name, located_plugin_path)| {
match located_plugin_path {
Some(path) => Some(path),
None => {
log::error!("Have not found plugin path for {plugin_name:?} inside {prettier_node_modules:?}");
None},
}
}).collect();
log::debug!("Formatting file {:?} with prettier, plugins :{plugins:?}, options: {prettier_options:?}", buffer.file().map(|f| f.full_path(cx)));
anyhow::Ok(FormatParams {
text: buffer.text(),
options: FormatOptions {
parser,
plugins,
path: buffer_path,
prettier_options,
},
})
})?.context("prettier params calculation")?;
anyhow::Ok(FormatParams {
text: buffer.text(),
options: FormatOptions {
parser,
plugins,
path: buffer_path,
prettier_options,
},
})
})?
.context("prettier params calculation")?;
let response = local
.server
.request::<Format>(params)

View file

@ -39,11 +39,11 @@ use language2::{
deserialize_anchor, deserialize_fingerprint, deserialize_line_ending, deserialize_version,
serialize_anchor, serialize_version, split_operations,
},
range_from_lsp, range_to_lsp, Bias, Buffer, BufferSnapshot, BundledFormatter, CachedLspAdapter,
CodeAction, CodeLabel, Completion, Diagnostic, DiagnosticEntry, DiagnosticSet, Diff,
Event as BufferEvent, File as _, Language, LanguageRegistry, LanguageServerName, LocalFile,
LspAdapterDelegate, OffsetRangeExt, Operation, Patch, PendingLanguageServer, PointUtf16,
TextBufferSnapshot, ToOffset, ToPointUtf16, Transaction, Unclipped,
range_from_lsp, range_to_lsp, Bias, Buffer, BufferSnapshot, CachedLspAdapter, CodeAction,
CodeLabel, Completion, Diagnostic, DiagnosticEntry, DiagnosticSet, Diff, Event as BufferEvent,
File as _, Language, LanguageRegistry, LanguageServerName, LocalFile, LspAdapterDelegate,
OffsetRangeExt, Operation, Patch, PendingLanguageServer, PointUtf16, TextBufferSnapshot,
ToOffset, ToPointUtf16, Transaction, Unclipped,
};
use log::error;
use lsp2::{
@ -8410,12 +8410,7 @@ impl Project {
let Some(buffer_language) = buffer.language() else {
return Task::ready(None);
};
if !buffer_language
.lsp_adapters()
.iter()
.flat_map(|adapter| adapter.enabled_formatters())
.any(|formatter| matches!(formatter, BundledFormatter::Prettier { .. }))
{
if buffer_language.prettier_parser_name().is_none() {
return Task::ready(None);
}
@ -8574,16 +8569,15 @@ impl Project {
};
let mut prettier_plugins = None;
for formatter in new_language
.lsp_adapters()
.into_iter()
.flat_map(|adapter| adapter.enabled_formatters())
{
match formatter {
BundledFormatter::Prettier { plugin_names, .. } => prettier_plugins
.get_or_insert_with(|| HashSet::default())
.extend(plugin_names),
}
if new_language.prettier_parser_name().is_some() {
prettier_plugins
.get_or_insert_with(|| HashSet::default())
.extend(
new_language
.lsp_adapters()
.iter()
.flat_map(|adapter| adapter.prettier_plugins()),
)
}
let Some(prettier_plugins) = prettier_plugins else {
return Task::ready(Ok(()));

View file

@ -1,7 +1,7 @@
use crate::{settings_store::parse_json_with_comments, SettingsAssets};
use anyhow::{anyhow, Context, Result};
use collections::BTreeMap;
use gpui2::{AppContext, KeyBinding};
use gpui2::{AppContext, KeyBinding, SharedString};
use schemars::{
gen::{SchemaGenerator, SchemaSettings},
schema::{InstanceType, Schema, SchemaObject, SingleOrVec, SubschemaValidation},
@ -96,7 +96,7 @@ impl KeymapFile {
Ok(())
}
pub fn generate_json_schema(action_names: &[&'static str]) -> serde_json::Value {
pub fn generate_json_schema(action_names: &[SharedString]) -> serde_json::Value {
let mut root_schema = SchemaSettings::draft07()
.with(|settings| settings.option_add_null_type = false)
.into_generator()

View file

@ -108,6 +108,24 @@ pub struct SyntaxTheme {
}
impl SyntaxTheme {
// TOOD: Get this working with `#[cfg(test)]`. Why isn't it?
pub fn new_test(colors: impl IntoIterator<Item = (&'static str, Hsla)>) -> Self {
SyntaxTheme {
highlights: colors
.into_iter()
.map(|(key, color)| {
(
key.to_owned(),
HighlightStyle {
color: Some(color),
..Default::default()
},
)
})
.collect(),
}
}
pub fn get(&self, name: &str) -> HighlightStyle {
self.highlights
.iter()

View file

@ -47,7 +47,7 @@ install_cli = { path = "../install_cli" }
journal2 = { path = "../journal2" }
language2 = { path = "../language2" }
# language_selector = { path = "../language_selector" }
lsp = { path = "../lsp" }
lsp2 = { path = "../lsp2" }
language_tools = { path = "../language_tools" }
node_runtime = { path = "../node_runtime" }
# assistant = { path = "../assistant" }
@ -60,7 +60,7 @@ project2 = { path = "../project2" }
# recent_projects = { path = "../recent_projects" }
rpc2 = { path = "../rpc2" }
settings2 = { path = "../settings2" }
feature_flags = { path = "../feature_flags" }
feature_flags2 = { path = "../feature_flags2" }
sum_tree = { path = "../sum_tree" }
shellexpand = "2.1.0"
text = { path = "../text" }
@ -135,6 +135,7 @@ tree-sitter-yaml.workspace = true
tree-sitter-lua.workspace = true
tree-sitter-nix.workspace = true
tree-sitter-nu.workspace = true
tree-sitter-vue.workspace = true
url = "2.2"
urlencoding = "2.1.2"

View file

@ -0,0 +1,273 @@
use anyhow::Context;
use gpui2::AppContext;
pub use language2::*;
use node_runtime::NodeRuntime;
use rust_embed::RustEmbed;
use settings2::Settings;
use std::{borrow::Cow, str, sync::Arc};
use util::asset_str;
use self::elixir::ElixirSettings;
mod c;
mod css;
mod elixir;
mod go;
mod html;
mod json;
#[cfg(feature = "plugin_runtime")]
mod language_plugin;
mod lua;
mod php;
mod python;
mod ruby;
mod rust;
mod svelte;
mod tailwind;
mod typescript;
mod vue;
mod yaml;
// 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);
let language = |name, grammar, adapters| {
languages.register(name, load_config(name), grammar, adapters, load_queries)
};
language("bash", tree_sitter_bash::language(), vec![]);
language(
"c",
tree_sitter_c::language(),
vec![Arc::new(c::CLspAdapter) as Arc<dyn LspAdapter>],
);
language(
"cpp",
tree_sitter_cpp::language(),
vec![Arc::new(c::CLspAdapter)],
);
language(
"css",
tree_sitter_css::language(),
vec![
Arc::new(css::CssLspAdapter::new(node_runtime.clone())),
Arc::new(tailwind::TailwindLspAdapter::new(node_runtime.clone())),
],
);
match &ElixirSettings::get(None, cx).lsp {
elixir::ElixirLspSetting::ElixirLs => language(
"elixir",
tree_sitter_elixir::language(),
vec![
Arc::new(elixir::ElixirLspAdapter),
Arc::new(tailwind::TailwindLspAdapter::new(node_runtime.clone())),
],
),
elixir::ElixirLspSetting::NextLs => language(
"elixir",
tree_sitter_elixir::language(),
vec![Arc::new(elixir::NextLspAdapter)],
),
elixir::ElixirLspSetting::Local { path, arguments } => language(
"elixir",
tree_sitter_elixir::language(),
vec![Arc::new(elixir::LocalLspAdapter {
path: path.clone(),
arguments: arguments.clone(),
})],
),
}
language(
"go",
tree_sitter_go::language(),
vec![Arc::new(go::GoLspAdapter)],
);
language(
"heex",
tree_sitter_heex::language(),
vec![
Arc::new(elixir::ElixirLspAdapter),
Arc::new(tailwind::TailwindLspAdapter::new(node_runtime.clone())),
],
);
language(
"json",
tree_sitter_json::language(),
vec![Arc::new(json::JsonLspAdapter::new(
node_runtime.clone(),
languages.clone(),
))],
);
language("markdown", tree_sitter_markdown::language(), vec![]);
language(
"python",
tree_sitter_python::language(),
vec![Arc::new(python::PythonLspAdapter::new(
node_runtime.clone(),
))],
);
language(
"rust",
tree_sitter_rust::language(),
vec![Arc::new(rust::RustLspAdapter)],
);
language("toml", tree_sitter_toml::language(), vec![]);
language(
"tsx",
tree_sitter_typescript::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",
tree_sitter_typescript::language_typescript(),
vec![
Arc::new(typescript::TypeScriptLspAdapter::new(node_runtime.clone())),
Arc::new(typescript::EsLintLspAdapter::new(node_runtime.clone())),
],
);
language(
"javascript",
tree_sitter_typescript::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(
"html",
tree_sitter_html::language(),
vec![
Arc::new(html::HtmlLspAdapter::new(node_runtime.clone())),
Arc::new(tailwind::TailwindLspAdapter::new(node_runtime.clone())),
],
);
language(
"ruby",
tree_sitter_ruby::language(),
vec![Arc::new(ruby::RubyLanguageServer)],
);
language(
"erb",
tree_sitter_embedded_template::language(),
vec![
Arc::new(ruby::RubyLanguageServer),
Arc::new(tailwind::TailwindLspAdapter::new(node_runtime.clone())),
],
);
language("scheme", tree_sitter_scheme::language(), vec![]);
language("racket", tree_sitter_racket::language(), vec![]);
language(
"lua",
tree_sitter_lua::language(),
vec![Arc::new(lua::LuaLspAdapter)],
);
language(
"yaml",
tree_sitter_yaml::language(),
vec![Arc::new(yaml::YamlLspAdapter::new(node_runtime.clone()))],
);
language(
"svelte",
tree_sitter_svelte::language(),
vec![
Arc::new(svelte::SvelteLspAdapter::new(node_runtime.clone())),
Arc::new(tailwind::TailwindLspAdapter::new(node_runtime.clone())),
],
);
language(
"php",
tree_sitter_php::language(),
vec![
Arc::new(php::IntelephenseLspAdapter::new(node_runtime.clone())),
Arc::new(tailwind::TailwindLspAdapter::new(node_runtime.clone())),
],
);
language("elm", tree_sitter_elm::language(), vec![]);
language("glsl", tree_sitter_glsl::language(), vec![]);
language("nix", tree_sitter_nix::language(), vec![]);
language("nu", tree_sitter_nu::language(), vec![]);
language(
"vue",
tree_sitter_vue::language(),
vec![Arc::new(vue::VueLspAdapter::new(node_runtime))],
);
}
#[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 {
toml::from_slice(
&LanguageDir::get(&format!("{}/config.toml", name))
.unwrap()
.data,
)
.with_context(|| format!("failed to load config.toml for language {name:?}"))
.unwrap()
}
fn load_queries(name: &str) -> LanguageQueries {
LanguageQueries {
highlights: load_query(name, "/highlights"),
brackets: load_query(name, "/brackets"),
indents: load_query(name, "/indents"),
outline: load_query(name, "/outline"),
embedding: load_query(name, "/embedding"),
injections: load_query(name, "/injections"),
overrides: load_query(name, "/overrides"),
}
}
fn load_query(name: &str, filename_prefix: &str) -> Option<Cow<'static, str>> {
let mut result = None;
for path in LanguageDir::iter() {
if let Some(remainder) = path.strip_prefix(name) {
if remainder.starts_with(filename_prefix) {
let contents = asset_str::<LanguageDir>(path.as_ref());
match &mut result {
None => result = Some(contents),
Some(r) => r.to_mut().push_str(contents.as_ref()),
}
}
}
}
result
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -0,0 +1,12 @@
name = "C"
path_suffixes = ["c"]
line_comment = "// "
autoclose_before = ";:.,=}])>"
brackets = [
{ start = "{", end = "}", close = true, newline = true },
{ start = "[", end = "]", close = true, newline = true },
{ start = "(", end = ")", close = true, newline = true },
{ start = "\"", end = "\"", close = true, newline = false, not_in = ["string"] },
{ start = "'", end = "'", close = true, newline = false, not_in = ["string", "comment"] },
{ start = "/*", end = " */", close = true, newline = false, not_in = ["string", "comment"] },
]

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -0,0 +1,12 @@
name = "C++"
path_suffixes = ["cc", "cpp", "h", "hpp", "cxx", "hxx", "inl"]
line_comment = "// "
autoclose_before = ";:.,=}])>"
brackets = [
{ start = "{", end = "}", close = true, newline = true },
{ start = "[", end = "]", close = true, newline = true },
{ start = "(", end = ")", close = true, newline = true },
{ start = "\"", end = "\"", close = true, newline = false, not_in = ["string"] },
{ start = "'", end = "'", close = true, newline = false, not_in = ["string", "comment"] },
{ start = "/*", end = " */", close = true, newline = false, not_in = ["string", "comment"] },
]

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -0,0 +1,13 @@
name = "CSS"
path_suffixes = ["css"]
autoclose_before = ";:.,=}])>"
brackets = [
{ start = "{", end = "}", close = true, newline = true },
{ start = "[", end = "]", close = true, newline = true },
{ start = "(", end = ")", close = true, newline = true },
{ start = "\"", end = "\"", close = true, newline = false, not_in = ["string", "comment"] },
{ start = "'", end = "'", close = true, newline = false, not_in = ["string", "comment"] },
]
word_characters = ["-"]
block_comment = ["/* ", " */"]
prettier_parser_name = "css"

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -0,0 +1,16 @@
name = "Elixir"
path_suffixes = ["ex", "exs"]
line_comment = "# "
autoclose_before = ";:.,=}])>"
brackets = [
{ start = "{", end = "}", close = true, newline = true },
{ start = "[", end = "]", close = true, newline = true },
{ start = "(", end = ")", close = true, newline = true },
{ start = "\"", end = "\"", close = true, newline = false, not_in = ["string", "comment"] },
{ start = "'", end = "'", close = true, newline = false, not_in = ["string", "comment"] },
]
scope_opt_in_language_servers = ["tailwindcss-language-server"]
[overrides.string]
word_characters = ["-"]
opt_into_language_servers = ["tailwindcss-language-server"]

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -0,0 +1,11 @@
name = "Elm"
path_suffixes = ["elm"]
line_comment = "-- "
block_comment = ["{- ", " -}"]
brackets = [
{ start = "{", end = "}", close = true, newline = true },
{ start = "[", end = "]", close = true, newline = true },
{ start = "(", end = ")", close = true, newline = true },
{ start = "\"", end = "\"", close = true, newline = false, not_in = ["string"] },
{ start = "'", end = "'", close = true, newline = false, not_in = ["string", "comment"] },
]

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -0,0 +1,12 @@
name = "Go"
path_suffixes = ["go"]
line_comment = "// "
autoclose_before = ";:.,=}])>"
brackets = [
{ start = "{", end = "}", close = true, newline = true },
{ start = "[", end = "]", close = true, newline = true },
{ start = "(", end = ")", close = true, newline = true },
{ start = "\"", end = "\"", close = true, newline = false, not_in = ["comment", "string"] },
{ start = "'", end = "'", close = true, newline = false, not_in = ["comment", "string"] },
{ start = "/*", end = " */", close = true, newline = false, not_in = ["comment", "string"] },
]

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -0,0 +1,135 @@
use anyhow::{anyhow, bail, Result};
use async_compression::futures::bufread::GzipDecoder;
use async_tar::Archive;
use async_trait::async_trait;
use futures::{io::BufReader, StreamExt};
use language2::{LanguageServerName, LspAdapterDelegate};
use lsp2::LanguageServerBinary;
use smol::fs;
use std::{any::Any, env::consts, path::PathBuf};
use util::{
async_maybe,
github::{latest_github_release, GitHubLspBinaryVersion},
ResultExt,
};
#[derive(Copy, Clone)]
pub struct LuaLspAdapter;
#[async_trait]
impl super::LspAdapter for LuaLspAdapter {
async fn name(&self) -> LanguageServerName {
LanguageServerName("lua-language-server".into())
}
fn short_name(&self) -> &'static str {
"lua"
}
async fn fetch_latest_server_version(
&self,
delegate: &dyn LspAdapterDelegate,
) -> Result<Box<dyn 'static + Send + Any>> {
let release =
latest_github_release("LuaLS/lua-language-server", false, delegate.http_client())
.await?;
let version = release.name.clone();
let platform = match consts::ARCH {
"x86_64" => "x64",
"aarch64" => "arm64",
other => bail!("Running on unsupported platform: {other}"),
};
let asset_name = format!("lua-language-server-{version}-darwin-{platform}.tar.gz");
let asset = release
.assets
.iter()
.find(|asset| asset.name == asset_name)
.ok_or_else(|| anyhow!("no asset found matching {:?}", asset_name))?;
let version = GitHubLspBinaryVersion {
name: release.name.clone(),
url: asset.browser_download_url.clone(),
};
Ok(Box::new(version) as Box<_>)
}
async fn fetch_server_binary(
&self,
version: Box<dyn 'static + Send + Any>,
container_dir: PathBuf,
delegate: &dyn LspAdapterDelegate,
) -> Result<LanguageServerBinary> {
let version = version.downcast::<GitHubLspBinaryVersion>().unwrap();
let binary_path = container_dir.join("bin/lua-language-server");
if fs::metadata(&binary_path).await.is_err() {
let mut response = delegate
.http_client()
.get(&version.url, Default::default(), true)
.await
.map_err(|err| anyhow!("error downloading release: {}", err))?;
let decompressed_bytes = GzipDecoder::new(BufReader::new(response.body_mut()));
let archive = Archive::new(decompressed_bytes);
archive.unpack(container_dir).await?;
}
fs::set_permissions(
&binary_path,
<fs::Permissions as fs::unix::PermissionsExt>::from_mode(0o755),
)
.await?;
Ok(LanguageServerBinary {
path: binary_path,
arguments: Vec::new(),
})
}
async fn cached_server_binary(
&self,
container_dir: PathBuf,
_: &dyn LspAdapterDelegate,
) -> Option<LanguageServerBinary> {
get_cached_server_binary(container_dir).await
}
async fn installation_test_binary(
&self,
container_dir: PathBuf,
) -> Option<LanguageServerBinary> {
get_cached_server_binary(container_dir)
.await
.map(|mut binary| {
binary.arguments = vec!["--version".into()];
binary
})
}
}
async fn get_cached_server_binary(container_dir: PathBuf) -> Option<LanguageServerBinary> {
async_maybe!({
let mut last_binary_path = None;
let mut entries = fs::read_dir(&container_dir).await?;
while let Some(entry) = entries.next().await {
let entry = entry?;
if entry.file_type().await?.is_file()
&& entry
.file_name()
.to_str()
.map_or(false, |name| name == "lua-language-server")
{
last_binary_path = Some(entry.path());
}
}
if let Some(path) = last_binary_path {
Ok(LanguageServerBinary {
path,
arguments: Vec::new(),
})
} else {
Err(anyhow!("no cached binary"))
}
})
.await
.log_err()
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -0,0 +1,11 @@
name = "Markdown"
path_suffixes = ["md", "mdx"]
brackets = [
{ start = "{", end = "}", close = true, newline = true },
{ start = "[", end = "]", close = true, newline = true },
{ start = "(", end = ")", close = true, newline = true },
{ start = "<", end = ">", close = true, newline = true },
{ start = "\"", end = "\"", close = false, newline = false },
{ start = "'", end = "'", close = false, newline = false },
{ start = "`", end = "`", close = false, newline = false },
]

View file

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

View file

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

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