Migrate `features.edit_prediction_provider` to `edit_predictions.provider` (#48224) (cherry-pick to preview) (#48232)

zed-zippy[bot] and Ben Kunkle created

Cherry-pick of #48224 to preview

----
Closes #ISSUE

Release Notes:

- N/A *or* Added/Fixed/Improved ...

Co-authored-by: Ben Kunkle <ben@zed.dev>

Change summary

assets/settings/default.json                            |   7 
crates/agent_ui/src/agent_ui.rs                         |   8 
crates/edit_prediction/src/edit_prediction.rs           |   4 
crates/edit_prediction/src/onboarding_modal.rs          |   4 
crates/edit_prediction_ui/src/edit_prediction_button.rs |   8 
crates/language/src/language_settings.rs                |   4 
crates/migrator/src/migrations.rs                       |   6 
crates/migrator/src/migrations/m_2026_02_02/settings.rs |  40 +++
crates/migrator/src/migrator.rs                         | 134 ++++++++++
crates/settings/src/vscode_import.rs                    |   1 
crates/settings_content/src/language.rs                 |  14 
11 files changed, 186 insertions(+), 44 deletions(-)

Detailed changes

assets/settings/default.json 🔗

@@ -26,11 +26,6 @@
   // 5. "SublimeText"
   // 6. "TextMate"
   "base_keymap": "VSCode",
-  // Features that can be globally enabled or disabled
-  "features": {
-    // Which edit prediction provider to use.
-    "edit_prediction_provider": "zed",
-  },
   // The name of a font to use for rendering text in the editor
   // ".ZedMono" currently aliases to Lilex
   // but this may change in the future.
@@ -1501,6 +1496,8 @@
   //      "load_direnv": "disabled"
   "load_direnv": "direct",
   "edit_predictions": {
+    // Which edit prediction provider to use.
+    "provider": "zed",
     // A list of globs representing files that edit predictions should be disabled for.
     // There's a sensible default list of globs already included.
     // Any addition to this list will be merged with the default list.

crates/agent_ui/src/agent_ui.rs 🔗

@@ -590,9 +590,9 @@ mod tests {
                 store.update_user_settings(cx, |s| {
                     s.project
                         .all_languages
-                        .features
+                        .edit_predictions
                         .get_or_insert(Default::default())
-                        .edit_prediction_provider = Some(EditPredictionProvider::Copilot);
+                        .provider = Some(EditPredictionProvider::Copilot);
                 });
             });
             update_command_palette_filter(cx);
@@ -612,9 +612,9 @@ mod tests {
                 store.update_user_settings(cx, |s| {
                     s.project
                         .all_languages
-                        .features
+                        .edit_predictions
                         .get_or_insert(Default::default())
-                        .edit_prediction_provider = Some(EditPredictionProvider::None);
+                        .provider = Some(EditPredictionProvider::None);
                 });
             });
             update_command_palette_filter(cx);

crates/edit_prediction/src/edit_prediction.rs 🔗

@@ -2333,9 +2333,9 @@ pub fn init(cx: &mut App) {
                 settings
                     .project
                     .all_languages
-                    .features
+                    .edit_predictions
                     .get_or_insert_default()
-                    .edit_prediction_provider = Some(EditPredictionProvider::None)
+                    .provider = Some(EditPredictionProvider::None)
             });
         });
         fn copilot_for_project(project: &Entity<Project>, cx: &mut App) -> Option<Entity<Copilot>> {

crates/edit_prediction/src/onboarding_modal.rs 🔗

@@ -36,9 +36,9 @@ pub(crate) fn set_edit_prediction_provider(provider: EditPredictionProvider, cx:
         settings
             .project
             .all_languages
-            .features
+            .edit_predictions
             .get_or_insert(Default::default())
-            .edit_prediction_provider = Some(provider);
+            .provider = Some(provider);
     });
 }
 

crates/edit_prediction_ui/src/edit_prediction_button.rs 🔗

@@ -1276,9 +1276,9 @@ pub fn set_completion_provider(fs: Arc<dyn Fs>, cx: &mut App, provider: EditPred
         settings
             .project
             .all_languages
-            .features
+            .edit_predictions
             .get_or_insert_default()
-            .edit_prediction_provider = Some(provider);
+            .provider = Some(provider);
     });
 }
 
