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:
1 parent
97b51f2620
commit
25632b979b
35 files changed
+763
-576
No files matched your search
@@ -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,
|
||||
|
||||
@@ -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()),
|
||||
|
||||
@@ -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()),
|
||||
|
||||
@@ -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()),
|
||||
|
||||
@@ -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()),
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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()),
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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>,
|
||||
) {
|
||||
|
||||
@@ -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(),
|
||||
},
|
||||
),
|
||||
]
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
}
|
||||
@@ -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(),
|
||||
|
||||
@@ -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!(
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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()
|
||||
},
|
||||
|
||||
@@ -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()),
|
||||
|
||||
@@ -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()
|
||||
},
|
||||
|
||||
@@ -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()
|
||||
},
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in new issue
Block a user