Allow formatting of unsaved buffers with prettier (#12095)

Thorsten Ball created

This fixes #4529 by allowing unsaved buffers to be formatted with
prettier.

Steps to do that:

1. Create a new buffer
2. Set language for the buffer (e.g.: `language selector: toggle` and
JSON)
3. In settings, set prettier parser for language (can't be inferred,
since we don't have filename) and allow formatting with prettier:

   ```json
   {
     "languages": {
       "JSON": {
         "prettier": {
           "allowed": true,
           "parser": "json"
         }
       }
     }
   }
   ```

4. Use `editor: format`

Release Notes:

- Added ability to format unsaved buffers with Prettier. Requirement is
to set a Prettier parser in the user settings. Example for JSON: `{
"languages": { "JSON": { "prettier": { "allowed": true, "parser": "json"
} } } }` ([#4529](https://github.com/zed-industries/zed/issues/4529)).

Demo:


https://github.com/zed-industries/zed/assets/1185253/d24e490b-2e2c-4a5d-95a8-fc8675523780

Change summary

crates/prettier/src/prettier.rs        | 9 ++++++++-
crates/prettier/src/prettier_server.js | 8 +++++++-
crates/project/src/prettier_support.rs | 3 ++-
crates/project/src/project.rs          | 8 ++++----
4 files changed, 21 insertions(+), 7 deletions(-)

Detailed changes

crates/prettier/src/prettier.rs 🔗

@@ -1,4 +1,4 @@
-use anyhow::Context;
+use anyhow::{anyhow, Context};
 use collections::{HashMap, HashSet};
 use fs::Fs;
 use gpui::{AsyncAppContext, Model};
@@ -315,6 +315,12 @@ impl Prettier {
                                 }
                             })
                             .collect();
+
+                        if prettier_settings.parser.is_none() && buffer_path.is_none() {
+                            log::error!("Formatting unsaved file with prettier failed. No prettier parser configured for language");
+                            return Err(anyhow!("Cannot determine prettier parser for unsaved file"));
+                        }
+
                         log::debug!(
                             "Formatting file {:?} with prettier, plugins :{:?}, options: {:?}",
                             buffer.file().map(|f| f.full_path(cx)),
@@ -333,6 +339,7 @@ impl Prettier {
                         })
                     })?
                     .context("prettier params calculation")?;
+
                 let response = local
                     .server
                     .request::<Format>(params)

crates/prettier/src/prettier_server.js 🔗

@@ -186,11 +186,17 @@ async function handleMessage(message, prettier) {
     }
 
     let resolvedConfig = {};
-    if (params.options.filepath !== undefined) {
+    if (params.options.filepath) {
       resolvedConfig =
         (await prettier.prettier.resolveConfig(params.options.filepath)) || {};
     }
 
+    // Marking the params.options.filepath as undefined makes
+    // prettier.format() work even if no filepath is set.
+    if (params.options.filepath === null) {
+      params.options.filepath = undefined;
+    }
+
     const plugins =
       Array.isArray(resolvedConfig?.plugins) &&
       resolvedConfig.plugins.length > 0

crates/project/src/prettier_support.rs 🔗

@@ -61,7 +61,8 @@ pub(super) async fn format_with_prettier(
                 .update(cx, |buffer, cx| {
                     File::from_dyn(buffer.file()).map(|file| file.abs_path(cx))
                 })
-                .ok()?;
+                .ok()
+                .flatten();
 
             let format_result = prettier
                 .format(buffer, buffer_path, cx)

crates/project/src/project.rs 🔗

@@ -4702,11 +4702,11 @@ impl Project {
         if self.is_local() {
             let buffers_with_paths = buffers
                 .into_iter()
-                .filter_map(|buffer_handle| {
+                .map(|buffer_handle| {
                     let buffer = buffer_handle.read(cx);
-                    let file = File::from_dyn(buffer.file())?;
-                    let buffer_abs_path = file.as_local().map(|f| f.abs_path(cx));
-                    Some((buffer_handle, buffer_abs_path))
+                    let buffer_abs_path = File::from_dyn(buffer.file())
+                        .and_then(|file| file.as_local().map(|f| f.abs_path(cx)));
+                    (buffer_handle, buffer_abs_path)
                 })
                 .collect::<Vec<_>>();