language: Refactor available_languages into its own struct (#61388)

This adds a dedicated `AvailableLanguages` struct in preparation for a
more cabable language matching based on a given language config. No
functional changes, just shuffling some code around for this round.

Also made the LanguageMatcher non-cloneable in favor of wrapping it in
an Arc, since cloning is rather expensive for this and having a
reference is sufficient in all cases right now.

Release Notes:

- N/A

---------

Co-authored-by: Ben Brandt <benjamin.j.brandt@gmail.com>
This commit is contained in:
Finn EversandBen Brandt authored and GitHub committed 2026-07-23 11:22:43 +00:00
1 parent 97b51f2620
commit 25632b979b
35 files changed
+763 -576

No files matched your search

+3 -2
View File
@@ -537,10 +537,11 @@ mod tests {
let rust_language = Arc::new(language::Language::new(
language::LanguageConfig {
name: "Rust".into(),
matcher: language::LanguageMatcher {
matcher: (language::LanguageMatcher {
path_suffixes: vec!["rs".to_string()],
..Default::default()
},
})
.into(),
..Default::default()
},
None,
+3 -2
View File
@@ -3468,10 +3468,11 @@ mod tests {
let plain_text_language = Arc::new(language::Language::new(
language::LanguageConfig {
name: "Plain Text".into(),
matcher: language::LanguageMatcher {
matcher: (language::LanguageMatcher {
path_suffixes: vec!["txt".to_string()],
..Default::default()
},
})
.into(),
..Default::default()
},
None,
@@ -2307,10 +2307,11 @@ async fn test_propagate_saves_and_fs_changes(
let rust = Arc::new(Language::new(
LanguageConfig {
name: "Rust".into(),
matcher: LanguageMatcher {
matcher: (LanguageMatcher {
path_suffixes: vec!["rs".to_string()],
..Default::default()
},
})
.into(),
..Default::default()
},
Some(tree_sitter_rust::LANGUAGE.into()),
@@ -2318,10 +2319,11 @@ async fn test_propagate_saves_and_fs_changes(
let javascript = Arc::new(Language::new(
LanguageConfig {
name: "JavaScript".into(),
matcher: LanguageMatcher {
matcher: (LanguageMatcher {
path_suffixes: vec!["js".to_string()],
..Default::default()
},
})
.into(),
..Default::default()
},
Some(tree_sitter_rust::LANGUAGE.into()),
@@ -4130,10 +4132,11 @@ async fn test_collaborating_with_diagnostics(
client_a.language_registry().add(Arc::new(Language::new(
LanguageConfig {
name: "Rust".into(),
matcher: LanguageMatcher {
matcher: (LanguageMatcher {
path_suffixes: vec!["rs".to_string()],
..Default::default()
},
})
.into(),
..Default::default()
},
Some(tree_sitter_rust::LANGUAGE.into()),
@@ -4846,10 +4849,11 @@ async fn test_prettier_formatting_buffer(
client_a.language_registry().add(Arc::new(Language::new(
LanguageConfig {
name: "TypeScript".into(),
matcher: LanguageMatcher {
matcher: (LanguageMatcher {
path_suffixes: vec!["ts".to_string()],
..Default::default()
},
})
.into(),
..Default::default()
},
Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
@@ -1047,10 +1047,11 @@ impl RandomizedTest for ProjectCollaborationTest {
client.language_registry().add(Arc::new(Language::new(
LanguageConfig {
name: "Rust".into(),
matcher: LanguageMatcher {
matcher: (LanguageMatcher {
path_suffixes: vec!["rs".to_string()],
..Default::default()
},
})
.into(),
..Default::default()
},
None,
@@ -686,10 +686,11 @@ async fn test_ssh_collaboration_formatting_with_prettier(
let ts_lang = Arc::new(Language::new(
LanguageConfig {
name: "TypeScript".into(),
matcher: LanguageMatcher {
matcher: (LanguageMatcher {
path_suffixes: vec!["ts".to_string()],
..LanguageMatcher::default()
},
})
.into(),
..LanguageConfig::default()
},
Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
+15 -10
View File
@@ -1831,10 +1831,11 @@ fn python_lang() -> Language {
Language::new(
LanguageConfig {
name: "Python".into(),
matcher: LanguageMatcher {
matcher: (LanguageMatcher {
path_suffixes: vec!["py".to_string()],
..Default::default()
},
})
.into(),
..Default::default()
},
Some(tree_sitter_python::LANGUAGE.into()),
@@ -1849,10 +1850,11 @@ fn go_lang() -> Arc<Language> {
Language::new(
LanguageConfig {
name: "Go".into(),
matcher: LanguageMatcher {
matcher: (LanguageMatcher {
path_suffixes: vec!["go".to_string()],
..Default::default()
},
})
.into(),
..Default::default()
},
Some(tree_sitter_go::LANGUAGE.into()),
@@ -2268,10 +2270,11 @@ fn javascript_lang() -> Arc<Language> {
Language::new(
LanguageConfig {
name: "JavaScript".into(),
matcher: LanguageMatcher {
matcher: (LanguageMatcher {
path_suffixes: vec!["js".to_string()],
..Default::default()
},
})
.into(),
..Default::default()
},
Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
@@ -2287,10 +2290,11 @@ fn typescript_lang() -> Arc<Language> {
Language::new(
LanguageConfig {
name: "TypeScript".into(),
matcher: LanguageMatcher {
matcher: (LanguageMatcher {
path_suffixes: vec!["ts".to_string()],
..Default::default()
},
})
.into(),
..Default::default()
},
Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
@@ -2306,10 +2310,11 @@ fn tsx_lang() -> Arc<Language> {
Language::new(
LanguageConfig {
name: "TSX".into(),
matcher: LanguageMatcher {
matcher: (LanguageMatcher {
path_suffixes: vec!["tsx".to_string()],
..Default::default()
},
})
.into(),
..Default::default()
},
Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
+3 -2
View File
@@ -1709,10 +1709,11 @@ mod tests {
language_registry.add(Arc::new(language::Language::new(
language::LanguageConfig {
name: "TypeScript".into(),
matcher: language::LanguageMatcher {
matcher: (language::LanguageMatcher {
path_suffixes: vec!["ts".to_string()],
..language::LanguageMatcher::default()
},
})
.into(),
..language::LanguageConfig::default()
},
Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
+9 -6
View File
@@ -3269,10 +3269,11 @@ pub mod tests {
Language::new(
LanguageConfig {
name: "Test".into(),
matcher: LanguageMatcher {
matcher: (LanguageMatcher {
path_suffixes: vec![".test".to_string()],
..Default::default()
},
})
.into(),
..Default::default()
},
Some(tree_sitter_rust::LANGUAGE.into()),
@@ -3717,10 +3718,11 @@ pub mod tests {
Language::new(
LanguageConfig {
name: "Test".into(),
matcher: LanguageMatcher {
matcher: (LanguageMatcher {
path_suffixes: vec![".test".to_string()],
..Default::default()
},
})
.into(),
..Default::default()
},
Some(tree_sitter_rust::LANGUAGE.into()),
@@ -3804,10 +3806,11 @@ pub mod tests {
Language::new(
LanguageConfig {
name: "Test".into(),
matcher: LanguageMatcher {
matcher: (LanguageMatcher {
path_suffixes: vec![".test".to_string()],
..Default::default()
},
})
.into(),
..Default::default()
},
Some(tree_sitter_rust::LANGUAGE.into()),
+45 -30
View File
@@ -17239,10 +17239,11 @@ async fn test_document_format_manual_trigger(cx: &mut TestAppContext) {
language_registry.add(Arc::new(Language::new(
LanguageConfig {
name: "Rust".into(),
matcher: LanguageMatcher {
matcher: (LanguageMatcher {
path_suffixes: vec!["rs".to_string()],
..Default::default()
},
})
.into(),
..LanguageConfig::default()
},
Some(tree_sitter_rust::LANGUAGE.into()),
@@ -17690,10 +17691,11 @@ async fn test_organize_imports_manual_trigger(cx: &mut TestAppContext) {
language_registry.add(Arc::new(Language::new(
LanguageConfig {
name: "TypeScript".into(),
matcher: LanguageMatcher {
matcher: (LanguageMatcher {
path_suffixes: vec!["ts".to_string()],
..Default::default()
},
})
.into(),
..LanguageConfig::default()
},
Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
@@ -21133,10 +21135,11 @@ async fn test_multiline_completion(cx: &mut TestAppContext) {
let typescript_language = Arc::new(Language::new(
LanguageConfig {
name: "TypeScript".into(),
matcher: LanguageMatcher {
matcher: (LanguageMatcher {
path_suffixes: vec!["ts".to_string()],
..LanguageMatcher::default()
},
})
.into(),
line_comments: vec!["// ".into()],
..LanguageConfig::default()
},
@@ -23944,10 +23947,11 @@ async fn test_on_type_formatting_not_triggered(cx: &mut TestAppContext) {
language_registry.add(Arc::new(Language::new(
LanguageConfig {
name: "Rust".into(),
matcher: LanguageMatcher {
matcher: (LanguageMatcher {
path_suffixes: vec!["rs".to_string()],
..Default::default()
},
})
.into(),
brackets: BracketPairConfig {
pairs: vec![BracketPair {
start: "{".to_string(),
@@ -24119,10 +24123,11 @@ async fn test_language_server_restart_due_to_settings_change(cx: &mut TestAppCon
language_registry.add(Arc::new(Language::new(
LanguageConfig {
name: language_name.clone(),
matcher: LanguageMatcher {
matcher: (LanguageMatcher {
path_suffixes: vec!["rs".to_string()],
..Default::default()
},
})
.into(),
..Default::default()
},
Some(tree_sitter_rust::LANGUAGE.into()),
@@ -25308,10 +25313,11 @@ async fn test_completions_in_languages_with_extra_word_characters(cx: &mut TestA
let mut cx = EditorLspTestContext::new(
Language::new(
LanguageConfig {
matcher: LanguageMatcher {
matcher: (LanguageMatcher {
path_suffixes: vec!["jsx".into()],
..Default::default()
},
})
.into(),
overrides: [(
"element".into(),
LanguageConfigOverride {
@@ -25423,10 +25429,11 @@ async fn test_document_format_with_prettier(cx: &mut TestAppContext) {
language_registry.add(Arc::new(Language::new(
LanguageConfig {
name: "TypeScript".into(),
matcher: LanguageMatcher {
matcher: (LanguageMatcher {
path_suffixes: vec!["ts".to_string()],
..Default::default()
},
})
.into(),
..Default::default()
},
Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
@@ -25513,10 +25520,11 @@ async fn test_document_format_with_prettier_explicit_language(cx: &mut TestAppCo
let ts_lang = Arc::new(Language::new(
LanguageConfig {
name: "TypeScript".into(),
matcher: LanguageMatcher {
matcher: (LanguageMatcher {
path_suffixes: vec!["ts".to_string()],
..LanguageMatcher::default()
},
})
.into(),
prettier_parser_name: Some("typescript".to_string()),
..LanguageConfig::default()
},
@@ -25615,10 +25623,11 @@ async fn test_range_format_with_prettier(cx: &mut TestAppContext) {
language_registry.add(Arc::new(Language::new(
LanguageConfig {
name: "TypeScript".into(),
matcher: LanguageMatcher {
matcher: (LanguageMatcher {
path_suffixes: vec!["ts".to_string()],
..Default::default()
},
})
.into(),
..Default::default()
},
Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
@@ -25691,10 +25700,11 @@ async fn test_range_format_with_prettier_explicit_language(cx: &mut TestAppConte
let ts_lang = Arc::new(Language::new(
LanguageConfig {
name: "TypeScript".into(),
matcher: LanguageMatcher {
matcher: (LanguageMatcher {
path_suffixes: vec!["ts".to_string()],
..LanguageMatcher::default()
},
})
.into(),
prettier_parser_name: Some("typescript".to_string()),
..LanguageConfig::default()
},
@@ -32802,10 +32812,11 @@ async fn test_apply_code_lens_actions_with_commands(cx: &mut gpui::TestAppContex
language_registry.add(Arc::new(Language::new(
LanguageConfig {
name: "TypeScript".into(),
matcher: LanguageMatcher {
matcher: (LanguageMatcher {
path_suffixes: vec!["ts".to_string()],
..Default::default()
},
})
.into(),
..Default::default()
},
Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
@@ -33566,10 +33577,11 @@ async fn test_html_linked_edits_on_completion(cx: &mut TestAppContext) {
let html_language = Arc::new(Language::new(
LanguageConfig {
name: "HTML".into(),
matcher: LanguageMatcher {
matcher: (LanguageMatcher {
path_suffixes: vec!["html".to_string()],
..LanguageMatcher::default()
},
})
.into(),
brackets: BracketPairConfig {
pairs: vec![BracketPair {
start: "<".into(),
@@ -33689,10 +33701,11 @@ async fn test_linked_edits_on_typing_punctuation(cx: &mut TestAppContext) {
let language = Arc::new(Language::new(
LanguageConfig {
name: "TSX".into(),
matcher: LanguageMatcher {
matcher: (LanguageMatcher {
path_suffixes: vec!["tsx".to_string()],
..LanguageMatcher::default()
},
})
.into(),
brackets: BracketPairConfig {
pairs: vec![BracketPair {
start: "<".into(),
@@ -33760,10 +33773,11 @@ async fn test_linked_edits_on_typing_dot_without_language_override(cx: &mut Test
let language = Arc::new(Language::new(
LanguageConfig {
name: "HTML".into(),
matcher: LanguageMatcher {
matcher: (LanguageMatcher {
path_suffixes: vec!["html".to_string()],
..LanguageMatcher::default()
},
})
.into(),
brackets: BracketPairConfig {
pairs: vec![BracketPair {
start: "<".into(),
@@ -41519,10 +41533,11 @@ async fn test_tsx_nested_jsx_member_expression_highlights(cx: &mut TestAppContex
Language::new(
LanguageConfig {
name: "TSX".into(),
matcher: LanguageMatcher {
matcher: (LanguageMatcher {
path_suffixes: vec!["tsx".to_string()],
..LanguageMatcher::default()
},
})
.into(),
..LanguageConfig::default()
},
Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
@@ -99,10 +99,11 @@ mod tests {
Language::new(
LanguageConfig {
name: "Rust".into(),
matcher: LanguageMatcher {
matcher: (LanguageMatcher {
path_suffixes: vec!["rs".to_string()],
..Default::default()
},
})
.into(),
brackets: BracketPairConfig {
pairs: vec![
BracketPair {
+6 -4
View File
@@ -1402,10 +1402,11 @@ pub mod tests {
language_registry.add(Arc::new(Language::new(
LanguageConfig {
name: name.into(),
matcher: LanguageMatcher {
matcher: (LanguageMatcher {
path_suffixes: vec![path_suffix.to_string()],
..Default::default()
},
})
.into(),
..Default::default()
},
Some(tree_sitter_rust::LANGUAGE.into()),
@@ -4636,10 +4637,11 @@ let c = 3;"#
language_registry.add(Arc::new(Language::new(
LanguageConfig {
name: name.into(),
matcher: LanguageMatcher {
matcher: (LanguageMatcher {
path_suffixes: vec![path_suffix.to_string()],
..Default::default()
},
})
.into(),
..Default::default()
},
Some(tree_sitter_rust::LANGUAGE.into()),
+12 -8
View File
@@ -859,10 +859,11 @@ mod tests {
let toml_language = Arc::new(Language::new(
LanguageConfig {
name: "TOML".into(),
matcher: LanguageMatcher {
matcher: (LanguageMatcher {
path_suffixes: vec!["toml".into()],
..LanguageMatcher::default()
},
})
.into(),
..LanguageConfig::default()
},
None,
@@ -1086,10 +1087,11 @@ mod tests {
let toml_language = Arc::new(Language::new(
LanguageConfig {
name: "TOML".into(),
matcher: LanguageMatcher {
matcher: (LanguageMatcher {
path_suffixes: vec!["toml".into()],
..LanguageMatcher::default()
},
})
.into(),
..LanguageConfig::default()
},
None,
@@ -1097,10 +1099,11 @@ mod tests {
let rust_language = Arc::new(Language::new(
LanguageConfig {
name: "Rust".into(),
matcher: LanguageMatcher {
matcher: (LanguageMatcher {
path_suffixes: vec!["rs".into()],
..LanguageMatcher::default()
},
})
.into(),
..LanguageConfig::default()
},
None,
@@ -1375,10 +1378,11 @@ mod tests {
let rust_language = Arc::new(Language::new(
LanguageConfig {
name: "Rust".into(),
matcher: LanguageMatcher {
matcher: (LanguageMatcher {
path_suffixes: vec!["rs".into()],
..LanguageMatcher::default()
},
})
.into(),
..LanguageConfig::default()
},
None,
@@ -177,10 +177,10 @@ impl EditorLspTestContext {
let language = Language::new(
LanguageConfig {
name: "Typescript".into(),
matcher: LanguageMatcher {
matcher: (LanguageMatcher {
path_suffixes: vec!["ts".to_string()],
..Default::default()
},
}).into(),
brackets: language::BracketPairConfig {
pairs: vec![language::BracketPair {
start: "{".to_string(),
@@ -282,10 +282,10 @@ impl EditorLspTestContext {
let language = Language::new(
LanguageConfig {
name: "TSX".into(),
matcher: LanguageMatcher {
matcher: (LanguageMatcher {
path_suffixes: vec!["tsx".to_string()],
..Default::default()
},
}).into(),
brackets: language::BracketPairConfig {
pairs: vec![language::BracketPair {
start: "{".to_string(),
@@ -390,10 +390,11 @@ impl EditorLspTestContext {
let language = Language::new(
LanguageConfig {
name: "HTML".into(),
matcher: LanguageMatcher {
matcher: (LanguageMatcher {
path_suffixes: vec!["html".into()],
..Default::default()
},
})
.into(),
block_comment: Some(BlockCommentConfig {
start: "<!--".into(),
prefix: "".into(),
+2 -2
View File
@@ -232,7 +232,7 @@ pub trait ExtensionLanguageProxy: Send + Sync + 'static {
&self,
language: LanguageName,
grammar: Option<Arc<str>>,
matcher: LanguageMatcher,
matcher: Arc<LanguageMatcher>,
hidden: bool,
load: Arc<dyn Fn() -> Result<LoadedLanguage> + Send + Sync + 'static>,
);
@@ -250,7 +250,7 @@ impl ExtensionLanguageProxy for ExtensionHostProxy {
&self,
language: LanguageName,
grammar: Option<Arc<str>>,
matcher: LanguageMatcher,
matcher: Arc<LanguageMatcher>,
hidden: bool,
load: Arc<dyn Fn() -> Result<LoadedLanguage> + Send + Sync + 'static>,
) {
+1 -1
View File
@@ -249,7 +249,7 @@ pub struct ExtensionIndexIconThemeEntry {
pub struct ExtensionIndexLanguageEntry {
pub extension: Arc<str>,
pub path: PathBuf,
pub matcher: LanguageMatcher,
pub matcher: Arc<LanguageMatcher>,
pub hidden: bool,
pub grammar: Option<Arc<str>>,
}
@@ -56,7 +56,7 @@ fn remote_sync_language_entry(extension: &str, path: &str) -> ExtensionIndexLang
ExtensionIndexLanguageEntry {
extension: extension.into(),
path: path.into(),
matcher: LanguageMatcher::default(),
matcher: LanguageMatcher::default().into(),
hidden: false,
grammar: None,
}
@@ -393,11 +393,12 @@ async fn test_extension_store(cx: &mut TestAppContext) {
path: "languages/erb".into(),
grammar: Some("embedded_template".into()),
hidden: false,
matcher: LanguageMatcher {
matcher: (LanguageMatcher {
path_suffixes: vec!["erb".into()],
first_line_pattern: None,
..LanguageMatcher::default()
},
})
.into(),
},
),
(
@@ -407,11 +408,12 @@ async fn test_extension_store(cx: &mut TestAppContext) {
path: "languages/ruby".into(),
grammar: Some("ruby".into()),
hidden: false,
matcher: LanguageMatcher {
matcher: (LanguageMatcher {
path_suffixes: vec!["rb".into()],
first_line_pattern: None,
..LanguageMatcher::default()
},
})
.into(),
},
),
]
+2 -2
View File
@@ -967,9 +967,9 @@ async fn build_buffer(
let text = Rope::from(text);
let language =
cx.update(|_, cx| language_registry.language_for_file(&blob, Some(&text), cx))?;
let language = if let Some(language) = language {
let language = if let Some(language_id) = language {
language_registry
.load_language(&language)
.load_language(language_id)
.await
.ok()
.and_then(|e| e.log_err())
+367
View File
@@ -0,0 +1,367 @@
use crate::{LanguageId, LanguageMatcher, LanguageName, LoadedLanguage, ManifestName};
use anyhow::Result;
use collections::FxHashMap;
use globset::GlobSet;
use smallvec::SmallVec;
use std::{cell::LazyCell, path::Path, sync::Arc};
use sum_tree::Bias;
use text::{Point, Rope};
use unicase::UniCase;
#[derive(Clone)]
pub struct AvailableLanguage {
pub(super) id: LanguageId,
pub(super) name: LanguageName,
pub(super) grammar: Option<Arc<str>>,
pub(super) matcher: Arc<LanguageMatcher>,
pub(super) hidden: bool,
pub(super) load: Arc<dyn Fn() -> Result<LoadedLanguage> + 'static + Send + Sync>,
pub(super) loaded: bool,
pub(super) manifest_name: Option<ManifestName>,
}
impl AvailableLanguage {
pub fn id(&self) -> LanguageId {
self.id
}
pub fn name(&self) -> LanguageName {
self.name.clone()
}
pub fn matcher(&self) -> &LanguageMatcher {
&self.matcher
}
pub fn hidden(&self) -> bool {
self.hidden
}
}
#[derive(Default)]
pub(super) struct AvailableLanguages(Vec<AvailableLanguage>);
#[derive(Copy, Clone, Default)]
enum LanguageMatchPrecedence {
#[default]
Undetermined,
PathOrContent(usize),
UserConfigured(usize),
}
impl AvailableLanguages {
pub(super) fn register(
&mut self,
name: LanguageName,
grammar: Option<Arc<str>>,
matcher: Arc<LanguageMatcher>,
hidden: bool,
manifest_name: Option<ManifestName>,
load: Arc<dyn Fn() -> Result<LoadedLanguage> + 'static + Send + Sync>,
) -> bool {
if let Some(existing_language) = self
.0
.iter_mut()
.find(|existing_language| existing_language.name == name)
{
existing_language.grammar = grammar;
existing_language.matcher = matcher;
existing_language.load = load;
existing_language.manifest_name = manifest_name;
false
} else {
self.add(AvailableLanguage {
id: LanguageId::new(),
name,
grammar,
matcher,
hidden,
load,
loaded: false,
manifest_name,
});
true
}
}
pub(super) fn add(&mut self, language: AvailableLanguage) {
self.0.push(language);
}
pub(super) fn unloaded_language_names(&self) -> Vec<LanguageName> {
self.0
.iter()
.filter_map(|language| (!language.loaded).then_some(language.name.clone()))
.collect()
}
#[cfg(any(test, feature = "test-support"))]
pub(super) fn name_for_id(&self, id: LanguageId) -> Option<LanguageName> {
self.0
.iter()
.find(|language| language.id == id)
.map(|language| language.name.clone())
}
pub(super) fn get_language(&self, id: LanguageId) -> Option<&AvailableLanguage> {
self.0.iter().find(|language| language.id == id)
}
pub(super) fn find_name_by_extension(&self, extension: &str) -> Option<LanguageName> {
self.0
.iter()
.find(|language| {
language
.matcher
.path_suffixes
.iter()
.any(|suffix| suffix == extension)
})
.map(|language| language.name.clone())
}
pub(super) fn find_by_exact_name(&self, name: &str) -> Option<AvailableLanguage> {
self.0
.iter()
.find(|language| language.name.0.as_ref() == name)
.cloned()
}
pub(super) fn find_by_modeline_name(&self, modeline_name: &str) -> Option<AvailableLanguage> {
let modeline_name = modeline_name.to_lowercase();
self.0
.iter()
.find(|language| {
language
.matcher
.modeline_aliases
.iter()
.any(|alias| alias.to_lowercase() == modeline_name)
})
.or_else(|| {
self.0.iter().find(|language| {
language
.grammar
.as_ref()
.is_some_and(|grammar| grammar.to_lowercase() == modeline_name)
})
})
.or_else(|| {
self.0
.iter()
.find(|language| language.name.0.to_lowercase() == modeline_name)
})
.cloned()
}
pub(super) fn mark_all_unloaded(&mut self) {
for language in &mut self.0 {
language.loaded = false;
}
}
pub(super) fn remove(&mut self, names: &[LanguageName]) {
self.0.retain(|language| !names.contains(&language.name));
}
pub(super) fn mark_loaded(&mut self, id: LanguageId) {
if let Some(language) = self.0.iter_mut().find(|language| language.id == id) {
language.loaded = true;
}
}
pub(super) fn find_by_name(&self, name: &str) -> Option<LanguageId> {
let name = UniCase::new(name);
self.find_best_match(
|language_name, _, current_best_match| match current_best_match {
LanguageMatchPrecedence::Undetermined if UniCase::new(&language_name.0) == name => {
Some(LanguageMatchPrecedence::PathOrContent(name.len()))
}
LanguageMatchPrecedence::Undetermined
| LanguageMatchPrecedence::UserConfigured(_)
| LanguageMatchPrecedence::PathOrContent(_) => None,
},
)
}
pub(super) fn find_by_name_or_extension(&self, string: &str) -> Option<LanguageId> {
let string = UniCase::new(string);
self.find_best_match(|name, matcher, current_best_match| {
let name_matches = || {
UniCase::new(&name.0) == string
|| matcher
.path_suffixes
.iter()
.any(|suffix| UniCase::new(suffix) == string)
};
match current_best_match {
LanguageMatchPrecedence::Undetermined => {
name_matches().then_some(LanguageMatchPrecedence::PathOrContent(string.len()))
}
LanguageMatchPrecedence::PathOrContent(len) => (string.len() > len
&& name_matches())
.then_some(LanguageMatchPrecedence::PathOrContent(string.len())),
LanguageMatchPrecedence::UserConfigured(_) => None,
}
})
}
pub(super) fn find_for_file(
&self,
path: &Path,
content: Option<&Rope>,
user_file_types: Option<&FxHashMap<Arc<str>, (GlobSet, Vec<String>)>>,
) -> Option<LanguageId> {
let filename = path.file_name().and_then(|filename| filename.to_str());
// `Path.extension()` returns None for files with a leading '.'
// and no other extension which is not the desired behavior here,
// as we want `.zshrc` to result in extension being `Some("zshrc")`
let extension = filename.and_then(|filename| filename.split('.').next_back());
let path_suffixes = [extension, filename, path.to_str()]
.iter()
.filter_map(|suffix| suffix.map(|suffix| (suffix, globset::Candidate::new(suffix))))
.collect::<SmallVec<[_; 3]>>();
let content = LazyCell::new(|| {
content.map(|content| {
let end = content.clip_point(Point::new(0, 256), Bias::Left);
let end = content.point_to_offset(end);
content.chunks_in_range(0..end).collect::<String>()
})
});
self.find_best_match(move |language_name, matcher, current_best_match| {
let path_matches_default_suffix = || {
let len =
matcher
.path_suffixes
.iter()
.fold(0, |acc: usize, path_suffix: &String| {
let ext = ".".to_string() + path_suffix;
let matched_suffix_len = path_suffixes
.iter()
.find(|(suffix, _)| suffix.ends_with(&ext) || suffix == path_suffix)
.map(|(suffix, _)| suffix.len());
matched_suffix_len.map_or(acc, |len| acc.max(len))
});
(len > 0).then_some(len)
};
let path_matches_custom_suffix = || {
user_file_types
.and_then(|types| types.get(language_name.as_ref()))
.and_then(|(custom_suffixes, _)| {
path_suffixes
.iter()
.find(|(_, candidate)| custom_suffixes.is_match_candidate(candidate))
.map(|(suffix, _)| suffix.len())
})
};
let content_matches = || {
matcher.first_line_pattern.as_ref().is_some_and(|pattern| {
content
.as_ref()
.is_some_and(|content| pattern.is_match(content))
})
};
// Only return a match for the given file if we have a better match than
// the current one.
match current_best_match {
LanguageMatchPrecedence::PathOrContent(current_len) => {
if let Some(len) = path_matches_custom_suffix() {
// >= because user config should win tie with system ext len
(len >= current_len).then_some(LanguageMatchPrecedence::UserConfigured(len))
} else if let Some(len) = path_matches_default_suffix() {
// >= because user config should win tie with system ext len
(len >= current_len).then_some(LanguageMatchPrecedence::PathOrContent(len))
} else {
None
}
}
LanguageMatchPrecedence::Undetermined => {
if let Some(len) = path_matches_custom_suffix() {
Some(LanguageMatchPrecedence::UserConfigured(len))
} else if let Some(len) = path_matches_default_suffix() {
Some(LanguageMatchPrecedence::PathOrContent(len))
} else if content_matches() {
Some(LanguageMatchPrecedence::PathOrContent(1))
} else {
None
}
}
LanguageMatchPrecedence::UserConfigured(_) => None,
}
})
}
fn find_best_match(
&self,
callback: impl Fn(
&LanguageName,
&LanguageMatcher,
LanguageMatchPrecedence,
) -> Option<LanguageMatchPrecedence>,
) -> Option<LanguageId> {
self.0
.iter()
.rev()
.fold(None, |best_language_match, language| {
let current_match_type = best_language_match
.as_ref()
.map_or(LanguageMatchPrecedence::default(), |(_, score)| *score);
let language_score =
callback(&language.name, &language.matcher, current_match_type);
match (language_score, current_match_type) {
// no current best, so our candidate is better
(
Some(
LanguageMatchPrecedence::PathOrContent(_)
| LanguageMatchPrecedence::UserConfigured(_),
),
LanguageMatchPrecedence::Undetermined,
) => language_score.map(|new_score| (language, new_score)),
// our candidate is better only if the name is longer
(
Some(LanguageMatchPrecedence::PathOrContent(new_len)),
LanguageMatchPrecedence::PathOrContent(current_len),
)
| (
Some(LanguageMatchPrecedence::UserConfigured(new_len)),
LanguageMatchPrecedence::UserConfigured(current_len),
)
| (
Some(LanguageMatchPrecedence::PathOrContent(new_len)),
LanguageMatchPrecedence::UserConfigured(current_len),
) => {
if new_len > current_len {
language_score.map(|new_score| (language, new_score))
} else {
best_language_match
}
}
// our candidate is better if the name is longer or equal to
(
Some(LanguageMatchPrecedence::UserConfigured(new_len)),
LanguageMatchPrecedence::PathOrContent(current_len),
) => {
if new_len >= current_len {
language_score.map(|new_score| (language, new_score))
} else {
best_language_match
}
}
// no candidate, use current best
(None, _) | (Some(LanguageMatchPrecedence::Undetermined), _) => {
best_language_match
}
}
})
.map(|(available_language, _)| available_language.id())
}
}
+58 -44
View File
@@ -153,10 +153,11 @@ fn test_select_language(cx: &mut App) {
registry.add(Arc::new(Language::new(
LanguageConfig {
name: LanguageName::new_static("Rust"),
matcher: LanguageMatcher {
matcher: (LanguageMatcher {
path_suffixes: vec!["rs".to_string()],
..Default::default()
},
})
.into(),
..Default::default()
},
Some(tree_sitter_rust::LANGUAGE.into()),
@@ -164,10 +165,11 @@ fn test_select_language(cx: &mut App) {
registry.add(Arc::new(Language::new(
LanguageConfig {
name: "Rust with longer extension".into(),
matcher: LanguageMatcher {
matcher: (LanguageMatcher {
path_suffixes: vec!["longer.rs".to_string()],
..Default::default()
},
})
.into(),
..Default::default()
},
Some(tree_sitter_rust::LANGUAGE.into()),
@@ -175,10 +177,11 @@ fn test_select_language(cx: &mut App) {
registry.add(Arc::new(Language::new(
LanguageConfig {
name: LanguageName::new_static("Make"),
matcher: LanguageMatcher {
matcher: (LanguageMatcher {
path_suffixes: vec!["Makefile".to_string(), "mk".to_string()],
..Default::default()
},
})
.into(),
..Default::default()
},
Some(tree_sitter_rust::LANGUAGE.into()),
@@ -188,13 +191,13 @@ fn test_select_language(cx: &mut App) {
assert_eq!(
registry
.language_for_file(&file("src/lib.rs"), None, cx)
.map(|l| l.name()),
.and_then(|id| registry.language_name_for_id(id)),
Some("Rust".into())
);
assert_eq!(
registry
.language_for_file(&file("src/lib.mk"), None, cx)
.map(|l| l.name()),
.and_then(|id| registry.language_name_for_id(id)),
Some("Make".into())
);
@@ -202,7 +205,7 @@ fn test_select_language(cx: &mut App) {
assert_eq!(
registry
.language_for_file(&file("src/lib.longer.rs"), None, cx)
.map(|l| l.name()),
.and_then(|id| registry.language_name_for_id(id)),
Some("Rust with longer extension".into())
);
@@ -210,7 +213,7 @@ fn test_select_language(cx: &mut App) {
assert_eq!(
registry
.language_for_file(&file("src/Makefile"), None, cx)
.map(|l| l.name()),
.and_then(|id| registry.language_name_for_id(id)),
Some("Make".into())
);
@@ -218,19 +221,19 @@ fn test_select_language(cx: &mut App) {
assert_eq!(
registry
.language_for_file(&file("zed/cars"), None, cx)
.map(|l| l.name()),
.and_then(|id| registry.language_name_for_id(id)),
None
);
assert_eq!(
registry
.language_for_file(&file("zed/a.cars"), None, cx)
.map(|l| l.name()),
.and_then(|id| registry.language_name_for_id(id)),
None
);
assert_eq!(
registry
.language_for_file(&file("zed/sumk"), None, cx)
.map(|l| l.name()),
.and_then(|id| registry.language_name_for_id(id)),
None
);
}
@@ -244,11 +247,12 @@ async fn test_first_line_pattern(cx: &mut TestAppContext) {
languages.register_test_language(LanguageConfig {
name: "JavaScript".into(),
matcher: LanguageMatcher {
matcher: (LanguageMatcher {
path_suffixes: vec!["js".into()],
first_line_pattern: Some(Regex::new(r"\bnode\b").unwrap()),
..LanguageMatcher::default()
},
})
.into(),
..Default::default()
});
@@ -267,8 +271,8 @@ async fn test_first_line_pattern(cx: &mut TestAppContext) {
Some(&"#!/bin/env node".into()),
cx
))
.unwrap()
.name(),
.and_then(|id| languages.language_name_for_id(id))
.unwrap(),
"JavaScript"
);
}
@@ -293,46 +297,52 @@ async fn test_language_for_file_with_custom_file_types(cx: &mut TestAppContext)
});
let languages = Arc::new(LanguageRegistry::test(cx.executor()));
let language_name = |id| languages.language_name_for_id(id).unwrap();
for config in [
LanguageConfig {
name: "JavaScript".into(),
matcher: LanguageMatcher {
matcher: (LanguageMatcher {
path_suffixes: vec!["js".to_string()],
..Default::default()
},
})
.into(),
..Default::default()
},
LanguageConfig {
name: "TypeScript".into(),
matcher: LanguageMatcher {
matcher: (LanguageMatcher {
path_suffixes: vec!["ts".to_string(), "ts.ecmascript".to_string()],
..Default::default()
},
})
.into(),
..Default::default()
},
LanguageConfig {
name: "C++".into(),
matcher: LanguageMatcher {
matcher: (LanguageMatcher {
path_suffixes: vec!["cpp".to_string()],
..Default::default()
},
})
.into(),
..Default::default()
},
LanguageConfig {
name: "C".into(),
matcher: LanguageMatcher {
matcher: (LanguageMatcher {
path_suffixes: vec!["c".to_string()],
..Default::default()
},
})
.into(),
..Default::default()
},
LanguageConfig {
name: "Dockerfile".into(),
matcher: LanguageMatcher {
matcher: (LanguageMatcher {
path_suffixes: vec!["Dockerfile".to_string()],
..Default::default()
},
})
.into(),
..Default::default()
},
] {
@@ -343,48 +353,48 @@ async fn test_language_for_file_with_custom_file_types(cx: &mut TestAppContext)
let language = cx
.read(|cx| languages.language_for_file(&file("foo.ts"), None, cx))
.unwrap();
assert_eq!(language.name(), "TypeScript");
assert_eq!(language_name(language), "TypeScript");
let language = cx
.read(|cx| languages.language_for_file(&file("foo.ts.ecmascript"), None, cx))
.unwrap();
assert_eq!(language.name(), "TypeScript");
assert_eq!(language_name(language), "TypeScript");
let language = cx
.read(|cx| languages.language_for_file(&file("foo.cpp"), None, cx))
.unwrap();
assert_eq!(language.name(), "C++");
assert_eq!(language_name(language), "C++");
// user configured lang extension, same length as system-provided
let language = cx
.read(|cx| languages.language_for_file(&file("foo.js"), None, cx))
.unwrap();
assert_eq!(language.name(), "TypeScript");
assert_eq!(language_name(language), "TypeScript");
let language = cx
.read(|cx| languages.language_for_file(&file("foo.c"), None, cx))
.unwrap();
assert_eq!(language.name(), "C++");
assert_eq!(language_name(language), "C++");
// user configured lang extension, longer than system-provided
let language = cx
.read(|cx| languages.language_for_file(&file("foo.longer.ts"), None, cx))
.unwrap();
assert_eq!(language.name(), "JavaScript");
assert_eq!(language_name(language), "JavaScript");
// user configured lang extension, shorter than system-provided
let language = cx
.read(|cx| languages.language_for_file(&file("foo.ecmascript"), None, cx))
.unwrap();
assert_eq!(language.name(), "JavaScript");
assert_eq!(language_name(language), "JavaScript");
// user configured glob matches
let language = cx
.read(|cx| languages.language_for_file(&file("c-plus-plus.dev"), None, cx))
.unwrap();
assert_eq!(language.name(), "C++");
assert_eq!(language_name(language), "C++");
// should match Dockerfile.* => Dockerfile, not *.dev => C++
let language = cx
.read(|cx| languages.language_for_file(&file("Dockerfile.dev"), None, cx))
.unwrap();
assert_eq!(language.name(), "Dockerfile");
assert_eq!(language_name(language), "Dockerfile");
}
fn file(path: &str) -> Arc<dyn File> {
@@ -1205,10 +1215,11 @@ fn test_text_objects_with_has_parent_predicate(cx: &mut App) {
let language = Language::new(
LanguageConfig {
name: "Rust".into(),
matcher: LanguageMatcher {
matcher: (LanguageMatcher {
path_suffixes: vec!["rs".to_string()],
..Default::default()
},
})
.into(),
..Default::default()
},
Some(tree_sitter_rust::LANGUAGE.into()),
@@ -1254,10 +1265,11 @@ fn test_text_objects_with_not_has_parent_predicate(cx: &mut App) {
let language = Language::new(
LanguageConfig {
name: "Rust".into(),
matcher: LanguageMatcher {
matcher: (LanguageMatcher {
path_suffixes: vec!["rs".to_string()],
..Default::default()
},
})
.into(),
..Default::default()
},
Some(tree_sitter_rust::LANGUAGE.into()),
@@ -4348,10 +4360,11 @@ fn ruby_lang() -> Language {
Language::new(
LanguageConfig {
name: "Ruby".into(),
matcher: LanguageMatcher {
matcher: (LanguageMatcher {
path_suffixes: vec!["rb".to_string()],
..Default::default()
},
})
.into(),
line_comments: vec!["# ".into()],
..Default::default()
},
@@ -4404,10 +4417,11 @@ fn erb_lang() -> Language {
Language::new(
LanguageConfig {
name: "HTML+ERB".into(),
matcher: LanguageMatcher {
matcher: (LanguageMatcher {
path_suffixes: vec!["erb".to_string()],
..Default::default()
},
})
.into(),
block_comment: Some(BlockCommentConfig {
start: "<%#".into(),
prefix: "".into(),
+12 -8
View File
@@ -6,6 +6,7 @@
//! - Exposes [`LanguageConfig`] that describes how constructs (like brackets or line comments) should be handled by the editor for a source file of a particular language.
//!
//! Notably we do *not* assign a single language to a single file; in real world a single file can consist of multiple programming languages - HTML is a good example of that - and `language` crate tends to reflect that status quo in its API.
mod available_languages;
mod buffer;
mod diagnostic;
mod diagnostic_set;
@@ -96,14 +97,14 @@ pub use toolchain::{
use tree_sitter::{self, QueryCursor, WasmStore, wasmtime};
use util::rel_path::RelPath;
pub use available_languages::AvailableLanguage;
pub use buffer::Operation;
pub use buffer::*;
pub use diagnostic::{Diagnostic, DiagnosticSourceKind};
pub use diagnostic_set::{DiagnosticEntry, DiagnosticEntryRef, DiagnosticGroup};
pub use file_content::{ByteContent, FILE_ANALYSIS_BYTES, analyze_byte_content};
pub use language_registry::{
AvailableLanguage, BinaryStatus, LanguageNotFound, LanguageQueries, LanguageRegistry,
QUERY_FILENAME_PREFIXES,
BinaryStatus, LanguageNotFound, LanguageQueries, LanguageRegistry, QUERY_FILENAME_PREFIXES,
};
pub use lsp::{LanguageServerId, LanguageServerName};
pub use outline::*;
@@ -168,11 +169,12 @@ pub static PLAIN_TEXT: LazyLock<Arc<Language>> = LazyLock::new(|| {
name: "Plain Text".into(),
soft_wrap: Some(SoftWrap::EditorWidth),
autoclose_before: ")]}".into(),
matcher: LanguageMatcher {
matcher: (LanguageMatcher {
path_suffixes: vec!["txt".to_owned()],
first_line_pattern: None,
modeline_aliases: vec!["text".to_owned(), "txt".to_owned()],
},
})
.into(),
brackets: BracketPairConfig {
pairs: vec![
BracketPair {
@@ -1750,19 +1752,21 @@ mod tests {
languages.register_test_language(LanguageConfig {
name: "JSON".into(),
grammar: Some("json".into()),
matcher: LanguageMatcher {
matcher: (LanguageMatcher {
path_suffixes: vec!["json".into()],
..Default::default()
},
})
.into(),
..Default::default()
});
languages.register_test_language(LanguageConfig {
name: "Rust".into(),
grammar: Some("rust".into()),
matcher: LanguageMatcher {
matcher: (LanguageMatcher {
path_suffixes: vec!["rs".into()],
..Default::default()
},
})
.into(),
..Default::default()
});
assert_eq!(
+88 -355
View File
@@ -1,7 +1,9 @@
use crate::available_languages::AvailableLanguage;
use crate::{
CachedLspAdapter, File, Language, LanguageConfig, LanguageId, LanguageMatcher,
LanguageServerName, LspAdapter, ManifestName, PLAIN_TEXT, ToolchainLister,
language_settings::all_language_settings, task_context::ContextProvider, with_parser,
available_languages::AvailableLanguages, language_settings::all_language_settings,
task_context::ContextProvider, with_parser,
};
use anyhow::{Context as _, Result, anyhow};
use collections::{FxHashMap, HashMap, HashSet, hash_map};
@@ -21,18 +23,14 @@ use lsp::LanguageServerId;
use parking_lot::{Mutex, RwLock};
use postage::watch;
use smallvec::SmallVec;
use std::{
cell::LazyCell,
ffi::OsStr,
ops::Not,
path::{Path, PathBuf},
sync::Arc,
};
use sum_tree::Bias;
use text::{Point, Rope};
use text::Rope;
use theme::Theme;
use unicase::UniCase;
use util::{maybe, post_inc};
pub struct LanguageRegistry {
@@ -46,7 +44,7 @@ struct LanguageRegistryState {
next_language_server_id: usize,
languages: Vec<Arc<Language>>,
language_settings: AllLanguageSettingsContent,
available_languages: Vec<AvailableLanguage>,
available_languages: AvailableLanguages,
grammars: HashMap<Arc<str>, AvailableGrammar>,
lsp_adapters: HashMap<LanguageName, Vec<Arc<CachedLspAdapter>>>,
all_lsp_adapters: HashMap<LanguageServerName, Arc<CachedLspAdapter>>,
@@ -70,40 +68,6 @@ pub struct FakeLanguageServerEntry {
pub _server: Option<lsp::FakeLanguageServer>,
}
#[derive(Clone)]
pub struct AvailableLanguage {
id: LanguageId,
name: LanguageName,
grammar: Option<Arc<str>>,
matcher: LanguageMatcher,
hidden: bool,
load: Arc<dyn Fn() -> Result<LoadedLanguage> + 'static + Send + Sync>,
loaded: bool,
manifest_name: Option<ManifestName>,
}
impl AvailableLanguage {
pub fn name(&self) -> LanguageName {
self.name.clone()
}
pub fn matcher(&self) -> &LanguageMatcher {
&self.matcher
}
pub fn hidden(&self) -> bool {
self.hidden
}
}
#[derive(Copy, Clone, Default)]
enum LanguageMatchPrecedence {
#[default]
Undetermined,
PathOrContent(usize),
UserConfigured(usize),
}
enum AvailableGrammar {
Native(tree_sitter::Language),
Loaded(#[allow(unused)] PathBuf, tree_sitter::Language),
@@ -143,7 +107,7 @@ impl LanguageRegistry {
state: RwLock::new(LanguageRegistryState {
next_language_server_id: 0,
languages: Vec::new(),
available_languages: Vec::new(),
available_languages: AvailableLanguages::default(),
grammars: Default::default(),
language_settings: Default::default(),
loading_languages: Default::default(),
@@ -407,33 +371,25 @@ impl LanguageRegistry {
&self,
name: LanguageName,
grammar_name: Option<Arc<str>>,
matcher: LanguageMatcher,
matcher: Arc<LanguageMatcher>,
hidden: bool,
manifest_name: Option<ManifestName>,
load: Arc<dyn Fn() -> Result<LoadedLanguage> + 'static + Send + Sync>,
) {
let state = &mut *self.state.write();
for existing_language in &mut state.available_languages {
if existing_language.name == name {
existing_language.grammar = grammar_name;
existing_language.matcher = matcher;
existing_language.load = load;
existing_language.manifest_name = manifest_name;
return;
}
let was_added = state.available_languages.register(
name,
grammar_name,
matcher,
hidden,
manifest_name,
load,
);
if !was_added {
return;
}
state.available_languages.push(AvailableLanguage {
id: LanguageId::new(),
name,
grammar: grammar_name,
matcher,
load,
hidden,
loaded: false,
manifest_name,
});
state.version += 1;
state.reload_count += 1;
*state.subscription.0.borrow_mut() = ();
@@ -475,12 +431,13 @@ impl LanguageRegistry {
pub fn language_names(&self) -> Vec<LanguageName> {
let state = self.state.read();
let mut result = state
.available_languages
.iter()
.filter_map(|l| l.loaded.not().then_some(l.name.clone()))
.chain(state.languages.iter().map(|l| l.config.name.clone()))
.collect::<Vec<_>>();
let mut result = state.available_languages.unloaded_language_names();
result.extend(
state
.languages
.iter()
.map(|language| language.config.name.clone()),
);
result.sort_unstable_by_key(|language_name| language_name.as_ref().to_lowercase());
result
}
@@ -495,7 +452,7 @@ impl LanguageRegistry {
/// Add a pre-loaded language to the registry.
pub fn add(&self, language: Arc<Language>) {
let mut state = self.state.write();
state.available_languages.push(AvailableLanguage {
state.available_languages.add(AvailableLanguage {
id: language.id,
name: language.name(),
grammar: language.config.grammar.clone(),
@@ -539,88 +496,38 @@ impl LanguageRegistry {
self: &Arc<Self>,
name: &str,
) -> impl Future<Output = Result<Arc<Language>>> + use<> {
let name = UniCase::new(name);
let rx = self.get_or_load_language(|language_name, _, current_best_match| {
match current_best_match {
LanguageMatchPrecedence::Undetermined if UniCase::new(&language_name.0) == name => {
Some(LanguageMatchPrecedence::PathOrContent(name.len()))
}
LanguageMatchPrecedence::Undetermined
| LanguageMatchPrecedence::UserConfigured(_)
| LanguageMatchPrecedence::PathOrContent(_) => None,
}
});
let language_id = self.state.read().available_languages.find_by_name(name);
let rx = self.get_or_load_language(language_id);
async move { rx.await? }
}
pub async fn language_for_id(self: &Arc<Self>, id: LanguageId) -> Result<Arc<Language>> {
let available_language = {
let state = self.state.read();
let Some(available_language) = state
.available_languages
.iter()
.find(|lang| lang.id == id)
.cloned()
else {
anyhow::bail!(LanguageNotFound);
};
available_language
};
self.load_language(&available_language).await?
#[cfg(any(test, feature = "test-support"))]
pub fn language_name_for_id(&self, id: LanguageId) -> Option<LanguageName> {
self.state.read().available_languages.name_for_id(id)
}
pub fn language_name_for_extension(self: &Arc<Self>, extension: &str) -> Option<LanguageName> {
self.state.try_read().and_then(|state| {
state
.available_languages
.iter()
.find(|language| {
language
.matcher()
.path_suffixes
.iter()
.any(|suffix| *suffix == extension)
})
.map(|language| language.name.clone())
})
self.state
.try_read()
.and_then(|state| state.available_languages.find_name_by_extension(extension))
}
pub fn language_for_name_or_extension(
self: &Arc<Self>,
string: &str,
) -> impl Future<Output = Result<Arc<Language>>> {
let string = UniCase::new(string);
let rx = self.get_or_load_language(|name, config, current_best_match| {
let name_matches = || {
UniCase::new(&name.0) == string
|| config
.path_suffixes
.iter()
.any(|suffix| UniCase::new(suffix) == string)
};
match current_best_match {
LanguageMatchPrecedence::Undetermined => {
name_matches().then_some(LanguageMatchPrecedence::PathOrContent(string.len()))
}
LanguageMatchPrecedence::PathOrContent(len) => (string.len() > len
&& name_matches())
.then_some(LanguageMatchPrecedence::PathOrContent(string.len())),
LanguageMatchPrecedence::UserConfigured(_) => None,
}
});
let language_id = self
.state
.read()
.available_languages
.find_by_name_or_extension(string);
let rx = self.get_or_load_language(language_id);
async move { rx.await? }
}
pub fn available_language_for_name(self: &Arc<Self>, name: &str) -> Option<AvailableLanguage> {
let state = self.state.read();
state
.available_languages
.iter()
.find(|l| l.name.0.as_ref() == name)
.cloned()
state.available_languages.find_by_exact_name(name)
}
/// Look up a language by its modeline name (vim filetype or emacs mode).
@@ -633,32 +540,10 @@ impl LanguageRegistry {
self: &Arc<Self>,
modeline_name: &str,
) -> Option<AvailableLanguage> {
let modeline_name_lower = modeline_name.to_lowercase();
let state = self.state.read();
state
self.state
.read()
.available_languages
.iter()
.find(|lang| {
lang.matcher
.modeline_aliases
.iter()
.any(|alias| alias.to_lowercase() == modeline_name_lower)
})
.or_else(|| {
state.available_languages.iter().find(|lang| {
lang.grammar
.as_ref()
.is_some_and(|g| g.to_lowercase() == modeline_name_lower)
})
})
.or_else(|| {
state
.available_languages
.iter()
.find(|lang| lang.name.0.to_lowercase() == modeline_name_lower)
})
.cloned()
.find_by_modeline_name(modeline_name)
}
pub fn language_for_file(
@@ -666,7 +551,7 @@ impl LanguageRegistry {
file: &Arc<dyn File>,
content: Option<&Rope>,
cx: &App,
) -> Option<AvailableLanguage> {
) -> Option<LanguageId> {
let user_file_types = all_language_settings(Some(file), cx);
self.language_for_file_internal(
@@ -676,7 +561,7 @@ impl LanguageRegistry {
)
}
pub fn language_for_file_path(self: &Arc<Self>, path: &Path) -> Option<AvailableLanguage> {
pub fn language_for_file_path(self: &Arc<Self>, path: &Path) -> Option<LanguageId> {
self.language_for_file_internal(path, None, None)
}
@@ -689,8 +574,8 @@ impl LanguageRegistry {
let this = self.clone();
async move {
if let Some(language) = language {
this.load_language(&language).await?
if let Some(language_id) = language {
this.load_language(language_id).await?
} else {
Err(anyhow!(LanguageNotFound))
}
@@ -702,171 +587,17 @@ impl LanguageRegistry {
path: &Path,
content: Option<&Rope>,
user_file_types: Option<&FxHashMap<Arc<str>, (GlobSet, Vec<String>)>>,
) -> Option<AvailableLanguage> {
let filename = path.file_name().and_then(|filename| filename.to_str());
// `Path.extension()` returns None for files with a leading '.'
// and no other extension which is not the desired behavior here,
// as we want `.zshrc` to result in extension being `Some("zshrc")`
let extension = filename.and_then(|filename| filename.split('.').next_back());
let path_suffixes = [extension, filename, path.to_str()]
.iter()
.filter_map(|suffix| suffix.map(|suffix| (suffix, globset::Candidate::new(suffix))))
.collect::<SmallVec<[_; 3]>>();
let content = LazyCell::new(|| {
content.map(|content| {
let end = content.clip_point(Point::new(0, 256), Bias::Left);
let end = content.point_to_offset(end);
content.chunks_in_range(0..end).collect::<String>()
})
});
self.find_matching_language(move |language_name, config, current_best_match| {
let path_matches_default_suffix = || {
let len =
config
.path_suffixes
.iter()
.fold(0, |acc: usize, path_suffix: &String| {
let ext = ".".to_string() + path_suffix;
let matched_suffix_len = path_suffixes
.iter()
.find(|(suffix, _)| suffix.ends_with(&ext) || suffix == path_suffix)
.map(|(suffix, _)| suffix.len());
match matched_suffix_len {
Some(len) => acc.max(len),
None => acc,
}
});
(len > 0).then_some(len)
};
let path_matches_custom_suffix = || {
user_file_types
.and_then(|types| types.get(language_name.as_ref()))
.map_or(None, |(custom_suffixes, _)| {
path_suffixes
.iter()
.find(|(_, candidate)| custom_suffixes.is_match_candidate(candidate))
.map(|(suffix, _)| suffix.len())
})
};
let content_matches = || {
config.first_line_pattern.as_ref().is_some_and(|pattern| {
content
.as_ref()
.is_some_and(|content| pattern.is_match(content))
})
};
// Only return a match for the given file if we have a better match than
// the current one.
match current_best_match {
LanguageMatchPrecedence::PathOrContent(current_len) => {
if let Some(len) = path_matches_custom_suffix() {
// >= because user config should win tie with system ext len
(len >= current_len).then_some(LanguageMatchPrecedence::UserConfigured(len))
} else if let Some(len) = path_matches_default_suffix() {
// >= because user config should win tie with system ext len
(len >= current_len).then_some(LanguageMatchPrecedence::PathOrContent(len))
} else {
None
}
}
LanguageMatchPrecedence::Undetermined => {
if let Some(len) = path_matches_custom_suffix() {
Some(LanguageMatchPrecedence::UserConfigured(len))
} else if let Some(len) = path_matches_default_suffix() {
Some(LanguageMatchPrecedence::PathOrContent(len))
} else if content_matches() {
Some(LanguageMatchPrecedence::PathOrContent(1))
} else {
None
}
}
LanguageMatchPrecedence::UserConfigured(_) => None,
}
})
}
fn find_matching_language(
self: &Arc<Self>,
callback: impl Fn(
&LanguageName,
&LanguageMatcher,
LanguageMatchPrecedence,
) -> Option<LanguageMatchPrecedence>,
) -> Option<AvailableLanguage> {
let state = self.state.read();
let available_language = state
) -> Option<LanguageId> {
self.state
.read()
.available_languages
.iter()
.rev()
.fold(None, |best_language_match, language| {
let current_match_type = best_language_match
.as_ref()
.map_or(LanguageMatchPrecedence::default(), |(_, score)| *score);
let language_score =
callback(&language.name, &language.matcher, current_match_type);
match (language_score, current_match_type) {
// no current best, so our candidate is better
(
Some(
LanguageMatchPrecedence::PathOrContent(_)
| LanguageMatchPrecedence::UserConfigured(_),
),
LanguageMatchPrecedence::Undetermined,
) => language_score.map(|new_score| (language.clone(), new_score)),
// our candidate is better only if the name is longer
(
Some(LanguageMatchPrecedence::PathOrContent(new_len)),
LanguageMatchPrecedence::PathOrContent(current_len),
)
| (
Some(LanguageMatchPrecedence::UserConfigured(new_len)),
LanguageMatchPrecedence::UserConfigured(current_len),
)
| (
Some(LanguageMatchPrecedence::PathOrContent(new_len)),
LanguageMatchPrecedence::UserConfigured(current_len),
) => {
if new_len > current_len {
language_score.map(|new_score| (language.clone(), new_score))
} else {
best_language_match
}
}
// our candidate is better if the name is longer or equal to
(
Some(LanguageMatchPrecedence::UserConfigured(new_len)),
LanguageMatchPrecedence::PathOrContent(current_len),
) => {
if new_len >= current_len {
language_score.map(|new_score| (language.clone(), new_score))
} else {
best_language_match
}
}
// no candidate, use current best
(None, _) | (Some(LanguageMatchPrecedence::Undetermined), _) => {
best_language_match
}
}
})
.map(|(available_language, _)| available_language);
drop(state);
available_language
.find_for_file(path, content, user_file_types)
}
#[ztracing::instrument(skip_all)]
pub fn load_language(
self: &Arc<Self>,
language: &AvailableLanguage,
language_id: LanguageId,
) -> oneshot::Receiver<Result<Arc<Language>>> {
let (tx, rx) = oneshot::channel();
@@ -874,13 +605,23 @@ impl LanguageRegistry {
// If the language is already loaded, resolve with it immediately.
for loaded_language in state.languages.iter() {
if loaded_language.id == language.id {
if loaded_language.id == language_id {
tx.send(Ok(loaded_language.clone())).unwrap();
return rx;
}
}
match state.loading_languages.entry(language.id) {
let Some(available_language) = state.available_languages.get_language(language_id) else {
tx.send(Err(anyhow!(LanguageNotFound))).ok();
return rx;
};
let (language_name, language_load) = (
available_language.name.clone(),
available_language.load.clone(),
);
match state.loading_languages.entry(language_id) {
// If the language is already being loaded, then add this
// channel to a list that will be sent to when the load completes.
hash_map::Entry::Occupied(mut entry) => entry.get_mut().push(tx),
@@ -889,10 +630,6 @@ impl LanguageRegistry {
hash_map::Entry::Vacant(entry) => {
let this = self.clone();
let id = language.id;
let name = language.name.clone();
let language_load = language.load.clone();
self.executor
.spawn(async move {
let language = async {
@@ -900,16 +637,22 @@ impl LanguageRegistry {
if let Some(grammar) = loaded_language.config.grammar.clone() {
let grammar = Some(this.get_or_load_grammar(grammar).await?);
Language::new_with_id(id, loaded_language.config, grammar)
Language::new_with_id(language_id, loaded_language.config, grammar)
.with_context_provider(loaded_language.context_provider)
.with_toolchain_lister(loaded_language.toolchain_provider)
.with_manifest(loaded_language.manifest_name)
.with_queries(loaded_language.queries)
} else {
Ok(Language::new_with_id(id, loaded_language.config, None)
Ok(
Language::new_with_id(
language_id,
loaded_language.config,
None,
)
.with_context_provider(loaded_language.context_provider)
.with_manifest(loaded_language.manifest_name)
.with_toolchain_lister(loaded_language.toolchain_provider))
.with_toolchain_lister(loaded_language.toolchain_provider),
)
}
}
.await;
@@ -920,21 +663,23 @@ impl LanguageRegistry {
let mut state = this.state.write();
state.add(language.clone());
state.mark_language_loaded(id);
if let Some(mut txs) = state.loading_languages.remove(&id) {
state.mark_language_loaded(language_id);
if let Some(mut txs) = state.loading_languages.remove(&language_id)
{
for tx in txs.drain(..) {
let _ = tx.send(Ok(language.clone()));
}
}
}
Err(e) => {
log::error!("failed to load language {name}:\n{e:?}");
log::error!("failed to load language {language_name}:\n{e:?}");
let mut state = this.state.write();
state.mark_language_loaded(id);
if let Some(mut txs) = state.loading_languages.remove(&id) {
state.mark_language_loaded(language_id);
if let Some(mut txs) = state.loading_languages.remove(&language_id)
{
for tx in txs.drain(..) {
let _ = tx.send(Err(anyhow!(
"failed to load language {name}: {e}",
"failed to load language {language_name}: {e}",
)));
}
}
@@ -954,19 +699,15 @@ impl LanguageRegistry {
#[ztracing::instrument(skip_all)]
fn get_or_load_language(
self: &Arc<Self>,
callback: impl Fn(
&LanguageName,
&LanguageMatcher,
LanguageMatchPrecedence,
) -> Option<LanguageMatchPrecedence>,
language_id: Option<LanguageId>,
) -> oneshot::Receiver<Result<Arc<Language>>> {
let Some(language) = self.find_matching_language(callback) else {
let Some(language_id) = language_id else {
let (tx, rx) = oneshot::channel();
let _ = tx.send(Err(anyhow!(LanguageNotFound)));
return rx;
};
self.load_language(&language)
self.load_language(language_id)
}
fn get_or_load_grammar(
@@ -1141,9 +882,7 @@ impl LanguageRegistryState {
self.languages.clear();
self.version += 1;
self.reload_count += 1;
for language in &mut self.available_languages {
language.loaded = false;
}
self.available_languages.mark_all_unloaded();
*self.subscription.0.borrow_mut() = ();
}
@@ -1188,8 +927,7 @@ impl LanguageRegistryState {
self.languages
.retain(|language| !languages_to_remove.contains(&language.name()));
self.available_languages
.retain(|language| !languages_to_remove.contains(&language.name));
self.available_languages.remove(languages_to_remove);
self.grammars
.retain(|name, _| !grammars_to_remove.contains(name));
self.version += 1;
@@ -1200,12 +938,7 @@ impl LanguageRegistryState {
/// Mark the given language as having been loaded, so that the
/// language registry won't try to load it again.
fn mark_language_loaded(&mut self, id: LanguageId) {
for language in &mut self.available_languages {
if language.id == id {
language.loaded = true;
break;
}
}
self.available_languages.mark_loaded(id);
}
}
+3 -2
View File
@@ -385,10 +385,11 @@ mod tests {
let language = Language::new(
LanguageConfig {
name: "Rust".into(),
matcher: LanguageMatcher {
matcher: (LanguageMatcher {
path_suffixes: vec!["rs".to_string()],
..Default::default()
},
})
.into(),
..Default::default()
},
Some(tree_sitter_rust::LANGUAGE.into()),
@@ -1397,10 +1397,11 @@ fn html_lang() -> Language {
Language::new(
LanguageConfig {
name: "HTML".into(),
matcher: LanguageMatcher {
matcher: (LanguageMatcher {
path_suffixes: vec!["html".to_string()],
..Default::default()
},
})
.into(),
..Default::default()
},
Some(tree_sitter_html::LANGUAGE.into()),
@@ -1419,10 +1420,11 @@ fn ruby_lang() -> Language {
Language::new(
LanguageConfig {
name: "Ruby".into(),
matcher: LanguageMatcher {
matcher: (LanguageMatcher {
path_suffixes: vec!["rb".to_string()],
..Default::default()
},
})
.into(),
..Default::default()
},
Some(tree_sitter_ruby::LANGUAGE.into()),
@@ -1441,10 +1443,11 @@ fn erb_lang() -> Language {
Language::new(
LanguageConfig {
name: "ERB".into(),
matcher: LanguageMatcher {
matcher: (LanguageMatcher {
path_suffixes: vec!["erb".to_string()],
..Default::default()
},
})
.into(),
..Default::default()
},
Some(tree_sitter_embedded_template::LANGUAGE.into()),
@@ -1477,10 +1480,11 @@ fn elixir_lang() -> Language {
Language::new(
LanguageConfig {
name: "Elixir".into(),
matcher: LanguageMatcher {
matcher: (LanguageMatcher {
path_suffixes: vec!["ex".into()],
..Default::default()
},
})
.into(),
..Default::default()
},
Some(tree_sitter_elixir::LANGUAGE.into()),
@@ -1497,10 +1501,11 @@ fn heex_lang() -> Language {
Language::new(
LanguageConfig {
name: "HEEx".into(),
matcher: LanguageMatcher {
matcher: (LanguageMatcher {
path_suffixes: vec!["heex".into()],
..Default::default()
},
})
.into(),
..Default::default()
},
Some(tree_sitter_heex::LANGUAGE.into()),
@@ -1529,10 +1534,11 @@ fn python_lang() -> Language {
Language::new(
LanguageConfig {
name: "Python".into(),
matcher: LanguageMatcher {
matcher: (LanguageMatcher {
path_suffixes: vec!["py".to_string()],
..Default::default()
},
})
.into(),
line_comments: vec!["# ".into()],
..Default::default()
},
+3 -3
View File
@@ -39,7 +39,7 @@ pub struct LanguageConfig {
pub grammar: Option<Arc<str>>,
/// The criteria for matching this language to a given file.
#[serde(flatten)]
pub matcher: LanguageMatcher,
pub matcher: Arc<LanguageMatcher>,
/// List of bracket types in a language.
#[serde(default)]
pub brackets: BracketPairConfig,
@@ -163,7 +163,7 @@ impl Default for LanguageConfig {
code_fence_block_name: None,
kernel_language_names: Default::default(),
grammar: None,
matcher: LanguageMatcher::default(),
matcher: Arc::default(),
brackets: Default::default(),
auto_indent_using_last_non_empty_line: default_true(),
auto_indent_on_paste: None,
@@ -223,7 +223,7 @@ pub struct TaskListConfig {
pub continuation: Arc<str>,
}
#[derive(Clone, Debug, Serialize, Deserialize, Default, JsonSchema)]
#[derive(Debug, Serialize, Deserialize, Default, JsonSchema)]
pub struct LanguageMatcher {
/// Given a list of `LanguageConfig`'s, the language of a file can be determined based on the path extension matching any of the `path_suffixes`.
#[serde(default)]
@@ -48,7 +48,7 @@ impl ExtensionLanguageProxy for LanguageServerRegistryProxy {
&self,
language: LanguageName,
grammar: Option<Arc<str>>,
matcher: LanguageMatcher,
matcher: Arc<LanguageMatcher>,
hidden: bool,
load: Arc<dyn Fn() -> Result<LoadedLanguage> + Send + Sync + 'static>,
) {
@@ -366,10 +366,11 @@ mod tests {
language_registry.add(Arc::new(Language::new(
LanguageConfig {
name: language_name.into(),
matcher: LanguageMatcher {
matcher: (LanguageMatcher {
path_suffixes: vec![path_suffix.to_string()],
..Default::default()
},
})
.into(),
..Default::default()
},
None,
@@ -37,10 +37,11 @@ async fn test_lsp_log_view(cx: &mut TestAppContext) {
language_registry.add(Arc::new(Language::new(
LanguageConfig {
name: "Rust".into(),
matcher: LanguageMatcher {
matcher: (LanguageMatcher {
path_suffixes: vec!["rs".to_string()],
..Default::default()
},
})
.into(),
..Default::default()
},
Some(tree_sitter_rust::LANGUAGE.into()),
+3 -2
View File
@@ -41,11 +41,12 @@ pub static LANGUAGE_GIT_COMMIT: std::sync::LazyLock<Arc<Language>> =
LanguageConfig {
name: "Git Commit".into(),
soft_wrap: Some(language::SoftWrap::EditorWidth),
matcher: LanguageMatcher {
matcher: (LanguageMatcher {
path_suffixes: vec!["COMMIT_EDITMSG".to_owned()],
first_line_pattern: None,
..LanguageMatcher::default()
},
})
.into(),
line_comments: vec![Arc::from("#")],
..LanguageConfig::default()
},
+3 -2
View File
@@ -4991,10 +4991,11 @@ mod tests {
let javascript_language = Arc::new(Language::new(
LanguageConfig {
name: "JavaScript".into(),
matcher: LanguageMatcher {
matcher: (LanguageMatcher {
path_suffixes: vec!["js".to_string()],
..Default::default()
},
})
.into(),
word_characters: ['$', '#'].into_iter().collect(),
..Default::default()
},
+8 -9
View File
@@ -5192,10 +5192,12 @@ impl LspStore {
&mut self,
buffer_handle: &Entity<Buffer>,
cx: &mut Context<Self>,
) -> Option<language::AvailableLanguage> {
) {
// If the buffer has a language, set it and start the language server if we haven't already.
let buffer = buffer_handle.read(cx);
let file = buffer.file()?;
let Some(file) = buffer.file() else {
return;
};
let content = buffer.as_rope();
let modeline_settings = buffer.modeline().map(Arc::as_ref);
@@ -5206,14 +5208,13 @@ impl LspStore {
{
self.languages
.available_language_for_modeline_name(mode_name)
.map(|language| language.id())
} else {
self.languages.language_for_file(file, Some(content), cx)
};
if let Some(available_language) = &available_language {
if let Some(Ok(Ok(new_language))) = self
.languages
.load_language(available_language)
.now_or_never()
if let Some(language_id) = available_language {
if let Some(Ok(Ok(new_language))) =
self.languages.load_language(language_id).now_or_never()
{
self.set_language_for_buffer(buffer_handle, new_language, cx);
}
@@ -5223,8 +5224,6 @@ impl LspStore {
new_language: None,
});
}
available_language
}
pub(crate) fn set_language_for_buffer(
@@ -14971,10 +14971,11 @@ fn json_lang() -> Arc<Language> {
Arc::new(Language::new(
LanguageConfig {
name: "JSON".into(),
matcher: LanguageMatcher {
matcher: (LanguageMatcher {
path_suffixes: vec!["json".to_string()],
..Default::default()
},
})
.into(),
..Default::default()
},
None,
@@ -14985,10 +14986,11 @@ fn js_lang() -> Arc<Language> {
Arc::new(Language::new(
LanguageConfig {
name: "JavaScript".into(),
matcher: LanguageMatcher {
matcher: (LanguageMatcher {
path_suffixes: vec!["js".to_string()],
..Default::default()
},
})
.into(),
..Default::default()
},
None,
@@ -15053,10 +15055,11 @@ fn python_lang(fs: Arc<FakeFs>) -> Arc<Language> {
Language::new(
LanguageConfig {
name: "Python".into(),
matcher: LanguageMatcher {
matcher: (LanguageMatcher {
path_suffixes: vec!["py".to_string()],
..Default::default()
},
})
.into(),
..Default::default()
},
None, // We're not testing Python parsing with this language.
@@ -15072,10 +15075,11 @@ fn typescript_lang() -> Arc<Language> {
Arc::new(Language::new(
LanguageConfig {
name: "TypeScript".into(),
matcher: LanguageMatcher {
matcher: (LanguageMatcher {
path_suffixes: vec!["ts".to_string()],
..Default::default()
},
})
.into(),
..Default::default()
},
Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
@@ -15086,10 +15090,11 @@ fn tsx_lang() -> Arc<Language> {
Arc::new(Language::new(
LanguageConfig {
name: "tsx".into(),
matcher: LanguageMatcher {
matcher: (LanguageMatcher {
path_suffixes: vec!["tsx".to_string()],
..Default::default()
},
})
.into(),
..Default::default()
},
Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
@@ -358,10 +358,11 @@ mod tests {
language_registry.add(Arc::new(Language::new(
LanguageConfig {
name: "Rust".into(),
matcher: LanguageMatcher {
matcher: (LanguageMatcher {
path_suffixes: vec!["rs".to_string()],
..Default::default()
},
})
.into(),
..Default::default()
},
None,
@@ -511,10 +512,11 @@ mod tests {
language_registry.add(Arc::new(Language::new(
LanguageConfig {
name: "Rust".into(),
matcher: LanguageMatcher {
matcher: (LanguageMatcher {
path_suffixes: vec!["rs".to_string()],
..Default::default()
},
})
.into(),
..Default::default()
},
None,
@@ -699,10 +699,11 @@ async fn test_remote_lsp(cx: &mut TestAppContext, server_cx: &mut TestAppContext
cx.update_entity(&project, |project, _| {
project.languages().register_test_language(LanguageConfig {
name: "Rust".into(),
matcher: LanguageMatcher {
matcher: (LanguageMatcher {
path_suffixes: vec!["rs".into()],
..Default::default()
},
})
.into(),
..Default::default()
});
project.languages().register_fake_lsp_adapter(
@@ -932,10 +933,11 @@ async fn test_remote_code_action_resolve(cx: &mut TestAppContext, server_cx: &mu
cx.update_entity(&project, |project, _| {
project.languages().register_test_language(LanguageConfig {
name: "Rust".into(),
matcher: LanguageMatcher {
matcher: (LanguageMatcher {
path_suffixes: vec!["rs".into()],
..Default::default()
},
})
.into(),
..Default::default()
});
project.languages().register_fake_lsp_adapter(
@@ -1090,10 +1092,11 @@ async fn test_remote_code_lens_fetch_after_lsp_starts(
cx.update_entity(&project, |project, _| {
project.languages().register_test_language(LanguageConfig {
name: "Rust".into(),
matcher: LanguageMatcher {
matcher: (LanguageMatcher {
path_suffixes: vec!["rs".into()],
..Default::default()
},
})
.into(),
..Default::default()
});
project.languages().register_fake_lsp_adapter(
@@ -1225,10 +1228,10 @@ async fn test_remote_code_lens_refetch_after_refresh(
cx.update_entity(&project, |project, _| {
project.languages().register_test_language(LanguageConfig {
name: "Rust".into(),
matcher: LanguageMatcher {
matcher: Arc::new(LanguageMatcher {
path_suffixes: vec!["rs".into()],
..LanguageMatcher::default()
},
}),
..LanguageConfig::default()
});
project.languages().register_fake_lsp_adapter(
@@ -1371,10 +1374,10 @@ async fn test_remote_lsp_data_cache_converges_with_multiple_servers(
cx.update_entity(&project, |project, _| {
project.languages().register_test_language(LanguageConfig {
name: "Rust".into(),
matcher: LanguageMatcher {
matcher: Arc::new(LanguageMatcher {
path_suffixes: vec!["rs".into()],
..LanguageMatcher::default()
},
}),
..LanguageConfig::default()
});
project.languages().register_fake_lsp_adapter(
@@ -1536,10 +1539,10 @@ async fn test_remote_per_server_refresh_queries_only_the_refreshed_server(
// detects the buffer's language and starts the servers.
let fake_language_config = || LanguageConfig {
name: "Fake".into(),
matcher: LanguageMatcher {
matcher: Arc::new(LanguageMatcher {
path_suffixes: vec!["fake".into()],
..LanguageMatcher::default()
},
}),
..LanguageConfig::default()
};
let fake_adapter =
@@ -1691,10 +1694,11 @@ async fn test_remote_code_lens_resolve(cx: &mut TestAppContext, server_cx: &mut
cx.update_entity(&project, |project, _| {
project.languages().register_test_language(LanguageConfig {
name: "Rust".into(),
matcher: LanguageMatcher {
matcher: (LanguageMatcher {
path_suffixes: vec!["rs".into()],
..Default::default()
},
})
.into(),
..Default::default()
});
project.languages().register_fake_lsp_adapter(
@@ -1851,10 +1855,11 @@ async fn test_remote_cancel_language_server_work(
cx.update_entity(&project, |project, _| {
project.languages().register_test_language(LanguageConfig {
name: "Rust".into(),
matcher: LanguageMatcher {
matcher: (LanguageMatcher {
path_suffixes: vec!["rs".into()],
..Default::default()
},
})
.into(),
..Default::default()
});
project.languages().register_fake_lsp_adapter(
@@ -3867,10 +3872,11 @@ async fn test_remote_apply_code_action_skips_unadvertised_command(
cx.update_entity(&project, |project, _| {
project.languages().register_test_language(LanguageConfig {
name: "Rust".into(),
matcher: LanguageMatcher {
matcher: (LanguageMatcher {
path_suffixes: vec!["rs".into()],
..Default::default()
},
})
.into(),
..Default::default()
});
project.languages().register_fake_lsp_adapter(
+3 -2
View File
@@ -398,10 +398,11 @@ mod tests {
Language::new(
LanguageConfig {
name: "Python".into(),
matcher: LanguageMatcher {
matcher: (LanguageMatcher {
path_suffixes: vec!["py".to_string()],
..Default::default()
},
})
.into(),
..Default::default()
},
None,
+9 -6
View File
@@ -1052,10 +1052,11 @@ mod tests {
Language::new(
LanguageConfig {
name: "Test".into(),
matcher: LanguageMatcher {
matcher: (LanguageMatcher {
path_suffixes: vec!["test".to_string()],
..LanguageMatcher::default()
},
})
.into(),
..LanguageConfig::default()
},
None,
@@ -1134,10 +1135,11 @@ mod tests {
Language::new(
LanguageConfig {
name: "TypeScript".into(),
matcher: LanguageMatcher {
matcher: (LanguageMatcher {
path_suffixes: vec!["ts".to_string()],
..LanguageMatcher::default()
},
})
.into(),
..LanguageConfig::default()
},
None,
@@ -1166,10 +1168,11 @@ mod tests {
Language::new(
LanguageConfig {
name: "Rust".into(),
matcher: LanguageMatcher {
matcher: (LanguageMatcher {
path_suffixes: vec!["rs".to_string()],
..LanguageMatcher::default()
},
})
.into(),
..LanguageConfig::default()
},
None,