mirror of
https://github.com/zed-industries/zed.git
synced 2024-11-07 20:39:04 +03:00
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:
parent
53b711c2b4
commit
3d1bf09299
@ -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")?,
|
||||
));
|
||||
}
|
||||
}
|
||||
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>,
|
||||
|
@ -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.
|
||||
|
Loading…
Reference in New Issue
Block a user