Detailed changes
@@ -364,7 +364,7 @@ runtimelib = { version = "0.12", default-features = false, features = [
] }
rusqlite = { version = "0.29.0", features = ["blob", "array", "modern_sqlite"] }
rust-embed = { version = "8.4", features = ["include-exclude"] }
-schemars = "0.8"
+schemars = {version = "0.8", features = ["impl_json_schema"]}
semver = "1.0"
serde = { version = "1.0", features = ["derive", "rc"] }
serde_derive = { version = "1.0", features = ["deserialize_in_place"] }
@@ -18,7 +18,9 @@ use gpui::{
TestAppContext, UpdateGlobal,
};
use language::{
- language_settings::{AllLanguageSettings, Formatter, PrettierSettings},
+ language_settings::{
+ AllLanguageSettings, Formatter, FormatterList, PrettierSettings, SelectedFormatter,
+ },
tree_sitter_rust, Diagnostic, DiagnosticEntry, FakeLspAdapter, Language, LanguageConfig,
LanguageMatcher, LineEnding, OffsetRangeExt, Point, Rope,
};
@@ -4409,10 +4411,13 @@ async fn test_formatting_buffer(
cx_a.update(|cx| {
SettingsStore::update_global(cx, |store, cx| {
store.update_user_settings::<AllLanguageSettings>(cx, |file| {
- file.defaults.formatter = Some(Formatter::External {
- command: "awk".into(),
- arguments: vec!["{sub(/two/,\"{buffer_path}\")}1".to_string()].into(),
- });
+ file.defaults.formatter = Some(SelectedFormatter::List(FormatterList(
+ vec![Formatter::External {
+ command: "awk".into(),
+ arguments: vec!["{sub(/two/,\"{buffer_path}\")}1".to_string()].into(),
+ }]
+ .into(),
+ )));
});
});
});
@@ -4493,7 +4498,7 @@ async fn test_prettier_formatting_buffer(
cx_a.update(|cx| {
SettingsStore::update_global(cx, |store, cx| {
store.update_user_settings::<AllLanguageSettings>(cx, |file| {
- file.defaults.formatter = Some(Formatter::Auto);
+ file.defaults.formatter = Some(SelectedFormatter::Auto);
file.defaults.prettier = Some(PrettierSettings {
allowed: true,
..PrettierSettings::default()
@@ -4504,7 +4509,9 @@ async fn test_prettier_formatting_buffer(
cx_b.update(|cx| {
SettingsStore::update_global(cx, |store, cx| {
store.update_user_settings::<AllLanguageSettings>(cx, |file| {
- file.defaults.formatter = Some(Formatter::LanguageServer);
+ file.defaults.formatter = Some(SelectedFormatter::List(FormatterList(
+ vec![Formatter::LanguageServer { name: None }].into(),
+ )));
file.defaults.prettier = Some(PrettierSettings {
allowed: true,
..PrettierSettings::default()
@@ -23,7 +23,7 @@ use language::{
FakeLspAdapter, IndentGuide, LanguageConfig, LanguageConfigOverride, LanguageMatcher, Override,
ParsedMarkdown, Point,
};
-use language_settings::IndentGuideSettings;
+use language_settings::{Formatter, FormatterList, IndentGuideSettings};
use multi_buffer::MultiBufferIndentGuide;
use parking_lot::Mutex;
use project::FakeFs;
@@ -6559,7 +6559,9 @@ async fn test_range_format_during_save(cx: &mut gpui::TestAppContext) {
#[gpui::test]
async fn test_document_format_manual_trigger(cx: &mut gpui::TestAppContext) {
init_test(cx, |settings| {
- settings.defaults.formatter = Some(language_settings::Formatter::LanguageServer)
+ settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(
+ FormatterList(vec![Formatter::LanguageServer { name: None }].into()),
+ ))
});
let fs = FakeFs::new(cx.executor());
@@ -6720,7 +6722,7 @@ async fn test_concurrent_format_requests(cx: &mut gpui::TestAppContext) {
#[gpui::test]
async fn test_strip_whitespace_and_format_via_lsp(cx: &mut gpui::TestAppContext) {
init_test(cx, |settings| {
- settings.defaults.formatter = Some(language_settings::Formatter::Auto)
+ settings.defaults.formatter = Some(language_settings::SelectedFormatter::Auto)
});
let mut cx = EditorLspTestContext::new_rust(
@@ -9723,7 +9725,9 @@ async fn test_completions_in_languages_with_extra_word_characters(cx: &mut gpui:
#[gpui::test]
async fn test_document_format_with_prettier(cx: &mut gpui::TestAppContext) {
init_test(cx, |settings| {
- settings.defaults.formatter = Some(language_settings::Formatter::Prettier)
+ settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(
+ FormatterList(vec![Formatter::Prettier].into()),
+ ))
});
let fs = FakeFs::new(cx.executor());
@@ -9783,7 +9787,7 @@ async fn test_document_format_with_prettier(cx: &mut gpui::TestAppContext) {
);
update_test_language_settings(cx, |settings| {
- settings.defaults.formatter = Some(language_settings::Formatter::Auto)
+ settings.defaults.formatter = Some(language_settings::SelectedFormatter::Auto)
});
let format = editor.update(cx, |editor, cx| {
editor.perform_format(project.clone(), FormatTrigger::Manual, cx)
@@ -3,14 +3,19 @@
use crate::{File, Language, LanguageServerName};
use anyhow::Result;
use collections::{HashMap, HashSet};
+use core::slice;
use globset::{Glob, GlobMatcher, GlobSet, GlobSetBuilder};
use gpui::AppContext;
use itertools::{Either, Itertools};
use schemars::{
- schema::{InstanceType, ObjectValidation, Schema, SchemaObject},
+ schema::{InstanceType, ObjectValidation, Schema, SchemaObject, SingleOrVec},
JsonSchema,
};
-use serde::{Deserialize, Serialize};
+use serde::{
+ de::{self, IntoDeserializer, MapAccess, SeqAccess, Visitor},
+ Deserialize, Deserializer, Serialize,
+};
+use serde_json::Value;
use settings::{Settings, SettingsLocation, SettingsSources};
use std::{num::NonZeroU32, path::Path, sync::Arc};
use util::serde::default_true;
@@ -89,7 +94,7 @@ pub struct LanguageSettings {
/// when saving it.
pub ensure_final_newline_on_save: bool,
/// How to perform a buffer format.
- pub formatter: Formatter,
+ pub formatter: SelectedFormatter,
/// Zed's Prettier integration settings.
pub prettier: PrettierSettings,
/// Whether to use language servers to provide code intelligence.
@@ -274,7 +279,7 @@ pub struct LanguageSettingsContent {
///
/// Default: auto
#[serde(default)]
- pub formatter: Option<Formatter>,
+ pub formatter: Option<SelectedFormatter>,
/// 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.
@@ -381,24 +386,115 @@ pub enum SoftWrap {
}
/// Controls the behavior of formatting files when they are saved.
-#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
-#[serde(rename_all = "snake_case")]
+#[derive(Debug, Clone, PartialEq, Eq)]
pub enum FormatOnSave {
/// Files should be formatted on save.
On,
/// Files should not be formatted on save.
Off,
- /// Files should be formatted using the current language server.
- LanguageServer,
- /// The external program to use to format the files on save.
- External {
- /// The external program to run.
- command: Arc<str>,
- /// The arguments to pass to the program.
- arguments: Arc<[String]>,
- },
- /// Files should be formatted using code actions executed by language servers.
- CodeActions(HashMap<String, bool>),
+ List(FormatterList),
+}
+
+impl JsonSchema for FormatOnSave {
+ fn schema_name() -> String {
+ "OnSaveFormatter".into()
+ }
+
+ fn json_schema(generator: &mut schemars::r#gen::SchemaGenerator) -> Schema {
+ let mut schema = SchemaObject::default();
+ let formatter_schema = Formatter::json_schema(generator);
+ schema.instance_type = Some(
+ vec![
+ InstanceType::Object,
+ InstanceType::String,
+ InstanceType::Array,
+ ]
+ .into(),
+ );
+
+ let mut valid_raw_values = SchemaObject::default();
+ valid_raw_values.enum_values = Some(vec![
+ Value::String("on".into()),
+ Value::String("off".into()),
+ Value::String("prettier".into()),
+ Value::String("language_server".into()),
+ ]);
+ let mut nested_values = SchemaObject::default();
+
+ nested_values.array().items = Some(formatter_schema.clone().into());
+
+ schema.subschemas().any_of = Some(vec![
+ nested_values.into(),
+ valid_raw_values.into(),
+ formatter_schema,
+ ]);
+ schema.into()
+ }
+}
+
+impl Serialize for FormatOnSave {
+ fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
+ where
+ S: serde::Serializer,
+ {
+ match self {
+ Self::On => serializer.serialize_str("on"),
+ Self::Off => serializer.serialize_str("off"),
+ Self::List(list) => list.serialize(serializer),
+ }
+ }
+}
+
+impl<'de> Deserialize<'de> for FormatOnSave {
+ 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 = FormatOnSave;
+
+ fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
+ formatter.write_str("a valid on-save formatter kind")
+ }
+ fn visit_str<E>(self, v: &str) -> std::result::Result<Self::Value, E>
+ where
+ E: serde::de::Error,
+ {
+ if v == "on" {
+ Ok(Self::Value::On)
+ } else if v == "off" {
+ Ok(Self::Value::Off)
+ } else if v == "language_server" {
+ Ok(Self::Value::List(FormatterList(
+ Formatter::LanguageServer { name: None }.into(),
+ )))
+ } else {
+ let ret: Result<FormatterList, _> =
+ Deserialize::deserialize(v.into_deserializer());
+ ret.map(Self::Value::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(Self::Value::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(Self::Value::List)
+ }
+ }
+ deserializer.deserialize_any(FormatDeserializer)
+ }
}
/// Controls how whitespace should be displayedin the editor.
@@ -421,15 +517,131 @@ pub enum ShowWhitespaceSetting {
}
/// Controls which formatter should be used when formatting code.
-#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
-#[serde(rename_all = "snake_case")]
-pub enum Formatter {
+#[derive(Clone, Debug, Default, PartialEq, Eq)]
+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() -> String {
+ "Formatter".into()
+ }
+
+ fn json_schema(generator: &mut schemars::r#gen::SchemaGenerator) -> Schema {
+ let mut schema = SchemaObject::default();
+ let formatter_schema = Formatter::json_schema(generator);
+ schema.instance_type = Some(
+ vec![
+ InstanceType::Object,
+ InstanceType::String,
+ InstanceType::Array,
+ ]
+ .into(),
+ );
+
+ let mut valid_raw_values = SchemaObject::default();
+ valid_raw_values.enum_values = Some(vec![
+ Value::String("auto".into()),
+ Value::String("prettier".into()),
+ Value::String("language_server".into()),
+ ]);
+ let mut nested_values = SchemaObject::default();
+
+ nested_values.array().items = Some(formatter_schema.clone().into());
+
+ schema.subschemas().any_of = Some(vec![
+ nested_values.into(),
+ valid_raw_values.into(),
+ formatter_schema,
+ ]);
+ schema.into()
+ }
+}
+
+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(
+ Formatter::LanguageServer { name: None }.into(),
+ )))
+ } 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 formatter should be used when formatting code.
+#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
+#[serde(rename_all = "snake_case", transparent)]
+pub struct FormatterList(pub SingleOrVec<Formatter>);
+
+impl AsRef<[Formatter]> for FormatterList {
+ fn as_ref(&self) -> &[Formatter] {
+ match &self.0 {
+ SingleOrVec::Single(single) => slice::from_ref(single),
+ SingleOrVec::Vec(v) => &v,
+ }
+ }
+}
+
+/// Controls which formatter should be used when formatting code. If there are multiple formatters, they are executed in the order of declaration.
+#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
+#[serde(rename_all = "snake_case")]
+pub enum Formatter {
/// Format code using the current language server.
- LanguageServer,
+ LanguageServer { name: Option<String> },
/// Format code using Zed's Prettier integration.
Prettier,
/// Format code using an external command.
@@ -898,6 +1110,41 @@ pub struct PrettierSettings {
mod tests {
use super::*;
+ #[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));
+ let raw = "{\"formatter\": \"language_server\"}";
+ let settings: LanguageSettingsContent = serde_json::from_str(raw).unwrap();
+ assert_eq!(
+ settings.formatter,
+ Some(SelectedFormatter::List(FormatterList(
+ Formatter::LanguageServer { name: None }.into()
+ )))
+ );
+ 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![Formatter::LanguageServer { name: None }].into()
+ )))
+ );
+ let raw = "{\"formatter\": [{\"language_server\": {\"name\": null}}, \"prettier\"]}";
+ let settings: LanguageSettingsContent = serde_json::from_str(raw).unwrap();
+ assert_eq!(
+ settings.formatter,
+ Some(SelectedFormatter::List(FormatterList(
+ vec![
+ Formatter::LanguageServer { name: None },
+ Formatter::Prettier
+ ]
+ .into()
+ )))
+ );
+ }
+
#[test]
pub fn test_resolve_language_servers() {
fn language_server_names(names: &[&str]) -> Vec<LanguageServerName> {
@@ -13,7 +13,7 @@ use futures::{
};
use gpui::{AsyncAppContext, Model, ModelContext, Task, WeakModel};
use language::{
- language_settings::{Formatter, LanguageSettings},
+ language_settings::{Formatter, LanguageSettings, SelectedFormatter},
Buffer, LanguageServerName, LocalFile,
};
use lsp::{LanguageServer, LanguageServerId};
@@ -30,8 +30,12 @@ pub fn prettier_plugins_for_language(
language_settings: &LanguageSettings,
) -> Option<&HashSet<String>> {
match &language_settings.formatter {
- Formatter::Prettier { .. } | Formatter::Auto => Some(&language_settings.prettier.plugins),
- Formatter::LanguageServer | Formatter::External { .. } | Formatter::CodeActions(_) => None,
+ SelectedFormatter::Auto => Some(&language_settings.prettier.plugins),
+
+ SelectedFormatter::List(list) => list
+ .as_ref()
+ .contains(&Formatter::Prettier)
+ .then_some(&language_settings.prettier.plugins),
}
}
@@ -43,7 +43,7 @@ use itertools::Itertools;
use language::{
language_settings::{
language_settings, AllLanguageSettings, FormatOnSave, Formatter, InlayHintKind,
- LanguageSettings,
+ LanguageSettings, SelectedFormatter,
},
markdown, point_to_lsp, prepare_completion_documentation,
proto::{
@@ -5056,107 +5056,180 @@ impl Project {
.as_ref()
.zip(buffer_abs_path.as_ref());
- let mut format_operation = None;
let prettier_settings = buffer.read_with(&mut cx, |buffer, cx| {
language_settings(buffer.language(), buffer.file(), cx)
.prettier
.clone()
})?;
- match (&settings.formatter, &settings.format_on_save) {
- (_, FormatOnSave::Off) if trigger == FormatTrigger::Save => {}
-
- (Formatter::CodeActions(code_actions), FormatOnSave::On | FormatOnSave::Off)
- | (_, FormatOnSave::CodeActions(code_actions)) => {
- let code_actions = deserialize_code_actions(code_actions);
- if !code_actions.is_empty() {
- Self::execute_code_actions_on_servers(
- &project,
- &adapters_and_servers,
- code_actions,
- buffer,
- push_to_history,
- &mut project_transaction,
- &mut cx,
- )
- .await?;
- }
- }
- (Formatter::LanguageServer, FormatOnSave::On | FormatOnSave::Off)
- | (_, FormatOnSave::LanguageServer) => {
- if let Some((language_server, buffer_abs_path)) = server_and_buffer {
- format_operation = Some(FormatOperation::Lsp(
- Self::format_via_lsp(
- &project,
- buffer,
- buffer_abs_path,
- language_server,
- &settings,
- &mut cx,
- )
- .await
- .context("failed to format via language server")?,
- ));
- }
- }
- (
- Formatter::External { command, arguments },
- FormatOnSave::On | FormatOnSave::Off,
- )
- | (_, FormatOnSave::External { command, arguments }) => {
- let buffer_abs_path = buffer_abs_path.as_ref().map(|path| path.as_path());
- format_operation = Self::format_via_external_command(
- buffer,
- buffer_abs_path,
- command,
- arguments,
- &mut cx,
- )
- .await
- .context(format!(
- "failed to format via external command {:?}",
- command
- ))?
- .map(FormatOperation::External);
- }
- (Formatter::Auto, FormatOnSave::On | FormatOnSave::Off) => {
- let prettier = if prettier_settings.allowed {
- prettier_support::format_with_prettier(&project, buffer, &mut cx)
- .await
- .transpose()
- .ok()
- .flatten()
- } else {
- None
- };
+ let mut format_operations: Vec<FormatOperation> = vec![];
+ {
+ match trigger {
+ FormatTrigger::Save => {
+ match &settings.format_on_save {
+ FormatOnSave::Off => {
+ // nothing
+ }
+ FormatOnSave::On => {
+ match &settings.formatter {
+ SelectedFormatter::Auto => {
+ // do the auto-format: prefer prettier, fallback to primary language server
+ let diff = {
+ if prettier_settings.allowed {
+ Self::perform_format(
+ &Formatter::Prettier,
+ server_and_buffer,
+ project.clone(),
+ buffer,
+ buffer_abs_path,
+ &settings,
+ &adapters_and_servers,
+ push_to_history,
+ &mut project_transaction,
+ &mut cx,
+ )
+ .await
+ } else {
+ Self::perform_format(
+ &Formatter::LanguageServer { name: None },
+ server_and_buffer,
+ project.clone(),
+ buffer,
+ buffer_abs_path,
+ &settings,
+ &adapters_and_servers,
+ push_to_history,
+ &mut project_transaction,
+ &mut cx,
+ )
+ .await
+ }
+ }
+ .log_err()
+ .flatten();
+ if let Some(op) = diff {
+ format_operations.push(op);
+ }
+ }
+ SelectedFormatter::List(formatters) => {
+ for formatter in formatters.as_ref() {
+ let diff = Self::perform_format(
+ formatter,
+ server_and_buffer,
+ project.clone(),
+ buffer,
+ buffer_abs_path,
+ &settings,
+ &adapters_and_servers,
+ push_to_history,
+ &mut project_transaction,
+ &mut cx,
+ )
+ .await
+ .log_err()
+ .flatten();
+ if let Some(op) = diff {
+ format_operations.push(op);
+ }
- if let Some(operation) = prettier {
- format_operation = Some(operation);
- } else if let Some((language_server, buffer_abs_path)) = server_and_buffer {
- format_operation = Some(FormatOperation::Lsp(
- Self::format_via_lsp(
- &project,
- buffer,
- buffer_abs_path,
- language_server,
- &settings,
- &mut cx,
- )
- .await
- .context("failed to format via language server")?,
- ));
+ // format with formatter
+ }
+ }
+ }
+ }
+ FormatOnSave::List(formatters) => {
+ for formatter in formatters.as_ref() {
+ let diff = Self::perform_format(
+ &formatter,
+ server_and_buffer,
+ project.clone(),
+ buffer,
+ buffer_abs_path,
+ &settings,
+ &adapters_and_servers,
+ push_to_history,
+ &mut project_transaction,
+ &mut cx,
+ )
+ .await
+ .log_err()
+ .flatten();
+ if let Some(op) = diff {
+ format_operations.push(op);
+ }
+ }
+ }
+ }
}
- }
- (Formatter::Prettier, FormatOnSave::On | FormatOnSave::Off) => {
- if prettier_settings.allowed {
- if let Some(operation) =
- prettier_support::format_with_prettier(&project, buffer, &mut cx).await
- {
- format_operation = Some(operation?);
+ FormatTrigger::Manual => {
+ match &settings.formatter {
+ SelectedFormatter::Auto => {
+ // do the auto-format: prefer prettier, fallback to primary language server
+ let diff = {
+ if prettier_settings.allowed {
+ Self::perform_format(
+ &Formatter::Prettier,
+ server_and_buffer,
+ project.clone(),
+ buffer,
+ buffer_abs_path,
+ &settings,
+ &adapters_and_servers,
+ push_to_history,
+ &mut project_transaction,
+ &mut cx,
+ )
+ .await
+ } else {
+ Self::perform_format(
+ &Formatter::LanguageServer { name: None },
+ server_and_buffer,
+ project.clone(),
+ buffer,
+ buffer_abs_path,
+ &settings,
+ &adapters_and_servers,
+ push_to_history,
+ &mut project_transaction,
+ &mut cx,
+ )
+ .await
+ }
+ }
+ .log_err()
+ .flatten();
+
+ if let Some(op) = diff {
+ format_operations.push(op)
+ }
+ }
+ SelectedFormatter::List(formatters) => {
+ for formatter in formatters.as_ref() {
+ // format with formatter
+ let diff = Self::perform_format(
+ formatter,
+ server_and_buffer,
+ project.clone(),
+ buffer,
+ buffer_abs_path,
+ &settings,
+ &adapters_and_servers,
+ push_to_history,
+ &mut project_transaction,
+ &mut cx,
+ )
+ .await
+ .log_err()
+ .flatten();
+ if let Some(op) = diff {
+ format_operations.push(op);
+ }
+ }
+ }
}
}
}
- };
+ }
buffer.update(&mut cx, |b, cx| {
// If the buffer had its whitespace formatted and was edited while the language-specific
@@ -5166,13 +5239,13 @@ impl Project {
if b.peek_undo_stack()
.map_or(true, |e| e.transaction_id() != transaction_id)
{
- format_operation.take();
+ format_operations.clear();
}
}
// Apply any language-specific formatting, and group the two formatting operations
// in the buffer's undo history.
- if let Some(operation) = format_operation {
+ for operation in format_operations {
match operation {
FormatOperation::Lsp(edits) => {
b.edit(edits, None, cx);
@@ -5204,6 +5277,91 @@ impl Project {
Ok(project_transaction)
}
+ #[allow(clippy::too_many_arguments)]
+ async fn perform_format(
+ formatter: &Formatter,
+ primary_server_and_buffer: Option<(&Arc<LanguageServer>, &PathBuf)>,
+ project: WeakModel<Project>,
+ buffer: &Model<Buffer>,
+ buffer_abs_path: &Option<PathBuf>,
+ settings: &LanguageSettings,
+ adapters_and_servers: &Vec<(Arc<CachedLspAdapter>, Arc<LanguageServer>)>,
+ push_to_history: bool,
+ transaction: &mut ProjectTransaction,
+ mut cx: &mut AsyncAppContext,
+ ) -> Result<Option<FormatOperation>, anyhow::Error> {
+ let result = match formatter {
+ Formatter::LanguageServer { name } => {
+ if let Some((language_server, buffer_abs_path)) = primary_server_and_buffer {
+ let language_server = if let Some(name) = name {
+ adapters_and_servers
+ .iter()
+ .find_map(|(adapter, server)| {
+ adapter.name.0.as_ref().eq(name.as_str()).then_some(server)
+ })
+ .unwrap_or_else(|| language_server)
+ } else {
+ language_server
+ };
+ Some(FormatOperation::Lsp(
+ Self::format_via_lsp(
+ &project,
+ buffer,
+ buffer_abs_path,
+ language_server,
+ settings,
+ cx,
+ )
+ .await
+ .context("failed to format via language server")?,
+ ))
+ } else {
+ None
+ }
+ }
+ Formatter::Prettier => {
+ prettier_support::format_with_prettier(&project, buffer, &mut cx)
+ .await
+ .transpose()
+ .ok()
+ .flatten()
+ }
+ Formatter::External { command, arguments } => {
+ let buffer_abs_path = buffer_abs_path.as_ref().map(|path| path.as_path());
+ Self::format_via_external_command(
+ buffer,
+ buffer_abs_path,
+ &command,
+ &arguments,
+ &mut cx,
+ )
+ .await
+ .context(format!(
+ "failed to format via external command {:?}",
+ command
+ ))?
+ .map(FormatOperation::External)
+ }
+ Formatter::CodeActions(code_actions) => {
+ let code_actions = deserialize_code_actions(&code_actions);
+ if !code_actions.is_empty() {
+ Self::execute_code_actions_on_servers(
+ &project,
+ &adapters_and_servers,
+ code_actions,
+ buffer,
+ push_to_history,
+ transaction,
+ cx,
+ )
+ .await?;
+ }
+ None
+ }
+ };
+ anyhow::Ok(result)
+ }
+
async fn format_via_lsp(
this: &WeakModel<Self>,
buffer: &Model<Buffer>,
@@ -601,6 +601,21 @@ To override settings for a language, add an entry for that language server's nam
}
```
+4. Or to use multiple formatters consecutively, use an array of formatters:
+```json
+{
+ "formatter": [
+ {"language_server": {"name": "rust-analyzer"}},
+ {"external": {
+ "command": "sed",
+ "arguments": ["-e", "s/ *$//"]
+ }
+ ]
+}
+```
+Here `rust-analyzer` will be used first to format the code, followed by a call of sed.
+If any of the formatters fails, the subsequent ones will still be executed.
+
## Code Actions On Format
- Description: The code actions to perform with the primary language server when formatting the buffer.