Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 59 additions & 8 deletions crates/migrator/src/migrations/m_2025_10_10/settings.rs
Original file line number Diff line number Diff line change
@@ -1,18 +1,62 @@
use anyhow::Result;
use serde_json::Value;

use crate::patterns::migrate_language_setting;

pub fn remove_code_actions_on_format(value: &mut Value) -> Result<()> {
migrate_language_setting(value, remove_code_actions_on_format_inner)
let defaults =
settings::parse_json_with_comments::<Value>(settings::default_settings().as_ref()).unwrap();
let default_root_formatter = defaults.get("formatter");

let root_code_actions =
remove_code_actions_on_format_inner(value, default_root_formatter, &[], &[])?;
let user_default_formatter = value.get("formatter").cloned();

let user_languages = value
.as_object_mut()
.and_then(|obj| obj.get_mut("languages"))
.and_then(|languages| languages.as_object_mut());
let default_languages = defaults
.as_object()
.and_then(|obj| obj.get("languages"))
.and_then(|languages| languages.as_object());

if let Some(languages) = user_languages {
for (language_name, language) in languages.iter_mut() {
let path = vec!["languages", language_name];
let language_default_formatter = default_languages
.and_then(|langs| langs.get(language_name))
.and_then(|lang| lang.get("formatter"));

// override order:
// 4. root from defaults
// 3. language from defaults
// 2. root from user
// 1. language from user
let default_formatter_for_language = user_default_formatter
.as_ref()
.or(language_default_formatter)
.or(default_root_formatter);
remove_code_actions_on_format_inner(
language,
default_formatter_for_language,
&root_code_actions,
&path,
)?;
}
}
Ok(())
}

fn remove_code_actions_on_format_inner(value: &mut Value, path: &[&str]) -> Result<()> {
fn remove_code_actions_on_format_inner(
value: &mut Value,
default_formatters: Option<&Value>,
parent_code_actions: &[String],
path: &[&str],
) -> Result<Vec<String>> {
let Some(obj) = value.as_object_mut() else {
return Ok(());
return Ok(vec![]);
};
let Some(code_actions_on_format) = obj.get("code_actions_on_format").cloned() else {
return Ok(());
return Ok(vec![]);
};

fn fmt_path(path: &[&str], key: &str) -> String {
Expand All @@ -35,6 +79,7 @@ fn remove_code_actions_on_format_inner(value: &mut Value, path: &[&str]) -> Resu
}
code_actions.push(code_action.clone());
}
code_actions.extend(parent_code_actions.iter().cloned());

let mut formatter_array = vec![];
if let Some(formatter) = obj.get("formatter") {
Expand All @@ -43,12 +88,18 @@ fn remove_code_actions_on_format_inner(value: &mut Value, path: &[&str]) -> Resu
} else {
formatter_array.insert(0, formatter.clone());
}
} else if let Some(formatter) = default_formatters {
if let Some(array) = formatter.as_array() {
formatter_array = array.clone();
} else {
formatter_array.push(formatter.clone());
}
};
let found_code_actions = !code_actions.is_empty();
formatter_array.splice(
0..0,
code_actions
.into_iter()
.iter()
.map(|code_action| serde_json::json!({"code_action": code_action})),
);

Expand All @@ -57,5 +108,5 @@ fn remove_code_actions_on_format_inner(value: &mut Value, path: &[&str]) -> Resu
obj.insert("formatter".to_string(), Value::Array(formatter_array));
}

Ok(())
Ok(code_actions)
}
58 changes: 52 additions & 6 deletions crates/migrator/src/migrator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2010,7 +2010,8 @@ mod tests {
},
{
"code_action": "source.fixAll"
}
},
"auto"
]
}
"#
Expand Down Expand Up @@ -2041,7 +2042,8 @@ mod tests {
},
{
"code_action": "c"
}
},
"auto"
]
}
"#
Expand Down Expand Up @@ -2127,7 +2129,7 @@ mod tests {
"Go": {
"code_actions_on_format": {
"source.organizeImports": true
}
},
}
}
}"#
Expand All @@ -2139,14 +2141,19 @@ mod tests {
"formatter": [
{
"code_action": "source.fixAll.eslint"
}
},
"auto"
]
},
"Go": {
"formatter": [
{
"code_action": "source.organizeImports"
}
},
{
"code_action": "source.organizeImports"
},
"language_server"
]
}
}
Expand Down Expand Up @@ -2233,14 +2240,24 @@ mod tests {
{
"code_action": "source.organizeImports"
},
{
"code_action": "source.fixAll"
},
"rust-analyzer"
]
},
"Python": {
"formatter": [
{
"code_action": "source.organizeImports"
}
},
{
"code_action": "source.fixAll"
},
{
"code_action": "source.fixAll"
},
"prettier"
]
}
}
Expand All @@ -2250,6 +2267,35 @@ mod tests {
);
}

#[test]
fn test_code_actions_on_format_inserts_default_formatters() {
assert_migrate_settings_with_migrations(
&[MigrationType::Json(
migrations::m_2025_10_10::remove_code_actions_on_format,
)],
&r#"{
"code_actions_on_format": {
"source.organizeImports": false,
"source.fixAll.eslint": true
}
}"#
.unindent(),
Some(
&r#"
{
"formatter": [
{
"code_action": "source.fixAll.eslint"
},
"auto"
]
}
"#
.unindent(),
),
)
}

#[test]
fn test_code_actions_on_format_no_migration_when_not_present() {
assert_migrate_settings_with_migrations(
Expand Down
5 changes: 0 additions & 5 deletions crates/project/src/lsp_store.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1710,12 +1710,7 @@ impl LocalLspStore {
formatting_transaction_id,
cx,
|buffer, cx| {
zlog::info!(
"Applying edits {edits:?}. Content: {:?}",
buffer.text()
);
buffer.edit(edits, None, cx);
zlog::info!("Applied edits. New Content: {:?}", buffer.text());
},
)?;
}
Expand Down
11 changes: 8 additions & 3 deletions docs/src/configuring-languages.md
Original file line number Diff line number Diff line change
Expand Up @@ -320,13 +320,18 @@ To run linter fixes automatically on save:
```json [settings]
"languages": {
"JavaScript": {
"formatter": {
"code_action": "source.fixAll.eslint"
}
"formatter": [
{
"code_action": "source.fixAll.eslint"
},
"auto"
]
}
}
```

This specifies that when a format is requested, Zed will first run the `source.fixAll.eslint` action, and then perform the actual format using the language-specific default formatter (for JavaScript this is Prettier, and for other languages it often corresponds to formatting using the default language server).

### Integrating Formatting and Linting

Zed allows you to run both formatting and linting on save. Here's an example that uses Prettier for formatting and ESLint for linting JavaScript files:
Expand Down
18 changes: 12 additions & 6 deletions docs/src/languages/javascript.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,12 @@ You can configure Zed to format code using `eslint --fix` by running the ESLint
{
"languages": {
"JavaScript": {
"formatter": {
"code_action": "source.fixAll.eslint"
}
"formatter": [
{
"code_action": "source.fixAll.eslint"
},
"auto"
]
}
}
}
Expand All @@ -63,9 +66,12 @@ You can also only execute a single ESLint rule when using `fixAll`:
{
"languages": {
"JavaScript": {
"formatter": {
"code_action": "source.fixAll.eslint"
}
"formatter": [
{
"code_action": "source.fixAll.eslint"
},
"auto"
]
}
},
"lsp": {
Expand Down
Loading