settings: Make "auto" and "language_server" valid format steps (#40113)

Follow up for: #39983 and
https://github.com/zed-industries/zed/pull/40040#issuecomment-3393902691

Previously it was possible to have formatting done using prettier or
language server using `"formatter": "auto"` and specify code actions to
apply on format using the `"code_actions_on_format"` setting. However,
post #39983 this is no longer possible due to the removal of the
`"code_actions_on_format"` setting. To rectify this regression, this PR
makes it so that the `"auto"` and `"language_server"` strings that were
previously only allowed as top level values on the `"formatter"` key,
are now allowed as format steps like so:
```json
{
      "formatter": ["auto", "language_server"]
}
```

Therefore to replicate the previous behavior using `"auto"` and
`"code_actions_on_format"` you can use the following configuration:

```json
{
      "formatter": [{"code_action": ...}, "auto"]
}
```

Release Notes:

- N/A *or* Added/Fixed/Improved ...
This commit is contained in:
Ben Kunkle
2025-10-13 12:56:40 -04:00
committed by GitHub
parent 8dfbafd345
commit 997f6c6a19
10 changed files with 183 additions and 191 deletions
+14 -8
View File
@@ -1107,22 +1107,28 @@
// 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:
// How to perform a buffer format. This setting can take multiple values:
//
// 1. Format code using the current language server:
// 1. Default. Format files using Zed's Prettier integration (if applicable),
// or falling back to formatting via language server:
// "formatter": "auto"
// 2. Format code using the current language server:
// "formatter": "language_server"
// 2. Format code using an external command:
// 3. Format code using a specific language server:
// "formatter": {"language_server": {"name": "ruff"}}
// 4. Format code using an external command:
// "formatter": {
// "external": {
// "command": "prettier",
// "arguments": ["--stdin-filepath", "{buffer_path}"]
// }
// }
// 3. Format code using Zed's Prettier integration:
// 5. Format code using Zed's Prettier integration:
// "formatter": "prettier"
// 4. Default. Format files using Zed's Prettier integration (if applicable),
// or falling back to formatting via language server:
// "formatter": "auto"
// 6. Format code using a code action
// "formatter": {"code_action": "source.fixAll.eslint"}
// 7. An array of any format step specified above to apply in order
// "formatter": [{"code_action": "source.fixAll.eslint"}, "prettier"]
"formatter": "auto",
// How to soft-wrap long lines of text.
// Possible values:
@@ -1690,7 +1696,7 @@
"preferred_line_length": 72
},
"Go": {
"formatter": [{ "code_action": "source.organizeImports" }, { "language_server": {} }],
"formatter": [{ "code_action": "source.organizeImports" }, "language_server"],
"debuggers": ["Delve"]
},
"GraphQL": {
+1 -1
View File
@@ -790,7 +790,7 @@ mod tests {
store.update_user_settings(cx, |settings| {
settings.project.all_languages.defaults.format_on_save = Some(FormatOnSave::On);
settings.project.all_languages.defaults.formatter =
Some(language::language_settings::SelectedFormatter::Auto);
Some(language::language_settings::FormatterList::default());
});
});
});
+1 -1
View File
@@ -1538,7 +1538,7 @@ mod tests {
store.update_user_settings(cx, |settings| {
settings.project.all_languages.defaults.format_on_save = Some(FormatOnSave::On);
settings.project.all_languages.defaults.formatter =
Some(language::language_settings::SelectedFormatter::Auto);
Some(language::language_settings::FormatterList::default());
});
});
});
+8 -9
View File
@@ -25,7 +25,7 @@ use gpui::{
use language::{
Diagnostic, DiagnosticEntry, DiagnosticSourceKind, FakeLspAdapter, Language, LanguageConfig,
LanguageMatcher, LineEnding, OffsetRangeExt, Point, Rope,
language_settings::{Formatter, FormatterList, SelectedFormatter},
language_settings::{Formatter, FormatterList},
tree_sitter_rust, tree_sitter_typescript,
};
use lsp::{LanguageServerId, OneOf};
@@ -39,7 +39,7 @@ use project::{
use prompt_store::PromptBuilder;
use rand::prelude::*;
use serde_json::json;
use settings::{PrettierSettingsContent, SettingsStore};
use settings::{LanguageServerFormatterSpecifier, PrettierSettingsContent, SettingsStore};
use std::{
cell::{Cell, RefCell},
env, future, mem,
@@ -4610,14 +4610,13 @@ async fn test_formatting_buffer(
cx_a.update(|cx| {
SettingsStore::update_global(cx, |store, cx| {
store.update_user_settings(cx, |file| {
file.project.all_languages.defaults.formatter = Some(SelectedFormatter::List(
FormatterList::Single(Formatter::External {
file.project.all_languages.defaults.formatter =
Some(FormatterList::Single(Formatter::External {
command: "awk".into(),
arguments: Some(
vec!["{sub(/two/,\"{buffer_path}\")}1".to_string()].into(),
),
}),
));
}));
});
});
});
@@ -4708,7 +4707,7 @@ async fn test_prettier_formatting_buffer(
cx_a.update(|cx| {
SettingsStore::update_global(cx, |store, cx| {
store.update_user_settings(cx, |file| {
file.project.all_languages.defaults.formatter = Some(SelectedFormatter::Auto);
file.project.all_languages.defaults.formatter = Some(FormatterList::default());
file.project.all_languages.defaults.prettier = Some(PrettierSettingsContent {
allowed: Some(true),
..Default::default()
@@ -4719,8 +4718,8 @@ async fn test_prettier_formatting_buffer(
cx_b.update(|cx| {
SettingsStore::update_global(cx, |store, cx| {
store.update_user_settings(cx, |file| {
file.project.all_languages.defaults.formatter = Some(SelectedFormatter::List(
FormatterList::Single(Formatter::LanguageServer { name: None }),
file.project.all_languages.defaults.formatter = Some(FormatterList::Single(
Formatter::LanguageServer(LanguageServerFormatterSpecifier::Current),
));
file.project.all_languages.defaults.prettier = Some(PrettierSettingsContent {
allowed: Some(true),
@@ -14,7 +14,7 @@ use gpui::{
use http_client::BlockedHttpClient;
use language::{
FakeLspAdapter, Language, LanguageConfig, LanguageMatcher, LanguageRegistry,
language_settings::{Formatter, FormatterList, SelectedFormatter, language_settings},
language_settings::{Formatter, FormatterList, language_settings},
tree_sitter_typescript,
};
use node_runtime::NodeRuntime;
@@ -27,7 +27,7 @@ use remote::RemoteClient;
use remote_server::{HeadlessAppState, HeadlessProject};
use rpc::proto;
use serde_json::json;
use settings::{PrettierSettingsContent, SettingsStore};
use settings::{LanguageServerFormatterSpecifier, PrettierSettingsContent, SettingsStore};
use std::{
path::Path,
sync::{Arc, atomic::AtomicUsize},
@@ -491,7 +491,7 @@ async fn test_ssh_collaboration_formatting_with_prettier(
cx_a.update(|cx| {
SettingsStore::update_global(cx, |store, cx| {
store.update_user_settings(cx, |file| {
file.project.all_languages.defaults.formatter = Some(SelectedFormatter::Auto);
file.project.all_languages.defaults.formatter = Some(FormatterList::default());
file.project.all_languages.defaults.prettier = Some(PrettierSettingsContent {
allowed: Some(true),
..Default::default()
@@ -502,8 +502,8 @@ async fn test_ssh_collaboration_formatting_with_prettier(
cx_b.update(|cx| {
SettingsStore::update_global(cx, |store, cx| {
store.update_user_settings(cx, |file| {
file.project.all_languages.defaults.formatter = Some(SelectedFormatter::List(
FormatterList::Single(Formatter::LanguageServer { name: None }),
file.project.all_languages.defaults.formatter = Some(FormatterList::Single(
Formatter::LanguageServer(LanguageServerFormatterSpecifier::Current),
));
file.project.all_languages.defaults.prettier = Some(PrettierSettingsContent {
allowed: Some(true),
@@ -550,7 +550,7 @@ async fn test_ssh_collaboration_formatting_with_prettier(
cx_a.update(|cx| {
SettingsStore::update_global(cx, |store, cx| {
store.update_user_settings(cx, |file| {
file.project.all_languages.defaults.formatter = Some(SelectedFormatter::Auto);
file.project.all_languages.defaults.formatter = Some(FormatterList::default());
file.project.all_languages.defaults.prettier = Some(PrettierSettingsContent {
allowed: Some(true),
..Default::default()
+11 -14
View File
@@ -27,7 +27,6 @@ use language::{
LanguageConfigOverride, LanguageMatcher, LanguageName, Override, Point,
language_settings::{
CompletionSettingsContent, FormatterList, LanguageSettingsContent, LspInsertMode,
SelectedFormatter,
},
tree_sitter_python,
};
@@ -11908,8 +11907,8 @@ async fn test_range_format_respects_language_tab_size_override(cx: &mut TestAppC
#[gpui::test]
async fn test_document_format_manual_trigger(cx: &mut TestAppContext) {
init_test(cx, |settings| {
settings.defaults.formatter = Some(SelectedFormatter::List(FormatterList::Single(
Formatter::LanguageServer { name: None },
settings.defaults.formatter = Some(FormatterList::Single(Formatter::LanguageServer(
settings::LanguageServerFormatterSpecifier::Current,
)))
});
@@ -12034,11 +12033,11 @@ async fn test_document_format_manual_trigger(cx: &mut TestAppContext) {
async fn test_multiple_formatters(cx: &mut TestAppContext) {
init_test(cx, |settings| {
settings.defaults.remove_trailing_whitespace_on_save = Some(true);
settings.defaults.formatter = Some(SelectedFormatter::List(FormatterList::Vec(vec![
Formatter::LanguageServer { name: None },
settings.defaults.formatter = Some(FormatterList::Vec(vec![
Formatter::LanguageServer(settings::LanguageServerFormatterSpecifier::Current),
Formatter::CodeAction("code-action-1".into()),
Formatter::CodeAction("code-action-2".into()),
])))
]))
});
let fs = FakeFs::new(cx.executor());
@@ -12293,9 +12292,9 @@ async fn test_multiple_formatters(cx: &mut TestAppContext) {
#[gpui::test]
async fn test_organize_imports_manual_trigger(cx: &mut TestAppContext) {
init_test(cx, |settings| {
settings.defaults.formatter = Some(SelectedFormatter::List(FormatterList::Vec(vec![
Formatter::LanguageServer { name: None },
])))
settings.defaults.formatter = Some(FormatterList::Vec(vec![Formatter::LanguageServer(
settings::LanguageServerFormatterSpecifier::Current,
)]))
});
let fs = FakeFs::new(cx.executor());
@@ -12498,7 +12497,7 @@ async fn test_concurrent_format_requests(cx: &mut TestAppContext) {
#[gpui::test]
async fn test_strip_whitespace_and_format_via_lsp(cx: &mut TestAppContext) {
init_test(cx, |settings| {
settings.defaults.formatter = Some(SelectedFormatter::Auto)
settings.defaults.formatter = Some(FormatterList::default())
});
let mut cx = EditorLspTestContext::new_rust(
@@ -18187,9 +18186,7 @@ fn completion_menu_entries(menu: &CompletionsMenu) -> Vec<String> {
#[gpui::test]
async fn test_document_format_with_prettier(cx: &mut TestAppContext) {
init_test(cx, |settings| {
settings.defaults.formatter = Some(SelectedFormatter::List(FormatterList::Single(
Formatter::Prettier,
)))
settings.defaults.formatter = Some(FormatterList::Single(Formatter::Prettier))
});
let fs = FakeFs::new(cx.executor());
@@ -18256,7 +18253,7 @@ async fn test_document_format_with_prettier(cx: &mut TestAppContext) {
);
update_test_language_settings(cx, |settings| {
settings.defaults.formatter = Some(SelectedFormatter::Auto)
settings.defaults.formatter = Some(FormatterList::default())
});
let format = editor.update_in(cx, |editor, window, cx| {
editor.perform_format(
+2 -2
View File
@@ -13,7 +13,7 @@ use itertools::{Either, Itertools};
pub use settings::{
CompletionSettingsContent, EditPredictionProvider, EditPredictionsMode, FormatOnSave,
Formatter, FormatterList, InlayHintKind, LanguageSettingsContent, LspInsertMode,
RewrapBehavior, SelectedFormatter, ShowWhitespaceSetting, SoftWrap, WordsCompletionMode,
RewrapBehavior, ShowWhitespaceSetting, SoftWrap, WordsCompletionMode,
};
use settings::{ExtendingVec, Settings, SettingsContent, SettingsLocation, SettingsStore};
use shellexpand;
@@ -96,7 +96,7 @@ pub struct LanguageSettings {
/// when saving it.
pub ensure_final_newline_on_save: bool,
/// How to perform a buffer format.
pub formatter: settings::SelectedFormatter,
pub formatter: settings::FormatterList,
/// Zed's Prettier integration settings.
pub prettier: PrettierSettings,
/// Whether to automatically close JSX tags.
+28 -26
View File
@@ -61,9 +61,7 @@ use language::{
LanguageRegistry, LocalFile, LspAdapter, LspAdapterDelegate, LspInstaller, ManifestDelegate,
ManifestName, Patch, PointUtf16, TextBufferSnapshot, ToOffset, ToPointUtf16, Toolchain,
Transaction, Unclipped,
language_settings::{
FormatOnSave, Formatter, LanguageSettings, SelectedFormatter, language_settings,
},
language_settings::{FormatOnSave, Formatter, LanguageSettings, language_settings},
point_to_lsp,
proto::{
deserialize_anchor, deserialize_lsp_edit, deserialize_version, serialize_anchor,
@@ -1338,23 +1336,24 @@ impl LocalLspStore {
let formatters = match (trigger, &settings.format_on_save) {
(FormatTrigger::Save, FormatOnSave::Off) => &[],
(FormatTrigger::Manual, _) | (FormatTrigger::Save, FormatOnSave::On) => {
match &settings.formatter {
SelectedFormatter::Auto => {
if settings.prettier.allowed {
zlog::trace!(logger => "Formatter set to auto: defaulting to prettier");
std::slice::from_ref(&Formatter::Prettier)
} else {
zlog::trace!(logger => "Formatter set to auto: defaulting to primary language server");
std::slice::from_ref(&Formatter::LanguageServer { name: None })
}
}
SelectedFormatter::List(formatter_list) => formatter_list.as_ref(),
}
settings.formatter.as_ref()
}
};
for formatter in formatters {
let formatter = if formatter == &Formatter::Auto {
if settings.prettier.allowed {
zlog::trace!(logger => "Formatter set to auto: defaulting to prettier");
&Formatter::Prettier
} else {
zlog::trace!(logger => "Formatter set to auto: defaulting to primary language server");
&Formatter::LanguageServer(settings::LanguageServerFormatterSpecifier::Current)
}
} else {
formatter
};
match formatter {
Formatter::Auto => unreachable!("Auto resolved above"),
Formatter::Prettier => {
let logger = zlog::scoped!(logger => "prettier");
zlog::trace!(logger => "formatting");
@@ -1409,7 +1408,7 @@ impl LocalLspStore {
},
)?;
}
Formatter::LanguageServer { name } => {
Formatter::LanguageServer(specifier) => {
let logger = zlog::scoped!(logger => "language-server");
zlog::trace!(logger => "formatting");
let _timer = zlog::time!(logger => "Formatting buffer using language server");
@@ -1419,16 +1418,19 @@ impl LocalLspStore {
continue;
};
let language_server = if let Some(name) = name.as_deref() {
adapters_and_servers.iter().find_map(|(adapter, server)| {
if adapter.name.0.as_ref() == name {
Some(server.clone())
} else {
None
}
})
} else {
adapters_and_servers.first().map(|e| e.1.clone())
let language_server = match specifier {
settings::LanguageServerFormatterSpecifier::Specific { name } => {
adapters_and_servers.iter().find_map(|(adapter, server)| {
if adapter.name.0.as_ref() == name {
Some(server.clone())
} else {
None
}
})
}
settings::LanguageServerFormatterSpecifier::Current => {
adapters_and_servers.first().map(|e| e.1.clone())
}
};
let Some(language_server) = language_server else {
+5 -8
View File
@@ -16,7 +16,7 @@ use futures::{
use gpui::{AppContext as _, AsyncApp, Context, Entity, EventEmitter, Task, WeakEntity};
use language::{
Buffer, LanguageRegistry, LocalFile,
language_settings::{Formatter, LanguageSettings, SelectedFormatter},
language_settings::{Formatter, LanguageSettings},
};
use lsp::{LanguageServer, LanguageServerId, LanguageServerName};
use node_runtime::NodeRuntime;
@@ -700,14 +700,11 @@ impl PrettierStore {
pub fn prettier_plugins_for_language(
language_settings: &LanguageSettings,
) -> Option<&HashSet<String>> {
match &language_settings.formatter {
SelectedFormatter::Auto => Some(&language_settings.prettier.plugins),
SelectedFormatter::List(list) => list
.as_ref()
.contains(&Formatter::Prettier)
.then_some(&language_settings.prettier.plugins),
let formatters = language_settings.formatter.as_ref();
if formatters.contains(&Formatter::Prettier) || formatters.contains(&Formatter::Auto) {
return Some(&language_settings.prettier.plugins);
}
None
}
pub(super) async fn format_with_prettier(
+107 -116
View File
@@ -1,12 +1,9 @@
use std::{borrow::Cow, num::NonZeroU32};
use std::num::NonZeroU32;
use collections::{HashMap, HashSet};
use gpui::{Modifiers, SharedString};
use schemars::{JsonSchema, json_schema};
use serde::{
Deserialize, Deserializer, Serialize,
de::{self, IntoDeserializer, MapAccess, SeqAccess, Visitor},
};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use serde_with::skip_serializing_none;
use settings_macros::MergeFrom;
use std::sync::Arc;
@@ -246,7 +243,7 @@ pub struct LanguageSettingsContent {
/// How to perform a buffer format.
///
/// Default: auto
pub formatter: Option<SelectedFormatter>,
pub formatter: Option<FormatterList>,
/// Zed's Prettier integration settings.
/// Allows to enable/disable formatting with Prettier
/// and configure default Prettier, used when no project-level Prettier installation is found.
@@ -639,102 +636,6 @@ pub enum FormatOnSave {
Off,
}
/// Controls which formatter should be used when formatting code.
#[derive(Clone, Debug, Default, PartialEq, Eq, MergeFrom)]
pub enum SelectedFormatter {
/// Format files using Zed's Prettier integration (if applicable),
/// or falling back to formatting via language server.
#[default]
Auto,
List(FormatterList),
}
impl JsonSchema for SelectedFormatter {
fn schema_name() -> Cow<'static, str> {
"Formatter".into()
}
fn json_schema(generator: &mut schemars::SchemaGenerator) -> schemars::Schema {
let formatter_schema = Formatter::json_schema(generator);
json_schema!({
"oneOf": [
{
"type": "array",
"items": formatter_schema
},
{
"type": "string",
"enum": ["auto", "language_server"]
},
formatter_schema
]
})
}
}
impl Serialize for SelectedFormatter {
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
match self {
SelectedFormatter::Auto => serializer.serialize_str("auto"),
SelectedFormatter::List(list) => list.serialize(serializer),
}
}
}
impl<'de> Deserialize<'de> for SelectedFormatter {
fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
where
D: Deserializer<'de>,
{
struct FormatDeserializer;
impl<'d> Visitor<'d> for FormatDeserializer {
type Value = SelectedFormatter;
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
formatter.write_str("a valid formatter kind")
}
fn visit_str<E>(self, v: &str) -> std::result::Result<Self::Value, E>
where
E: serde::de::Error,
{
if v == "auto" {
Ok(Self::Value::Auto)
} else if v == "language_server" {
Ok(Self::Value::List(FormatterList::Single(
Formatter::LanguageServer { name: None },
)))
} else {
let ret: Result<FormatterList, _> =
Deserialize::deserialize(v.into_deserializer());
ret.map(SelectedFormatter::List)
}
}
fn visit_map<A>(self, map: A) -> Result<Self::Value, A::Error>
where
A: MapAccess<'d>,
{
let ret: Result<FormatterList, _> =
Deserialize::deserialize(de::value::MapAccessDeserializer::new(map));
ret.map(SelectedFormatter::List)
}
fn visit_seq<A>(self, map: A) -> Result<Self::Value, A::Error>
where
A: SeqAccess<'d>,
{
let ret: Result<FormatterList, _> =
Deserialize::deserialize(de::value::SeqAccessDeserializer::new(map));
ret.map(SelectedFormatter::List)
}
}
deserializer.deserialize_any(FormatDeserializer)
}
}
/// Controls which formatters should be used when formatting code.
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema, MergeFrom)]
#[serde(untagged)]
@@ -762,10 +663,11 @@ impl AsRef<[Formatter]> for FormatterList {
#[derive(Clone, Default, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema, MergeFrom)]
#[serde(rename_all = "snake_case")]
pub enum Formatter {
/// Format code using the current language server.
LanguageServer { name: Option<String> },
/// Format code using Zed's Prettier integration.
/// Format files using Zed's Prettier integration (if applicable),
/// or falling back to formatting via language server.
#[default]
Auto,
/// Format code using Zed's Prettier integration.
Prettier,
/// Format code using an external command.
External {
@@ -776,6 +678,73 @@ pub enum Formatter {
},
/// Files should be formatted using a code action executed by language servers.
CodeAction(String),
/// Format code using a language server.
#[serde(untagged)]
LanguageServer(LanguageServerFormatterSpecifier),
}
#[derive(Clone, Default, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema, MergeFrom)]
#[serde(
rename_all = "snake_case",
// allow specifying language servers as "language_server" or {"language_server": {"name": ...}}
from = "LanguageServerVariantContent",
into = "LanguageServerVariantContent"
)]
pub enum LanguageServerFormatterSpecifier {
Specific {
name: String,
},
#[default]
Current,
}
impl From<LanguageServerVariantContent> for LanguageServerFormatterSpecifier {
fn from(value: LanguageServerVariantContent) -> Self {
match value {
LanguageServerVariantContent::Specific {
language_server: LanguageServerSpecifierContent { name: Some(name) },
} => Self::Specific { name },
_ => Self::Current,
}
}
}
impl From<LanguageServerFormatterSpecifier> for LanguageServerVariantContent {
fn from(value: LanguageServerFormatterSpecifier) -> Self {
match value {
LanguageServerFormatterSpecifier::Specific { name } => Self::Specific {
language_server: LanguageServerSpecifierContent { name: Some(name) },
},
LanguageServerFormatterSpecifier::Current => {
Self::Current(CurrentLanguageServerContent::LanguageServer)
}
}
}
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema, MergeFrom)]
#[serde(rename_all = "snake_case", untagged)]
enum LanguageServerVariantContent {
/// Format code using a specific language server.
Specific {
language_server: LanguageServerSpecifierContent,
},
/// Format code using the current language server.
Current(CurrentLanguageServerContent),
}
#[derive(Clone, Default, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema, MergeFrom)]
#[serde(rename_all = "snake_case")]
enum CurrentLanguageServerContent {
#[default]
LanguageServer,
}
#[derive(Clone, Default, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema, MergeFrom)]
#[serde(rename_all = "snake_case")]
struct LanguageServerSpecifierContent {
/// The name of the language server to format with
name: Option<String>,
}
/// The settings for indent guides.
@@ -884,31 +853,53 @@ mod test {
fn test_formatter_deserialization() {
let raw_auto = "{\"formatter\": \"auto\"}";
let settings: LanguageSettingsContent = serde_json::from_str(raw_auto).unwrap();
assert_eq!(settings.formatter, Some(SelectedFormatter::Auto));
assert_eq!(
settings.formatter,
Some(FormatterList::Single(Formatter::Auto))
);
let raw = "{\"formatter\": \"language_server\"}";
let settings: LanguageSettingsContent = serde_json::from_str(raw).unwrap();
assert_eq!(
settings.formatter,
Some(SelectedFormatter::List(FormatterList::Single(
Formatter::LanguageServer { name: None }
Some(FormatterList::Single(Formatter::LanguageServer(
LanguageServerFormatterSpecifier::Current
)))
);
let raw = "{\"formatter\": [{\"language_server\": {\"name\": null}}]}";
let settings: LanguageSettingsContent = serde_json::from_str(raw).unwrap();
assert_eq!(
settings.formatter,
Some(SelectedFormatter::List(FormatterList::Vec(vec![
Formatter::LanguageServer { name: None }
])))
Some(FormatterList::Vec(vec![Formatter::LanguageServer(
LanguageServerFormatterSpecifier::Current
)]))
);
let raw = "{\"formatter\": [{\"language_server\": {\"name\": null}}, \"prettier\"]}";
let raw = "{\"formatter\": [{\"language_server\": {\"name\": null}}, \"language_server\", \"prettier\"]}";
let settings: LanguageSettingsContent = serde_json::from_str(raw).unwrap();
assert_eq!(
settings.formatter,
Some(SelectedFormatter::List(FormatterList::Vec(vec![
Formatter::LanguageServer { name: None },
Some(FormatterList::Vec(vec![
Formatter::LanguageServer(LanguageServerFormatterSpecifier::Current),
Formatter::LanguageServer(LanguageServerFormatterSpecifier::Current),
Formatter::Prettier
])))
]))
);
let raw = "{\"formatter\": [{\"language_server\": {\"name\": \"ruff\"}}, \"prettier\"]}";
let settings: LanguageSettingsContent = serde_json::from_str(raw).unwrap();
assert_eq!(
settings.formatter,
Some(FormatterList::Vec(vec![
Formatter::LanguageServer(LanguageServerFormatterSpecifier::Specific {
name: "ruff".to_string()
}),
Formatter::Prettier
]))
);
assert_eq!(
serde_json::to_string(&LanguageServerFormatterSpecifier::Current).unwrap(),
"\"language_server\"",
);
}