From ab7ce328881b3edf814e966039ff93757076dc17 Mon Sep 17 00:00:00 2001 From: Joshua Farayola Date: Mon, 20 May 2024 09:13:35 +0100 Subject: [PATCH] Add glob support for custom file type language (#12043) Release Notes: - Added glob support for file_types configuration ([#10765](https://github.com/zed-industries/zed/issues/10765)). `file_types` can now be written like this: ```json "file_types": { "Dockerfile": [ "Dockerfile", "Dockerfile.*", ] } ``` --- crates/language/src/buffer_tests.rs | 17 +++++++++++++++++ crates/language/src/language_registry.rs | 13 ++++++++----- crates/language/src/language_settings.rs | 17 ++++++++++------- docs/src/configuring-zed.md | 7 ++++--- 4 files changed, 39 insertions(+), 15 deletions(-) diff --git a/crates/language/src/buffer_tests.rs b/crates/language/src/buffer_tests.rs index 80929c04f7bbade2ef62cea984cbf9c4cabc7d93..97a1f9c9937fd18afedc655c39c983d11627e4ab 100644 --- a/crates/language/src/buffer_tests.rs +++ b/crates/language/src/buffer_tests.rs @@ -184,6 +184,10 @@ async fn test_language_for_file_with_custom_file_types(cx: &mut TestAppContext) settings.file_types.extend([ ("TypeScript".into(), vec!["js".into()]), ("C++".into(), vec!["c".into()]), + ( + "Dockerfile".into(), + vec!["Dockerfile".into(), "Dockerfile.*".into()], + ), ]); }) }); @@ -223,6 +227,14 @@ async fn test_language_for_file_with_custom_file_types(cx: &mut TestAppContext) }, ..Default::default() }, + LanguageConfig { + name: "Dockerfile".into(), + matcher: LanguageMatcher { + path_suffixes: vec!["Dockerfile".to_string()], + ..Default::default() + }, + ..Default::default() + }, ] { languages.add(Arc::new(Language::new(config, None))); } @@ -237,6 +249,11 @@ async fn test_language_for_file_with_custom_file_types(cx: &mut TestAppContext) .await .unwrap(); assert_eq!(language.name().as_ref(), "C++"); + let language = cx + .read(|cx| languages.language_for_file(&file("Dockerfile.dev"), None, cx)) + .await + .unwrap(); + assert_eq!(language.name().as_ref(), "Dockerfile"); } fn file(path: &str) -> Arc { diff --git a/crates/language/src/language_registry.rs b/crates/language/src/language_registry.rs index e0faa10c3a065cf2041627949e7a64e2f6bba5ba..44454b562c553a0e81ec13dbfdc35642e1bf9084 100644 --- a/crates/language/src/language_registry.rs +++ b/crates/language/src/language_registry.rs @@ -14,6 +14,7 @@ use futures::{ future::Shared, Future, FutureExt as _, }; +use globset::GlobSet; use gpui::{AppContext, BackgroundExecutor, Task}; use lsp::LanguageServerId; use parking_lot::{Mutex, RwLock}; @@ -506,23 +507,25 @@ impl LanguageRegistry { self: &Arc, path: &Path, content: Option<&Rope>, - user_file_types: Option<&HashMap, Vec>>, + user_file_types: Option<&HashMap, GlobSet>>, ) -> impl Future>> { let filename = path.file_name().and_then(|name| name.to_str()); let extension = path.extension_or_hidden_file_name(); let path_suffixes = [extension, filename]; - let empty = Vec::new(); + let empty = GlobSet::empty(); let rx = self.get_or_load_language(move |language_name, config| { let path_matches_default_suffix = config .path_suffixes .iter() .any(|suffix| path_suffixes.contains(&Some(suffix.as_str()))); - let path_matches_custom_suffix = user_file_types + let custom_suffixes = user_file_types .and_then(|types| types.get(language_name)) - .unwrap_or(&empty) + .unwrap_or(&empty); + let path_matches_custom_suffix = path_suffixes .iter() - .any(|suffix| path_suffixes.contains(&Some(suffix.as_str()))); + .map(|suffix| suffix.unwrap_or("")) + .any(|suffix| custom_suffixes.is_match(suffix)); let content_matches = content.zip(config.first_line_pattern.as_ref()).map_or( false, |(content, pattern)| { diff --git a/crates/language/src/language_settings.rs b/crates/language/src/language_settings.rs index 303564d3900f7342b2c65cdf6aa1ee74208123ec..b414bb4edd2ed0458b8d0d41452450e4f21db2dc 100644 --- a/crates/language/src/language_settings.rs +++ b/crates/language/src/language_settings.rs @@ -3,7 +3,7 @@ use crate::{File, Language, LanguageServerName}; use anyhow::Result; use collections::{HashMap, HashSet}; -use globset::GlobMatcher; +use globset::{Glob, GlobMatcher, GlobSet, GlobSetBuilder}; use gpui::AppContext; use itertools::{Either, Itertools}; use schemars::{ @@ -55,7 +55,7 @@ pub struct AllLanguageSettings { pub inline_completions: InlineCompletionSettings, defaults: LanguageSettings, languages: HashMap, LanguageSettings>, - pub(crate) file_types: HashMap, Vec>, + pub(crate) file_types: HashMap, GlobSet>, } /// The settings for a particular language. @@ -573,7 +573,7 @@ impl settings::Settings for AllLanguageSettings { .and_then(|c| c.disabled_globs.as_ref()) .ok_or_else(Self::missing_default)?; - let mut file_types: HashMap, Vec> = HashMap::default(); + let mut file_types: HashMap, GlobSet> = HashMap::default(); for user_settings in sources.customizations() { if let Some(copilot) = user_settings.features.as_ref().and_then(|f| f.copilot) { copilot_enabled = Some(copilot); @@ -611,10 +611,13 @@ impl settings::Settings for AllLanguageSettings { } for (language, suffixes) in &user_settings.file_types { - file_types - .entry(language.clone()) - .or_default() - .extend_from_slice(suffixes); + let mut builder = GlobSetBuilder::new(); + + for suffix in suffixes { + builder.add(Glob::new(suffix)?); + } + + file_types.insert(language.clone(), builder.build()?); } } diff --git a/docs/src/configuring-zed.md b/docs/src/configuring-zed.md index 7aa8f09c480b1ced96e8838a9f04c40ed86424a8..8e55e4af4d84bbfb057ef5504aac5c9daa0aa6f8 100644 --- a/docs/src/configuring-zed.md +++ b/docs/src/configuring-zed.md @@ -651,18 +651,19 @@ The result is still `)))` and not `))))))`, which is what it would be by default ## File Types - Setting: `file_types` -- Description: Configure how Zed selects a language for a file based on its filename or extension. +- Description: Configure how Zed selects a language for a file based on its filename or extension. Supports glob entries. - Default: `{}` **Examples** -To interpret all `.c` files as C++, and files called `MyLockFile` as TOML: +To interpret all `.c` files as C++, files called `MyLockFile` as TOML and files starting with `Dockerfile` as Dockerfile: ```json { "file_types": { "C++": ["c"], - "TOML": ["MyLockFile"] + "TOML": ["MyLockFile"], + "Dockerfile": ["Dockerfile*"] } } ```