Autocomplete language names in file_types setting (#58595)
## Context Closes #58571 When editing `settings.json`, typing a new key under `languages` triggers language-name autocomplete. The same did not work for `file_types`, even though both keys map language names to configuration. The root cause: the JSON schema generator uses `replace_subschema` to inject installed language names as the allowed properties of a named type. The `languages` field backed by `LanguageToSettingsMap` (a named newtype) got this treatment; `file_types` was backed by a raw `HashMap<Arc<str>, ExtendingVec<String>>` with no named type to target. The fix introduces a `FileTypeMap` newtype parallel to `LanguageToSettingsMap` and wires it into the same schema-injection path. Video of manual test below : [Screencast from 2026-06-05 00-04-16.webm](https://github.com/user-attachments/assets/5d8afb60-d566-4c99-ad2a-66ebbb47ed2e) ## How to Review - `crates/settings_content/src/language.rs` : Adds `FileTypeMap(HashMap<Arc<str>, ExtendingVec<String>>)` with the full set of derives (`Debug`, `Clone`, `Default`, `PartialEq`, `Serialize`, `Deserialize`, `JsonSchema`, `MergeFrom`) and an `IntoIterator for &FileTypeMap` impl so existing call sites (`all_languages.file_types.iter().flatten()`) continue to work without changes. Changes the `file_types` field in `AllLanguageSettingsContent` from the raw HashMap to `Option<FileTypeMap>`. - `crates/settings/src/settings_store.rs` : In `configure_schema_generator`, adds a `replace_subschema::<FileTypeMap>` call mirroring the existing `LanguageToSettingsMap` one: injects installed language names as the allowed properties, with `ExtendingVec<String>` (array of glob patterns) as the value schema. Adds `test_file_types_schema_generation` to verify the injected properties. - `crates/settings/src/vscode_import.rs` : Updates `file_types()` to return `Option<FileTypeMap>` and wraps the constructed map accordingly. - `crates/language/src/buffer_tests.rs` : Updates the existing custom `file_types` test setup to extend the inner map of the new `FileTypeMap` wrapper explicitly, keeping the language crate test build passing after the type change. ## Self-Review Checklist - [x] I've reviewed my own diff for quality, security, and reliability - [ ] Unsafe blocks (if any) have justifying comments - [x] The content is consistent with the UI/UX checklist - [x] Tests cover the new/changed behavior - [x] Performance impact has been considered and is acceptable **Release Notes :** - Fixed language name autocomplete not working in the `file_types` setting --------- Co-authored-by: Kirill Bulatov <kirill@zed.dev>
This commit is contained in:
1 parent
23c2eb2d0c
commit
1806aee21f
4 files changed
+68
-7
No files matched your search
@@ -277,7 +277,7 @@ async fn test_first_line_pattern(cx: &mut TestAppContext) {
|
||||
async fn test_language_for_file_with_custom_file_types(cx: &mut TestAppContext) {
|
||||
cx.update(|cx| {
|
||||
init_settings(cx, |settings| {
|
||||
settings.file_types.get_or_insert_default().extend([
|
||||
settings.file_types.get_or_insert_default().0.extend([
|
||||
("TypeScript".into(), vec!["js".into()].into()),
|
||||
(
|
||||
"JavaScript".into(),
|
||||
|
||||
@@ -32,12 +32,12 @@ use util::{
|
||||
use crate::editorconfig_store::EditorconfigStore;
|
||||
|
||||
use crate::{
|
||||
ActiveSettingsProfileName, FontFamilyName, IconThemeName, LanguageSettingsContent,
|
||||
ActiveSettingsProfileName, FileTypeMap, FontFamilyName, IconThemeName, LanguageSettingsContent,
|
||||
LanguageToSettingsMap, LspSettings, LspSettingsMap, SemanticTokenRules, ThemeName,
|
||||
UserSettingsContentExt, VsCodeSettings, WorktreeId,
|
||||
settings_content::{
|
||||
ExtensionsSettingsContent, ProfileBase, ProjectSettingsContent, RootUserSettings,
|
||||
SettingsContent, UserSettingsContent, merge_from::MergeFrom,
|
||||
ExtendingVec, ExtensionsSettingsContent, ProfileBase, ProjectSettingsContent,
|
||||
RootUserSettings, SettingsContent, UserSettingsContent, merge_from::MergeFrom,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1201,6 +1201,17 @@ impl SettingsStore {
|
||||
"properties": params.language_names.iter().map(|name| (name.clone(), language_settings_content_ref.clone())).collect::<serde_json::Map<_, _>>()
|
||||
})
|
||||
});
|
||||
|
||||
let file_type_patterns_ref =
|
||||
generator.subschema_for::<ExtendingVec<String>>().to_value();
|
||||
replace_subschema::<FileTypeMap>(generator, || {
|
||||
json_schema!({
|
||||
"type": "object",
|
||||
"errorMessage": "No language with this name is installed.",
|
||||
"properties": params.language_names.iter().map(|name| (name.clone(), file_type_patterns_ref.clone())).collect::<serde_json::Map<_, _>>(),
|
||||
"additionalProperties": file_type_patterns_ref.clone()
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
generator.subschema_for::<LspSettings>();
|
||||
@@ -3166,6 +3177,42 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn test_file_types_schema_generation(cx: &mut App) {
|
||||
SettingsStore::test(cx);
|
||||
|
||||
let schema = SettingsStore::json_schema(&SettingsJsonSchemaParams {
|
||||
language_names: &["Rust".to_string(), "TypeScript".to_string()],
|
||||
font_names: &["Zed Mono".to_string()],
|
||||
theme_names: &["One Dark".into()],
|
||||
icon_theme_names: &["Zed Icons".into()],
|
||||
lsp_adapter_names: &[],
|
||||
action_names: &[],
|
||||
action_documentation: &HashMap::default(),
|
||||
deprecations: &HashMap::default(),
|
||||
deprecation_messages: &HashMap::default(),
|
||||
});
|
||||
|
||||
let file_type_map = schema
|
||||
.pointer("/$defs/FileTypeMap")
|
||||
.expect("schema should have a FileTypeMap definition");
|
||||
let properties = file_type_map
|
||||
.pointer("/properties")
|
||||
.expect("FileTypeMap should have properties")
|
||||
.as_object()
|
||||
.expect("FileTypeMap properties should be an object");
|
||||
|
||||
let mut language_names = properties.keys().collect::<Vec<_>>();
|
||||
language_names.sort();
|
||||
assert_eq!(language_names, ["Rust", "TypeScript"]);
|
||||
|
||||
let patterns_schema = file_type_map
|
||||
.pointer("/additionalProperties")
|
||||
.expect("FileTypeMap should validate values of unknown language names");
|
||||
assert_eq!(properties.get("Rust"), Some(patterns_schema));
|
||||
assert_eq!(properties.get("TypeScript"), Some(patterns_schema));
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn test_project_json_schema_differs_from_user_schema(cx: &mut App) {
|
||||
SettingsStore::test(cx);
|
||||
|
||||
@@ -642,7 +642,7 @@ impl VsCodeSettings {
|
||||
}
|
||||
}
|
||||
|
||||
fn file_types(&self) -> Option<HashMap<Arc<str>, ExtendingVec<String>>> {
|
||||
fn file_types(&self) -> Option<FileTypeMap> {
|
||||
// vscodes file association map is inverted from ours, so we flip the mapping before merging
|
||||
let mut associations: HashMap<Arc<str>, ExtendingVec<String>> = HashMap::default();
|
||||
let map = self.read_value("files.associations")?.as_object()?;
|
||||
@@ -650,7 +650,7 @@ impl VsCodeSettings {
|
||||
let Some(v) = v.as_str() else { continue };
|
||||
associations.entry(v.into()).or_default().0.push(k.clone());
|
||||
}
|
||||
skip_default(associations)
|
||||
skip_default(FileTypeMap(associations))
|
||||
}
|
||||
|
||||
fn edit_predictions_settings_content(&self) -> Option<EditPredictionSettingsContent> {
|
||||
|
||||
@@ -44,7 +44,7 @@ pub struct AllLanguageSettingsContent {
|
||||
pub languages: LanguageToSettingsMap,
|
||||
/// Settings for associating file extensions and filenames
|
||||
/// with languages.
|
||||
pub file_types: Option<HashMap<Arc<str>, ExtendingVec<String>>>,
|
||||
pub file_types: Option<FileTypeMap>,
|
||||
}
|
||||
|
||||
impl merge_from::MergeFrom for AllLanguageSettingsContent {
|
||||
@@ -1144,6 +1144,20 @@ pub struct LanguageTaskSettingsContent {
|
||||
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize, JsonSchema, MergeFrom)]
|
||||
pub struct LanguageToSettingsMap(pub HashMap<String, LanguageSettingsContent>);
|
||||
|
||||
/// Map from language name to file patterns.
|
||||
#[with_fallible_options]
|
||||
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize, JsonSchema, MergeFrom)]
|
||||
pub struct FileTypeMap(pub HashMap<Arc<str>, ExtendingVec<String>>);
|
||||
|
||||
impl<'a> IntoIterator for &'a FileTypeMap {
|
||||
type Item = (&'a Arc<str>, &'a ExtendingVec<String>);
|
||||
type IntoIter = std::collections::hash_map::Iter<'a, Arc<str>, ExtendingVec<String>>;
|
||||
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
self.0.iter()
|
||||
}
|
||||
}
|
||||
|
||||
/// Determines how indent guides are colored.
|
||||
#[derive(
|
||||
Default,
|
||||
|
||||
Reference in new issue
Block a user