Detailed changes
@@ -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",
@@ -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(
@@ -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
}
}
@@ -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());
- }),
- }
- parsers_with_plugins
- },
+ .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 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);
+ 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
+ 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,
+ ),
+ ));
}
- }).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)
}
- (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,
+ 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,
- path: buffer_path,
prettier_options,
- },
- })
- })?.context("prettier params calculation")?;
+ 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")?;
let response = local
.server
.request::<Format>(params)
@@ -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(()));
@@ -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()
@@ -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()
@@ -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"
@@ -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
+}
@@ -0,0 +1,3 @@
+("(" @open ")" @close)
+("[" @open "]" @close)
+("{" @open "}" @close)
@@ -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"] },
+]
@@ -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 "^-")
+)
@@ -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
+ });
+ }
+}
@@ -0,0 +1,3 @@
+("[" @open "]" @close)
+("{" @open "}" @close)
+("\"" @open "\"" @close)
@@ -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"] },
+]
@@ -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
+ )
@@ -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
+
@@ -0,0 +1,9 @@
+[
+ (field_expression)
+ (assignment_expression)
+ (if_statement)
+ (for_statement)
+] @indent
+
+(_ "{" "}" @end) @indent
+(_ "(" ")" @end) @indent
@@ -0,0 +1,7 @@
+(preproc_def
+ value: (preproc_arg) @content
+ (#set! "language" "c"))
+
+(preproc_function_def
+ value: (preproc_arg) @content
+ (#set! "language" "c"))
@@ -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
@@ -0,0 +1,2 @@
+(comment) @comment
+(string_literal) @string
@@ -0,0 +1,3 @@
+("[" @open "]" @close)
+("{" @open "}" @close)
+("\"" @open "\"" @close)
@@ -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"] },
+]
@@ -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
+)
@@ -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
@@ -0,0 +1,7 @@
+[
+ (field_expression)
+ (assignment_expression)
+] @indent
+
+(_ "{" "}" @end) @indent
+(_ "(" ")" @end) @indent
@@ -0,0 +1,7 @@
+(preproc_def
+ value: (preproc_arg) @content
+ (#set! "language" "c++"))
+
+(preproc_function_def
+ value: (preproc_arg) @content
+ (#set! "language" "c++"))
@@ -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
@@ -0,0 +1,2 @@
+(comment) @comment
+(string_literal) @string
@@ -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()
+}
@@ -0,0 +1,3 @@
+("(" @open ")" @close)
+("[" @open "]" @close)
+("{" @open "}" @close)
@@ -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"
@@ -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
@@ -0,0 +1 @@
+(_ "{" "}" @end) @indent
@@ -0,0 +1,2 @@
+(comment) @comment
+(string_value) @string
@@ -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(),
+ })
+}
@@ -0,0 +1,5 @@
+("(" @open ")" @close)
+("[" @open "]" @close)
+("{" @open "}" @close)
+("\"" @open "\"" @close)
+("do" @open "end" @close)
@@ -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"]
@@ -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
@@ -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"))
@@ -0,0 +1,6 @@
+(call) @indent
+
+(_ "[" "]" @end) @indent
+(_ "{" "}" @end) @indent
+(_ "(" ")" @end) @indent
+(_ "do" "end" @end) @indent
@@ -0,0 +1,7 @@
+; Phoenix HTML template
+
+((sigil
+ (sigil_name) @_sigil_name
+ (quoted_content) @content)
+ (#eq? @_sigil_name "H")
+ (#set! language "heex"))
@@ -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
@@ -0,0 +1,2 @@
+(comment) @comment
+[(string) (charlist)] @string
@@ -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"] },
+]
@@ -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
@@ -0,0 +1,2 @@
+((glsl_content) @content
+ (#set! "language" "glsl"))
@@ -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
@@ -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"]
@@ -0,0 +1,12 @@
+(comment_directive) @comment
+
+[
+ "<%#"
+ "<%"
+ "<%="
+ "<%_"
+ "<%-"
+ "%>"
+ "-%>"
+ "_%>"
+] @keyword
@@ -0,0 +1,7 @@
+((code) @content
+ (#set! "language" "ruby")
+ (#set! "combined"))
+
+((content) @content
+ (#set! "language" "html")
+ (#set! "combined"))
@@ -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 },
+]
@@ -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_")
+)
@@ -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)],
+ })
+ );
+ }
+}
@@ -0,0 +1,3 @@
+("[" @open "]" @close)
+("{" @open "}" @close)
+("\"" @open "\"" @close)
@@ -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"] },
+]
@@ -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
+)
@@ -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
@@ -0,0 +1,9 @@
+[
+ (assignment_statement)
+ (call_expression)
+ (selector_expression)
+] @indent
+
+(_ "[" "]" @end) @indent
+(_ "{" "}" @end) @indent
+(_ "(" ")" @end) @indent
@@ -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
@@ -0,0 +1,6 @@
+(comment) @comment
+[
+ (interpreted_string_literal)
+ (raw_string_literal)
+ (rune_literal)
+] @string
@@ -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"]
@@ -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
+ ])
@@ -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"))
@@ -0,0 +1,4 @@
+[
+ (attribute_value)
+ (quoted_attribute_value)
+] @string
@@ -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()
+}
@@ -0,0 +1,2 @@
+("<" @open ">" @close)
+("\"" @open "\"" @close)
@@ -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"
@@ -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
@@ -0,0 +1,6 @@
+(start_tag ">" @end) @indent
+(self_closing_tag "/>" @end) @indent
+
+(element
+ (start_tag) @start
+ (end_tag)? @end) @indent
@@ -0,0 +1,7 @@
+(script_element
+ (raw_text) @content
+ (#set! "language" "javascript"))
+
+(style_element
+ (raw_text) @content
+ (#set! "language" "css"))
@@ -0,0 +1,2 @@
+(comment) @comment
+(quoted_attribute_value) @string
@@ -0,0 +1,5 @@
+("(" @open ")" @close)
+("[" @open "]" @close)
+("{" @open "}" @close)
+("<" @open ">" @close)
+("\"" @open "\"" @close)
@@ -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"]
@@ -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
+)
@@ -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
@@ -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
@@ -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
@@ -0,0 +1,13 @@
+(comment) @comment
+
+[
+ (string)
+ (template_string)
+] @string
+
+[
+ (jsx_element)
+ (jsx_fragment)
+ (jsx_self_closing_element)
+ (jsx_expression)
+] @element
@@ -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()
+}
@@ -0,0 +1,3 @@
+("[" @open "]" @close)
+("{" @open "}" @close)
+("\"" @open "\"" @close)
@@ -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"
@@ -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)
@@ -0,0 +1,21 @@
+(comment) @comment
+
+(string) @string
+
+(pair
+ key: (string) @property)
+
+(number) @number
+
+[
+ (true)
+ (false)
+ (null)
+] @constant
+
+[
+ "{"
+ "}"
+ "["
+ "]"
+] @punctuation.bracket
@@ -0,0 +1,2 @@
+(array "]" @end) @indent
+(object "}" @end) @indent
@@ -0,0 +1,2 @@
+(pair
+ key: (string (string_content) @name)) @item
@@ -0,0 +1 @@
+(string) @string
@@ -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()
+ }
+}
@@ -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()
+}
@@ -0,0 +1,3 @@
+("[" @open "]" @close)
+("{" @open "}" @close)
+("(" @open ")" @close)
@@ -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 = "--[ ... ]--"
@@ -0,0 +1,10 @@
+(
+ (comment)* @context
+ .
+ (function_declaration
+ "function" @name
+ name: (_) @name
+ (comment)* @collapse
+ body: (block) @collapse
+ ) @item
+)
@@ -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
@@ -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
@@ -0,0 +1,3 @@
+(function_declaration
+ "function" @context
+ name: (_) @name) @item
@@ -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 },
+]
@@ -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
@@ -0,0 +1,4 @@
+(fenced_code_block
+ (info_string
+ (language) @language)
+ (code_fence_content) @content)
@@ -0,0 +1,11 @@
+name = "Nix"
+path_suffixes = ["nix"]
+line_comment = "# "
+block_comment = ["/* ", " */"]
+autoclose_before = ";:.,=}])>` \n\t\""
+brackets = [
+ { start = "{", end = "}", close = true, newline = true },
+ { start = "[", end = "]", close = true, newline = true },
+ { start = "(", end = ")", close = true, newline = true },
+ { start = "<", end = ">", close = true, newline = true },
+]
@@ -0,0 +1,95 @@
+(comment) @comment
+
+[
+ "if"
+ "then"
+ "else"
+ "let"
+ "inherit"
+ "in"
+ "rec"
+ "with"
+ "assert"
+ "or"
+] @keyword
+
+[
+ (string_expression)
+ (indented_string_expression)
+] @string
+
+[
+ (path_expression)
+ (hpath_expression)
+ (spath_expression)
+] @string.special.path
+
+(uri_expression) @link_uri
+
+[
+ (integer_expression)
+ (float_expression)
+] @number
+
+(interpolation
+ "${" @punctuation.special
+ "}" @punctuation.special) @embedded
+
+(escape_sequence) @escape
+(dollar_escape) @escape
+
+(function_expression
+ universal: (identifier) @parameter
+)
+
+(formal
+ name: (identifier) @parameter
+ "?"? @punctuation.delimiter)
+
+(select_expression
+ attrpath: (attrpath (identifier)) @property)
+
+(apply_expression
+ function: [
+ (variable_expression (identifier)) @function
+ (select_expression
+ attrpath: (attrpath
+ attr: (identifier) @function .))])
+
+(unary_expression
+ operator: _ @operator)
+
+(binary_expression
+ operator: _ @operator)
+
+(variable_expression (identifier) @variable)
+
+(binding
+ attrpath: (attrpath (identifier)) @property)
+
+"=" @operator
+
+[
+ ";"
+ "."
+ ","
+] @punctuation.delimiter
+
+[
+ "("
+ ")"
+ "["
+ "]"
+ "{"
+ "}"
+] @punctuation.bracket
+
+(identifier) @variable
+
+((identifier) @function.builtin
@@ -0,0 +1,4 @@
+("(" @open ")" @close)
+("[" @open "]" @close)
+("{" @open "}" @close)
+(parameter_pipes "|" @open "|" @close)
@@ -0,0 +1,9 @@
+name = "Nu"
+path_suffixes = ["nu"]
+line_comment = "# "
+autoclose_before = ";:.,=}])>` \n\t\""
+brackets = [
+ { start = "{", end = "}", close = true, newline = true },
+ { start = "[", end = "]", close = true, newline = true },
+ { start = "(", end = ")", close = true, newline = true },
+]
@@ -0,0 +1,302 @@
+;;; ---
+;;; keywords
+[
+ "def"
+ "def-env"
+ "alias"
+ "export-env"
+ "export"
+ "extern"
+ "module"
+
+ "let"
+ "let-env"
+ "mut"
+ "const"
+
+ "hide-env"
+
+ "source"
+ "source-env"
+
+ "overlay"
+ "register"
+
+ "loop"
+ "while"
+ "error"
+
+ "do"
+ "if"
+ "else"
+ "try"
+ "catch"
+ "match"
+
+ "break"
+ "continue"
+ "return"
+
+] @keyword
+
+(hide_mod "hide" @keyword)
+(decl_use "use" @keyword)
+
+(ctrl_for
+ "for" @keyword
+ "in" @keyword
+)
+(overlay_list "list" @keyword)
+(overlay_hide "hide" @keyword)
+(overlay_new "new" @keyword)
+(overlay_use
+ "use" @keyword
+ "as" @keyword
+)
+(ctrl_error "make" @keyword)
+
+;;; ---
+;;; literals
+(val_number) @constant
+(val_duration
+ unit: [
+ "ns" "µs" "us" "ms" "sec" "min" "hr" "day" "wk"
+ ] @variable
+)
+(val_filesize
+ unit: [
+ "b" "B"
+
+ "kb" "kB" "Kb" "KB"
+ "mb" "mB" "Mb" "MB"
+ "gb" "gB" "Gb" "GB"
+ "tb" "tB" "Tb" "TB"
+ "pb" "pB" "Pb" "PB"
+ "eb" "eB" "Eb" "EB"
+ "zb" "zB" "Zb" "ZB"
+
+ "kib" "kiB" "kIB" "kIb" "Kib" "KIb" "KIB"
+ "mib" "miB" "mIB" "mIb" "Mib" "MIb" "MIB"
+ "gib" "giB" "gIB" "gIb" "Gib" "GIb" "GIB"
+ "tib" "tiB" "tIB" "tIb" "Tib" "TIb" "TIB"
+ "pib" "piB" "pIB" "pIb" "Pib" "PIb" "PIB"
+ "eib" "eiB" "eIB" "eIb" "Eib" "EIb" "EIB"
+ "zib" "ziB" "zIB" "zIb" "Zib" "ZIb" "ZIB"
+ ] @variable
+)
+(val_binary
+ [
+ "0b"
+ "0o"
+ "0x"
+ ] @constant
+ "[" @punctuation.bracket
+ digit: [
+ "," @punctuation.delimiter
+ (hex_digit) @constant
+ ]
+ "]" @punctuation.bracket
+) @constant
+(val_bool) @constant.builtin
+(val_nothing) @constant.builtin
+(val_string) @string
+(val_date) @constant
+(inter_escape_sequence) @constant
+(escape_sequence) @constant
+(val_interpolated [
+ "$\""
+ "$\'"
+ "\""
+ "\'"
+] @string)
+(unescaped_interpolated_content) @string
+(escaped_interpolated_content) @string
+(expr_interpolated ["(" ")"] @variable)
+
+;;; ---
+;;; operators
+(expr_binary [
+ "+"
+ "-"
+ "*"
+ "/"
+ "mod"
+ "//"
+ "++"
+ "**"
+ "=="
+ "!="
+ "<"
+ "<="
+ ">"
+ ">="
+ "=~"
+ "!~"
+ "and"
+ "or"
+ "xor"
+ "bit-or"
+ "bit-xor"
+ "bit-and"
+ "bit-shl"
+ "bit-shr"
+ "in"
+ "not-in"
+ "starts-with"
+ "ends-with"
+] @operator)
+
+(expr_binary opr: ([
+ "and"
+ "or"
+ "xor"
+ "bit-or"
+ "bit-xor"
+ "bit-and"
+ "bit-shl"
+ "bit-shr"
+ "in"
+ "not-in"
+ "starts-with"
+ "ends-with"
+]) @keyword)
+
+(where_command [
+ "+"
+ "-"
+ "*"
+ "/"
+ "mod"
+ "//"
+ "++"
+ "**"
+ "=="
+ "!="
+ "<"
+ "<="
+ ">"
+ ">="
+ "=~"
+ "!~"
+ "and"
+ "or"
+ "xor"
+ "bit-or"
+ "bit-xor"
+ "bit-and"
+ "bit-shl"
+ "bit-shr"
+ "in"
+ "not-in"
+ "starts-with"
+ "ends-with"
+] @operator)
+
+(assignment [
+ "="
+ "+="
+ "-="
+ "*="
+ "/="
+ "++="
+] @operator)
+
+(expr_unary ["not" "-"] @operator)
+
+(val_range [
+ ".."
+ "..="
+ "..<"
+] @operator)
+
+["=>" "=" "|"] @operator
+
+[
+ "o>" "out>"
+ "e>" "err>"
+ "e+o>" "err+out>"
+ "o+e>" "out+err>"
+] @special
+
+;;; ---
+;;; punctuation
+[
+ ","
+ ";"
+] @punctuation.delimiter
+
+(param_short_flag "-" @punctuation.delimiter)
+(param_long_flag ["--"] @punctuation.delimiter)
+(long_flag ["--"] @punctuation.delimiter)
+(param_rest "..." @punctuation.delimiter)
+(param_type [":"] @punctuation.special)
+(param_value ["="] @punctuation.special)
+(param_cmd ["@"] @punctuation.special)
+(param_opt ["?"] @punctuation.special)
+
+[
+ "(" ")"
+ "{" "}"
+ "[" "]"
+] @punctuation.bracket
+
+(val_record
+ (record_entry ":" @punctuation.delimiter))
+;;; ---
+;;; identifiers
+(param_rest
+ name: (_) @variable)
+(param_opt
+ name: (_) @variable)
+(parameter
+ param_name: (_) @variable)
+(param_cmd
+ (cmd_identifier) @string)
+(param_long_flag) @variable
+(param_short_flag) @variable
+
+(short_flag) @variable
+(long_flag) @variable
+
+(scope_pattern [(wild_card) @function])
+
+(cmd_identifier) @function
+
+(command
+ "^" @punctuation.delimiter
+ head: (_) @function
+)
+
+"where" @function
+
+(path
+ ["." "?"] @punctuation.delimiter
+) @variable
+
+(val_variable
+ "$" @operator
+ [
+ (identifier) @variable
+ "in" @type.builtin
+ "nu" @type.builtin
+ "env" @type.builtin
+ "nothing" @type.builtin
+ ] ; If we have a special styling, use it here
+)
+;;; ---
+;;; types
+(flat_type) @type.builtin
+(list_type
+ "list" @type
+ ["<" ">"] @punctuation.bracket
+)
+(collection_type
+ ["record" "table"] @type
+ "<" @punctuation.bracket
+ key: (_) @variable
+ ["," ":"] @punctuation.delimiter
+ ">" @punctuation.bracket
+)
+
+(shebang) @comment
+(comment) @comment
@@ -0,0 +1,3 @@
+(_ "[" "]" @end) @indent
+(_ "{" "}" @end) @indent
+(_ "(" ")" @end) @indent
@@ -0,0 +1,137 @@
+use anyhow::{anyhow, Result};
+
+use async_trait::async_trait;
+use collections::HashMap;
+
+use language2::{LanguageServerName, LspAdapter, LspAdapterDelegate};
+use lsp2::LanguageServerBinary;
+use node_runtime::NodeRuntime;
+
+use smol::{fs, stream::StreamExt};
+use std::{
+ any::Any,
+ ffi::OsString,
+ path::{Path, PathBuf},
+ sync::Arc,
+};
+use util::ResultExt;
+
+fn intelephense_server_binary_arguments(server_path: &Path) -> Vec<OsString> {
+ vec![server_path.into(), "--stdio".into()]
+}
+
+pub struct IntelephenseVersion(String);
+
+pub struct IntelephenseLspAdapter {
+ node: Arc<dyn NodeRuntime>,
+}
+
+impl IntelephenseLspAdapter {
+ const SERVER_PATH: &'static str = "node_modules/intelephense/lib/intelephense.js";
+
+ #[allow(unused)]
+ pub fn new(node: Arc<dyn NodeRuntime>) -> Self {
+ Self { node }
+ }
+}
+
+#[async_trait]
+impl LspAdapter for IntelephenseLspAdapter {
+ async fn name(&self) -> LanguageServerName {
+ LanguageServerName("intelephense".into())
+ }
+
+ fn short_name(&self) -> &'static str {
+ "php"
+ }
+
+ async fn fetch_latest_server_version(
+ &self,
+ _delegate: &dyn LspAdapterDelegate,
+ ) -> Result<Box<dyn 'static + Send + Any>> {
+ Ok(Box::new(IntelephenseVersion(
+ self.node.npm_package_latest_version("intelephense").await?,
+ )) as Box<_>)
+ }
+
+ async fn fetch_server_binary(
+ &self,
+ version: Box<dyn 'static + Send + Any>,
+ container_dir: PathBuf,
+ _delegate: &dyn LspAdapterDelegate,
+ ) -> Result<LanguageServerBinary> {
+ let version = version.downcast::<IntelephenseVersion>().unwrap();
+ let server_path = container_dir.join(Self::SERVER_PATH);
+
+ if fs::metadata(&server_path).await.is_err() {
+ self.node
+ .npm_install_packages(&container_dir, &[("intelephense", version.0.as_str())])
+ .await?;
+ }
+ Ok(LanguageServerBinary {
+ path: self.node.binary_path().await?,
+ arguments: intelephense_server_binary_arguments(&server_path),
+ })
+ }
+
+ async fn cached_server_binary(
+ &self,
+ container_dir: PathBuf,
+ _: &dyn LspAdapterDelegate,
+ ) -> Option<LanguageServerBinary> {
+ get_cached_server_binary(container_dir, &*self.node).await
+ }
+
+ async fn installation_test_binary(
+ &self,
+ container_dir: PathBuf,
+ ) -> Option<LanguageServerBinary> {
+ get_cached_server_binary(container_dir, &*self.node).await
+ }
+
+ async fn label_for_completion(
+ &self,
+ _item: &lsp2::CompletionItem,
+ _language: &Arc<language2::Language>,
+ ) -> Option<language2::CodeLabel> {
+ None
+ }
+
+ async fn initialization_options(&self) -> Option<serde_json::Value> {
+ None
+ }
+ async fn language_ids(&self) -> HashMap<String, String> {
+ HashMap::from_iter([("PHP".into(), "php".into())])
+ }
+}
+
+async fn get_cached_server_binary(
+ container_dir: PathBuf,
+ node: &dyn NodeRuntime,
+) -> Option<LanguageServerBinary> {
+ (|| async move {
+ let mut last_version_dir = None;
+ let mut entries = fs::read_dir(&container_dir).await?;
+ while let Some(entry) = entries.next().await {
+ let entry = entry?;
+ if entry.file_type().await?.is_dir() {
+ last_version_dir = Some(entry.path());
+ }
+ }
+ let last_version_dir = last_version_dir.ok_or_else(|| anyhow!("no cached binary"))?;
+ let server_path = last_version_dir.join(IntelephenseLspAdapter::SERVER_PATH);
+ if server_path.exists() {
+ Ok(LanguageServerBinary {
+ path: node.binary_path().await?,
+ arguments: intelephense_server_binary_arguments(&server_path),
+ })
+ } else {
+ Err(anyhow!(
+ "missing executable in directory {:?}",
+ last_version_dir
+ ))
+ }
+ })()
+ .await
+ .log_err()
+}
@@ -0,0 +1,14 @@
+name = "PHP"
+path_suffixes = ["php"]
+first_line_pattern = '^#!.*php'
+line_comment = "// "
+autoclose_before = ";:.,=}])>"
+brackets = [
+ { start = "{", end = "}", close = true, newline = true },
+ { start = "[", end = "]", close = true, newline = true },
+ { start = "(", end = ")", close = true, newline = true },
+ { start = "\"", end = "\"", close = true, newline = false, not_in = ["string"] },
+]
+collapsed_placeholder = "/* ... */"
+word_characters = ["$"]
+scope_opt_in_language_servers = ["tailwindcss-language-server"]
@@ -0,0 +1,36 @@
+(
+ (comment)* @context
+ .
+ [
+ (function_definition
+ "function" @name
+ name: (_) @name
+ body: (_
+ "{" @keep
+ "}" @keep) @collapse
+ )
+
+ (trait_declaration
+ "trait" @name
+ name: (_) @name)
+
+ (method_declaration
+ "function" @name
+ name: (_) @name
+ body: (_
+ "{" @keep
+ "}" @keep) @collapse
+ )
+
+ (interface_declaration
+ "interface" @name
+ name: (_) @name
+ )
+
+ (enum_declaration
+ "enum" @name
+ name: (_) @name
+ )
+
+ ] @item
+ )
@@ -0,0 +1,123 @@
+(php_tag) @tag
+"?>" @tag
+
+; Types
+
+(primitive_type) @type.builtin
+(cast_type) @type.builtin
+(named_type (name) @type) @type
+(named_type (qualified_name) @type) @type
+
+; Functions
+
+(array_creation_expression "array" @function.builtin)
+(list_literal "list" @function.builtin)
+
+(method_declaration
+ name: (name) @function.method)
+
+(function_call_expression
+ function: [(qualified_name (name)) (name)] @function)
+
+(scoped_call_expression
+ name: (name) @function)
+
+(member_call_expression
+ name: (name) @function.method)
+
+(function_definition
+ name: (name) @function)
+
+; Member
+
+(property_element
+ (variable_name) @property)
+
+(member_access_expression
+ name: (variable_name (name)) @property)
+(member_access_expression
+ name: (name) @property)
+
+; Variables
+
+(relative_scope) @variable.builtin
+
+((name) @constant
+ (#match? @constant "^_?[A-Z][A-Z\\d_]+$"))
+((name) @constant.builtin
+ (#match? @constant.builtin "^__[A-Z][A-Z\d_]+__$"))
+
+((name) @constructor
+ (#match? @constructor "^[A-Z]"))
+
+((name) @variable.builtin
+ (#eq? @variable.builtin "this"))
+
+(variable_name) @variable
+
+; Basic tokens
+[
+ (string)
+ (string_value)
+ (encapsed_string)
+ (heredoc)
+ (heredoc_body)
+ (nowdoc_body)
+] @string
+(boolean) @constant.builtin
+(null) @constant.builtin
+(integer) @number
+(float) @number
+(comment) @comment
+
+"$" @operator
+
+; Keywords
+
+"abstract" @keyword
+"as" @keyword
+"break" @keyword
+"case" @keyword
+"catch" @keyword
+"class" @keyword
+"const" @keyword
+"continue" @keyword
+"declare" @keyword
+"default" @keyword
+"do" @keyword
+"echo" @keyword
+"else" @keyword
+"elseif" @keyword
+"enum" @keyword
+"enddeclare" @keyword
+"endforeach" @keyword
+"endif" @keyword
+"endswitch" @keyword
+"endwhile" @keyword
+"extends" @keyword
+"final" @keyword
+"finally" @keyword
+"foreach" @keyword
+"function" @keyword
+"global" @keyword
+"if" @keyword
+"implements" @keyword
+"include_once" @keyword
+"include" @keyword
+"insteadof" @keyword
+"interface" @keyword
+"namespace" @keyword
+"new" @keyword
+"private" @keyword
+"protected" @keyword
+"public" @keyword
+"require_once" @keyword
+"require" @keyword
+"return" @keyword
+"static" @keyword
+"switch" @keyword
+"throw" @keyword
+"trait" @keyword
+"try" @keyword
+"use" @keyword
+"while" @keyword
@@ -0,0 +1,3 @@
+((text) @content
+ (#set! "language" "html")
+ (#set! "combined"))
@@ -0,0 +1,29 @@
+(class_declaration
+ "class" @context
+ name: (name) @name
+ ) @item
+
+(function_definition
+ "function" @context
+ name: (_) @name
+ ) @item
+
+(method_declaration
+ "function" @context
+ name: (_) @name
+ ) @item
+
+(interface_declaration
+ "interface" @context
+ name: (_) @name
+ ) @item
+
+(enum_declaration
+ "enum" @context
+ name: (_) @name
+ ) @item
+
+(trait_declaration
+ "trait" @context
+ name: (_) @name
+ ) @item
@@ -0,0 +1,40 @@
+(namespace_definition
+ name: (namespace_name) @name) @module
+
+(interface_declaration
+ name: (name) @name) @definition.interface
+
+(trait_declaration
+ name: (name) @name) @definition.interface
+
+(class_declaration
+ name: (name) @name) @definition.class
+
+(class_interface_clause [(name) (qualified_name)] @name) @impl
+
+(property_declaration
+ (property_element (variable_name (name) @name))) @definition.field
+
+(function_definition
+ name: (name) @name) @definition.function
+
+(method_declaration
+ name: (name) @name) @definition.function
+
+(object_creation_expression
+ [
+ (qualified_name (name) @name)
+ (variable_name (name) @name)
+ ]) @reference.class
+
+(function_call_expression
+ function: [
+ (qualified_name (name) @name)
+ (variable_name (name)) @name
+ ]) @reference.call
+
+(scoped_call_expression
+ name: (name) @name) @reference.call
+
+(member_call_expression
+ name: (name) @name) @reference.call
@@ -0,0 +1,296 @@
+use anyhow::Result;
+use async_trait::async_trait;
+use language2::{LanguageServerName, LspAdapter, LspAdapterDelegate};
+use lsp2::LanguageServerBinary;
+use node_runtime::NodeRuntime;
+use smol::fs;
+use std::{
+ any::Any,
+ ffi::OsString,
+ path::{Path, PathBuf},
+ sync::Arc,
+};
+use util::ResultExt;
+
+const SERVER_PATH: &'static str = "node_modules/pyright/langserver.index.js";
+
+fn server_binary_arguments(server_path: &Path) -> Vec<OsString> {
+ vec![server_path.into(), "--stdio".into()]
+}
+
+pub struct PythonLspAdapter {
+ node: Arc<dyn NodeRuntime>,
+}
+
+impl PythonLspAdapter {
+ pub fn new(node: Arc<dyn NodeRuntime>) -> Self {
+ PythonLspAdapter { node }
+ }
+}
+
+#[async_trait]
+impl LspAdapter for PythonLspAdapter {
+ async fn name(&self) -> LanguageServerName {
+ LanguageServerName("pyright".into())
+ }
+
+ fn short_name(&self) -> &'static str {
+ "pyright"
+ }
+
+ async fn fetch_latest_server_version(
+ &self,
+ _: &dyn LspAdapterDelegate,
+ ) -> Result<Box<dyn 'static + Any + Send>> {
+ Ok(Box::new(self.node.npm_package_latest_version("pyright").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, &[("pyright", 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 process_completion(&self, item: &mut lsp2::CompletionItem) {
+ // Pyright assigns each completion item a `sortText` of the form `XX.YYYY.name`.
+ // Where `XX` is the sorting category, `YYYY` is based on most recent usage,
+ // and `name` is the symbol name itself.
+ //
+ // Because the the symbol name is included, there generally are not ties when
+ // sorting by the `sortText`, so the symbol's fuzzy match score is not taken
+ // into account. Here, we remove the symbol name from the sortText in order
+ // to allow our own fuzzy score to be used to break ties.
+ //
+ // see https://github.com/microsoft/pyright/blob/95ef4e103b9b2f129c9320427e51b73ea7cf78bd/packages/pyright-internal/src/languageService/completionProvider.ts#LL2873
+ let Some(sort_text) = &mut item.sort_text else {
+ return;
+ };
+ let mut parts = sort_text.split('.');
+ let Some(first) = parts.next() else { return };
+ let Some(second) = parts.next() else { return };
+ let Some(_) = parts.next() else { return };
+ sort_text.replace_range(first.len() + second.len() + 1.., "");
+ }
+
+ async fn label_for_completion(
+ &self,
+ item: &lsp2::CompletionItem,
+ language: &Arc<language2::Language>,
+ ) -> Option<language2::CodeLabel> {
+ let label = &item.label;
+ let grammar = language.grammar()?;
+ let highlight_id = match item.kind? {
+ lsp2::CompletionItemKind::METHOD => grammar.highlight_id_for_name("function.method")?,
+ lsp2::CompletionItemKind::FUNCTION => grammar.highlight_id_for_name("function")?,
+ lsp2::CompletionItemKind::CLASS => grammar.highlight_id_for_name("type")?,
+ lsp2::CompletionItemKind::CONSTANT => grammar.highlight_id_for_name("constant")?,
+ _ => return None,
+ };
+ Some(language2::CodeLabel {
+ text: label.clone(),
+ runs: vec![(0..label.len(), highlight_id)],
+ filter_range: 0..label.len(),
+ })
+ }
+
+ async fn label_for_symbol(
+ &self,
+ name: &str,
+ kind: lsp2::SymbolKind,
+ language: &Arc<language2::Language>,
+ ) -> Option<language2::CodeLabel> {
+ let (text, filter_range, display_range) = match kind {
+ lsp2::SymbolKind::METHOD | lsp2::SymbolKind::FUNCTION => {
+ let text = format!("def {}():\n", name);
+ let filter_range = 4..4 + name.len();
+ let display_range = 0..filter_range.end;
+ (text, filter_range, display_range)
+ }
+ 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!("{} = 0", name);
+ let filter_range = 0..name.len();
+ let display_range = 0..filter_range.end;
+ (text, filter_range, display_range)
+ }
+ _ => return None,
+ };
+
+ Some(language2::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,
+ node: &dyn NodeRuntime,
+) -> Option<LanguageServerBinary> {
+ let server_path = container_dir.join(SERVER_PATH);
+ if server_path.exists() {
+ Some(LanguageServerBinary {
+ path: node.binary_path().await.log_err()?,
+ arguments: server_binary_arguments(&server_path),
+ })
+ } else {
+ log::error!("missing executable in directory {:?}", server_path);
+ None
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use gpui2::{Context, ModelContext, TestAppContext};
+ use language2::{language_settings::AllLanguageSettings, AutoindentMode, Buffer};
+ use settings2::SettingsStore;
+ use std::num::NonZeroU32;
+
+ #[gpui2::test]
+ async fn test_python_autoindent(cx: &mut TestAppContext) {
+ // cx.executor().set_block_on_ticks(usize::MAX..=usize::MAX);
+ let language =
+ crate::languages::language("python", tree_sitter_python::language(), None).await;
+ 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);
+ });
+ });
+ });
+
+ cx.build_model(|cx| {
+ let mut buffer =
+ Buffer::new(0, cx.entity_id().as_u64(), "").with_language(language, cx);
+ let append = |buffer: &mut Buffer, text: &str, cx: &mut ModelContext<Buffer>| {
+ let ix = buffer.len();
+ buffer.edit([(ix..ix, text)], Some(AutoindentMode::EachLine), cx);
+ };
+
+ // indent after "def():"
+ append(&mut buffer, "def a():\n", cx);
+ assert_eq!(buffer.text(), "def a():\n ");
+
+ // preserve indent after blank line
+ append(&mut buffer, "\n ", cx);
+ assert_eq!(buffer.text(), "def a():\n \n ");
+
+ // indent after "if"
+ append(&mut buffer, "if a:\n ", cx);
+ assert_eq!(buffer.text(), "def a():\n \n if a:\n ");
+
+ // preserve indent after statement
+ append(&mut buffer, "b()\n", cx);
+ assert_eq!(buffer.text(), "def a():\n \n if a:\n b()\n ");
+
+ // preserve indent after statement
+ append(&mut buffer, "else", cx);
+ assert_eq!(buffer.text(), "def a():\n \n if a:\n b()\n else");
+
+ // dedent "else""
+ append(&mut buffer, ":", cx);
+ assert_eq!(buffer.text(), "def a():\n \n if a:\n b()\n else:");
+
+ // indent lines after else
+ append(&mut buffer, "\n", cx);
+ assert_eq!(
+ buffer.text(),
+ "def a():\n \n if a:\n b()\n else:\n "
+ );
+
+ // indent after an open paren. the closing paren is not indented
+ // because there is another token before it on the same line.
+ append(&mut buffer, "foo(\n1)", cx);
+ assert_eq!(
+ buffer.text(),
+ "def a():\n \n if a:\n b()\n else:\n foo(\n 1)"
+ );
+
+ // dedent the closing paren if it is shifted to the beginning of the line
+ let argument_ix = buffer.text().find('1').unwrap();
+ buffer.edit(
+ [(argument_ix..argument_ix + 1, "")],
+ Some(AutoindentMode::EachLine),
+ cx,
+ );
+ assert_eq!(
+ buffer.text(),
+ "def a():\n \n if a:\n b()\n else:\n foo(\n )"
+ );
+
+ // preserve indent after the close paren
+ append(&mut buffer, "\n", cx);
+ assert_eq!(
+ buffer.text(),
+ "def a():\n \n if a:\n b()\n else:\n foo(\n )\n "
+ );
+
+ // manually outdent the last line
+ let end_whitespace_ix = buffer.len() - 4;
+ buffer.edit(
+ [(end_whitespace_ix..buffer.len(), "")],
+ Some(AutoindentMode::EachLine),
+ cx,
+ );
+ assert_eq!(
+ buffer.text(),
+ "def a():\n \n if a:\n b()\n else:\n foo(\n )\n"
+ );
+
+ // preserve the newly reduced indentation on the next newline
+ append(&mut buffer, "\n", cx);
+ assert_eq!(
+ buffer.text(),
+ "def a():\n \n if a:\n b()\n else:\n foo(\n )\n\n"
+ );
+
+ // reset to a simple if statement
+ buffer.edit([(0..buffer.len(), "if a:\n b(\n )")], None, cx);
+
+ // dedent "else" on the line after a closing paren
+ append(&mut buffer, "\n else:\n", cx);
+ assert_eq!(buffer.text(), "if a:\n b(\n )\nelse:\n ");
+
+ buffer
+ });
+ }
+}
@@ -0,0 +1,3 @@
+("(" @open ")" @close)
+("[" @open "]" @close)
+("{" @open "}" @close)
@@ -0,0 +1,16 @@
+name = "Python"
+path_suffixes = ["py", "pyi", "mpy"]
+first_line_pattern = '^#!.*\bpython[0-9.]*\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 = true, newline = false, not_in = ["string"] },
+ { start = "'", end = "'", close = false, newline = false, not_in = ["string"] },
+]
+
+auto_indent_using_last_non_empty_line = false
+increase_indent_pattern = ":\\s*$"
+decrease_indent_pattern = "^\\s*(else|elif|except|finally)\\b.*:"
@@ -0,0 +1,9 @@
+(class_definition
+ "class" @context
+ name: (identifier) @name
+ ) @item
+
+(function_definition
+ "async"? @context
+ "def" @context
+ name: (_) @name) @item
@@ -0,0 +1,125 @@
+(attribute attribute: (identifier) @property)
+(type (identifier) @type)
+
+; Function calls
+
+(decorator) @function
+
+(call
+ function: (attribute attribute: (identifier) @function.method))
+(call
+ function: (identifier) @function)
+
+; Function definitions
+
+(function_definition
+ name: (identifier) @function)
+
+; Identifier naming conventions
+
+((identifier) @type
+ (#match? @type "^[A-Z]"))
+
+((identifier) @constant
+ (#match? @constant "^_*[A-Z][A-Z\\d_]*$"))
+
+; Builtin functions
+
+((call
+ function: (identifier) @function.builtin)
+ (#match?
+ @function.builtin
+ "^(abs|all|any|ascii|bin|bool|breakpoint|bytearray|bytes|callable|chr|classmethod|compile|complex|delattr|dict|dir|divmod|enumerate|eval|exec|filter|float|format|frozenset|getattr|globals|hasattr|hash|help|hex|id|input|int|isinstance|issubclass|iter|len|list|locals|map|max|memoryview|min|next|object|oct|open|ord|pow|print|property|range|repr|reversed|round|set|setattr|slice|sorted|staticmethod|str|sum|super|tuple|type|vars|zip|__import__)$"))
+
+; Literals
+
+[
+ (none)
+ (true)
+ (false)
+] @constant.builtin
+
+[
+ (integer)
+ (float)
+] @number
+
+(comment) @comment
+(string) @string
+(escape_sequence) @escape
+
+(interpolation
+ "{" @punctuation.special
+ "}" @punctuation.special) @embedded
+
+[
+ "-"
+ "-="
+ "!="
+ "*"
+ "**"
+ "**="
+ "*="
+ "/"
+ "//"
+ "//="
+ "/="
+ "&"
+ "%"
+ "%="
+ "^"
+ "+"
+ "->"
+ "+="
+ "<"
+ "<<"
+ "<="
+ "<>"
+ "="
+ ":="
+ "=="
+ ">"
+ ">="
+ ">>"
+ "|"
+ "~"
+ "and"
+ "in"
+ "is"
+ "not"
+ "or"
+] @operator
+
+[
+ "as"
+ "assert"
+ "async"
+ "await"
+ "break"
+ "class"
+ "continue"
+ "def"
+ "del"
+ "elif"
+ "else"
+ "except"
+ "exec"
+ "finally"
+ "for"
+ "from"
+ "global"
+ "if"
+ "import"
+ "lambda"
+ "nonlocal"
+ "pass"
+ "print"
+ "raise"
+ "return"
+ "try"
+ "while"
+ "with"
+ "yield"
+ "match"
+ "case"
+] @keyword
@@ -0,0 +1,3 @@
+(_ "[" "]" @end) @indent
+(_ "{" "}" @end) @indent
+(_ "(" ")" @end) @indent
@@ -0,0 +1,9 @@
+(class_definition
+ "class" @context
+ name: (identifier) @name
+ ) @item
+
+(function_definition
+ "async"? @context
+ "def" @context
+ name: (_) @name) @item
@@ -0,0 +1,2 @@
+(comment) @comment
+(string) @string
@@ -0,0 +1,3 @@
+("(" @open ")" @close)
+("[" @open "]" @close)
+("{" @open "}" @close)
@@ -0,0 +1,9 @@
+name = "Racket"
+path_suffixes = ["rkt"]
+line_comment = "; "
+autoclose_before = "])"
+brackets = [
+ { start = "[", end = "]", close = true, newline = false },
+ { start = "(", end = ")", close = true, newline = false },
+ { start = "\"", end = "\"", close = true, newline = false },
+]
@@ -0,0 +1,40 @@
+["(" ")" "[" "]" "{" "}"] @punctuation.bracket
+
+[(string)
+ (here_string)
+ (byte_string)] @string
+(regex) @string.regex
+(escape_sequence) @escape
+
+[(comment)
+ (block_comment)
+ (sexp_comment)] @comment
+
+(symbol) @variable
+
+(number) @number
+(character) @constant.builtin
+(boolean) @constant.builtin
+(keyword) @constant
+(quote . (symbol)) @constant
+
+(extension) @keyword
+(lang_name) @variable.special
+
+((symbol) @operator
+ (#match? @operator "^(\\+|-|\\*|/|=|>|<|>=|<=)$"))
+
+(list
+ .
+ (symbol) @function)
+
+(list
+ .
+ (symbol) @keyword
+ (#match? @keyword
@@ -0,0 +1,3 @@
+(_ "[" "]") @indent
+(_ "{" "}") @indent
+(_ "(" ")") @indent
@@ -0,0 +1,10 @@
+(list
+ .
+ (symbol) @start-symbol @context
+ .
+ [
+ (symbol) @name
+ (list . (symbol) @name)
+ ]
+ (#match? @start-symbol "^define")
+) @item
@@ -0,0 +1,160 @@
+use anyhow::{anyhow, Result};
+use async_trait::async_trait;
+use language2::{LanguageServerName, LspAdapter, LspAdapterDelegate};
+use lsp2::LanguageServerBinary;
+use std::{any::Any, path::PathBuf, sync::Arc};
+
+pub struct RubyLanguageServer;
+
+#[async_trait]
+impl LspAdapter for RubyLanguageServer {
+ async fn name(&self) -> LanguageServerName {
+ LanguageServerName("solargraph".into())
+ }
+
+ fn short_name(&self) -> &'static str {
+ "solargraph"
+ }
+
+ async fn fetch_latest_server_version(
+ &self,
+ _: &dyn LspAdapterDelegate,
+ ) -> Result<Box<dyn 'static + Any + Send>> {
+ Ok(Box::new(()))
+ }
+
+ async fn fetch_server_binary(
+ &self,
+ _version: Box<dyn 'static + Send + Any>,
+ _container_dir: PathBuf,
+ _: &dyn LspAdapterDelegate,
+ ) -> Result<LanguageServerBinary> {
+ Err(anyhow!("solargraph must be installed manually"))
+ }
+
+ async fn cached_server_binary(
+ &self,
+ _: PathBuf,
+ _: &dyn LspAdapterDelegate,
+ ) -> Option<LanguageServerBinary> {
+ Some(LanguageServerBinary {
+ path: "solargraph".into(),
+ arguments: vec!["stdio".into()],
+ })
+ }
+
+ fn can_be_reinstalled(&self) -> bool {
+ false
+ }
+
+ async fn installation_test_binary(&self, _: PathBuf) -> Option<LanguageServerBinary> {
+ None
+ }
+
+ async fn label_for_completion(
+ &self,
+ item: &lsp2::CompletionItem,
+ language: &Arc<language2::Language>,
+ ) -> Option<language2::CodeLabel> {
+ let label = &item.label;
+ let grammar = language.grammar()?;
+ let highlight_id = match item.kind? {
+ lsp2::CompletionItemKind::METHOD => grammar.highlight_id_for_name("function.method")?,
+ lsp2::CompletionItemKind::CONSTANT => grammar.highlight_id_for_name("constant")?,
+ lsp2::CompletionItemKind::CLASS | lsp2::CompletionItemKind::MODULE => {
+ grammar.highlight_id_for_name("type")?
+ }
+ lsp2::CompletionItemKind::KEYWORD => {
+ if label.starts_with(':') {
+ grammar.highlight_id_for_name("string.special.symbol")?
+ } else {
+ grammar.highlight_id_for_name("keyword")?
+ }
+ }
+ lsp2::CompletionItemKind::VARIABLE => {
+ if label.starts_with('@') {
+ grammar.highlight_id_for_name("property")?
+ } else {
+ return None;
+ }
+ }
+ _ => return None,
+ };
+ Some(language2::CodeLabel {
+ text: label.clone(),
+ runs: vec![(0..label.len(), highlight_id)],
+ filter_range: 0..label.len(),
+ })
+ }
+
+ async fn label_for_symbol(
+ &self,
+ label: &str,
+ kind: lsp2::SymbolKind,
+ language: &Arc<language2::Language>,
+ ) -> Option<language2::CodeLabel> {
+ let grammar = language.grammar()?;
+ match kind {
+ lsp2::SymbolKind::METHOD => {
+ let mut parts = label.split('#');
+ let classes = parts.next()?;
+ let method = parts.next()?;
+ if parts.next().is_some() {
+ return None;
+ }
+
+ let class_id = grammar.highlight_id_for_name("type")?;
+ let method_id = grammar.highlight_id_for_name("function.method")?;
+
+ let mut ix = 0;
+ let mut runs = Vec::new();
+ for (i, class) in classes.split("::").enumerate() {
+ if i > 0 {
+ ix += 2;
+ }
+ let end_ix = ix + class.len();
+ runs.push((ix..end_ix, class_id));
+ ix = end_ix;
+ }
+
+ ix += 1;
+ let end_ix = ix + method.len();
+ runs.push((ix..end_ix, method_id));
+ Some(language2::CodeLabel {
+ text: label.to_string(),
+ runs,
+ filter_range: 0..label.len(),
+ })
+ }
+ lsp2::SymbolKind::CONSTANT => {
+ let constant_id = grammar.highlight_id_for_name("constant")?;
+ Some(language2::CodeLabel {
+ text: label.to_string(),
+ runs: vec![(0..label.len(), constant_id)],
+ filter_range: 0..label.len(),
+ })
+ }
+ lsp2::SymbolKind::CLASS | lsp2::SymbolKind::MODULE => {
+ let class_id = grammar.highlight_id_for_name("type")?;
+
+ let mut ix = 0;
+ let mut runs = Vec::new();
+ for (i, class) in label.split("::").enumerate() {
+ if i > 0 {
+ ix += "::".len();
+ }
+ let end_ix = ix + class.len();
+ runs.push((ix..end_ix, class_id));
+ ix = end_ix;
+ }
+
+ Some(language2::CodeLabel {
+ text: label.to_string(),
+ runs,
+ filter_range: 0..label.len(),
+ })
+ }
+ _ => return None,
+ }
+ }
+}
@@ -0,0 +1,14 @@
+("[" @open "]" @close)
+("{" @open "}" @close)
+("\"" @open "\"" @close)
+("do" @open "end" @close)
+
+(block_parameters "|" @open "|" @close)
+(interpolation "#{" @open "}" @close)
+
+(if "if" @open "end" @close)
+(unless "unless" @open "end" @close)
+(begin "begin" @open "end" @close)
+(module "module" @open "end" @close)
+(_ . "def" @open "end" @close)
+(_ . "class" @open "end" @close)
@@ -0,0 +1,13 @@
+name = "Ruby"
+path_suffixes = ["rb", "Gemfile"]
+first_line_pattern = '^#!.*\bruby\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 = true, newline = false, not_in = ["comment", "string"] },
+ { start = "'", end = "'", close = true, newline = false, not_in = ["comment", "string"] },
+]
+collapsed_placeholder = "# ..."
@@ -0,0 +1,22 @@
+(
+ (comment)* @context
+ .
+ [
+ (module
+ "module" @name
+ name: (_) @name)
+ (method
+ "def" @name
+ name: (_) @name
+ body: (body_statement) @collapse)
+ (class
+ "class" @name
+ name: (_) @name)
+ (singleton_method
+ "def" @name
+ object: (_) @name
+ "." @name
+ name: (_) @name
+ body: (body_statement) @collapse)
+ ] @item
+ )
@@ -0,0 +1,181 @@
+; Keywords
+
+[
+ "alias"
+ "and"
+ "begin"
+ "break"
+ "case"
+ "class"
+ "def"
+ "do"
+ "else"
+ "elsif"
+ "end"
+ "ensure"
+ "for"
+ "if"
+ "in"
+ "module"
+ "next"
+ "or"
+ "rescue"
+ "retry"
+ "return"
+ "then"
+ "unless"
+ "until"
+ "when"
+ "while"
+ "yield"
+] @keyword
+
+(identifier) @variable
+
+((identifier) @keyword
+ (#match? @keyword "^(private|protected|public)$"))
+
+; Function calls
+
+((identifier) @function.method.builtin
+ (#eq? @function.method.builtin "require"))
+
+"defined?" @function.method.builtin
+
+(call
+ method: [(identifier) (constant)] @function.method)
+
+; Function definitions
+
+(alias (identifier) @function.method)
+(setter (identifier) @function.method)
+(method name: [(identifier) (constant)] @function.method)
+(singleton_method name: [(identifier) (constant)] @function.method)
+
+; Identifiers
+
+[
+ (class_variable)
+ (instance_variable)
+] @property
+
+((identifier) @constant.builtin
+ (#match? @constant.builtin "^__(FILE|LINE|ENCODING)__$"))
+
+(file) @constant.builtin
+(line) @constant.builtin
+(encoding) @constant.builtin
+
+(hash_splat_nil
+ "**" @operator
+) @constant.builtin
+
+((constant) @constant
+ (#match? @constant "^[A-Z\\d_]+$"))
+
+(constant) @type
+
+(self) @variable.special
+(super) @variable.special
+
+; Literals
+
+[
+ (string)
+ (bare_string)
+ (subshell)
+ (heredoc_body)
+ (heredoc_beginning)
+] @string
+
+[
+ (simple_symbol)
+ (delimited_symbol)
+ (hash_key_symbol)
+ (bare_symbol)
+] @string.special.symbol
+
+(regex) @string.regex
+(escape_sequence) @escape
+
+[
+ (integer)
+ (float)
+] @number
+
+[
+ (nil)
+ (true)
+ (false)
+] @constant.builtin
+
+(comment) @comment
+
+; Operators
+
+[
+ "!"
+ "~"
+ "+"
+ "-"
+ "**"
+ "*"
+ "/"
+ "%"
+ "<<"
+ ">>"
+ "&"
+ "|"
+ "^"
+ ">"
+ "<"
+ "<="
+ ">="
+ "=="
+ "!="
+ "=~"
+ "!~"
+ "<=>"
+ "||"
+ "&&"
+ ".."
+ "..."
+ "="
+ "**="
+ "*="
+ "/="
+ "%="
+ "+="
+ "-="
+ "<<="
+ ">>="
+ "&&="
+ "&="
+ "||="
+ "|="
+ "^="
+ "=>"
+ "->"
+ (operator)
+] @operator
+
+[
+ ","
+ ";"
+ "."
+] @punctuation.delimiter
+
+[
+ "("
+ ")"
+ "["
+ "]"
+ "{"
+ "}"
+ "%w("
+ "%i("
+] @punctuation.bracket
+
+(interpolation
+ "#{" @punctuation.special
+ "}" @punctuation.special) @embedded
@@ -0,0 +1,17 @@
+(method "end" @end) @indent
+(class "end" @end) @indent
+(module "end" @end) @indent
+(begin "end" @end) @indent
+(do_block "end" @end) @indent
+
+(then) @indent
+(call) @indent
+
+(ensure) @outdent
+(rescue) @outdent
+(else) @outdent
+
+
+(_ "[" "]" @end) @indent
+(_ "{" "}" @end) @indent
+(_ "(" ")" @end) @indent
@@ -0,0 +1,17 @@
+(class
+ "class" @context
+ name: (_) @name) @item
+
+(method
+ "def" @context
+ name: (_) @name) @item
+
+(singleton_method
+ "def" @context
+ object: (_) @context
+ "." @context
+ name: (_) @name) @item
+
+(module
+ "module" @context
+ name: (_) @name) @item
@@ -0,0 +1,2 @@
+(comment) @comment
+(string) @string
@@ -0,0 +1,568 @@
+use anyhow::{anyhow, Result};
+use async_compression::futures::bufread::GzipDecoder;
+use async_trait::async_trait;
+use futures::{io::BufReader, StreamExt};
+pub use language2::*;
+use lazy_static::lazy_static;
+use lsp2::LanguageServerBinary;
+use regex::Regex;
+use smol::fs::{self, File};
+use std::{any::Any, borrow::Cow, env::consts, path::PathBuf, str, sync::Arc};
+use util::{
+ fs::remove_matching,
+ github::{latest_github_release, GitHubLspBinaryVersion},
+ ResultExt,
+};
+
+pub struct RustLspAdapter;
+
+#[async_trait]
+impl LspAdapter for RustLspAdapter {
+ async fn name(&self) -> LanguageServerName {
+ LanguageServerName("rust-analyzer".into())
+ }
+
+ fn short_name(&self) -> &'static str {
+ "rust"
+ }
+
+ async fn fetch_latest_server_version(
+ &self,
+ delegate: &dyn LspAdapterDelegate,
+ ) -> Result<Box<dyn 'static + Send + Any>> {
+ let release =
+ latest_github_release("rust-analyzer/rust-analyzer", false, delegate.http_client())
+ .await?;
+ let asset_name = format!("rust-analyzer-{}-apple-darwin.gz", consts::ARCH);
+ let asset = release
+ .assets
+ .iter()
+ .find(|asset| asset.name == asset_name)
+ .ok_or_else(|| anyhow!("no asset found matching {:?}", asset_name))?;
+ Ok(Box::new(GitHubLspBinaryVersion {
+ name: release.name,
+ url: asset.browser_download_url.clone(),
+ }))
+ }
+
+ async fn fetch_server_binary(
+ &self,
+ version: Box<dyn 'static + Send + Any>,
+ container_dir: PathBuf,
+ delegate: &dyn LspAdapterDelegate,
+ ) -> Result<LanguageServerBinary> {
+ let version = version.downcast::<GitHubLspBinaryVersion>().unwrap();
+ let destination_path = container_dir.join(format!("rust-analyzer-{}", version.name));
+
+ if fs::metadata(&destination_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 mut file = File::create(&destination_path).await?;
+ futures::io::copy(decompressed_bytes, &mut file).await?;
+ fs::set_permissions(
+ &destination_path,
+ <fs::Permissions as fs::unix::PermissionsExt>::from_mode(0o755),
+ )
+ .await?;
+
+ remove_matching(&container_dir, |entry| entry != destination_path).await;
+ }
+
+ Ok(LanguageServerBinary {
+ path: destination_path,
+ arguments: Default::default(),
+ })
+ }
+
+ 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 disk_based_diagnostic_sources(&self) -> Vec<String> {
+ vec!["rustc".into()]
+ }
+
+ async fn disk_based_diagnostics_progress_token(&self) -> Option<String> {
+ Some("rust-analyzer/flycheck".into())
+ }
+
+ fn process_diagnostics(&self, params: &mut lsp2::PublishDiagnosticsParams) {
+ lazy_static! {
+ static ref REGEX: Regex = Regex::new("(?m)`([^`]+)\n`$").unwrap();
+ }
+
+ for diagnostic in &mut params.diagnostics {
+ for message in diagnostic
+ .related_information
+ .iter_mut()
+ .flatten()
+ .map(|info| &mut info.message)
+ .chain([&mut diagnostic.message])
+ {
+ if let Cow::Owned(sanitized) = REGEX.replace_all(message, "`$1`") {
+ *message = sanitized;
+ }
+ }
+ }
+ }
+
+ async fn label_for_completion(
+ &self,
+ completion: &lsp2::CompletionItem,
+ language: &Arc<Language>,
+ ) -> Option<CodeLabel> {
+ match completion.kind {
+ Some(lsp2::CompletionItemKind::FIELD) if completion.detail.is_some() => {
+ let detail = completion.detail.as_ref().unwrap();
+ let name = &completion.label;
+ let text = format!("{}: {}", name, detail);
+ let source = Rope::from(format!("struct S {{ {} }}", text).as_str());
+ let runs = language.highlight_text(&source, 11..11 + text.len());
+ return Some(CodeLabel {
+ text,
+ runs,
+ filter_range: 0..name.len(),
+ });
+ }
+ Some(lsp2::CompletionItemKind::CONSTANT | lsp2::CompletionItemKind::VARIABLE)
+ if completion.detail.is_some()
+ && completion.insert_text_format != Some(lsp2::InsertTextFormat::SNIPPET) =>
+ {
+ let detail = completion.detail.as_ref().unwrap();
+ let name = &completion.label;
+ let text = format!("{}: {}", name, detail);
+ let source = Rope::from(format!("let {} = ();", text).as_str());
+ let runs = language.highlight_text(&source, 4..4 + text.len());
+ return Some(CodeLabel {
+ text,
+ runs,
+ filter_range: 0..name.len(),
+ });
+ }
+ Some(lsp2::CompletionItemKind::FUNCTION | lsp2::CompletionItemKind::METHOD)
+ if completion.detail.is_some() =>
+ {
+ lazy_static! {
+ static ref REGEX: Regex = Regex::new("\\(…?\\)").unwrap();
+ }
+ let detail = completion.detail.as_ref().unwrap();
+ const FUNCTION_PREFIXES: [&'static str; 2] = ["async fn", "fn"];
+ let prefix = FUNCTION_PREFIXES
+ .iter()
+ .find_map(|prefix| detail.strip_prefix(*prefix).map(|suffix| (prefix, suffix)));
+ // fn keyword should be followed by opening parenthesis.
+ if let Some((prefix, suffix)) = prefix {
+ if suffix.starts_with('(') {
+ let text = REGEX.replace(&completion.label, suffix).to_string();
+ let source = Rope::from(format!("{prefix} {} {{}}", text).as_str());
+ let run_start = prefix.len() + 1;
+ let runs =
+ language.highlight_text(&source, run_start..run_start + text.len());
+ return Some(CodeLabel {
+ filter_range: 0..completion.label.find('(').unwrap_or(text.len()),
+ text,
+ runs,
+ });
+ }
+ }
+ }
+ Some(kind) => {
+ let highlight_name = match kind {
+ lsp2::CompletionItemKind::STRUCT
+ | lsp2::CompletionItemKind::INTERFACE
+ | 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,
+ };
+ let highlight_id = language.grammar()?.highlight_id_for_name(highlight_name?)?;
+ let mut label = CodeLabel::plain(completion.label.clone(), None);
+ label.runs.push((
+ 0..label.text.rfind('(').unwrap_or(label.text.len()),
+ highlight_id,
+ ));
+ return Some(label);
+ }
+ _ => {}
+ }
+ 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!("fn {} () {{}}", name);
+ let filter_range = 3..3 + name.len();
+ let display_range = 0..filter_range.end;
+ (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 => {
+ let text = format!("trait {} {{}}", 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 {}: () = ();", name);
+ let filter_range = 6..6 + name.len();
+ let display_range = 0..filter_range.end;
+ (text, filter_range, display_range)
+ }
+ lsp2::SymbolKind::MODULE => {
+ let text = format!("mod {} {{}}", name);
+ let filter_range = 4..4 + name.len();
+ let display_range = 0..filter_range.end;
+ (text, filter_range, display_range)
+ }
+ lsp2::SymbolKind::TYPE_PARAMETER => {
+ let text = format!("type {} {{}}", name);
+ let filter_range = 5..5 + 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 = None;
+ let mut entries = fs::read_dir(&container_dir).await?;
+ while let Some(entry) = entries.next().await {
+ last = Some(entry?.path());
+ }
+
+ anyhow::Ok(LanguageServerBinary {
+ path: last.ok_or_else(|| anyhow!("no cached binary"))?,
+ arguments: Default::default(),
+ })
+ })()
+ .await
+ .log_err()
+}
+
+#[cfg(test)]
+mod tests {
+ use std::num::NonZeroU32;
+
+ use super::*;
+ use crate::languages::language;
+ use gpui2::{Context, Hsla, TestAppContext};
+ use language2::language_settings::AllLanguageSettings;
+ use settings2::SettingsStore;
+ use theme2::SyntaxTheme;
+
+ #[gpui2::test]
+ async fn test_process_rust_diagnostics() {
+ let mut params = lsp2::PublishDiagnosticsParams {
+ uri: lsp2::Url::from_file_path("/a").unwrap(),
+ version: None,
+ diagnostics: vec![
+ // no newlines
+ lsp2::Diagnostic {
+ message: "use of moved value `a`".to_string(),
+ ..Default::default()
+ },
+ // newline at the end of a code span
+ lsp2::Diagnostic {
+ message: "consider importing this struct: `use b::c;\n`".to_string(),
+ ..Default::default()
+ },
+ // code span starting right after a newline
+ lsp2::Diagnostic {
+ message: "cannot borrow `self.d` as mutable\n`self` is a `&` reference"
+ .to_string(),
+ ..Default::default()
+ },
+ ],
+ };
+ RustLspAdapter.process_diagnostics(&mut params);
+
+ assert_eq!(params.diagnostics[0].message, "use of moved value `a`");
+
+ // remove trailing newline from code span
+ assert_eq!(
+ params.diagnostics[1].message,
+ "consider importing this struct: `use b::c;`"
+ );
+
+ // do not remove newline before the start of code span
+ assert_eq!(
+ params.diagnostics[2].message,
+ "cannot borrow `self.d` as mutable\n`self` is a `&` reference"
+ );
+ }
+
+ #[gpui2::test]
+ async fn test_rust_label_for_completion() {
+ let language = language(
+ "rust",
+ tree_sitter_rust::language(),
+ Some(Arc::new(RustLspAdapter)),
+ )
+ .await;
+ let grammar = language.grammar().unwrap();
+ let theme = SyntaxTheme::new_test([
+ ("type", Hsla::default()),
+ ("keyword", Hsla::default()),
+ ("function", Hsla::default()),
+ ("property", Hsla::default()),
+ ]);
+
+ language.set_theme(&theme);
+
+ 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_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("fn(&mut Option<T>) -> Vec<T>".to_string()),
+ ..Default::default()
+ })
+ .await,
+ Some(CodeLabel {
+ text: "hello(&mut Option<T>) -> Vec<T>".to_string(),
+ filter_range: 0..5,
+ runs: vec![
+ (0..5, highlight_function),
+ (7..10, highlight_keyword),
+ (11..17, highlight_type),
+ (18..19, highlight_type),
+ (25..28, highlight_type),
+ (29..30, highlight_type),
+ ],
+ })
+ );
+ assert_eq!(
+ language
+ .label_for_completion(&lsp2::CompletionItem {
+ kind: Some(lsp2::CompletionItemKind::FUNCTION),
+ label: "hello(…)".to_string(),
+ detail: Some("async fn(&mut Option<T>) -> Vec<T>".to_string()),
+ ..Default::default()
+ })
+ .await,
+ Some(CodeLabel {
+ text: "hello(&mut Option<T>) -> Vec<T>".to_string(),
+ filter_range: 0..5,
+ runs: vec![
+ (0..5, highlight_function),
+ (7..10, highlight_keyword),
+ (11..17, highlight_type),
+ (18..19, highlight_type),
+ (25..28, highlight_type),
+ (29..30, highlight_type),
+ ],
+ })
+ );
+ assert_eq!(
+ language
+ .label_for_completion(&lsp2::CompletionItem {
+ kind: Some(lsp2::CompletionItemKind::FIELD),
+ label: "len".to_string(),
+ detail: Some("usize".to_string()),
+ ..Default::default()
+ })
+ .await,
+ Some(CodeLabel {
+ text: "len: usize".to_string(),
+ filter_range: 0..3,
+ runs: vec![(0..3, highlight_field), (5..10, highlight_type),],
+ })
+ );
+
+ assert_eq!(
+ language
+ .label_for_completion(&lsp2::CompletionItem {
+ kind: Some(lsp2::CompletionItemKind::FUNCTION),
+ label: "hello(…)".to_string(),
+ detail: Some("fn(&mut Option<T>) -> Vec<T>".to_string()),
+ ..Default::default()
+ })
+ .await,
+ Some(CodeLabel {
+ text: "hello(&mut Option<T>) -> Vec<T>".to_string(),
+ filter_range: 0..5,
+ runs: vec![
+ (0..5, highlight_function),
+ (7..10, highlight_keyword),
+ (11..17, highlight_type),
+ (18..19, highlight_type),
+ (25..28, highlight_type),
+ (29..30, highlight_type),
+ ],
+ })
+ );
+ }
+
+ #[gpui2::test]
+ async fn test_rust_label_for_symbol() {
+ let language = language(
+ "rust",
+ tree_sitter_rust::language(),
+ Some(Arc::new(RustLspAdapter)),
+ )
+ .await;
+ let grammar = language.grammar().unwrap();
+ let theme = SyntaxTheme::new_test([
+ ("type", Hsla::default()),
+ ("keyword", Hsla::default()),
+ ("function", Hsla::default()),
+ ("property", Hsla::default()),
+ ]);
+
+ language.set_theme(&theme);
+
+ 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();
+
+ assert_eq!(
+ language
+ .label_for_symbol("hello", lsp2::SymbolKind::FUNCTION)
+ .await,
+ Some(CodeLabel {
+ text: "fn hello".to_string(),
+ filter_range: 3..8,
+ runs: vec![(0..2, highlight_keyword), (3..8, highlight_function)],
+ })
+ );
+
+ assert_eq!(
+ language
+ .label_for_symbol("World", lsp2::SymbolKind::TYPE_PARAMETER)
+ .await,
+ Some(CodeLabel {
+ text: "type World".to_string(),
+ filter_range: 5..10,
+ runs: vec![(0..4, highlight_keyword), (5..10, highlight_type)],
+ })
+ );
+ }
+
+ #[gpui2::test]
+ async fn test_rust_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("rust", tree_sitter_rust::language(), None).await;
+
+ cx.build_model(|cx| {
+ let mut buffer =
+ Buffer::new(0, cx.entity_id().as_u64(), "").with_language(language, cx);
+
+ // indent between braces
+ buffer.set_text("fn a() {}", cx);
+ let ix = buffer.len() - 1;
+ buffer.edit([(ix..ix, "\n\n")], Some(AutoindentMode::EachLine), cx);
+ assert_eq!(buffer.text(), "fn a() {\n \n}");
+
+ // indent between braces, even after empty lines
+ buffer.set_text("fn a() {\n\n\n}", cx);
+ let ix = buffer.len() - 2;
+ buffer.edit([(ix..ix, "\n")], Some(AutoindentMode::EachLine), cx);
+ assert_eq!(buffer.text(), "fn a() {\n\n\n \n}");
+
+ // indent a line that continues a field expression
+ buffer.set_text("fn a() {\n \n}", cx);
+ let ix = buffer.len() - 2;
+ buffer.edit([(ix..ix, "b\n.c")], Some(AutoindentMode::EachLine), cx);
+ assert_eq!(buffer.text(), "fn a() {\n b\n .c\n}");
+
+ // indent further lines that continue the field expression, even after empty lines
+ let ix = buffer.len() - 2;
+ buffer.edit([(ix..ix, "\n\n.d")], Some(AutoindentMode::EachLine), cx);
+ assert_eq!(buffer.text(), "fn a() {\n b\n .c\n \n .d\n}");
+
+ // dedent the line after the field expression
+ let ix = buffer.len() - 2;
+ buffer.edit([(ix..ix, ";\ne")], Some(AutoindentMode::EachLine), cx);
+ assert_eq!(
+ buffer.text(),
+ "fn a() {\n b\n .c\n \n .d;\n e\n}"
+ );
+
+ // indent inside a struct within a call
+ buffer.set_text("const a: B = c(D {});", cx);
+ let ix = buffer.len() - 3;
+ buffer.edit([(ix..ix, "\n\n")], Some(AutoindentMode::EachLine), cx);
+ assert_eq!(buffer.text(), "const a: B = c(D {\n \n});");
+
+ // indent further inside a nested call
+ let ix = buffer.len() - 4;
+ buffer.edit([(ix..ix, "e: f(\n\n)")], Some(AutoindentMode::EachLine), cx);
+ assert_eq!(buffer.text(), "const a: B = c(D {\n e: f(\n \n )\n});");
+
+ // keep that indent after an empty line
+ let ix = buffer.len() - 8;
+ buffer.edit([(ix..ix, "\n")], Some(AutoindentMode::EachLine), cx);
+ assert_eq!(
+ buffer.text(),
+ "const a: B = c(D {\n e: f(\n \n \n )\n});"
+ );
+
+ buffer
+ });
+ }
+}
@@ -0,0 +1,6 @@
+("(" @open ")" @close)
+("[" @open "]" @close)
+("{" @open "}" @close)
+("<" @open ">" @close)
+("\"" @open "\"" @close)
+(closure_parameters "|" @open "|" @close)
@@ -0,0 +1,13 @@
+name = "Rust"
+path_suffixes = ["rs"]
+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 = ["string", "comment"] },
+ { start = "\"", end = "\"", close = true, newline = false, not_in = ["string"] },
+ { start = "/*", end = " */", close = true, newline = false, not_in = ["string", "comment"] },
+]
+collapsed_placeholder = " /* ... */ "
@@ -0,0 +1,32 @@
+(
+ [(line_comment) (attribute_item)]* @context
+ .
+ [
+
+ (struct_item
+ name: (_) @name)
+
+ (enum_item
+ name: (_) @name)
+
+ (impl_item
+ trait: (_)? @name
+ "for"? @name
+ type: (_) @name)
+
+ (trait_item
+ name: (_) @name)
+
+ (function_item
+ name: (_) @name
+ body: (block
+ "{" @keep
+ "}" @keep) @collapse)
+
+ (macro_definition
+ name: (_) @name)
+ ] @item
+ )
+
+(attribute_item) @collapse
+(use_declaration) @collapse
@@ -0,0 +1,116 @@
+(type_identifier) @type
+(primitive_type) @type.builtin
+(self) @variable.special
+(field_identifier) @property
+
+(call_expression
+ function: [
+ (identifier) @function
+ (scoped_identifier
+ name: (identifier) @function)
+ (field_expression
+ field: (field_identifier) @function.method)
+ ])
+
+(generic_function
+ function: [
+ (identifier) @function
+ (scoped_identifier
+ name: (identifier) @function)
+ (field_expression
+ field: (field_identifier) @function.method)
+ ])
+
+(function_item name: (identifier) @function.definition)
+(function_signature_item name: (identifier) @function.definition)
+
+(macro_invocation
+ macro: [
+ (identifier) @function.special
+ (scoped_identifier
+ name: (identifier) @function.special)
+ ])
+
+(macro_definition
+ name: (identifier) @function.special.definition)
+
+; Identifier conventions
+
+; Assume uppercase names are types/enum-constructors
+((identifier) @type
+ (#match? @type "^[A-Z]"))
+
+; Assume all-caps names are constants
+((identifier) @constant
+ (#match? @constant "^_*[A-Z][A-Z\\d_]*$"))
+
+[
+ "("
+ ")"
+ "{"
+ "}"
+ "["
+ "]"
+] @punctuation.bracket
+
+(_
+ .
+ "<" @punctuation.bracket
+ ">" @punctuation.bracket)
+
+[
+ "as"
+ "async"
+ "await"
+ "break"
+ "const"
+ "continue"
+ "default"
+ "dyn"
+ "else"
+ "enum"
+ "extern"
+ "for"
+ "fn"
+ "if"
+ "in"
+ "impl"
+ "let"
+ "loop"
+ "macro_rules!"
+ "match"
+ "mod"
+ "move"
+ "pub"
+ "ref"
+ "return"
+ "static"
+ "struct"
+ "trait"
+ "type"
+ "use"
+ "where"
+ "while"
+ "union"
+ "unsafe"
+ (mutable_specifier)
+ (super)
+] @keyword
+
+[
+ (string_literal)
+ (raw_string_literal)
+ (char_literal)
+] @string
+
+[
+ (integer_literal)
+ (float_literal)
+] @number
+
+(boolean_literal) @constant
+
+[
+ (line_comment)
+ (block_comment)
+] @comment
@@ -0,0 +1,14 @@
+[
+ ((where_clause) _ @end)
+ (field_expression)
+ (call_expression)
+ (assignment_expression)
+ (let_declaration)
+ (let_chain)
+ (await_expression)
+] @indent
+
+(_ "[" "]" @end) @indent
+(_ "<" ">" @end) @indent
+(_ "{" "}" @end) @indent
+(_ "(" ")" @end) @indent
@@ -0,0 +1,7 @@
+(macro_invocation
+ (token_tree) @content
+ (#set! "language" "rust"))
+
+(macro_rule
+ (token_tree) @content
+ (#set! "language" "rust"))
@@ -0,0 +1,63 @@
+(struct_item
+ (visibility_modifier)? @context
+ "struct" @context
+ name: (_) @name) @item
+
+(enum_item
+ (visibility_modifier)? @context
+ "enum" @context
+ name: (_) @name) @item
+
+(enum_variant
+ (visibility_modifier)? @context
+ name: (_) @name) @item
+
+(impl_item
+ "impl" @context
+ trait: (_)? @name
+ "for"? @context
+ type: (_) @name) @item
+
+(trait_item
+ (visibility_modifier)? @context
+ "trait" @context
+ name: (_) @name) @item
+
+(function_item
+ (visibility_modifier)? @context
+ (function_modifiers)? @context
+ "fn" @context
+ name: (_) @name) @item
+
+(function_signature_item
+ (visibility_modifier)? @context
+ (function_modifiers)? @context
+ "fn" @context
+ name: (_) @name) @item
+
+(macro_definition
+ . "macro_rules!" @context
+ name: (_) @name) @item
+
+(mod_item
+ (visibility_modifier)? @context
+ "mod" @context
+ name: (_) @name) @item
+
+(type_item
+ (visibility_modifier)? @context
+ "type" @context
+ name: (_) @name) @item
+
+(associated_type
+ "type" @context
+ name: (_) @name) @item
+
+(const_item
+ (visibility_modifier)? @context
+ "const" @context
+ name: (_) @name) @item
+
+(field_declaration
+ (visibility_modifier)? @context
+ name: (_) @name) @item
@@ -0,0 +1,8 @@
+[
+ (string_literal)
+ (raw_string_literal)
+] @string
+[
+ (line_comment)
+ (block_comment)
+] @comment
@@ -0,0 +1,3 @@
+("(" @open ")" @close)
+("[" @open "]" @close)
+("{" @open "}" @close)
@@ -0,0 +1,9 @@
+name = "Scheme"
+path_suffixes = ["scm", "ss"]
+line_comment = "; "
+autoclose_before = "])"
+brackets = [
+ { start = "[", end = "]", close = true, newline = false },
+ { start = "(", end = ")", close = true, newline = false },
+ { start = "\"", end = "\"", close = true, newline = false, not_in = ["comment", "string"] },
+]
@@ -0,0 +1,28 @@
+["(" ")" "[" "]" "{" "}"] @punctuation.bracket
+
+(number) @number
+(character) @constant.builtin
+(boolean) @constant.builtin
+
+(symbol) @variable
+(string) @string
+
+(escape_sequence) @escape
+
+[(comment)
+ (block_comment)
+ (directive)] @comment
+
+((symbol) @operator
+ (#match? @operator "^(\\+|-|\\*|/|=|>|<|>=|<=)$"))
+
+(list
+ .
+ (symbol) @function)
+
+(list
+ .
+ (symbol) @keyword
+ (#match? @keyword
+ "^(define-syntax|let\\*|lambda|λ|case|=>|quote-splicing|unquote-splicing|set!|let|letrec|letrec-syntax|let-values|let\\*-values|do|else|define|cond|syntax-rules|unquote|begin|quote|let-syntax|and|if|quasiquote|letrec|delay|or|when|unless|identifier-syntax|assert|library|export|import|rename|only|except|prefix)$"
+ ))
@@ -0,0 +1,3 @@
+(_ "[" "]") @indent
+(_ "{" "}") @indent
+(_ "(" ")") @indent
@@ -0,0 +1,10 @@
+(list
+ .
+ (symbol) @start-symbol @context
+ .
+ [
+ (symbol) @name
+ (list . (symbol) @name)
+ ]
+ (#match? @start-symbol "^define")
+) @item
@@ -0,0 +1,6 @@
+[
+ (comment)
+ (block_comment)
+ (directive)
+] @comment
+(string) @string
@@ -0,0 +1,133 @@
+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/svelte-language-server/bin/server.js";
+
+fn server_binary_arguments(server_path: &Path) -> Vec<OsString> {
+ vec![server_path.into(), "--stdio".into()]
+}
+
+pub struct SvelteLspAdapter {
+ node: Arc<dyn NodeRuntime>,
+}
+
+impl SvelteLspAdapter {
+ pub fn new(node: Arc<dyn NodeRuntime>) -> Self {
+ SvelteLspAdapter { node }
+ }
+}
+
+#[async_trait]
+impl LspAdapter for SvelteLspAdapter {
+ async fn name(&self) -> LanguageServerName {
+ LanguageServerName("svelte-language-server".into())
+ }
+
+ fn short_name(&self) -> &'static str {
+ "svelte"
+ }
+
+ async fn fetch_latest_server_version(
+ &self,
+ _: &dyn LspAdapterDelegate,
+ ) -> Result<Box<dyn 'static + Any + Send>> {
+ Ok(Box::new(
+ self.node
+ .npm_package_latest_version("svelte-language-server")
+ .await?,
+ ) as Box<_>)
+ }
+
+ async fn fetch_server_binary(
+ &self,
+ version: Box<dyn 'static + Send + Any>,
+ container_dir: PathBuf,
+ _: &dyn LspAdapterDelegate,
+ ) -> Result<LanguageServerBinary> {
+ let version = version.downcast::<String>().unwrap();
+ let server_path = container_dir.join(SERVER_PATH);
+
+ if fs::metadata(&server_path).await.is_err() {
+ self.node
+ .npm_install_packages(
+ &container_dir,
+ &[("svelte-language-server", 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 prettier_plugins(&self) -> &[&'static str] {
+ &["prettier-plugin-svelte"]
+ }
+}
+
+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()
+}
@@ -0,0 +1,20 @@
+name = "Svelte"
+path_suffixes = ["svelte"]
+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 = ["string", "comment"] },
+ { 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"] },
+ { start = "/*", end = " */", close = true, newline = false, not_in = ["string", "comment"] },
+]
+scope_opt_in_language_servers = ["tailwindcss-language-server"]
+prettier_parser_name = "svelte"
+
+[overrides.string]
+word_characters = ["-"]
+opt_into_language_servers = ["tailwindcss-language-server"]
@@ -0,0 +1,9 @@
+[
+ (style_element)
+ (script_element)
+ (element)
+ (if_statement)
+ (else_statement)
+ (each_statement)
+ (await_statement)
+] @fold
@@ -0,0 +1,42 @@
+; Special identifiers
+;--------------------
+
+; TODO:
+(tag_name) @tag
+(attribute_name) @property
+(erroneous_end_tag_name) @keyword
+(comment) @comment
+
+[
+ (attribute_value)
+ (quoted_attribute_value)
+] @string
+
+[
+ (text)
+ (raw_text_expr)
+] @none
+
+[
+ (special_block_keyword)
+ (then)
+ (as)
+] @keyword
+
+[
+ "{"
+ "}"
+] @punctuation.bracket
+
+"=" @operator
+
+[
+ "<"
+ ">"
+ "</"
+ "/>"
+ "#"
+ ":"
+ "/"
+ "@"
+] @tag.delimiter
@@ -0,0 +1,8 @@
+[
+ (element)
+ (if_statement)
+ (each_statement)
+ (await_statement)
+ (script_element)
+ (style_element)
+] @indent
@@ -0,0 +1,28 @@
+; injections.scm
+; --------------
+(script_element
+ (raw_text) @content
+ (#set! "language" "javascript"))
+
+ ((script_element
+ (start_tag
+ (attribute
+ (quoted_attribute_value (attribute_value) @_language)))
+ (raw_text) @content)
+ (#eq? @_language "ts")
+ (#set! "language" "typescript"))
+
+((script_element
+ (start_tag
+ (attribute
+ (quoted_attribute_value (attribute_value) @_language)))
+ (raw_text) @content)
+ (#eq? @_language "typescript")
+ (#set! "language" "typescript"))
+
+(style_element
+ (raw_text) @content
+ (#set! "language" "css"))
+
+((raw_text_expr) @content
+ (#set! "language" "javascript"))
@@ -0,0 +1,7 @@
+(comment) @comment
+
+[
+ (raw_text)
+ (attribute_value)
+ (quoted_attribute_value)
+] @string
@@ -0,0 +1,167 @@
+use anyhow::{anyhow, Result};
+use async_trait::async_trait;
+use collections::HashMap;
+use futures::{
+ future::{self, BoxFuture},
+ FutureExt, StreamExt,
+};
+use gpui2::AppContext;
+use language2::{LanguageServerName, LspAdapter, LspAdapterDelegate};
+use lsp2::LanguageServerBinary;
+use node_runtime::NodeRuntime;
+use serde_json::{json, Value};
+use smol::fs;
+use std::{
+ any::Any,
+ ffi::OsString,
+ path::{Path, PathBuf},
+ sync::Arc,
+};
+use util::ResultExt;
+
+const SERVER_PATH: &'static str = "node_modules/.bin/tailwindcss-language-server";
+
+fn server_binary_arguments(server_path: &Path) -> Vec<OsString> {
+ vec![server_path.into(), "--stdio".into()]
+}
+
+pub struct TailwindLspAdapter {
+ node: Arc<dyn NodeRuntime>,
+}
+
+impl TailwindLspAdapter {
+ pub fn new(node: Arc<dyn NodeRuntime>) -> Self {
+ TailwindLspAdapter { node }
+ }
+}
+
+#[async_trait]
+impl LspAdapter for TailwindLspAdapter {
+ async fn name(&self) -> LanguageServerName {
+ LanguageServerName("tailwindcss-language-server".into())
+ }
+
+ fn short_name(&self) -> &'static str {
+ "tailwind"
+ }
+
+ async fn fetch_latest_server_version(
+ &self,
+ _: &dyn LspAdapterDelegate,
+ ) -> Result<Box<dyn 'static + Any + Send>> {
+ Ok(Box::new(
+ self.node
+ .npm_package_latest_version("@tailwindcss/language-server")
+ .await?,
+ ) as Box<_>)
+ }
+
+ async fn fetch_server_binary(
+ &self,
+ version: Box<dyn 'static + Send + Any>,
+ container_dir: PathBuf,
+ _: &dyn LspAdapterDelegate,
+ ) -> Result<LanguageServerBinary> {
+ let version = version.downcast::<String>().unwrap();
+ let server_path = container_dir.join(SERVER_PATH);
+
+ if fs::metadata(&server_path).await.is_err() {
+ self.node
+ .npm_install_packages(
+ &container_dir,
+ &[("@tailwindcss/language-server", 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,
+ "userLanguages": {
+ "html": "html",
+ "css": "css",
+ "javascript": "javascript",
+ "typescriptreact": "typescriptreact",
+ },
+ }))
+ }
+
+ fn workspace_configuration(&self, _: &mut AppContext) -> BoxFuture<'static, Value> {
+ future::ready(json!({
+ "tailwindCSS": {
+ "emmetCompletions": true,
+ }
+ }))
+ .boxed()
+ }
+
+ async fn language_ids(&self) -> HashMap<String, String> {
+ HashMap::from_iter([
+ ("HTML".to_string(), "html".to_string()),
+ ("CSS".to_string(), "css".to_string()),
+ ("JavaScript".to_string(), "javascript".to_string()),
+ ("TSX".to_string(), "typescriptreact".to_string()),
+ ("Svelte".to_string(), "svelte".to_string()),
+ ("Elixir".to_string(), "phoenix-heex".to_string()),
+ ("HEEX".to_string(), "phoenix-heex".to_string()),
+ ("ERB".to_string(), "erb".to_string()),
+ ("PHP".to_string(), "php".to_string()),
+ ])
+ }
+
+ fn prettier_plugins(&self) -> &[&'static str] {
+ &["prettier-plugin-tailwindcss"]
+ }
+}
+
+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()
+}
@@ -0,0 +1,3 @@
+("[" @open "]" @close)
+("{" @open "}" @close)
+("\"" @open "\"" @close)
@@ -0,0 +1,10 @@
+name = "TOML"
+path_suffixes = ["Cargo.lock", "toml"]
+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 = ["comment", "string"] },
+ { start = "'", end = "'", close = true, newline = false, not_in = ["comment", "string"] },
+]
@@ -0,0 +1,37 @@
+; Properties
+;-----------
+
+(bare_key) @property
+(quoted_key) @property
+
+; Literals
+;---------
+
+(boolean) @constant
+(comment) @comment
+(string) @string
+(integer) @number
+(float) @number
+(offset_date_time) @string.special
+(local_date_time) @string.special
+(local_date) @string.special
+(local_time) @string.special
+
+; Punctuation
+;------------
+
+[
+ "."
+ ","
+] @punctuation.delimiter
+
+"=" @operator
+
+[
+ "["
+ "]"
+ "[["
+ "]]"
+ "{"
+ "}"
+] @punctuation.bracket
@@ -0,0 +1,15 @@
+(table
+ .
+ "["
+ .
+ (_) @name) @item
+
+(table_array_element
+ .
+ "[["
+ .
+ (_) @name) @item
+
+(pair
+ .
+ (_) @name) @item
@@ -0,0 +1,2 @@
+(comment) @comment
+(string) @string
@@ -0,0 +1 @@
+../typescript/brackets.scm
@@ -0,0 +1,25 @@
+name = "TSX"
+path_suffixes = ["tsx"]
+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 = ["string", "comment"] },
+ { 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"] },
+ { start = "/*", end = " */", close = true, newline = false, not_in = ["string", "comment"] },
+]
+word_characters = ["#", "$"]
+scope_opt_in_language_servers = ["tailwindcss-language-server"]
+prettier_parser_name = "typescript"
+
+[overrides.element]
+line_comment = { remove = true }
+block_comment = ["{/* ", " */}"]
+
+[overrides.string]
+word_characters = ["-"]
+opt_into_language_servers = ["tailwindcss-language-server"]
@@ -0,0 +1,85 @@
+(
+ (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
+ .
+ [
+ (export_statement
+ (type_alias_declaration
+ "type" @name
+ name: (_) @name))
+ (type_alias_declaration
+ "type" @name
+ name: (_) @name)
+ ] @item
+ )
+
+(
+ (comment)* @context
+ .
+ (method_definition
+ [
+ "get"
+ "set"
+ "async"
+ "*"
+ "static"
+ ]* @name
+ name: (_) @name) @item
+ )
@@ -0,0 +1 @@
+../typescript/highlights.scm
@@ -0,0 +1 @@
+../typescript/indents.scm
@@ -0,0 +1 @@
+../typescript/outline.scm
@@ -0,0 +1,13 @@
+(comment) @comment
+
+[
+ (string)
+ (template_string)
+] @string
+
+[
+ (jsx_element)
+ (jsx_fragment)
+ (jsx_self_closing_element)
+ (jsx_expression)
+] @element
@@ -0,0 +1,384 @@
+use anyhow::{anyhow, Result};
+use async_compression::futures::bufread::GzipDecoder;
+use async_tar::Archive;
+use async_trait::async_trait;
+use futures::{future::BoxFuture, FutureExt};
+use gpui2::AppContext;
+use language2::{LanguageServerName, LspAdapter, LspAdapterDelegate};
+use lsp2::{CodeActionKind, LanguageServerBinary};
+use node_runtime::NodeRuntime;
+use serde_json::{json, Value};
+use smol::{fs, io::BufReader, stream::StreamExt};
+use std::{
+ any::Any,
+ ffi::OsString,
+ future,
+ path::{Path, PathBuf},
+ sync::Arc,
+};
+use util::{fs::remove_matching, github::latest_github_release};
+use util::{github::GitHubLspBinaryVersion, ResultExt};
+
+fn typescript_server_binary_arguments(server_path: &Path) -> Vec<OsString> {
+ vec![
+ server_path.into(),
+ "--stdio".into(),
+ "--tsserver-path".into(),
+ "node_modules/typescript/lib".into(),
+ ]
+}
+
+fn eslint_server_binary_arguments(server_path: &Path) -> Vec<OsString> {
+ vec![server_path.into(), "--stdio".into()]
+}
+
+pub struct TypeScriptLspAdapter {
+ node: Arc<dyn NodeRuntime>,
+}
+
+impl TypeScriptLspAdapter {
+ const OLD_SERVER_PATH: &'static str = "node_modules/typescript-language-server/lib/cli.js";
+ const NEW_SERVER_PATH: &'static str = "node_modules/typescript-language-server/lib/cli.mjs";
+
+ pub fn new(node: Arc<dyn NodeRuntime>) -> Self {
+ TypeScriptLspAdapter { node }
+ }
+}
+
+struct TypeScriptVersions {
+ typescript_version: String,
+ server_version: String,
+}
+
+#[async_trait]
+impl LspAdapter for TypeScriptLspAdapter {
+ async fn name(&self) -> LanguageServerName {
+ LanguageServerName("typescript-language-server".into())
+ }
+
+ fn short_name(&self) -> &'static str {
+ "tsserver"
+ }
+
+ async fn fetch_latest_server_version(
+ &self,
+ _: &dyn LspAdapterDelegate,
+ ) -> Result<Box<dyn 'static + Send + Any>> {
+ Ok(Box::new(TypeScriptVersions {
+ typescript_version: self.node.npm_package_latest_version("typescript").await?,
+ server_version: self
+ .node
+ .npm_package_latest_version("typescript-language-server")
+ .await?,
+ }) as Box<_>)
+ }
+
+ async fn fetch_server_binary(
+ &self,
+ version: Box<dyn 'static + Send + Any>,
+ container_dir: PathBuf,
+ _: &dyn LspAdapterDelegate,
+ ) -> Result<LanguageServerBinary> {
+ let version = version.downcast::<TypeScriptVersions>().unwrap();
+ let server_path = container_dir.join(Self::NEW_SERVER_PATH);
+
+ if fs::metadata(&server_path).await.is_err() {
+ self.node
+ .npm_install_packages(
+ &container_dir,
+ &[
+ ("typescript", version.typescript_version.as_str()),
+ (
+ "typescript-language-server",
+ version.server_version.as_str(),
+ ),
+ ],
+ )
+ .await?;
+ }
+
+ Ok(LanguageServerBinary {
+ path: self.node.binary_path().await?,
+ arguments: typescript_server_binary_arguments(&server_path),
+ })
+ }
+
+ async fn cached_server_binary(
+ &self,
+ container_dir: PathBuf,
+ _: &dyn LspAdapterDelegate,
+ ) -> Option<LanguageServerBinary> {
+ get_cached_ts_server_binary(container_dir, &*self.node).await
+ }
+
+ async fn installation_test_binary(
+ &self,
+ container_dir: PathBuf,
+ ) -> Option<LanguageServerBinary> {
+ get_cached_ts_server_binary(container_dir, &*self.node).await
+ }
+
+ fn code_action_kinds(&self) -> Option<Vec<CodeActionKind>> {
+ Some(vec![
+ CodeActionKind::QUICKFIX,
+ CodeActionKind::REFACTOR,
+ CodeActionKind::REFACTOR_EXTRACT,
+ CodeActionKind::SOURCE,
+ ])
+ }
+
+ async fn label_for_completion(
+ &self,
+ item: &lsp2::CompletionItem,
+ language: &Arc<language2::Language>,
+ ) -> Option<language2::CodeLabel> {
+ use lsp2::CompletionItemKind as Kind;
+ let len = item.label.len();
+ let grammar = language.grammar()?;
+ let highlight_id = match item.kind? {
+ Kind::CLASS | Kind::INTERFACE => grammar.highlight_id_for_name("type"),
+ Kind::CONSTRUCTOR => grammar.highlight_id_for_name("type"),
+ Kind::CONSTANT => grammar.highlight_id_for_name("constant"),
+ Kind::FUNCTION | Kind::METHOD => grammar.highlight_id_for_name("function"),
+ Kind::PROPERTY | Kind::FIELD => grammar.highlight_id_for_name("property"),
+ _ => None,
+ }?;
+
+ let text = match &item.detail {
+ Some(detail) => format!("{} {}", item.label, detail),
+ None => item.label.clone(),
+ };
+
+ Some(language2::CodeLabel {
+ text,
+ runs: vec![(0..len, highlight_id)],
+ filter_range: 0..len,
+ })
+ }
+
+ async fn initialization_options(&self) -> Option<serde_json::Value> {
+ Some(json!({
+ "provideFormatter": true
+ }))
+ }
+}
+
+async fn get_cached_ts_server_binary(
+ container_dir: PathBuf,
+ node: &dyn NodeRuntime,
+) -> Option<LanguageServerBinary> {
+ (|| async move {
+ let old_server_path = container_dir.join(TypeScriptLspAdapter::OLD_SERVER_PATH);
+ let new_server_path = container_dir.join(TypeScriptLspAdapter::NEW_SERVER_PATH);
+ if new_server_path.exists() {
+ Ok(LanguageServerBinary {
+ path: node.binary_path().await?,
+ arguments: typescript_server_binary_arguments(&new_server_path),
+ })
+ } else if old_server_path.exists() {
+ Ok(LanguageServerBinary {
+ path: node.binary_path().await?,
+ arguments: typescript_server_binary_arguments(&old_server_path),
+ })
+ } else {
+ Err(anyhow!(
+ "missing executable in directory {:?}",
+ container_dir
+ ))
+ }
+ })()
+ .await
+ .log_err()
+}
+
+pub struct EsLintLspAdapter {
+ node: Arc<dyn NodeRuntime>,
+}
+
+impl EsLintLspAdapter {
+ const SERVER_PATH: &'static str = "vscode-eslint/server/out/eslintServer.js";
+
+ #[allow(unused)]
+ pub fn new(node: Arc<dyn NodeRuntime>) -> Self {
+ EsLintLspAdapter { node }
+ }
+}
+
+#[async_trait]
+impl LspAdapter for EsLintLspAdapter {
+ fn workspace_configuration(&self, _: &mut AppContext) -> BoxFuture<'static, Value> {
+ future::ready(json!({
+ "": {
+ "validate": "on",
+ "rulesCustomizations": [],
+ "run": "onType",
+ "nodePath": null,
+ }
+ }))
+ .boxed()
+ }
+
+ async fn name(&self) -> LanguageServerName {
+ LanguageServerName("eslint".into())
+ }
+
+ fn short_name(&self) -> &'static str {
+ "eslint"
+ }
+
+ async fn fetch_latest_server_version(
+ &self,
+ delegate: &dyn LspAdapterDelegate,
+ ) -> Result<Box<dyn 'static + Send + Any>> {
+ // At the time of writing the latest vscode-eslint release was released in 2020 and requires
+ // special custom LSP protocol extensions be handled to fully initialize. Download the latest
+ // prerelease instead to sidestep this issue
+ let release =
+ latest_github_release("microsoft/vscode-eslint", true, delegate.http_client()).await?;
+ Ok(Box::new(GitHubLspBinaryVersion {
+ name: release.name,
+ url: release.tarball_url,
+ }))
+ }
+
+ 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 destination_path = container_dir.join(format!("vscode-eslint-{}", version.name));
+ let server_path = destination_path.join(Self::SERVER_PATH);
+
+ if fs::metadata(&server_path).await.is_err() {
+ remove_matching(&container_dir, |entry| entry != destination_path).await;
+
+ 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(&destination_path).await?;
+
+ let mut dir = fs::read_dir(&destination_path).await?;
+ let first = dir.next().await.ok_or(anyhow!("missing first file"))??;
+ let repo_root = destination_path.join("vscode-eslint");
+ fs::rename(first.path(), &repo_root).await?;
+
+ self.node
+ .run_npm_subcommand(Some(&repo_root), "install", &[])
+ .await?;
+
+ self.node
+ .run_npm_subcommand(Some(&repo_root), "run-script", &["compile"])
+ .await?;
+ }
+
+ Ok(LanguageServerBinary {
+ path: self.node.binary_path().await?,
+ arguments: eslint_server_binary_arguments(&server_path),
+ })
+ }
+
+ async fn cached_server_binary(
+ &self,
+ container_dir: PathBuf,
+ _: &dyn LspAdapterDelegate,
+ ) -> Option<LanguageServerBinary> {
+ get_cached_eslint_server_binary(container_dir, &*self.node).await
+ }
+
+ async fn installation_test_binary(
+ &self,
+ container_dir: PathBuf,
+ ) -> Option<LanguageServerBinary> {
+ get_cached_eslint_server_binary(container_dir, &*self.node).await
+ }
+
+ async fn label_for_completion(
+ &self,
+ _item: &lsp2::CompletionItem,
+ _language: &Arc<language2::Language>,
+ ) -> Option<language2::CodeLabel> {
+ None
+ }
+
+ async fn initialization_options(&self) -> Option<serde_json::Value> {
+ None
+ }
+}
+
+async fn get_cached_eslint_server_binary(
+ container_dir: PathBuf,
+ node: &dyn NodeRuntime,
+) -> Option<LanguageServerBinary> {
+ (|| async move {
+ // This is unfortunate but we don't know what the version is to build a path directly
+ let mut dir = fs::read_dir(&container_dir).await?;
+ let first = dir.next().await.ok_or(anyhow!("missing first file"))??;
+ if !first.file_type().await?.is_dir() {
+ return Err(anyhow!("First entry is not a directory"));
+ }
+ let server_path = first.path().join(EsLintLspAdapter::SERVER_PATH);
+
+ Ok(LanguageServerBinary {
+ path: node.binary_path().await?,
+ arguments: eslint_server_binary_arguments(&server_path),
+ })
+ })()
+ .await
+ .log_err()
+}
+
+#[cfg(test)]
+mod tests {
+ use gpui2::{Context, TestAppContext};
+ use unindent::Unindent;
+
+ #[gpui2::test]
+ async fn test_outline(cx: &mut TestAppContext) {
+ let language = crate::languages::language(
+ "typescript",
+ tree_sitter_typescript::language_typescript(),
+ None,
+ )
+ .await;
+
+ let text = r#"
+ function a() {
+ // local variables are omitted
+ let a1 = 1;
+ // all functions are included
+ async function a2() {}
+ }
+ // top-level variables are included
+ let b: C
+ function getB() {}
+ // exported variables are included
+ export const d = e;
+ "#
+ .unindent();
+
+ let buffer = cx.build_model(|cx| {
+ language2::Buffer::new(0, cx.entity_id().as_u64(), text).with_language(language, cx)
+ });
+ let outline = buffer.update(cx, |buffer, _| buffer.snapshot().outline(None).unwrap());
+ assert_eq!(
+ outline
+ .items
+ .iter()
+ .map(|item| (item.text.as_str(), item.depth))
+ .collect::<Vec<_>>(),
+ &[
+ ("function a()", 0),
+ ("async function a2()", 1),
+ ("let b", 0),
+ ("function getB()", 0),
+ ("const d", 0),
+ ]
+ );
+ }
+}
@@ -0,0 +1,5 @@
+("(" @open ")" @close)
+("[" @open "]" @close)
+("{" @open "}" @close)
+("<" @open ">" @close)
+("\"" @open "\"" @close)
@@ -0,0 +1,16 @@
+name = "TypeScript"
+path_suffixes = ["ts", "cts", "d.cts", "d.mts", "mts"]
+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 = ["string", "comment"] },
+ { 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"] },
+ { start = "/*", end = " */", close = true, newline = false, not_in = ["string", "comment"] },
+]
+word_characters = ["#", "$"]
+prettier_parser_name = "typescript"
@@ -0,0 +1,85 @@
+(
+ (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
+ .
+ [
+ (export_statement
+ (type_alias_declaration
+ "type" @name
+ name: (_) @name))
+ (type_alias_declaration
+ "type" @name
+ name: (_) @name)
+ ] @item
+)
+
+(
+ (comment)* @context
+ .
+ (method_definition
+ [
+ "get"
+ "set"
+ "async"
+ "*"
+ "static"
+ ]* @name
+ name: (_) @name) @item
+)
@@ -0,0 +1,221 @@
+; 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) @constructor
+ (#match? @constructor "^[A-Z]"))
+
+((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"
+ "satisfies"
+ "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
@@ -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
@@ -0,0 +1,65 @@
+(internal_module
+ "namespace" @context
+ name: (_) @name) @item
+
+(enum_declaration
+ "enum" @context
+ name: (_) @name) @item
+
+(type_alias_declaration
+ "type" @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
+
+(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
@@ -0,0 +1,2 @@
+(comment) @comment
+(string) @string
@@ -0,0 +1,220 @@
+use anyhow::{anyhow, ensure, Result};
+use async_trait::async_trait;
+use futures::StreamExt;
+pub use language2::*;
+use lsp2::{CodeActionKind, LanguageServerBinary};
+use node_runtime::NodeRuntime;
+use parking_lot::Mutex;
+use serde_json::Value;
+use smol::fs::{self};
+use std::{
+ any::Any,
+ ffi::OsString,
+ path::{Path, PathBuf},
+ sync::Arc,
+};
+use util::ResultExt;
+
+pub struct VueLspVersion {
+ vue_version: String,
+ ts_version: String,
+}
+
+pub struct VueLspAdapter {
+ node: Arc<dyn NodeRuntime>,
+ typescript_install_path: Mutex<Option<PathBuf>>,
+}
+
+impl VueLspAdapter {
+ const SERVER_PATH: &'static str =
+ "node_modules/@vue/language-server/bin/vue-language-server.js";
+ // TODO: this can't be hardcoded, yet we have to figure out how to pass it in initialization_options.
+ const TYPESCRIPT_PATH: &'static str = "node_modules/typescript/lib";
+ pub fn new(node: Arc<dyn NodeRuntime>) -> Self {
+ let typescript_install_path = Mutex::new(None);
+ Self {
+ node,
+ typescript_install_path,
+ }
+ }
+}
+#[async_trait]
+impl super::LspAdapter for VueLspAdapter {
+ async fn name(&self) -> LanguageServerName {
+ LanguageServerName("vue-language-server".into())
+ }
+
+ fn short_name(&self) -> &'static str {
+ "vue-language-server"
+ }
+
+ async fn fetch_latest_server_version(
+ &self,
+ _: &dyn LspAdapterDelegate,
+ ) -> Result<Box<dyn 'static + Send + Any>> {
+ Ok(Box::new(VueLspVersion {
+ vue_version: self
+ .node
+ .npm_package_latest_version("@vue/language-server")
+ .await?,
+ ts_version: self.node.npm_package_latest_version("typescript").await?,
+ }) as Box<_>)
+ }
+ async fn initialization_options(&self) -> Option<Value> {
+ let typescript_sdk_path = self.typescript_install_path.lock();
+ let typescript_sdk_path = typescript_sdk_path
+ .as_ref()
+ .expect("initialization_options called without a container_dir for typescript");
+
+ Some(serde_json::json!({
+ "typescript": {
+ "tsdk": typescript_sdk_path
+ }
+ }))
+ }
+ fn code_action_kinds(&self) -> Option<Vec<CodeActionKind>> {
+ // REFACTOR is explicitly disabled, as vue-lsp does not adhere to LSP protocol for code actions with these - it
+ // sends back a CodeAction with neither `command` nor `edits` fields set, which is against the spec.
+ Some(vec![
+ CodeActionKind::EMPTY,
+ CodeActionKind::QUICKFIX,
+ CodeActionKind::REFACTOR_REWRITE,
+ ])
+ }
+ async fn fetch_server_binary(
+ &self,
+ version: Box<dyn 'static + Send + Any>,
+ container_dir: PathBuf,
+ _: &dyn LspAdapterDelegate,
+ ) -> Result<LanguageServerBinary> {
+ let version = version.downcast::<VueLspVersion>().unwrap();
+ let server_path = container_dir.join(Self::SERVER_PATH);
+ let ts_path = container_dir.join(Self::TYPESCRIPT_PATH);
+ if fs::metadata(&server_path).await.is_err() {
+ self.node
+ .npm_install_packages(
+ &container_dir,
+ &[("@vue/language-server", version.vue_version.as_str())],
+ )
+ .await?;
+ }
+ ensure!(
+ fs::metadata(&server_path).await.is_ok(),
+ "@vue/language-server package installation failed"
+ );
+ if fs::metadata(&ts_path).await.is_err() {
+ self.node
+ .npm_install_packages(
+ &container_dir,
+ &[("typescript", version.ts_version.as_str())],
+ )
+ .await?;
+ }
+
+ ensure!(
+ fs::metadata(&ts_path).await.is_ok(),
+ "typescript for Vue package installation failed"
+ );
+ *self.typescript_install_path.lock() = Some(ts_path);
+ Ok(LanguageServerBinary {
+ path: self.node.binary_path().await?,
+ arguments: vue_server_binary_arguments(&server_path),
+ })
+ }
+
+ async fn cached_server_binary(
+ &self,
+ container_dir: PathBuf,
+ _: &dyn LspAdapterDelegate,
+ ) -> Option<LanguageServerBinary> {
+ let (server, ts_path) = get_cached_server_binary(container_dir, self.node.clone()).await?;
+ *self.typescript_install_path.lock() = Some(ts_path);
+ Some(server)
+ }
+
+ async fn installation_test_binary(
+ &self,
+ container_dir: PathBuf,
+ ) -> Option<LanguageServerBinary> {
+ let (server, ts_path) = get_cached_server_binary(container_dir, self.node.clone())
+ .await
+ .map(|(mut binary, ts_path)| {
+ binary.arguments = vec!["--help".into()];
+ (binary, ts_path)
+ })?;
+ *self.typescript_install_path.lock() = Some(ts_path);
+ Some(server)
+ }
+
+ async fn label_for_completion(
+ &self,
+ item: &lsp2::CompletionItem,
+ language: &Arc<language2::Language>,
+ ) -> Option<language2::CodeLabel> {
+ use lsp2::CompletionItemKind as Kind;
+ let len = item.label.len();
+ let grammar = language.grammar()?;
+ let highlight_id = match item.kind? {
+ Kind::CLASS | Kind::INTERFACE => grammar.highlight_id_for_name("type"),
+ Kind::CONSTRUCTOR => grammar.highlight_id_for_name("type"),
+ Kind::CONSTANT => grammar.highlight_id_for_name("constant"),
+ Kind::FUNCTION | Kind::METHOD => grammar.highlight_id_for_name("function"),
+ Kind::PROPERTY | Kind::FIELD => grammar.highlight_id_for_name("tag"),
+ Kind::VARIABLE => grammar.highlight_id_for_name("type"),
+ Kind::KEYWORD => grammar.highlight_id_for_name("keyword"),
+ Kind::VALUE => grammar.highlight_id_for_name("tag"),
+ _ => None,
+ }?;
+
+ let text = match &item.detail {
+ Some(detail) => format!("{} {}", item.label, detail),
+ None => item.label.clone(),
+ };
+
+ Some(language2::CodeLabel {
+ text,
+ runs: vec![(0..len, highlight_id)],
+ filter_range: 0..len,
+ })
+ }
+}
+
+fn vue_server_binary_arguments(server_path: &Path) -> Vec<OsString> {
+ vec![server_path.into(), "--stdio".into()]
+}
+
+type TypescriptPath = PathBuf;
+async fn get_cached_server_binary(
+ container_dir: PathBuf,
+ node: Arc<dyn NodeRuntime>,
+) -> Option<(LanguageServerBinary, TypescriptPath)> {
+ (|| 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(VueLspAdapter::SERVER_PATH);
+ let typescript_path = last_version_dir.join(VueLspAdapter::TYPESCRIPT_PATH);
+ if server_path.exists() && typescript_path.exists() {
+ Ok((
+ LanguageServerBinary {
+ path: node.binary_path().await?,
+ arguments: vue_server_binary_arguments(&server_path),
+ },
+ typescript_path,
+ ))
+ } else {
+ Err(anyhow!(
+ "missing executable in directory {:?}",
+ last_version_dir
+ ))
+ }
+ })()
+ .await
+ .log_err()
+}
@@ -0,0 +1,2 @@
+("<" @open ">" @close)
+("\"" @open "\"" @close)
@@ -0,0 +1,14 @@
+name = "Vue.js"
+path_suffixes = ["vue"]
+block_comment = ["<!-- ", " -->"]
+autoclose_before = ";:.,=}])>"
+brackets = [
+ { start = "{", end = "}", close = true, newline = true },
+ { start = "[", end = "]", close = true, newline = true },
+ { start = "(", end = ")", close = true, newline = true },
+ { start = "<", end = ">", close = true, newline = true, not_in = ["string", "comment"] },
+ { 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"] },
+]
+word_characters = ["-"]
@@ -0,0 +1,15 @@
+(attribute) @property
+(directive_attribute) @property
+(quoted_attribute_value) @string
+(interpolation) @punctuation.special
+(raw_text) @embedded
+
+((tag_name) @type
+ (#match? @type "^[A-Z]"))
+
+((directive_name) @keyword
+ (#match? @keyword "^v-"))
+
+(start_tag) @tag
+(end_tag) @tag
+(self_closing_tag) @tag
@@ -0,0 +1,7 @@
+(script_element
+ (raw_text) @content
+ (#set! "language" "javascript"))
+
+(style_element
+ (raw_text) @content
+ (#set! "language" "css"))
@@ -0,0 +1,142 @@
+use anyhow::{anyhow, Result};
+use async_trait::async_trait;
+use futures::{future::BoxFuture, FutureExt, StreamExt};
+use gpui2::AppContext;
+use language2::{
+ language_settings::all_language_settings, LanguageServerName, LspAdapter, LspAdapterDelegate,
+};
+use lsp2::LanguageServerBinary;
+use node_runtime::NodeRuntime;
+use serde_json::Value;
+use smol::fs;
+use std::{
+ any::Any,
+ ffi::OsString,
+ future,
+ path::{Path, PathBuf},
+ sync::Arc,
+};
+use util::ResultExt;
+
+const SERVER_PATH: &'static str = "node_modules/yaml-language-server/bin/yaml-language-server";
+
+fn server_binary_arguments(server_path: &Path) -> Vec<OsString> {
+ vec![server_path.into(), "--stdio".into()]
+}
+
+pub struct YamlLspAdapter {
+ node: Arc<dyn NodeRuntime>,
+}
+
+impl YamlLspAdapter {
+ pub fn new(node: Arc<dyn NodeRuntime>) -> Self {
+ YamlLspAdapter { node }
+ }
+}
+
+#[async_trait]
+impl LspAdapter for YamlLspAdapter {
+ async fn name(&self) -> LanguageServerName {
+ LanguageServerName("yaml-language-server".into())
+ }
+
+ fn short_name(&self) -> &'static str {
+ "yaml"
+ }
+
+ async fn fetch_latest_server_version(
+ &self,
+ _: &dyn LspAdapterDelegate,
+ ) -> Result<Box<dyn 'static + Any + Send>> {
+ Ok(Box::new(
+ self.node
+ .npm_package_latest_version("yaml-language-server")
+ .await?,
+ ) as Box<_>)
+ }
+
+ async fn fetch_server_binary(
+ &self,
+ version: Box<dyn 'static + Send + Any>,
+ container_dir: PathBuf,
+ _: &dyn LspAdapterDelegate,
+ ) -> Result<LanguageServerBinary> {
+ let version = version.downcast::<String>().unwrap();
+ let server_path = container_dir.join(SERVER_PATH);
+
+ if fs::metadata(&server_path).await.is_err() {
+ self.node
+ .npm_install_packages(
+ &container_dir,
+ &[("yaml-language-server", 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
+ }
+ fn workspace_configuration(&self, cx: &mut AppContext) -> BoxFuture<'static, Value> {
+ let tab_size = all_language_settings(None, cx)
+ .language(Some("YAML"))
+ .tab_size;
+
+ future::ready(serde_json::json!({
+ "yaml": {
+ "keyOrdering": false
+ },
+ "[yaml]": {
+ "editor.tabSize": tab_size,
+ }
+ }))
+ .boxed()
+ }
+}
+
+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()
+}
@@ -0,0 +1,3 @@
+("[" @open "]" @close)
+("{" @open "}" @close)
+("\"" @open "\"" @close)
@@ -0,0 +1,12 @@
+name = "YAML"
+path_suffixes = ["yml", "yaml"]
+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"] },
+]
+
+increase_indent_pattern = ":\\s*[|>]?\\s*$"
+prettier_parser_name = "yaml"
@@ -0,0 +1,49 @@
+(boolean_scalar) @boolean
+(null_scalar) @constant.builtin
+
+[
+ (double_quote_scalar)
+ (single_quote_scalar)
+ (block_scalar)
+ (string_scalar)
+] @string
+
+(escape_sequence) @string.escape
+
+[
+ (integer_scalar)
+ (float_scalar)
+] @number
+
+(comment) @comment
+
+[
+ (anchor_name)
+ (alias_name)
+ (tag)
+] @type
+
+key: (flow_node (plain_scalar (string_scalar) @property))
+
+[
+ ","
+ "-"
+ ":"
+ ">"
+ "?"
+ "|"
+] @punctuation.delimiter
+
+[
+ "["
+ "]"
+ "{"
+ "}"
+] @punctuation.bracket
+
+[
+ "*"
+ "&"
+ "---"
+ "..."
+] @punctuation.special
@@ -0,0 +1 @@
+(block_mapping_pair key: (flow_node (plain_scalar (string_scalar) @name))) @item
@@ -45,12 +45,8 @@ use util::{
paths, ResultExt,
};
use uuid::Uuid;
+use zed2::languages;
use zed2::{ensure_only_instance, AppState, Assets, IsOnlyInstance};
-// use zed2::{
-// assets::Assets,
-// build_window_options, handle_keymap_file_changes, initialize_workspace, languages, menus,
-// only_instance::{ensure_only_instance, IsOnlyInstance},
-// };
mod open_listener;
@@ -117,9 +113,11 @@ fn main() {
let copilot_language_server_id = languages.next_language_server_id();
languages.set_executor(cx.executor().clone());
languages.set_language_server_download_dir(paths::LANGUAGES_DIR.clone());
+ let languages = Arc::new(languages);
let node_runtime = RealNodeRuntime::new(http.clone());
language2::init(cx);
+ languages::init(languages.clone(), node_runtime.clone(), cx);
let user_store = cx.build_model(|cx| UserStore::new(client.clone(), http.clone(), cx));
// let workspace_store = cx.add_model(|cx| WorkspaceStore::new(client.clone(), cx));
@@ -1,4 +1,5 @@
mod assets;
+pub mod languages;
mod only_instance;
mod open_listener;