@@ -1359,9 +1359,9 @@ fn hide_copilot(fs: Arc<dyn Fs>, cx: &mut App) {
         settings
             .project
             .all_languages
-            .features
+            .edit_predictions
             .get_or_insert(Default::default())
-            .edit_prediction_provider = Some(EditPredictionProvider::None);
+            .provider = Some(EditPredictionProvider::None);
     });
 }
 

crates/language/src/language_settings.rs 🔗

@@ -645,9 +645,9 @@ impl settings::Settings for AllLanguageSettings {
         }
 
         let edit_prediction_provider = all_languages
-            .features
+            .edit_predictions
             .as_ref()
-            .and_then(|f| f.edit_prediction_provider);
+            .and_then(|ep| ep.provider);
 
         let edit_predictions = all_languages.edit_predictions.clone().unwrap();
         let edit_predictions_mode = edit_predictions.mode.unwrap();

crates/migrator/src/migrations.rs 🔗

@@ -165,3 +165,9 @@ pub(crate) mod m_2025_12_15 {
 
     pub(crate) use settings::SETTINGS_PATTERNS;
 }
+
+pub(crate) mod m_2026_02_02 {
+    mod settings;
+
+    pub(crate) use settings::move_edit_prediction_provider_to_edit_predictions;
+}

crates/migrator/src/migrations/m_2026_02_02/settings.rs 🔗

@@ -0,0 +1,40 @@
+use anyhow::Result;
+use serde_json::Value;
+
+pub fn move_edit_prediction_provider_to_edit_predictions(value: &mut Value) -> Result<()> {
+    let Some(obj) = value.as_object_mut() else {
+        return Ok(());
+    };
+
+    let Some(features) = obj.get_mut("features") else {
+        return Ok(());
+    };
+
+    let Some(features_obj) = features.as_object_mut() else {
+        return Ok(());
+    };
+
+    let Some(provider) = features_obj.remove("edit_prediction_provider") else {
+        return Ok(());
+    };
+
+    let features_is_empty = features_obj.is_empty();
+
+    if features_is_empty {
+        obj.remove("features");
+    }
+
+    let edit_predictions = obj
+        .entry("edit_predictions")
+        .or_insert_with(|| Value::Object(Default::default()));
+
+    let Some(edit_predictions_obj) = edit_predictions.as_object_mut() else {
+        anyhow::bail!("Expected edit_predictions to be an object");
+    };
+
+    if !edit_predictions_obj.contains_key("provider") {
+        edit_predictions_obj.insert("provider".to_string(), provider);
+    }
+
+    Ok(())
+}

crates/migrator/src/migrator.rs 🔗

@@ -232,6 +232,9 @@ pub fn migrate_settings(text: &str) -> Result<Option<String>> {
             migrations::m_2025_12_15::SETTINGS_PATTERNS,
             &SETTINGS_QUERY_2025_12_15,
         ),
+        MigrationType::Json(
+            migrations::m_2026_02_02::move_edit_prediction_provider_to_edit_predictions,
+        ),
     ];
     run_migrations(text, migrations)
 }
@@ -650,21 +653,23 @@ mod tests {
     #[test]
     fn test_nested_string_replace_for_settings() {
         assert_migrate_settings(
-            r#"
-                {
-                    "features": {
-                        "inline_completion_provider": "zed"
-                    },
-                }
-            "#,
+            &r#"
+            {
+                "features": {
+                    "inline_completion_provider": "zed"
+                },
+            }
+            "#
+            .unindent(),
             Some(
-                r#"
+                &r#"
                 {
-                    "features": {
-                        "edit_prediction_provider": "zed"
-                    },
+                    "edit_predictions": {
+                        "provider": "zed"
+                    }
                 }
-            "#,
+                "#
+                .unindent(),
             ),
         )
     }
@@ -2360,4 +2365,109 @@ mod tests {
             ),
         );
     }
