mirror of
https://github.com/zed-industries/zed.git
synced 2026-06-01 03:14:56 +07:00
Deprecate code actions on format setting (#39983)
Closes #ISSUE
Release Notes:
- settings: Deprecated `code_actions_on_format` in favor of specifying
code actions to run on format inline in the `formatter` array.
Previously, you would configure code actions to run on format like this:
```json
{
"code_actions_on_format": {
"source.organizeImports": true,
"source.fixAll.eslint": true
}
}
```
This has been migrated to the new format:
```json
{
"formatter": [
{
"code_action": "source.organizeImports"
},
{
"code_action": "source.fixAll.eslint"
}
]
}
```
This change will be automatically migrated for you. If you had an
existing `formatter` setting, the code actions are prepended to your
formatter array (matching the existing behavior). This migration applies
to both global settings and language-specific settings
This commit is contained in:
parent
f7e7a304e0
commit
3ba4b84107
8 changed files with 377 additions and 65 deletions
|
|
@ -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:
|
||||
|
|
@ -1518,7 +1518,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": {},
|
||||
|
|
@ -1688,9 +1687,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.
|
||||
|
|
@ -578,7 +576,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;
|
||||
}
|
||||
|
|
|
|||
70
crates/migrator/src/migrations/m_2025_10_10/settings.rs
Normal file
70
crates/migrator/src/migrations/m_2025_10_10/settings.rs
Normal file
|
|
@ -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(),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1335,32 +1335,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) => {
|
||||
|
|
@ -1379,11 +1353,6 @@ impl LocalLspStore {
|
|||
}
|
||||
};
|
||||
|
||||
let formatters = code_actions_on_format_formatters
|
||||
.iter()
|
||||
.flatten()
|
||||
.chain(formatters);
|
||||
|
||||
for formatter in formatters {
|
||||
match formatter {
|
||||
Formatter::Prettier => {
|
||||
|
|
|
|||
|
|
@ -322,11 +322,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",
|
||||
|
|
|
|||
Loading…
Reference in a new issue