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.*",
  ]
}
```
This commit is contained in:
Joshua Farayola 2024-05-20 09:13:35 +01:00 committed by GitHub
parent 4e935f9f0f
commit ab7ce32888
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 39 additions and 15 deletions

View file

@ -184,6 +184,10 @@ async fn test_language_for_file_with_custom_file_types(cx: &mut TestAppContext)
settings.file_types.extend([ settings.file_types.extend([
("TypeScript".into(), vec!["js".into()]), ("TypeScript".into(), vec!["js".into()]),
("C++".into(), vec!["c".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() ..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))); 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 .await
.unwrap(); .unwrap();
assert_eq!(language.name().as_ref(), "C++"); 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<dyn File> { fn file(path: &str) -> Arc<dyn File> {

View file

@ -14,6 +14,7 @@ use futures::{
future::Shared, future::Shared,
Future, FutureExt as _, Future, FutureExt as _,
}; };
use globset::GlobSet;
use gpui::{AppContext, BackgroundExecutor, Task}; use gpui::{AppContext, BackgroundExecutor, Task};
use lsp::LanguageServerId; use lsp::LanguageServerId;
use parking_lot::{Mutex, RwLock}; use parking_lot::{Mutex, RwLock};
@ -506,23 +507,25 @@ impl LanguageRegistry {
self: &Arc<Self>, self: &Arc<Self>,
path: &Path, path: &Path,
content: Option<&Rope>, content: Option<&Rope>,
user_file_types: Option<&HashMap<Arc<str>, Vec<String>>>, user_file_types: Option<&HashMap<Arc<str>, GlobSet>>,
) -> impl Future<Output = Result<Arc<Language>>> { ) -> impl Future<Output = Result<Arc<Language>>> {
let filename = path.file_name().and_then(|name| name.to_str()); let filename = path.file_name().and_then(|name| name.to_str());
let extension = path.extension_or_hidden_file_name(); let extension = path.extension_or_hidden_file_name();
let path_suffixes = [extension, filename]; 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 rx = self.get_or_load_language(move |language_name, config| {
let path_matches_default_suffix = config let path_matches_default_suffix = config
.path_suffixes .path_suffixes
.iter() .iter()
.any(|suffix| path_suffixes.contains(&Some(suffix.as_str()))); .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)) .and_then(|types| types.get(language_name))
.unwrap_or(&empty) .unwrap_or(&empty);
let path_matches_custom_suffix = path_suffixes
.iter() .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( let content_matches = content.zip(config.first_line_pattern.as_ref()).map_or(
false, false,
|(content, pattern)| { |(content, pattern)| {

View file

@ -3,7 +3,7 @@
use crate::{File, Language, LanguageServerName}; use crate::{File, Language, LanguageServerName};
use anyhow::Result; use anyhow::Result;
use collections::{HashMap, HashSet}; use collections::{HashMap, HashSet};
use globset::GlobMatcher; use globset::{Glob, GlobMatcher, GlobSet, GlobSetBuilder};
use gpui::AppContext; use gpui::AppContext;
use itertools::{Either, Itertools}; use itertools::{Either, Itertools};
use schemars::{ use schemars::{
@ -55,7 +55,7 @@ pub struct AllLanguageSettings {
pub inline_completions: InlineCompletionSettings, pub inline_completions: InlineCompletionSettings,
defaults: LanguageSettings, defaults: LanguageSettings,
languages: HashMap<Arc<str>, LanguageSettings>, languages: HashMap<Arc<str>, LanguageSettings>,
pub(crate) file_types: HashMap<Arc<str>, Vec<String>>, pub(crate) file_types: HashMap<Arc<str>, GlobSet>,
} }
/// The settings for a particular language. /// The settings for a particular language.
@ -573,7 +573,7 @@ impl settings::Settings for AllLanguageSettings {
.and_then(|c| c.disabled_globs.as_ref()) .and_then(|c| c.disabled_globs.as_ref())
.ok_or_else(Self::missing_default)?; .ok_or_else(Self::missing_default)?;
let mut file_types: HashMap<Arc<str>, Vec<String>> = HashMap::default(); let mut file_types: HashMap<Arc<str>, GlobSet> = HashMap::default();
for user_settings in sources.customizations() { for user_settings in sources.customizations() {
if let Some(copilot) = user_settings.features.as_ref().and_then(|f| f.copilot) { if let Some(copilot) = user_settings.features.as_ref().and_then(|f| f.copilot) {
copilot_enabled = Some(copilot); copilot_enabled = Some(copilot);
@ -611,10 +611,13 @@ impl settings::Settings for AllLanguageSettings {
} }
for (language, suffixes) in &user_settings.file_types { for (language, suffixes) in &user_settings.file_types {
file_types let mut builder = GlobSetBuilder::new();
.entry(language.clone())
.or_default() for suffix in suffixes {
.extend_from_slice(suffixes); builder.add(Glob::new(suffix)?);
}
file_types.insert(language.clone(), builder.build()?);
} }
} }

View file

@ -651,18 +651,19 @@ The result is still `)))` and not `))))))`, which is what it would be by default
## File Types ## File Types
- Setting: `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: `{}` - Default: `{}`
**Examples** **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 ```json
{ {
"file_types": { "file_types": {
"C++": ["c"], "C++": ["c"],
"TOML": ["MyLockFile"] "TOML": ["MyLockFile"],
"Dockerfile": ["Dockerfile*"]
} }
} }
``` ```