Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion crates/language/src/language_settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -892,7 +892,10 @@ impl settings::Settings for AllLanguageSettings {

file_types.insert(
language.clone(),
(builder.build().unwrap(), patterns.0.clone()),
(
builder.build().unwrap(),
patterns.0.iter().cloned().collect(),
),
);
}

Expand Down
30 changes: 28 additions & 2 deletions crates/settings/src/settings_store.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ use crate::{
LanguageToSettingsMap, LspSettings, LspSettingsMap, SemanticTokenRules, ThemeName,
UserSettingsContentExt, VsCodeSettings, WorktreeId,
settings_content::{
ExtendingVec, ExtensionsSettingsContent, ProfileBase, ProjectSettingsContent,
ExtendingSet, ExtensionsSettingsContent, ProfileBase, ProjectSettingsContent,
RootUserSettings, SettingsContent, UserSettingsContent, merge_from::MergeFrom,
},
};
Expand Down Expand Up @@ -1203,7 +1203,7 @@ impl SettingsStore {
});

let file_type_patterns_ref =
generator.subschema_for::<ExtendingVec<String>>().to_value();
generator.subschema_for::<ExtendingSet<String>>().to_value();
replace_subschema::<FileTypeMap>(generator, || {
json_schema!({
"type": "object",
Expand Down Expand Up @@ -2616,6 +2616,32 @@ mod tests {
.unindent(),
cx,
);

// re-importing a file association that is already present should be
// idempotent rather than appending a duplicate extension (#56536)
check_vscode_import(
&mut store,
r#"{
"file_types": {
"c": ["*.keymap"]
}
}
"#
.unindent(),
r#"{ "files.associations": { "*.keymap": "c" } }"#.to_owned(),
r#"{
"base_keymap": "VSCode",
"minimap": {
"show": "always"
},
"file_types": {
"c": ["*.keymap"]
}
}
"#
.unindent(),
cx,
);
}

#[track_caller]
Expand Down
8 changes: 6 additions & 2 deletions crates/settings/src/vscode_import.rs
Original file line number Diff line number Diff line change
Expand Up @@ -644,11 +644,15 @@ impl VsCodeSettings {

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 mut associations: HashMap<Arc<str>, ExtendingSet<String>> = HashMap::default();
let map = self.read_value("files.associations")?.as_object()?;
for (k, v) in map {
let Some(v) = v.as_str() else { continue };
associations.entry(v.into()).or_default().0.push(k.clone());
associations
.entry(v.into())
.or_default()
.0
.insert(k.clone());
}
skip_default(FileTypeMap(associations))
}
Expand Down
8 changes: 4 additions & 4 deletions crates/settings_content/src/language.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use serde::{Deserialize, Serialize};
use settings_macros::{MergeFrom, with_fallible_options};
use std::sync::Arc;

use crate::{DocumentFoldingRanges, DocumentSymbols, ExtendingVec, SemanticTokens, merge_from};
use crate::{DocumentFoldingRanges, DocumentSymbols, ExtendingSet, SemanticTokens, merge_from};

/// The state of the modifier keys at some point in time
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, JsonSchema, MergeFrom)]
Expand Down Expand Up @@ -1203,11 +1203,11 @@ 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>>);
pub struct FileTypeMap(pub HashMap<Arc<str>, ExtendingSet<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>>;
type Item = (&'a Arc<str>, &'a ExtendingSet<String>);
type IntoIter = std::collections::hash_map::Iter<'a, Arc<str>, ExtendingSet<String>>;

fn into_iter(self) -> Self::IntoIter {
self.0.iter()
Expand Down
23 changes: 22 additions & 1 deletion crates/settings_content/src/settings_content.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ pub use theme::*;
pub use title_bar::*;
pub use workspace::*;

use collections::{HashMap, IndexMap};
use collections::{HashMap, IndexMap, IndexSet};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use settings_macros::{MergeFrom, with_fallible_options};
Expand Down Expand Up @@ -1397,6 +1397,27 @@ impl<T: Clone> merge_from::MergeFrom for ExtendingVec<T> {
}
}

// An ExtendingSet in the settings can only accumulate new values, and ignores
// values that are already present, so merging the same source more than once
// (e.g. re-importing VS Code settings) is idempotent.
//
// Insertion order is preserved, so it round-trips through the user's settings
// file without reordering their entries.
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
pub struct ExtendingSet<T: std::hash::Hash + Eq>(pub IndexSet<T>);

impl<T: std::hash::Hash + Eq> From<Vec<T>> for ExtendingSet<T> {
fn from(vec: Vec<T>) -> Self {
ExtendingSet(vec.into_iter().collect())
}
}

impl<T: Clone + std::hash::Hash + Eq> merge_from::MergeFrom for ExtendingSet<T> {
fn merge_from(&mut self, other: &Self) {
self.0.extend(other.0.iter().cloned());
}
}

// A SaturatingBool in the settings can only ever be set to true,
// later attempts to set it to false will be ignored.
//
Expand Down
Loading