Cargo.lock 🔗
@@ -7515,6 +7515,7 @@ dependencies = [
"collections",
"feature_flags2",
"fs",
+ "fs2",
"futures 0.3.28",
"gpui2",
"indoc",
Conrad Irwin created
Cargo.lock | 1
crates/project2/src/terminals.rs | 2
crates/settings2/Cargo.toml | 2
crates/settings2/src/settings_file.rs | 2
crates/settings2/src/settings_store.rs | 856 ++++++++++++++--------------
5 files changed, 432 insertions(+), 431 deletions(-)
@@ -7515,6 +7515,7 @@ dependencies = [
"collections",
"feature_flags2",
"fs",
+ "fs2",
"futures 0.3.28",
"gpui2",
"indoc",
@@ -37,7 +37,7 @@ impl Project {
Some(settings.blinking.clone()),
settings.alternate_scroll,
window,
- |index, cx| todo!("color_for_index"),
+ |_, _| todo!("color_for_index"),
)
.map(|builder| {
let terminal_handle = cx.entity(|cx| builder.subscribe(cx));
@@ -15,7 +15,7 @@ test-support = ["gpui2/test-support", "fs/test-support"]
collections = { path = "../collections" }
gpui2 = { path = "../gpui2" }
sqlez = { path = "../sqlez" }
-fs = { path = "../fs" }
+fs2 = { path = "../fs2" }
feature_flags2 = { path = "../feature_flags2" }
util = { path = "../util" }
@@ -1,6 +1,6 @@
use crate::{settings_store::SettingsStore, Settings};
use anyhow::Result;
-use fs::Fs;
+use fs2::Fs;
use futures::{channel::mpsc, StreamExt};
use gpui2::{AppContext, Executor};
use std::{io::ErrorKind, path::PathBuf, str, sync::Arc, time::Duration};
@@ -871,431 +871,431 @@ pub fn parse_json_with_comments<T: DeserializeOwned>(content: &str) -> Result<T>
Ok(serde_json_lenient::from_str(content)?)
}
-// #[cfg(test)]
-// mod tests {
-// use super::*;
-// use serde_derive::Deserialize;
-// use unindent::Unindent;
-
-// #[gpui::test]
-// fn test_settings_store_basic(cx: &mut AppContext) {
-// let mut store = SettingsStore::default();
-// store.register_setting::<UserSettings>(cx);
-// store.register_setting::<TurboSetting>(cx);
-// store.register_setting::<MultiKeySettings>(cx);
-// store
-// .set_default_settings(
-// r#"{
-// "turbo": false,
-// "user": {
-// "name": "John Doe",
-// "age": 30,
-// "staff": false
-// }
-// }"#,
-// cx,
-// )
-// .unwrap();
-
-// assert_eq!(store.get::<TurboSetting>(None), &TurboSetting(false));
-// assert_eq!(
-// store.get::<UserSettings>(None),
-// &UserSettings {
-// name: "John Doe".to_string(),
-// age: 30,
-// staff: false,
-// }
-// );
-// assert_eq!(
-// store.get::<MultiKeySettings>(None),
-// &MultiKeySettings {
-// key1: String::new(),
-// key2: String::new(),
-// }
-// );
-
-// store
-// .set_user_settings(
-// r#"{
-// "turbo": true,
-// "user": { "age": 31 },
-// "key1": "a"
-// }"#,
-// cx,
-// )
-// .unwrap();
-
-// assert_eq!(store.get::<TurboSetting>(None), &TurboSetting(true));
-// assert_eq!(
-// store.get::<UserSettings>(None),
-// &UserSettings {
-// name: "John Doe".to_string(),
-// age: 31,
-// staff: false
-// }
-// );
-
-// store
-// .set_local_settings(
-// 1,
-// Path::new("/root1").into(),
-// Some(r#"{ "user": { "staff": true } }"#),
-// cx,
-// )
-// .unwrap();
-// store
-// .set_local_settings(
-// 1,
-// Path::new("/root1/subdir").into(),
-// Some(r#"{ "user": { "name": "Jane Doe" } }"#),
-// cx,
-// )
-// .unwrap();
-
-// store
-// .set_local_settings(
-// 1,
-// Path::new("/root2").into(),
-// Some(r#"{ "user": { "age": 42 }, "key2": "b" }"#),
-// cx,
-// )
-// .unwrap();
-
-// assert_eq!(
-// store.get::<UserSettings>(Some((1, Path::new("/root1/something")))),
-// &UserSettings {
-// name: "John Doe".to_string(),
-// age: 31,
-// staff: true
-// }
-// );
-// assert_eq!(
-// store.get::<UserSettings>(Some((1, Path::new("/root1/subdir/something")))),
-// &UserSettings {
-// name: "Jane Doe".to_string(),
-// age: 31,
-// staff: true
-// }
-// );
-// assert_eq!(
-// store.get::<UserSettings>(Some((1, Path::new("/root2/something")))),
-// &UserSettings {
-// name: "John Doe".to_string(),
-// age: 42,
-// staff: false
-// }
-// );
-// assert_eq!(
-// store.get::<MultiKeySettings>(Some((1, Path::new("/root2/something")))),
-// &MultiKeySettings {
-// key1: "a".to_string(),
-// key2: "b".to_string(),
-// }
-// );
-// }
-
-// #[gpui::test]
-// fn test_setting_store_assign_json_before_register(cx: &mut AppContext) {
-// let mut store = SettingsStore::default();
-// store
-// .set_default_settings(
-// r#"{
-// "turbo": true,
-// "user": {
-// "name": "John Doe",
-// "age": 30,
-// "staff": false
-// },
-// "key1": "x"
-// }"#,
-// cx,
-// )
-// .unwrap();
-// store
-// .set_user_settings(r#"{ "turbo": false }"#, cx)
-// .unwrap();
-// store.register_setting::<UserSettings>(cx);
-// store.register_setting::<TurboSetting>(cx);
-
-// assert_eq!(store.get::<TurboSetting>(None), &TurboSetting(false));
-// assert_eq!(
-// store.get::<UserSettings>(None),
-// &UserSettings {
-// name: "John Doe".to_string(),
-// age: 30,
-// staff: false,
-// }
-// );
-
-// store.register_setting::<MultiKeySettings>(cx);
-// assert_eq!(
-// store.get::<MultiKeySettings>(None),
-// &MultiKeySettings {
-// key1: "x".into(),
-// key2: String::new(),
-// }
-// );
-// }
-
-// #[gpui::test]
-// fn test_setting_store_update(cx: &mut AppContext) {
-// let mut store = SettingsStore::default();
-// store.register_setting::<MultiKeySettings>(cx);
-// store.register_setting::<UserSettings>(cx);
-// store.register_setting::<LanguageSettings>(cx);
-
-// // entries added and updated
-// check_settings_update::<LanguageSettings>(
-// &mut store,
-// r#"{
-// "languages": {
-// "JSON": {
-// "language_setting_1": true
-// }
-// }
-// }"#
-// .unindent(),
-// |settings| {
-// settings
-// .languages
-// .get_mut("JSON")
-// .unwrap()
-// .language_setting_1 = Some(false);
-// settings.languages.insert(
-// "Rust".into(),
-// LanguageSettingEntry {
-// language_setting_2: Some(true),
-// ..Default::default()
-// },
-// );
-// },
-// r#"{
-// "languages": {
-// "Rust": {
-// "language_setting_2": true
-// },
-// "JSON": {
-// "language_setting_1": false
-// }
-// }
-// }"#
-// .unindent(),
-// cx,
-// );
-
-// // weird formatting
-// check_settings_update::<UserSettings>(
-// &mut store,
-// r#"{
-// "user": { "age": 36, "name": "Max", "staff": true }
-// }"#
-// .unindent(),
-// |settings| settings.age = Some(37),
-// r#"{
-// "user": { "age": 37, "name": "Max", "staff": true }
-// }"#
-// .unindent(),
-// cx,
-// );
-
-// // single-line formatting, other keys
-// check_settings_update::<MultiKeySettings>(
-// &mut store,
-// r#"{ "one": 1, "two": 2 }"#.unindent(),
-// |settings| settings.key1 = Some("x".into()),
-// r#"{ "key1": "x", "one": 1, "two": 2 }"#.unindent(),
-// cx,
-// );
-
-// // empty object
-// check_settings_update::<UserSettings>(
-// &mut store,
-// r#"{
-// "user": {}
-// }"#
-// .unindent(),
-// |settings| settings.age = Some(37),
-// r#"{
-// "user": {
-// "age": 37
-// }
-// }"#
-// .unindent(),
-// cx,
-// );
-
-// // no content
-// check_settings_update::<UserSettings>(
-// &mut store,
-// r#""#.unindent(),
-// |settings| settings.age = Some(37),
-// r#"{
-// "user": {
-// "age": 37
-// }
-// }
-// "#
-// .unindent(),
-// cx,
-// );
-
-// check_settings_update::<UserSettings>(
-// &mut store,
-// r#"{
-// }
-// "#
-// .unindent(),
-// |settings| settings.age = Some(37),
-// r#"{
-// "user": {
-// "age": 37
-// }
-// }
-// "#
-// .unindent(),
-// cx,
-// );
-// }
-
-// fn check_settings_update<T: Setting>(
-// store: &mut SettingsStore,
-// old_json: String,
-// update: fn(&mut T::FileContent),
-// expected_new_json: String,
-// cx: &mut AppContext,
-// ) {
-// store.set_user_settings(&old_json, cx).ok();
-// let edits = store.edits_for_update::<T>(&old_json, update);
-// let mut new_json = old_json;
-// for (range, replacement) in edits.into_iter() {
-// new_json.replace_range(range, &replacement);
-// }
-// pretty_assertions::assert_eq!(new_json, expected_new_json);
-// }
-
-// #[derive(Debug, PartialEq, Deserialize)]
-// struct UserSettings {
-// name: String,
-// age: u32,
-// staff: bool,
-// }
-
-// #[derive(Default, Clone, Serialize, Deserialize, JsonSchema)]
-// struct UserSettingsJson {
-// name: Option<String>,
-// age: Option<u32>,
-// staff: Option<bool>,
-// }
-
-// impl Setting for UserSettings {
-// const KEY: Option<&'static str> = Some("user");
-// type FileContent = UserSettingsJson;
-
-// fn load(
-// default_value: &UserSettingsJson,
-// user_values: &[&UserSettingsJson],
-// _: &AppContext,
-// ) -> Result<Self> {
-// Self::load_via_json_merge(default_value, user_values)
-// }
-// }
-
-// #[derive(Debug, Deserialize, PartialEq)]
-// struct TurboSetting(bool);
-
-// impl Setting for TurboSetting {
-// const KEY: Option<&'static str> = Some("turbo");
-// type FileContent = Option<bool>;
-
-// fn load(
-// default_value: &Option<bool>,
-// user_values: &[&Option<bool>],
-// _: &AppContext,
-// ) -> Result<Self> {
-// Self::load_via_json_merge(default_value, user_values)
-// }
-// }
-
-// #[derive(Clone, Debug, PartialEq, Deserialize)]
-// struct MultiKeySettings {
-// #[serde(default)]
-// key1: String,
-// #[serde(default)]
-// key2: String,
-// }
-
-// #[derive(Clone, Default, Serialize, Deserialize, JsonSchema)]
-// struct MultiKeySettingsJson {
-// key1: Option<String>,
-// key2: Option<String>,
-// }
-
-// impl Setting for MultiKeySettings {
-// const KEY: Option<&'static str> = None;
-
-// type FileContent = MultiKeySettingsJson;
-
-// fn load(
-// default_value: &MultiKeySettingsJson,
-// user_values: &[&MultiKeySettingsJson],
-// _: &AppContext,
-// ) -> Result<Self> {
-// Self::load_via_json_merge(default_value, user_values)
-// }
-// }
-
-// #[derive(Debug, Deserialize)]
-// struct JournalSettings {
-// pub path: String,
-// pub hour_format: HourFormat,
-// }
-
-// #[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
-// #[serde(rename_all = "snake_case")]
-// enum HourFormat {
-// Hour12,
-// Hour24,
-// }
-
-// #[derive(Clone, Default, Debug, Serialize, Deserialize, JsonSchema)]
-// struct JournalSettingsJson {
-// pub path: Option<String>,
-// pub hour_format: Option<HourFormat>,
-// }
-
-// impl Setting for JournalSettings {
-// const KEY: Option<&'static str> = Some("journal");
-
-// type FileContent = JournalSettingsJson;
-
-// fn load(
-// default_value: &JournalSettingsJson,
-// user_values: &[&JournalSettingsJson],
-// _: &AppContext,
-// ) -> Result<Self> {
-// Self::load_via_json_merge(default_value, user_values)
-// }
-// }
-
-// #[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]
-// struct LanguageSettings {
-// #[serde(default)]
-// languages: HashMap<String, LanguageSettingEntry>,
-// }
-
-// #[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]
-// struct LanguageSettingEntry {
-// language_setting_1: Option<bool>,
-// language_setting_2: Option<bool>,
-// }
-
-// impl Setting for LanguageSettings {
-// const KEY: Option<&'static str> = None;
-
-// type FileContent = Self;
-
-// fn load(default_value: &Self, user_values: &[&Self], _: &AppContext) -> Result<Self> {
-// Self::load_via_json_merge(default_value, user_values)
-// }
-// }
-// }
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use serde_derive::Deserialize;
+ use unindent::Unindent;
+
+ #[gpui2::test]
+ fn test_settings_store_basic(cx: &mut AppContext) {
+ let mut store = SettingsStore::default();
+ store.register_setting::<UserSettings>(cx);
+ store.register_setting::<TurboSetting>(cx);
+ store.register_setting::<MultiKeySettings>(cx);
+ store
+ .set_default_settings(
+ r#"{
+ "turbo": false,
+ "user": {
+ "name": "John Doe",
+ "age": 30,
+ "staff": false
+ }
+ }"#,
+ cx,
+ )
+ .unwrap();
+
+ assert_eq!(store.get::<TurboSetting>(None), &TurboSetting(false));
+ assert_eq!(
+ store.get::<UserSettings>(None),
+ &UserSettings {
+ name: "John Doe".to_string(),
+ age: 30,
+ staff: false,
+ }
+ );
+ assert_eq!(
+ store.get::<MultiKeySettings>(None),
+ &MultiKeySettings {
+ key1: String::new(),
+ key2: String::new(),
+ }
+ );
+
+ store
+ .set_user_settings(
+ r#"{
+ "turbo": true,
+ "user": { "age": 31 },
+ "key1": "a"
+ }"#,
+ cx,
+ )
+ .unwrap();
+
+ assert_eq!(store.get::<TurboSetting>(None), &TurboSetting(true));
+ assert_eq!(
+ store.get::<UserSettings>(None),
+ &UserSettings {
+ name: "John Doe".to_string(),
+ age: 31,
+ staff: false
+ }
+ );
+
+ store
+ .set_local_settings(
+ 1,
+ Path::new("/root1").into(),
+ Some(r#"{ "user": { "staff": true } }"#),
+ cx,
+ )
+ .unwrap();
+ store
+ .set_local_settings(
+ 1,
+ Path::new("/root1/subdir").into(),
+ Some(r#"{ "user": { "name": "Jane Doe" } }"#),
+ cx,
+ )
+ .unwrap();
+
+ store
+ .set_local_settings(
+ 1,
+ Path::new("/root2").into(),
+ Some(r#"{ "user": { "age": 42 }, "key2": "b" }"#),
+ cx,
+ )
+ .unwrap();
+
+ assert_eq!(
+ store.get::<UserSettings>(Some((1, Path::new("/root1/something")))),
+ &UserSettings {
+ name: "John Doe".to_string(),
+ age: 31,
+ staff: true
+ }
+ );
+ assert_eq!(
+ store.get::<UserSettings>(Some((1, Path::new("/root1/subdir/something")))),
+ &UserSettings {
+ name: "Jane Doe".to_string(),
+ age: 31,
+ staff: true
+ }
+ );
+ assert_eq!(
+ store.get::<UserSettings>(Some((1, Path::new("/root2/something")))),
+ &UserSettings {
+ name: "John Doe".to_string(),
+ age: 42,
+ staff: false
+ }
+ );
+ assert_eq!(
+ store.get::<MultiKeySettings>(Some((1, Path::new("/root2/something")))),
+ &MultiKeySettings {
+ key1: "a".to_string(),
+ key2: "b".to_string(),
+ }
+ );
+ }
+
+ #[gpui2::test]
+ fn test_setting_store_assign_json_before_register(cx: &mut AppContext) {
+ let mut store = SettingsStore::default();
+ store
+ .set_default_settings(
+ r#"{
+ "turbo": true,
+ "user": {
+ "name": "John Doe",
+ "age": 30,
+ "staff": false
+ },
+ "key1": "x"
+ }"#,
+ cx,
+ )
+ .unwrap();
+ store
+ .set_user_settings(r#"{ "turbo": false }"#, cx)
+ .unwrap();
+ store.register_setting::<UserSettings>(cx);
+ store.register_setting::<TurboSetting>(cx);
+
+ assert_eq!(store.get::<TurboSetting>(None), &TurboSetting(false));
+ assert_eq!(
+ store.get::<UserSettings>(None),
+ &UserSettings {
+ name: "John Doe".to_string(),
+ age: 30,
+ staff: false,
+ }
+ );
+
+ store.register_setting::<MultiKeySettings>(cx);
+ assert_eq!(
+ store.get::<MultiKeySettings>(None),
+ &MultiKeySettings {
+ key1: "x".into(),
+ key2: String::new(),
+ }
+ );
+ }
+
+ #[gpui2::test]
+ fn test_setting_store_update(cx: &mut AppContext) {
+ let mut store = SettingsStore::default();
+ store.register_setting::<MultiKeySettings>(cx);
+ store.register_setting::<UserSettings>(cx);
+ store.register_setting::<LanguageSettings>(cx);
+
+ // entries added and updated
+ check_settings_update::<LanguageSettings>(
+ &mut store,
+ r#"{
+ "languages": {
+ "JSON": {
+ "language_setting_1": true
+ }
+ }
+ }"#
+ .unindent(),
+ |settings| {
+ settings
+ .languages
+ .get_mut("JSON")
+ .unwrap()
+ .language_setting_1 = Some(false);
+ settings.languages.insert(
+ "Rust".into(),
+ LanguageSettingEntry {
+ language_setting_2: Some(true),
+ ..Default::default()
+ },
+ );
+ },
+ r#"{
+ "languages": {
+ "Rust": {
+ "language_setting_2": true
+ },
+ "JSON": {
+ "language_setting_1": false
+ }
+ }
+ }"#
+ .unindent(),
+ cx,
+ );
+
+ // weird formatting
+ check_settings_update::<UserSettings>(
+ &mut store,
+ r#"{
+ "user": { "age": 36, "name": "Max", "staff": true }
+ }"#
+ .unindent(),
+ |settings| settings.age = Some(37),
+ r#"{
+ "user": { "age": 37, "name": "Max", "staff": true }
+ }"#
+ .unindent(),
+ cx,
+ );
+
+ // single-line formatting, other keys
+ check_settings_update::<MultiKeySettings>(
+ &mut store,
+ r#"{ "one": 1, "two": 2 }"#.unindent(),
+ |settings| settings.key1 = Some("x".into()),
+ r#"{ "key1": "x", "one": 1, "two": 2 }"#.unindent(),
+ cx,
+ );
+
+ // empty object
+ check_settings_update::<UserSettings>(
+ &mut store,
+ r#"{
+ "user": {}
+ }"#
+ .unindent(),
+ |settings| settings.age = Some(37),
+ r#"{
+ "user": {
+ "age": 37
+ }
+ }"#
+ .unindent(),
+ cx,
+ );
+
+ // no content
+ check_settings_update::<UserSettings>(
+ &mut store,
+ r#""#.unindent(),
+ |settings| settings.age = Some(37),
+ r#"{
+ "user": {
+ "age": 37
+ }
+ }
+ "#
+ .unindent(),
+ cx,
+ );
+
+ check_settings_update::<UserSettings>(
+ &mut store,
+ r#"{
+ }
+ "#
+ .unindent(),
+ |settings| settings.age = Some(37),
+ r#"{
+ "user": {
+ "age": 37
+ }
+ }
+ "#
+ .unindent(),
+ cx,
+ );
+ }
+
+ fn check_settings_update<T: Settings>(
+ store: &mut SettingsStore,
+ old_json: String,
+ update: fn(&mut T::FileContent),
+ expected_new_json: String,
+ cx: &mut AppContext,
+ ) {
+ store.set_user_settings(&old_json, cx).ok();
+ let edits = store.edits_for_update::<T>(&old_json, update);
+ let mut new_json = old_json;
+ for (range, replacement) in edits.into_iter() {
+ new_json.replace_range(range, &replacement);
+ }
+ pretty_assertions::assert_eq!(new_json, expected_new_json);
+ }
+
+ #[derive(Debug, PartialEq, Deserialize)]
+ struct UserSettings {
+ name: String,
+ age: u32,
+ staff: bool,
+ }
+
+ #[derive(Default, Clone, Serialize, Deserialize, JsonSchema)]
+ struct UserSettingsJson {
+ name: Option<String>,
+ age: Option<u32>,
+ staff: Option<bool>,
+ }
+
+ impl Settings for UserSettings {
+ const KEY: Option<&'static str> = Some("user");
+ type FileContent = UserSettingsJson;
+
+ fn load(
+ default_value: &UserSettingsJson,
+ user_values: &[&UserSettingsJson],
+ _: &mut AppContext,
+ ) -> Result<Self> {
+ Self::load_via_json_merge(default_value, user_values)
+ }
+ }
+
+ #[derive(Debug, Deserialize, PartialEq)]
+ struct TurboSetting(bool);
+
+ impl Settings for TurboSetting {
+ const KEY: Option<&'static str> = Some("turbo");
+ type FileContent = Option<bool>;
+
+ fn load(
+ default_value: &Option<bool>,
+ user_values: &[&Option<bool>],
+ _: &mut AppContext,
+ ) -> Result<Self> {
+ Self::load_via_json_merge(default_value, user_values)
+ }
+ }
+
+ #[derive(Clone, Debug, PartialEq, Deserialize)]
+ struct MultiKeySettings {
+ #[serde(default)]
+ key1: String,
+ #[serde(default)]
+ key2: String,
+ }
+
+ #[derive(Clone, Default, Serialize, Deserialize, JsonSchema)]
+ struct MultiKeySettingsJson {
+ key1: Option<String>,
+ key2: Option<String>,
+ }
+
+ impl Settings for MultiKeySettings {
+ const KEY: Option<&'static str> = None;
+
+ type FileContent = MultiKeySettingsJson;
+
+ fn load(
+ default_value: &MultiKeySettingsJson,
+ user_values: &[&MultiKeySettingsJson],
+ _: &mut AppContext,
+ ) -> Result<Self> {
+ Self::load_via_json_merge(default_value, user_values)
+ }
+ }
+
+ #[derive(Debug, Deserialize)]
+ struct JournalSettings {
+ pub path: String,
+ pub hour_format: HourFormat,
+ }
+
+ #[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
+ #[serde(rename_all = "snake_case")]
+ enum HourFormat {
+ Hour12,
+ Hour24,
+ }
+
+ #[derive(Clone, Default, Debug, Serialize, Deserialize, JsonSchema)]
+ struct JournalSettingsJson {
+ pub path: Option<String>,
+ pub hour_format: Option<HourFormat>,
+ }
+
+ impl Settings for JournalSettings {
+ const KEY: Option<&'static str> = Some("journal");
+
+ type FileContent = JournalSettingsJson;
+
+ fn load(
+ default_value: &JournalSettingsJson,
+ user_values: &[&JournalSettingsJson],
+ _: &mut AppContext,
+ ) -> Result<Self> {
+ Self::load_via_json_merge(default_value, user_values)
+ }
+ }
+
+ #[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]
+ struct LanguageSettings {
+ #[serde(default)]
+ languages: HashMap<String, LanguageSettingEntry>,
+ }
+
+ #[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]
+ struct LanguageSettingEntry {
+ language_setting_1: Option<bool>,
+ language_setting_2: Option<bool>,
+ }
+
+ impl Settings for LanguageSettings {
+ const KEY: Option<&'static str> = None;
+
+ type FileContent = Self;
+
+ fn load(default_value: &Self, user_values: &[&Self], _: &mut AppContext) -> Result<Self> {
+ Self::load_via_json_merge(default_value, user_values)
+ }
+ }
+}