Detailed changes
@@ -1101,7 +1101,7 @@
// Removes any lines containing only whitespace at the end of the file and
// ensures just one newline at the end.
"ensure_final_newline_on_save": true,
- // Whether or not to perform a buffer format before saving: [on, off, prettier, language_server]
+ // Whether or not to perform a buffer format before saving: [on, off]
// Keep in mind, if the autosave with delay is enabled, format_on_save will be ignored
"format_on_save": "on",
// How to perform a buffer format. This setting can take 4 values:
@@ -1515,7 +1515,6 @@
// A value of 45 preserves colorful themes while ensuring legibility.
"minimum_contrast": 45
},
- "code_actions_on_format": {},
// Settings related to running tasks.
"tasks": {
"variables": {},
@@ -1685,9 +1684,7 @@
"preferred_line_length": 72
},
"Go": {
- "code_actions_on_format": {
- "source.organizeImports": true
- },
+ "formatter": [{ "code_action": "source.organizeImports" }, { "language_server": {} }],
"debuggers": ["Delve"]
},
"GraphQL": {
@@ -142,8 +142,6 @@ pub struct LanguageSettings {
pub auto_indent_on_paste: bool,
/// Controls how the editor handles the autoclosed characters.
pub always_treat_brackets_as_autoclosed: bool,
- /// Which code actions to run on save
- pub code_actions_on_format: HashMap<String, bool>,
/// Whether to perform linked edits
pub linked_edits: bool,
/// Task configuration for this language.
@@ -568,7 +566,6 @@ impl settings::Settings for AllLanguageSettings {
always_treat_brackets_as_autoclosed: settings
.always_treat_brackets_as_autoclosed
.unwrap(),
- code_actions_on_format: settings.code_actions_on_format.unwrap(),
linked_edits: settings.linked_edits.unwrap(),
tasks: LanguageTaskSettings {
variables: tasks.variables.unwrap_or_default(),
@@ -117,3 +117,9 @@ pub(crate) mod m_2025_10_03 {
pub(crate) use settings::SETTINGS_PATTERNS;
}
+
+pub(crate) mod m_2025_10_10 {
+ mod settings;
+
+ pub(crate) use settings::remove_code_actions_on_format;
+}
@@ -0,0 +1,70 @@
+use anyhow::Result;
+use serde_json::Value;
+
+pub fn remove_code_actions_on_format(value: &mut Value) -> Result<()> {
+ remove_code_actions_on_format_inner(value, &[])?;
+ let languages = value
+ .as_object_mut()
+ .and_then(|obj| obj.get_mut("languages"))
+ .and_then(|languages| languages.as_object_mut());
+ if let Some(languages) = languages {
+ for (language_name, language) in languages.iter_mut() {
+ let path = vec!["languages", language_name];
+ remove_code_actions_on_format_inner(language, &path)?;
+ }
+ }
+ Ok(())
+}
+
+fn remove_code_actions_on_format_inner(value: &mut Value, path: &[&str]) -> Result<()> {
+ let Some(obj) = value.as_object_mut() else {
+ return Ok(());
+ };
+ let Some(code_actions_on_format) = obj.get("code_actions_on_format").cloned() else {
+ return Ok(());
+ };
+
+ fn fmt_path(path: &[&str], key: &str) -> String {
+ let mut path = path.to_vec();
+ path.push(key);
+ path.join(".")
+ }
+
+ anyhow::ensure!(
+ code_actions_on_format.is_object(),
+ r#"The `code_actions_on_format` setting is deprecated, but it is in an invalid state and cannot be migrated at {}. Please ensure the code_actions_on_format setting is a Map<String, bool>"#,
+ fmt_path(path, "code_actions_on_format"),
+ );
+
+ let code_actions_map = code_actions_on_format.as_object().unwrap();
+ let mut code_actions = vec![];
+ for (code_action, code_action_enabled) in code_actions_map {
+ if code_action_enabled.as_bool().map_or(false, |b| !b) {
+ continue;
+ }
+ code_actions.push(code_action.clone());
+ }
+
+ let mut formatter_array = vec![];
+ if let Some(formatter) = obj.get("formatter") {
+ if let Some(array) = formatter.as_array() {
+ formatter_array = array.clone();
+ } else {
+ formatter_array.insert(0, formatter.clone());
+ }
+ };
+ let found_code_actions = !code_actions.is_empty();
+ formatter_array.splice(
+ 0..0,
+ code_actions
+ .into_iter()
+ .map(|code_action| serde_json::json!({"code_action": code_action})),
+ );
+
+ obj.remove("code_actions_on_format");
+ if found_code_actions {
+ obj.insert("formatter".to_string(), Value::Array(formatter_array));
+ }
+
+ Ok(())
+}
@@ -213,6 +213,7 @@ pub fn migrate_settings(text: &str) -> Result<Option<String>> {
migrations::m_2025_10_03::SETTINGS_PATTERNS,
&SETTINGS_QUERY_2025_10_03,
),
+ MigrationType::Json(migrations::m_2025_10_10::remove_code_actions_on_format),
];
run_migrations(text, migrations)
}
@@ -1913,4 +1914,302 @@ mod tests {
None,
);
}
+
+ #[test]
+ fn test_code_actions_on_format_migration_basic() {
+ assert_migrate_settings_with_migrations(
+ &[MigrationType::Json(
+ migrations::m_2025_10_10::remove_code_actions_on_format,
+ )],
+ &r#"{
+ "code_actions_on_format": {
+ "source.organizeImports": true,
+ "source.fixAll": true
+ }
+ }"#
+ .unindent(),
+ Some(
+ &r#"{
+ "formatter": [
+ {
+ "code_action": "source.organizeImports"
+ },
+ {
+ "code_action": "source.fixAll"
+ }
+ ]
+ }
+ "#
+ .unindent(),
+ ),
+ );
+ }
+
+ #[test]
+ fn test_code_actions_on_format_migration_filters_false_values() {
+ assert_migrate_settings_with_migrations(
+ &[MigrationType::Json(
+ migrations::m_2025_10_10::remove_code_actions_on_format,
+ )],
+ &r#"{
+ "code_actions_on_format": {
+ "a": true,
+ "b": false,
+ "c": true
+ }
+ }"#
+ .unindent(),
+ Some(
+ &r#"{
+ "formatter": [
+ {
+ "code_action": "a"
+ },
+ {
+ "code_action": "c"
+ }
+ ]
+ }
+ "#
+ .unindent(),
+ ),
+ );
+ }
+
+ #[test]
+ fn test_code_actions_on_format_migration_with_existing_formatter_object() {
+ assert_migrate_settings_with_migrations(
+ &[MigrationType::Json(
+ migrations::m_2025_10_10::remove_code_actions_on_format,
+ )],
+ &r#"{
+ "formatter": "prettier",
+ "code_actions_on_format": {
+ "source.organizeImports": true
+ }
+ }"#
+ .unindent(),
+ Some(
+ &r#"{
+ "formatter": [
+ {
+ "code_action": "source.organizeImports"
+ },
+ "prettier"
+ ]
+ }"#
+ .unindent(),
+ ),
+ );
+ }
+
+ #[test]
+ fn test_code_actions_on_format_migration_with_existing_formatter_array() {
+ assert_migrate_settings_with_migrations(
+ &[MigrationType::Json(
+ migrations::m_2025_10_10::remove_code_actions_on_format,
+ )],
+ &r#"{
+ "formatter": ["prettier", {"language_server": "eslint"}],
+ "code_actions_on_format": {
+ "source.organizeImports": true,
+ "source.fixAll": true
+ }
+ }"#
+ .unindent(),
+ Some(
+ &r#"{
+ "formatter": [
+ {
+ "code_action": "source.organizeImports"
+ },
+ {
+ "code_action": "source.fixAll"
+ },
+ "prettier",
+ {
+ "language_server": "eslint"
+ }
+ ]
+ }"#
+ .unindent(),
+ ),
+ );
+ }
+
+ #[test]
+ fn test_code_actions_on_format_migration_in_languages() {
+ assert_migrate_settings_with_migrations(
+ &[MigrationType::Json(
+ migrations::m_2025_10_10::remove_code_actions_on_format,
+ )],
+ &r#"{
+ "languages": {
+ "JavaScript": {
+ "code_actions_on_format": {
+ "source.fixAll.eslint": true
+ }
+ },
+ "Go": {
+ "code_actions_on_format": {
+ "source.organizeImports": true
+ }
+ }
+ }
+ }"#
+ .unindent(),
+ Some(
+ &r#"{
+ "languages": {
+ "JavaScript": {
+ "formatter": [
+ {
+ "code_action": "source.fixAll.eslint"
+ }
+ ]
+ },
+ "Go": {
+ "formatter": [
+ {
+ "code_action": "source.organizeImports"
+ }
+ ]
+ }
+ }
+ }"#
+ .unindent(),
+ ),
+ );
+ }
+
+ #[test]
+ fn test_code_actions_on_format_migration_in_languages_with_existing_formatter() {
+ assert_migrate_settings_with_migrations(
+ &[MigrationType::Json(
+ migrations::m_2025_10_10::remove_code_actions_on_format,
+ )],
+ &r#"{
+ "languages": {
+ "JavaScript": {
+ "formatter": "prettier",
+ "code_actions_on_format": {
+ "source.fixAll.eslint": true,
+ "source.organizeImports": false
+ }
+ }
+ }
+ }"#
+ .unindent(),
+ Some(
+ &r#"{
+ "languages": {
+ "JavaScript": {
+ "formatter": [
+ {
+ "code_action": "source.fixAll.eslint"
+ },
+ "prettier"
+ ]
+ }
+ }
+ }"#
+ .unindent(),
+ ),
+ );
+ }
+
+ #[test]
+ fn test_code_actions_on_format_migration_mixed_global_and_languages() {
+ assert_migrate_settings_with_migrations(
+ &[MigrationType::Json(
+ migrations::m_2025_10_10::remove_code_actions_on_format,
+ )],
+ &r#"{
+ "formatter": "prettier",
+ "code_actions_on_format": {
+ "source.fixAll": true
+ },
+ "languages": {
+ "Rust": {
+ "formatter": "rust-analyzer",
+ "code_actions_on_format": {
+ "source.organizeImports": true
+ }
+ },
+ "Python": {
+ "code_actions_on_format": {
+ "source.organizeImports": true,
+ "source.fixAll": false
+ }
+ }
+ }
+ }"#
+ .unindent(),
+ Some(
+ &r#"{
+ "formatter": [
+ {
+ "code_action": "source.fixAll"
+ },
+ "prettier"
+ ],
+ "languages": {
+ "Rust": {
+ "formatter": [
+ {
+ "code_action": "source.organizeImports"
+ },
+ "rust-analyzer"
+ ]
+ },
+ "Python": {
+ "formatter": [
+ {
+ "code_action": "source.organizeImports"
+ }
+ ]
+ }
+ }
+ }"#
+ .unindent(),
+ ),
+ );
+ }
+
+ #[test]
+ fn test_code_actions_on_format_no_migration_when_not_present() {
+ assert_migrate_settings_with_migrations(
+ &[MigrationType::Json(
+ migrations::m_2025_10_10::remove_code_actions_on_format,
+ )],
+ &r#"{
+ "formatter": ["prettier"]
+ }"#
+ .unindent(),
+ None,
+ );
+ }
+
+ #[test]
+ fn test_code_actions_on_format_migration_all_false_values() {
+ assert_migrate_settings_with_migrations(
+ &[MigrationType::Json(
+ migrations::m_2025_10_10::remove_code_actions_on_format,
+ )],
+ &r#"{
+ "code_actions_on_format": {
+ "a": false,
+ "b": false
+ },
+ "formatter": "prettier"
+ }"#
+ .unindent(),
+ Some(
+ &r#"{
+ "formatter": "prettier"
+ }"#
+ .unindent(),
+ ),
+ );
+ }
}
@@ -1338,32 +1338,6 @@ impl LocalLspStore {
})?;
}
- // Formatter for `code_actions_on_format` that runs before
- // the rest of the formatters
- let mut code_actions_on_format_formatters = None;
- let should_run_code_actions_on_format = !matches!(
- (trigger, &settings.format_on_save),
- (FormatTrigger::Save, &FormatOnSave::Off)
- );
- if should_run_code_actions_on_format {
- let have_code_actions_to_run_on_format = settings
- .code_actions_on_format
- .values()
- .any(|enabled| *enabled);
- if have_code_actions_to_run_on_format {
- zlog::trace!(logger => "going to run code actions on format");
- code_actions_on_format_formatters = Some(
- settings
- .code_actions_on_format
- .iter()
- .filter_map(|(action, enabled)| enabled.then_some(action))
- .cloned()
- .map(Formatter::CodeAction)
- .collect::<Vec<_>>(),
- );
- }
- }
-
let formatters = match (trigger, &settings.format_on_save) {
(FormatTrigger::Save, FormatOnSave::Off) => &[],
(FormatTrigger::Manual, _) | (FormatTrigger::Save, FormatOnSave::On) => {
@@ -1382,11 +1356,6 @@ impl LocalLspStore {
}
};
- let formatters = code_actions_on_format_formatters
- .iter()
- .flatten()
- .chain(formatters);
-
for formatter in formatters {
match formatter {
Formatter::Prettier => {
@@ -304,11 +304,6 @@ pub struct LanguageSettingsContent {
///
/// Default: true
pub use_on_type_format: Option<bool>,
- /// Which code actions to run on save after the formatter.
- /// These are not run if formatting is off.
- ///
- /// Default: {} (or {"source.organizeImports": true} for Go).
- pub code_actions_on_format: Option<HashMap<String, bool>>,
/// Whether to perform linked edits of associated ranges, if the language server supports it.
/// For example, when editing opening <html> tag, the contents of the closing </html> tag will be edited as well.
///
@@ -4840,27 +4840,6 @@ fn language_settings_data() -> Vec<SettingsPageItem> {
metadata: None,
files: USER | LOCAL,
}),
- SettingsPageItem::SettingItem(SettingItem {
- title: "Code Actions On Format",
- description: "Which code actions to run on save after the formatter. These are not run if formatting is off",
- field: Box::new(
- SettingField {
- pick: |settings_content| {
- language_settings_field(settings_content, |language| {
- &language.code_actions_on_format
- })
- },
- pick_mut: |settings_content| {
- language_settings_field_mut(settings_content, |language| {
- &mut language.code_actions_on_format
- })
- },
- }
- .unimplemented(),
- ),
- metadata: None,
- files: USER | LOCAL,
- }),
SettingsPageItem::SectionHeader("Prettier"),
SettingsPageItem::SettingItem(SettingItem {
title: "Allowed",