JSON based migrations (#39398)
Closes #ISSUE Adds the ability to create settings and keymap migrations by mutating `serde_json::Value`s instead of using tree-sitter queries. This (hopefully) will make complicated migrations far simpler to implement. Release Notes: - N/A *or* Added/Fixed/Improved ... --------- Co-authored-by: Smit <heysmitbarmase@gmail.com> Co-authored-by: Smit <smit@zed.dev>
This commit is contained in:
Generated
+3
-1
@@ -9688,6 +9688,9 @@ dependencies = [
|
||||
"convert_case 0.8.0",
|
||||
"log",
|
||||
"pretty_assertions",
|
||||
"serde_json",
|
||||
"serde_json_lenient",
|
||||
"settings",
|
||||
"streaming-iterator",
|
||||
"tree-sitter",
|
||||
"tree-sitter-json",
|
||||
@@ -19686,7 +19689,6 @@ dependencies = [
|
||||
"core-foundation 0.9.4",
|
||||
"core-foundation-sys",
|
||||
"cranelift-codegen",
|
||||
"crc32fast",
|
||||
"crossbeam-channel",
|
||||
"crossbeam-epoch",
|
||||
"crossbeam-utils",
|
||||
|
||||
@@ -21,6 +21,9 @@ streaming-iterator.workspace = true
|
||||
tree-sitter-json.workspace = true
|
||||
tree-sitter.workspace = true
|
||||
workspace-hack.workspace = true
|
||||
serde_json_lenient.workspace = true
|
||||
serde_json.workspace = true
|
||||
settings.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
pretty_assertions.workspace = true
|
||||
|
||||
@@ -65,14 +65,40 @@ fn migrate(text: &str, patterns: MigrationPatterns, query: &Query) -> Result<Opt
|
||||
}
|
||||
}
|
||||
|
||||
fn run_migrations(
|
||||
text: &str,
|
||||
migrations: &[(MigrationPatterns, &Query)],
|
||||
) -> Result<Option<String>> {
|
||||
fn run_migrations(text: &str, migrations: &[MigrationType]) -> Result<Option<String>> {
|
||||
let mut current_text = text.to_string();
|
||||
let mut result: Option<String> = None;
|
||||
for (patterns, query) in migrations.iter() {
|
||||
if let Some(migrated_text) = migrate(¤t_text, patterns, query)? {
|
||||
for migration in migrations.iter() {
|
||||
let migrated_text = match migration {
|
||||
MigrationType::TreeSitter(patterns, query) => migrate(¤t_text, patterns, query)?,
|
||||
MigrationType::Json(callback) => {
|
||||
let old_content: serde_json_lenient::Value =
|
||||
settings::parse_json_with_comments(¤t_text)?;
|
||||
let old_value = serde_json::to_value(&old_content).unwrap();
|
||||
let mut new_value = old_value.clone();
|
||||
callback(&mut new_value);
|
||||
if new_value != old_value {
|
||||
let mut current = current_text.clone();
|
||||
let mut edits = vec![];
|
||||
settings::update_value_in_json_text(
|
||||
&mut current,
|
||||
&mut vec![],
|
||||
2,
|
||||
&old_value,
|
||||
&new_value,
|
||||
&mut edits,
|
||||
);
|
||||
let mut migrated_text = current_text.clone();
|
||||
for (range, replacement) in edits.into_iter() {
|
||||
migrated_text.replace_range(range, &replacement);
|
||||
}
|
||||
Some(migrated_text)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
};
|
||||
if let Some(migrated_text) = migrated_text {
|
||||
current_text = migrated_text.clone();
|
||||
result = Some(migrated_text);
|
||||
}
|
||||
@@ -81,24 +107,24 @@ fn run_migrations(
|
||||
}
|
||||
|
||||
pub fn migrate_keymap(text: &str) -> Result<Option<String>> {
|
||||
let migrations: &[(MigrationPatterns, &Query)] = &[
|
||||
(
|
||||
let migrations: &[MigrationType] = &[
|
||||
MigrationType::TreeSitter(
|
||||
migrations::m_2025_01_29::KEYMAP_PATTERNS,
|
||||
&KEYMAP_QUERY_2025_01_29,
|
||||
),
|
||||
(
|
||||
MigrationType::TreeSitter(
|
||||
migrations::m_2025_01_30::KEYMAP_PATTERNS,
|
||||
&KEYMAP_QUERY_2025_01_30,
|
||||
),
|
||||
(
|
||||
MigrationType::TreeSitter(
|
||||
migrations::m_2025_03_03::KEYMAP_PATTERNS,
|
||||
&KEYMAP_QUERY_2025_03_03,
|
||||
),
|
||||
(
|
||||
MigrationType::TreeSitter(
|
||||
migrations::m_2025_03_06::KEYMAP_PATTERNS,
|
||||
&KEYMAP_QUERY_2025_03_06,
|
||||
),
|
||||
(
|
||||
MigrationType::TreeSitter(
|
||||
migrations::m_2025_04_15::KEYMAP_PATTERNS,
|
||||
&KEYMAP_QUERY_2025_04_15,
|
||||
),
|
||||
@@ -106,65 +132,71 @@ pub fn migrate_keymap(text: &str) -> Result<Option<String>> {
|
||||
run_migrations(text, migrations)
|
||||
}
|
||||
|
||||
enum MigrationType<'a> {
|
||||
TreeSitter(MigrationPatterns, &'a Query),
|
||||
#[allow(unused)]
|
||||
Json(fn(&mut serde_json::Value)),
|
||||
}
|
||||
|
||||
pub fn migrate_settings(text: &str) -> Result<Option<String>> {
|
||||
let migrations: &[(MigrationPatterns, &Query)] = &[
|
||||
(
|
||||
let migrations: &[MigrationType] = &[
|
||||
MigrationType::TreeSitter(
|
||||
migrations::m_2025_01_02::SETTINGS_PATTERNS,
|
||||
&SETTINGS_QUERY_2025_01_02,
|
||||
),
|
||||
(
|
||||
MigrationType::TreeSitter(
|
||||
migrations::m_2025_01_29::SETTINGS_PATTERNS,
|
||||
&SETTINGS_QUERY_2025_01_29,
|
||||
),
|
||||
(
|
||||
MigrationType::TreeSitter(
|
||||
migrations::m_2025_01_30::SETTINGS_PATTERNS,
|
||||
&SETTINGS_QUERY_2025_01_30,
|
||||
),
|
||||
(
|
||||
MigrationType::TreeSitter(
|
||||
migrations::m_2025_03_29::SETTINGS_PATTERNS,
|
||||
&SETTINGS_QUERY_2025_03_29,
|
||||
),
|
||||
(
|
||||
MigrationType::TreeSitter(
|
||||
migrations::m_2025_04_15::SETTINGS_PATTERNS,
|
||||
&SETTINGS_QUERY_2025_04_15,
|
||||
),
|
||||
(
|
||||
MigrationType::TreeSitter(
|
||||
migrations::m_2025_04_21::SETTINGS_PATTERNS,
|
||||
&SETTINGS_QUERY_2025_04_21,
|
||||
),
|
||||
(
|
||||
MigrationType::TreeSitter(
|
||||
migrations::m_2025_04_23::SETTINGS_PATTERNS,
|
||||
&SETTINGS_QUERY_2025_04_23,
|
||||
),
|
||||
(
|
||||
MigrationType::TreeSitter(
|
||||
migrations::m_2025_05_05::SETTINGS_PATTERNS,
|
||||
&SETTINGS_QUERY_2025_05_05,
|
||||
),
|
||||
(
|
||||
MigrationType::TreeSitter(
|
||||
migrations::m_2025_05_08::SETTINGS_PATTERNS,
|
||||
&SETTINGS_QUERY_2025_05_08,
|
||||
),
|
||||
(
|
||||
MigrationType::TreeSitter(
|
||||
migrations::m_2025_05_29::SETTINGS_PATTERNS,
|
||||
&SETTINGS_QUERY_2025_05_29,
|
||||
),
|
||||
(
|
||||
MigrationType::TreeSitter(
|
||||
migrations::m_2025_06_16::SETTINGS_PATTERNS,
|
||||
&SETTINGS_QUERY_2025_06_16,
|
||||
),
|
||||
(
|
||||
MigrationType::TreeSitter(
|
||||
migrations::m_2025_06_25::SETTINGS_PATTERNS,
|
||||
&SETTINGS_QUERY_2025_06_25,
|
||||
),
|
||||
(
|
||||
MigrationType::TreeSitter(
|
||||
migrations::m_2025_06_27::SETTINGS_PATTERNS,
|
||||
&SETTINGS_QUERY_2025_06_27,
|
||||
),
|
||||
(
|
||||
MigrationType::TreeSitter(
|
||||
migrations::m_2025_07_08::SETTINGS_PATTERNS,
|
||||
&SETTINGS_QUERY_2025_07_08,
|
||||
),
|
||||
(
|
||||
MigrationType::TreeSitter(
|
||||
migrations::m_2025_10_01::SETTINGS_PATTERNS,
|
||||
&SETTINGS_QUERY_2025_10_01,
|
||||
),
|
||||
@@ -323,7 +355,7 @@ mod tests {
|
||||
}
|
||||
|
||||
fn assert_migrate_settings_with_migrations(
|
||||
migrations: &[(MigrationPatterns, &Query)],
|
||||
migrations: &[MigrationType],
|
||||
input: &str,
|
||||
output: Option<&str>,
|
||||
) {
|
||||
@@ -919,7 +951,7 @@ mod tests {
|
||||
#[test]
|
||||
fn test_mcp_settings_migration() {
|
||||
assert_migrate_settings_with_migrations(
|
||||
&[(
|
||||
&[MigrationType::TreeSitter(
|
||||
migrations::m_2025_06_16::SETTINGS_PATTERNS,
|
||||
&SETTINGS_QUERY_2025_06_16,
|
||||
)],
|
||||
@@ -1108,7 +1140,7 @@ mod tests {
|
||||
}
|
||||
}"#;
|
||||
assert_migrate_settings_with_migrations(
|
||||
&[(
|
||||
&[MigrationType::TreeSitter(
|
||||
migrations::m_2025_06_16::SETTINGS_PATTERNS,
|
||||
&SETTINGS_QUERY_2025_06_16,
|
||||
)],
|
||||
|
||||
+43
-20
@@ -1231,32 +1231,55 @@ pub fn handle_settings_file_changes(
|
||||
MigrationNotification::set_global(cx.new(|_| MigrationNotification), cx);
|
||||
|
||||
// Helper function to process settings content
|
||||
let process_settings =
|
||||
move |content: String, is_user: bool, store: &mut SettingsStore, cx: &mut App| -> bool {
|
||||
// Apply migrations to both user and global settings
|
||||
let (processed_content, content_migrated) =
|
||||
if let Ok(Some(migrated_content)) = migrate_settings(&content) {
|
||||
let process_settings = move |content: String,
|
||||
is_user: bool,
|
||||
store: &mut SettingsStore,
|
||||
cx: &mut App|
|
||||
-> bool {
|
||||
let id = NotificationId::Named("failed-to-migrate-settings".into());
|
||||
// Apply migrations to both user and global settings
|
||||
let (processed_content, content_migrated) = match migrate_settings(&content) {
|
||||
Ok(result) => {
|
||||
dismiss_app_notification(&id, cx);
|
||||
if let Some(migrated_content) = result {
|
||||
(migrated_content, true)
|
||||
} else {
|
||||
(content, false)
|
||||
};
|
||||
|
||||
let result = if is_user {
|
||||
store.set_user_settings(&processed_content, cx)
|
||||
} else {
|
||||
store.set_global_settings(&processed_content, cx)
|
||||
};
|
||||
|
||||
if let Err(err) = &result {
|
||||
let settings_type = if is_user { "user" } else { "global" };
|
||||
log::error!("Failed to load {} settings: {err}", settings_type);
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
show_app_notification(id, cx, move |cx| {
|
||||
cx.new(|cx| {
|
||||
MessageNotification::new(format!("Failed to migrate settings\n{err}"), cx)
|
||||
.primary_message("Open Settings File")
|
||||
.primary_icon(IconName::Settings)
|
||||
.primary_on_click(|window, cx| {
|
||||
window.dispatch_action(zed_actions::OpenSettings.boxed_clone(), cx);
|
||||
cx.emit(DismissEvent);
|
||||
})
|
||||
})
|
||||
});
|
||||
// notify user here
|
||||
(content, false)
|
||||
}
|
||||
|
||||
settings_changed(result.err(), cx);
|
||||
|
||||
content_migrated
|
||||
};
|
||||
|
||||
let result = if is_user {
|
||||
store.set_user_settings(&processed_content, cx)
|
||||
} else {
|
||||
store.set_global_settings(&processed_content, cx)
|
||||
};
|
||||
|
||||
if let Err(err) = &result {
|
||||
let settings_type = if is_user { "user" } else { "global" };
|
||||
log::error!("Failed to load {} settings: {err}", settings_type);
|
||||
}
|
||||
|
||||
settings_changed(result.err(), cx);
|
||||
|
||||
content_migrated
|
||||
};
|
||||
|
||||
// Initial load of both settings files
|
||||
let global_content = cx
|
||||
.background_executor()
|
||||
|
||||
@@ -46,7 +46,6 @@ clap = { version = "4", features = ["cargo", "derive", "string", "wrap_help"] }
|
||||
clap_builder = { version = "4", default-features = false, features = ["cargo", "color", "std", "string", "suggestions", "usage", "wrap_help"] }
|
||||
concurrent-queue = { version = "2" }
|
||||
cranelift-codegen = { version = "0.116", default-features = false, features = ["host-arch", "incremental-cache", "std", "timing", "unwind"] }
|
||||
crc32fast = { version = "1" }
|
||||
crossbeam-channel = { version = "0.5" }
|
||||
crossbeam-epoch = { version = "0.9" }
|
||||
crossbeam-utils = { version = "0.8" }
|
||||
@@ -177,7 +176,6 @@ clap = { version = "4", features = ["cargo", "derive", "string", "wrap_help"] }
|
||||
clap_builder = { version = "4", default-features = false, features = ["cargo", "color", "std", "string", "suggestions", "usage", "wrap_help"] }
|
||||
concurrent-queue = { version = "2" }
|
||||
cranelift-codegen = { version = "0.116", default-features = false, features = ["host-arch", "incremental-cache", "std", "timing", "unwind"] }
|
||||
crc32fast = { version = "1" }
|
||||
crossbeam-channel = { version = "0.5" }
|
||||
crossbeam-epoch = { version = "0.9" }
|
||||
crossbeam-utils = { version = "0.8" }
|
||||
|
||||
Reference in New Issue
Block a user