Allow user to use multiple formatters (#14846)

Fixes #4822
- [x] Release note
- [ ] Surface formatting errors via a toast
- [x] Doc updates
- [x] Have "language-server" accept an optional name of the server.

Release Notes:

- `format` and `format_on_save` now accept an array of formatting
actions to run.
- `language_server` formatter option now accepts the name of a language
server to use (e.g. `{"language_server": {"name: "ruff"}}`); when not
specified, a primary language server is used.

---------

Co-authored-by: Thorsten <thorsten@zed.dev>
This commit is contained in:
Piotr Osiewicz 2024-07-23 20:05:09 +02:00 committed by GitHub
parent 53b711c2b4
commit 3d1bf09299
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 565 additions and 130 deletions

View File

@ -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"] }

View File

@ -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()

View File

@ -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)

View File

@ -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> {

View File

@ -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),
}
}

View File

@ -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")?,
));
}
}
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);
}
(
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
};
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>,

View File

@ -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.