+
+    #[test]
+    fn test_move_edit_prediction_provider_to_edit_predictions() {
+        assert_migrate_settings_with_migrations(
+            &[MigrationType::Json(
+                migrations::m_2026_02_02::move_edit_prediction_provider_to_edit_predictions,
+            )],
+            &r#"{ }"#.unindent(),
+            None,
+        );
+
+        assert_migrate_settings_with_migrations(
+            &[MigrationType::Json(
+                migrations::m_2026_02_02::move_edit_prediction_provider_to_edit_predictions,
+            )],
+            &r#"
+            {
+                "features": {
+                    "edit_prediction_provider": "copilot"
+                }
+            }
+            "#
+            .unindent(),
+            Some(
+                &r#"
+                {
+                    "edit_predictions": {
+                        "provider": "copilot"
+                    }
+                }
+                "#
+                .unindent(),
+            ),
+        );
+
+        assert_migrate_settings_with_migrations(
+            &[MigrationType::Json(
+                migrations::m_2026_02_02::move_edit_prediction_provider_to_edit_predictions,
+            )],
+            &r#"
+            {
+                "features": {
+                    "edit_prediction_provider": "zed"
+                },
+                "edit_predictions": {
+                    "mode": "eager"
+                }
+            }
+            "#
+            .unindent(),
+            Some(
+                &r#"
+                {
+                    "edit_predictions": {
+                        "provider": "zed",
+                        "mode": "eager"
+                    }
+                }
+                "#
+                .unindent(),
+            ),
+        );
+
+        assert_migrate_settings_with_migrations(
+            &[MigrationType::Json(
+                migrations::m_2026_02_02::move_edit_prediction_provider_to_edit_predictions,
+            )],
+            &r#"
+            {
+                "features": {
+                    "edit_prediction_provider": "supermaven"
+                },
+                "edit_predictions": {
+                    "provider": "copilot"
+                }
+            }
+            "#
+            .unindent(),
+            Some(
+                &r#"
+                {
+                    "edit_predictions": {
+                        "provider": "copilot"
+                    }
+                }
+                "#
+                .unindent(),
+            ),
+        );
+
+        assert_migrate_settings_with_migrations(
+            &[MigrationType::Json(
+                migrations::m_2026_02_02::move_edit_prediction_provider_to_edit_predictions,
+            )],
+            &r#"
+            {
+                "edit_predictions": {
+                    "provider": "zed"
+                }
+            }
+            "#
+            .unindent(),
+            None,
+        );
+    }
 }

crates/settings/src/vscode_import.rs 🔗

@@ -392,7 +392,6 @@ impl VsCodeSettings {
     fn project_settings_content(&self) -> ProjectSettingsContent {
         ProjectSettingsContent {
             all_languages: AllLanguageSettingsContent {
-                features: None,
                 edit_predictions: self.edit_predictions_settings_content(),
                 defaults: self.default_language_settings_content(),
                 languages: Default::default(),

crates/settings_content/src/language.rs 🔗

@@ -34,8 +34,6 @@ pub struct ModifiersContent {
 #[with_fallible_options]
 #[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize, JsonSchema)]
 pub struct AllLanguageSettingsContent {
-    /// The settings for enabling/disabling features.
-    pub features: Option<FeaturesContent>,
     /// The edit prediction settings.
     pub edit_predictions: Option<EditPredictionSettingsContent>,
     /// The default language settings.
@@ -52,7 +50,6 @@ pub struct AllLanguageSettingsContent {
 impl merge_from::MergeFrom for AllLanguageSettingsContent {
     fn merge_from(&mut self, other: &Self) {
         self.file_types.merge_from(&other.file_types);
-        self.features.merge_from(&other.features);
         self.edit_predictions.merge_from(&other.edit_predictions);
 
         // A user's global settings override the default global settings and
@@ -77,15 +74,6 @@ impl merge_from::MergeFrom for AllLanguageSettingsContent {
     }
 }
 
-/// The settings for enabling/disabling features.
-#[with_fallible_options]
-#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize, JsonSchema, MergeFrom)]
-#[serde(rename_all = "snake_case")]
-pub struct FeaturesContent {
-    /// Determines which edit prediction provider to use.
-    pub edit_prediction_provider: Option<EditPredictionProvider>,
-}
-
 /// The provider that supplies edit predictions.
 #[derive(Copy, Clone, Debug, Default, Eq, PartialEq, Serialize, JsonSchema, MergeFrom)]
 #[serde(rename_all = "snake_case")]
@@ -192,6 +180,8 @@ impl EditPredictionProvider {
 #[with_fallible_options]
 #[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema, MergeFrom, PartialEq)]
 pub struct EditPredictionSettingsContent {
+    /// Determines which edit prediction provider to use.
+    pub provider: Option<EditPredictionProvider>,
     /// A list of globs representing files that edit predictions should be disabled for.
     /// This list adds to a pre-existing, sensible default set of globs.
     /// Any additional ones you add are combined with them.