diff --git a/assets/settings/default.json b/assets/settings/default.json index 0526f178837b515f6a3f6421525380edf1b3417a..b56845b9409f6449c18466ab1ad2bc2361569c7b 100644 --- a/assets/settings/default.json +++ b/assets/settings/default.json @@ -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": { diff --git a/crates/agent2/src/tools/edit_file_tool.rs b/crates/agent2/src/tools/edit_file_tool.rs index 7c51df0fae274e2d5906aa73e70c30105b1a2353..90bb68979439b92eef685a500a81147fde8099d6 100644 --- a/crates/agent2/src/tools/edit_file_tool.rs +++ b/crates/agent2/src/tools/edit_file_tool.rs @@ -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()); }); }); }); diff --git a/crates/assistant_tools/src/edit_file_tool.rs b/crates/assistant_tools/src/edit_file_tool.rs index f88978650a32cdc6922a1ff864b0ee898721df80..840f34aaae9381882e39f8435242625022dfc26c 100644 --- a/crates/assistant_tools/src/edit_file_tool.rs +++ b/crates/assistant_tools/src/edit_file_tool.rs @@ -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()); }); }); }); diff --git a/crates/collab/src/tests/integration_tests.rs b/crates/collab/src/tests/integration_tests.rs index f6a106b7db5c77ba8e98b307cc3e562766fb4dd4..f080b1cc4a56a7597115a35aac3c329f9039421c 100644 --- a/crates/collab/src/tests/integration_tests.rs +++ b/crates/collab/src/tests/integration_tests.rs @@ -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), diff --git a/crates/collab/src/tests/remote_editing_collaboration_tests.rs b/crates/collab/src/tests/remote_editing_collaboration_tests.rs index 84ee9a33906b976a68da5da7b81c1e89c96190b1..7fb76ffd16dda9f556a22eeefd78c72f80dc1457 100644 --- a/crates/collab/src/tests/remote_editing_collaboration_tests.rs +++ b/crates/collab/src/tests/remote_editing_collaboration_tests.rs @@ -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() diff --git a/crates/editor/src/editor_tests.rs b/crates/editor/src/editor_tests.rs index dd8fa50c33d730859a92da3077e2316b755f3f7c..ede8d1c20d664ecaa5f26955de85a4dbc5313a5e 100644 --- a/crates/editor/src/editor_tests.rs +++ b/crates/editor/src/editor_tests.rs @@ -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 { #[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( diff --git a/crates/language/src/language_settings.rs b/crates/language/src/language_settings.rs index 689a3f90f5a1d8826cd68ccc26bc7427c0aab29d..ad6a22c6200e69240bcc56cb1ca3682bb7409cfc 100644 --- a/crates/language/src/language_settings.rs +++ b/crates/language/src/language_settings.rs @@ -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. diff --git a/crates/project/src/lsp_store.rs b/crates/project/src/lsp_store.rs index 9845cce509208f979ad8a07784797c6df939d9f9..80d32e16690bf1bcb7bdec9d868193dd972f3614 100644 --- a/crates/project/src/lsp_store.rs +++ b/crates/project/src/lsp_store.rs @@ -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 { diff --git a/crates/project/src/prettier_store.rs b/crates/project/src/prettier_store.rs index 625f239b28ed0c6e4ad2a0fa3886896f0adeb723..40deac76404ddb4378fe08cae931d0f0e3583487 100644 --- a/crates/project/src/prettier_store.rs +++ b/crates/project/src/prettier_store.rs @@ -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> { - 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( diff --git a/crates/settings/src/settings_content/language.rs b/crates/settings/src/settings_content/language.rs index 856a0d9394626eb42583bdc32940fb3bebfcb552..d0f849cabb90073e05ec66472bc7cc36e0622cd9 100644 --- a/crates/settings/src/settings_content/language.rs +++ b/crates/settings/src/settings_content/language.rs @@ -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, + pub formatter: Option, /// 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(&self, serializer: S) -> std::result::Result - 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(deserializer: D) -> std::result::Result - 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(self, v: &str) -> std::result::Result - 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 = - Deserialize::deserialize(v.into_deserializer()); - ret.map(SelectedFormatter::List) - } - } - fn visit_map(self, map: A) -> Result - where - A: MapAccess<'d>, - { - let ret: Result = - Deserialize::deserialize(de::value::MapAccessDeserializer::new(map)); - ret.map(SelectedFormatter::List) - } - fn visit_seq(self, map: A) -> Result - where - A: SeqAccess<'d>, - { - let ret: Result = - 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 }, - /// 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 for LanguageServerFormatterSpecifier { + fn from(value: LanguageServerVariantContent) -> Self { + match value { + LanguageServerVariantContent::Specific { + language_server: LanguageServerSpecifierContent { name: Some(name) }, + } => Self::Specific { name }, + _ => Self::Current, + } + } +} + +impl From 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, } /// 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\"", ); }