@@ -19095,6 +19095,109 @@ async fn test_document_format_with_prettier(cx: &mut TestAppContext) {
);
}
+#[gpui::test]
+async fn test_document_format_with_prettier_explicit_language(cx: &mut TestAppContext) {
+ init_test(cx, |settings| {
+ settings.defaults.formatter = Some(FormatterList::Single(Formatter::Prettier))
+ });
+
+ let fs = FakeFs::new(cx.executor());
+ fs.insert_file(path!("/file.settings"), Default::default())
+ .await;
+
+ let project = Project::test(fs, [path!("/file.settings").as_ref()], cx).await;
+ let language_registry = project.read_with(cx, |project, _| project.languages().clone());
+
+ let ts_lang = Arc::new(Language::new(
+ LanguageConfig {
+ name: "TypeScript".into(),
+ matcher: LanguageMatcher {
+ path_suffixes: vec!["ts".to_string()],
+ ..LanguageMatcher::default()
+ },
+ prettier_parser_name: Some("typescript".to_string()),
+ ..LanguageConfig::default()
+ },
+ Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
+ ));
+
+ language_registry.add(ts_lang.clone());
+
+ update_test_language_settings(cx, |settings| {
+ settings.defaults.prettier.get_or_insert_default().allowed = Some(true);
+ });
+
+ let test_plugin = "test_plugin";
+ let _ = language_registry.register_fake_lsp(
+ "TypeScript",
+ FakeLspAdapter {
+ prettier_plugins: vec![test_plugin],
+ ..Default::default()
+ },
+ );
+
+ let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
+ let buffer = project
+ .update(cx, |project, cx| {
+ project.open_local_buffer(path!("/file.settings"), cx)
+ })
+ .await
+ .unwrap();
+
+ project.update(cx, |project, cx| {
+ project.set_language_for_buffer(&buffer, ts_lang, cx)
+ });
+
+ let buffer_text = "one\ntwo\nthree\n";
+ let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
+ let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
+ editor.update_in(cx, |editor, window, cx| {
+ editor.set_text(buffer_text, window, cx)
+ });
+
+ editor
+ .update_in(cx, |editor, window, cx| {
+ editor.perform_format(
+ project.clone(),
+ FormatTrigger::Manual,
+ FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
+ window,
+ cx,
+ )
+ })
+ .unwrap()
+ .await;
+ assert_eq!(
+ editor.update(cx, |editor, cx| editor.text(cx)),
+ buffer_text.to_string() + prettier_format_suffix + "\ntypescript",
+ "Test prettier formatting was not applied to the original buffer text",
+ );
+
+ update_test_language_settings(cx, |settings| {
+ settings.defaults.formatter = Some(FormatterList::default())
+ });
+ let format = editor.update_in(cx, |editor, window, cx| {
+ editor.perform_format(
+ project.clone(),
+ FormatTrigger::Manual,
+ FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
+ window,
+ cx,
+ )
+ });
+ format.await.unwrap();
+
+ assert_eq!(
+ editor.update(cx, |editor, cx| editor.text(cx)),
+ buffer_text.to_string()
+ + prettier_format_suffix
+ + "\ntypescript\n"
+ + prettier_format_suffix
+ + "\ntypescript",
+ "Autoformatting (via test prettier) was not applied to the original buffer text",
+ );
+}
+
#[gpui::test]
async fn test_addition_reverts(cx: &mut TestAppContext) {
init_test(cx, |_| {});
@@ -2,7 +2,8 @@ use anyhow::Context as _;
use collections::{HashMap, HashSet};
use fs::Fs;
use gpui::{AsyncApp, Entity};
-use language::{Buffer, Diff, language_settings::language_settings};
+use language::language_settings::PrettierSettings;
+use language::{Buffer, Diff, Language, language_settings::language_settings};
use lsp::{LanguageServer, LanguageServerId};
use node_runtime::NodeRuntime;
use paths::default_prettier_dir;
@@ -349,7 +350,7 @@ impl Prettier {
Self::Real(local) => {
let params = buffer
.update(cx, |buffer, cx| {
- let buffer_language = buffer.language();
+ let buffer_language = buffer.language().map(|language| language.as_ref());
let language_settings = language_settings(buffer_language.map(|l| l.name()), buffer.file(), cx);
let prettier_settings = &language_settings.prettier;
anyhow::ensure!(
@@ -449,15 +450,7 @@ impl Prettier {
})
.collect();
- let mut prettier_parser = prettier_settings.parser.as_deref();
- if buffer_path.is_none() {
- prettier_parser = prettier_parser.or_else(|| buffer_language.and_then(|language| language.prettier_parser_name()));
- if prettier_parser.is_none() {
- log::error!("Formatting unsaved file with prettier failed. No prettier parser configured for language {buffer_language:?}");
- anyhow::bail!("Cannot determine prettier parser for unsaved file");
- }
-
- }
+ let parser = prettier_parser_name(buffer_path.as_deref(), buffer_language, prettier_settings).context("getting prettier parser")?;
let ignore_path = ignore_dir.and_then(|dir| {
let ignore_file = dir.join(".prettierignore");
@@ -475,15 +468,15 @@ impl Prettier {
anyhow::Ok(FormatParams {
text: buffer.text(),
options: FormatOptions {
- parser: prettier_parser.map(ToOwned::to_owned),
- plugins,
path: buffer_path,
+ parser,
+ plugins,
prettier_options,
ignore_path,
},
})
- })?
- .context("building prettier request")?;
+ })?
+ .context("building prettier request")?;
let response = local
.server
@@ -503,7 +496,26 @@ impl Prettier {
{
Some("rust") => anyhow::bail!("prettier does not support Rust"),
Some(_other) => {
- let formatted_text = buffer.text() + FORMAT_SUFFIX;
+ let mut formatted_text = buffer.text() + FORMAT_SUFFIX;
+
+ let buffer_language =
+ buffer.language().map(|language| language.as_ref());
+ let language_settings = language_settings(
+ buffer_language.map(|l| l.name()),
+ buffer.file(),
+ cx,
+ );
+ let prettier_settings = &language_settings.prettier;
+ let parser = prettier_parser_name(
+ buffer_path.as_deref(),
+ buffer_language,
+ prettier_settings,
+ )?;
+
+ if let Some(parser) = parser {
+ formatted_text = format!("{formatted_text}\n{parser}");
+ }
+
Ok(buffer.diff(formatted_text, cx))
}
None => panic!("Should not format buffer without a language with prettier"),
@@ -551,6 +563,40 @@ impl Prettier {
}
}
+fn prettier_parser_name(
+ buffer_path: Option<&Path>,
+ buffer_language: Option<&Language>,
+ prettier_settings: &PrettierSettings,
+) -> anyhow::Result<Option<String>> {
+ let parser = if buffer_path.is_none() {
+ let parser = prettier_settings
+ .parser
+ .as_deref()
+ .or_else(|| buffer_language.and_then(|language| language.prettier_parser_name()));
+ if parser.is_none() {
+ log::error!(
+ "Formatting unsaved file with prettier failed. No prettier parser configured for language {buffer_language:?}"
+ );
+ anyhow::bail!("Cannot determine prettier parser for unsaved file");
+ }
+ parser
+ } else if let (Some(buffer_language), Some(buffer_path)) = (buffer_language, buffer_path)
+ && buffer_path.extension().is_some_and(|extension| {
+ !buffer_language
+ .config()
+ .matcher
+ .path_suffixes
+ .contains(&extension.to_string_lossy().into_owned())
+ })
+ {
+ buffer_language.prettier_parser_name()
+ } else {
+ prettier_settings.parser.as_deref()
+ };
+
+ Ok(parser.map(ToOwned::to_owned))
+}
+
async fn has_prettier_in_node_modules(fs: &dyn Fs, path: &Path) -> anyhow::Result<bool> {
let possible_node_modules_location = path.join("node_modules").join(PRETTIER_PACKAGE_NAME);
if let Some(node_modules_location_metadata) = fs