diff --git a/Cargo.lock b/Cargo.lock index d76e9f1f40cfb1be27799ee3433957639872b324..9688e0810822a73b743c5fd18e1ee6e07d32d532 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9390,6 +9390,7 @@ dependencies = [ "smol", "streaming-iterator", "strsim", + "strum 0.27.2", "sum_tree", "task", "text", diff --git a/crates/extension_host/src/extension_host.rs b/crates/extension_host/src/extension_host.rs index 5418f630537c1acd98edc8c6af753d9358b23e8f..e0a62df6f7383012028f21945f68b322cb7c16e6 100644 --- a/crates/extension_host/src/extension_host.rs +++ b/crates/extension_host/src/extension_host.rs @@ -37,8 +37,8 @@ use gpui::{ }; use http_client::{AsyncBody, HttpClient, HttpClientWithUrl}; use language::{ - LanguageConfig, LanguageMatcher, LanguageName, LanguageQueries, LoadedLanguage, - QUERY_FILENAME_PREFIXES, Rope, + LanguageConfig, LanguageMatcher, LanguageName, LanguageQueries, LanguageQuery, LoadedLanguage, + Rope, }; use node_runtime::NodeRuntime; use project::ContextProviderWithTasks; @@ -1877,26 +1877,14 @@ impl ExtensionStore { fn load_plugin_queries(root_path: &Path) -> LanguageQueries { let mut result = LanguageQueries::default(); - if let Some(entries) = std::fs::read_dir(root_path).log_err() { - for entry in entries { - let Some(entry) = entry.log_err() else { - continue; - }; - let path = entry.path(); - if let Some(remainder) = path.strip_prefix(root_path).ok().and_then(|p| p.to_str()) { - if !remainder.ends_with(".scm") { - continue; - } - for (name, query) in QUERY_FILENAME_PREFIXES { - if remainder.starts_with(name) { - if let Some(contents) = std::fs::read_to_string(&path).log_err() { - match query(&mut result) { - None => *query(&mut result) = Some(contents.into()), - Some(r) => r.to_mut().push_str(contents.as_ref()), - } - } - break; - } + if std::fs::metadata(root_path).is_ok_and(|metadata| metadata.is_dir()) { + for query in LanguageQuery::variants() { + let query_path = query.query_path(&root_path); + if let Some(contents) = std::fs::read_to_string(&query_path).log_err() { + let query = query.query_mut(&mut result); + match query { + None => *query = Some(contents.into()), + Some(r) => r.to_mut().push_str(contents.as_ref()), } } } diff --git a/crates/gpui_util/src/lib.rs b/crates/gpui_util/src/lib.rs index ae56c2ccd0ef8ff5ab339e1ddd017884abb168d4..28b52879fa9e81cb33b8b1144af350dd08e9cc88 100644 --- a/crates/gpui_util/src/lib.rs +++ b/crates/gpui_util/src/lib.rs @@ -82,6 +82,10 @@ pub trait ResultExt { /// Assert that this result should never be an error in development or tests. fn debug_assert_ok(self, reason: &str) -> Self; fn warn_on_err(self) -> Option; + fn is_err_or(self, f: F) -> bool + where + F: FnOnce(Self::Ok) -> bool; + fn log_with_level(self, level: log::Level) -> Option; fn anyhow(self) -> anyhow::Result where @@ -112,6 +116,16 @@ where self.log_with_level(log::Level::Warn) } + fn is_err_or(self, f: F) -> bool + where + F: FnOnce(T) -> bool, + { + match self { + Ok(value) => f(value), + Err(_) => true, + } + } + #[track_caller] fn log_with_level(self, level: log::Level) -> Option { match self { diff --git a/crates/language/Cargo.toml b/crates/language/Cargo.toml index 37c19172f7c48743e1436ba41e30d0c7ebf99d1d..44b38f952f9a44e6e4390f1ba921cffa2e5712c6 100644 --- a/crates/language/Cargo.toml +++ b/crates/language/Cargo.toml @@ -58,6 +58,7 @@ smallvec.workspace = true smol.workspace = true streaming-iterator.workspace = true strsim.workspace = true +strum.workspace = true sum_tree.workspace = true task.workspace = true text.workspace = true diff --git a/crates/language/src/language.rs b/crates/language/src/language.rs index 4e994a7e60f58b6e4ccd50c2cb0584f91bd351f2..c26b5053b33fb112881b6a6bedba2c917a9319a9 100644 --- a/crates/language/src/language.rs +++ b/crates/language/src/language.rs @@ -83,8 +83,8 @@ pub use buffer::Operation; pub use buffer::*; pub use diagnostic_set::{DiagnosticEntry, DiagnosticEntryRef, DiagnosticGroup}; pub use language_registry::{ - AvailableLanguage, BinaryStatus, LanguageNotFound, LanguageQueries, LanguageRegistry, - QUERY_FILENAME_PREFIXES, + AvailableLanguage, BinaryStatus, LanguageNotFound, LanguageQueries, LanguageQuery, + LanguageRegistry, }; pub use lsp::{LanguageServerId, LanguageServerName}; pub use outline::*; diff --git a/crates/language/src/language_registry.rs b/crates/language/src/language_registry.rs index d73a44fda3347ebcec9c6798325838acec543566..7d9387e339e5c83f10bc3d4f2b029e580d437416 100644 --- a/crates/language/src/language_registry.rs +++ b/crates/language/src/language_registry.rs @@ -232,22 +232,53 @@ impl std::fmt::Display for LanguageNotFound { } } -pub const QUERY_FILENAME_PREFIXES: &[( - &str, - fn(&mut LanguageQueries) -> &mut Option>, -)] = &[ - ("highlights", |q| &mut q.highlights), - ("brackets", |q| &mut q.brackets), - ("outline", |q| &mut q.outline), - ("indents", |q| &mut q.indents), - ("injections", |q| &mut q.injections), - ("overrides", |q| &mut q.overrides), - ("redactions", |q| &mut q.redactions), - ("runnables", |q| &mut q.runnables), - ("debugger", |q| &mut q.debugger), - ("textobjects", |q| &mut q.text_objects), - ("imports", |q| &mut q.imports), -]; +#[derive(Copy, Clone, PartialEq, Eq, strum::Display, strum::EnumIter)] +#[strum(serialize_all = "snake_case")] +pub enum LanguageQuery { + Highlights, + Brackets, + Outline, + Indents, + Injections, + Overrides, + Runnables, + Debugger, + Textobjects, + Imports, +} + +impl LanguageQuery { + pub fn variants() -> impl Iterator { + use strum::IntoEnumIterator; + Self::iter() + } + + pub fn query_mut<'a>( + &self, + queries: &'a mut LanguageQueries, + ) -> &'a mut Option> { + match self { + LanguageQuery::Highlights => &mut queries.highlights, + LanguageQuery::Brackets => &mut queries.brackets, + LanguageQuery::Outline => &mut queries.outline, + LanguageQuery::Indents => &mut queries.indents, + LanguageQuery::Injections => &mut queries.injections, + LanguageQuery::Overrides => &mut queries.overrides, + LanguageQuery::Runnables => &mut queries.runnables, + LanguageQuery::Debugger => &mut queries.debugger, + LanguageQuery::Textobjects => &mut queries.text_objects, + LanguageQuery::Imports => &mut queries.imports, + } + } + + pub fn query_path(&self, folder_path: &Path) -> PathBuf { + folder_path.join(self.file_name()) + } + + pub fn file_name(&self) -> String { + format!("{self}.scm") + } +} /// Tree-sitter language queries for a given language. #[derive(Debug, Default)] diff --git a/crates/languages/src/lib.rs b/crates/languages/src/lib.rs index 240935d2f817b43b2aae03dfdff4321de6522bf3..b0149c83534ba3cd2ddc18a6c6bf74e602e3a7a1 100644 --- a/crates/languages/src/lib.rs +++ b/crates/languages/src/lib.rs @@ -8,7 +8,7 @@ use rust_embed::RustEmbed; use settings::{SemanticTokenRules, SettingsStore}; use smol::stream::StreamExt; use std::{str, sync::Arc}; -use util::{ResultExt, asset_str}; +use util::{ResultExt, embedded_file_to_str}; pub use language::*; @@ -436,20 +436,19 @@ fn load_config(name: &str) -> LanguageConfig { fn load_queries(name: &str) -> LanguageQueries { let mut result = LanguageQueries::default(); - for path in LanguageDir::iter() { - if let Some(remainder) = path.strip_prefix(name).and_then(|p| p.strip_prefix('/')) { - if !remainder.ends_with(".scm") { - continue; - } - for (name, query) in QUERY_FILENAME_PREFIXES { - if remainder.starts_with(name) { - let contents = asset_str::(path.as_ref()); - match query(&mut result) { - None => *query(&mut result) = Some(contents), - Some(r) => r.to_mut().push_str(contents.as_ref()), - } - } - } + for query in LanguageQuery::variants() { + let Some(contents) = LanguageDir::get(&format!( + "{name}/{query_filename}", + query_filename = query.file_name() + )) + .map(embedded_file_to_str) else { + continue; + }; + + let query = query.query_mut(&mut result); + match query { + None => *query = Some(contents), + Some(r) => r.to_mut().push_str(contents.as_ref()), } } result diff --git a/crates/util/src/util.rs b/crates/util/src/util.rs index 4f129ef6d529aff0991b86882e5e60b6ad837d5c..6b9152e4ab77eb0befef51d55e6256c2e5bf25e7 100644 --- a/crates/util/src/util.rs +++ b/crates/util/src/util.rs @@ -20,6 +20,7 @@ pub mod time; use anyhow::Result; use itertools::Either; use regex::Regex; +use rust_embed::EmbeddedFile; use std::path::{Path, PathBuf}; use std::sync::LazyLock; use std::{ @@ -594,7 +595,12 @@ pub use rng::RandomCharIter; /// Get an embedded file as a string. pub fn asset_str(path: &str) -> Cow<'static, str> { - match A::get(path).expect(path).data { + let asset = A::get(path).expect(path); + embedded_file_to_str(asset) +} + +pub fn embedded_file_to_str(file: EmbeddedFile) -> Cow<'static, str> { + match file.data { Cow::Borrowed(bytes) => Cow::Borrowed(std::str::from_utf8(bytes).unwrap()), Cow::Owned(bytes) => Cow::Owned(String::from_utf8(bytes).unwrap()), }