Allow to configure default prettier

Kirill Bulatov created

Change summary

Cargo.lock                               |  1 
assets/settings/default.json             | 16 ++++++++++
crates/language/src/language_settings.rs |  8 +++--
crates/prettier/Cargo.toml               |  1 
crates/prettier/src/prettier.rs          | 36 +++++++++++++++++++++----
crates/prettier/src/prettier_server.js   |  8 +++++
crates/project/src/project.rs            |  3 -
7 files changed, 60 insertions(+), 13 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -5523,6 +5523,7 @@ name = "prettier"
 version = "0.1.0"
 dependencies = [
  "anyhow",
+ "collections",
  "fs",
  "futures 0.3.28",
  "gpui",

assets/settings/default.json 🔗

@@ -199,7 +199,11 @@
   //         "arguments": ["--stdin-filepath", "{buffer_path}"]
   //       }
   //     }
-  // TODO kb description
+  // 3. Format code using Zed's Prettier integration:
+  //     "formatter": "prettier"
+  // 4. Default. Format files using Zed's Prettier integration (if applicable),
+  //    or falling back to formatting via language server:
+  //     "formatter": "auto"
   "formatter": "auto",
   // How to soft-wrap long lines of text. This setting can take
   // three values:
@@ -430,6 +434,16 @@
       "tab_size": 2
     }
   },
+  // Zed's Prettier integration settings.
+  // If Prettier is enabled, Zed will use this its Prettier instance for any applicable file, if
+  // project has no other Prettier installed.
+  "prettier": {
+    // Use regular Prettier json configuration:
+    // "trailingComma": "es5",
+    // "tabWidth": 4,
+    // "semi": false,
+    // "singleQuote": true
+  },
   // LSP Specific settings.
   "lsp": {
     // Specify the LSP name as a key here.

crates/language/src/language_settings.rs 🔗

@@ -50,6 +50,7 @@ pub struct LanguageSettings {
     pub remove_trailing_whitespace_on_save: bool,
     pub ensure_final_newline_on_save: bool,
     pub formatter: Formatter,
+    pub prettier: HashMap<String, serde_json::Value>,
     pub enable_language_server: bool,
     pub show_copilot_suggestions: bool,
     pub show_whitespaces: ShowWhitespaceSetting,
@@ -98,6 +99,8 @@ pub struct LanguageSettingsContent {
     #[serde(default)]
     pub formatter: Option<Formatter>,
     #[serde(default)]
+    pub prettier: Option<HashMap<String, serde_json::Value>>,
+    #[serde(default)]
     pub enable_language_server: Option<bool>,
     #[serde(default)]
     pub show_copilot_suggestions: Option<bool>,
@@ -155,9 +158,7 @@ pub enum Formatter {
     #[default]
     Auto,
     LanguageServer,
-    Prettier {
-        config: (), // Support some of the most important settings in the prettier-vscode extension.
-    },
+    Prettier,
     External {
         command: Arc<str>,
         arguments: Arc<[String]>,
@@ -397,6 +398,7 @@ fn merge_settings(settings: &mut LanguageSettings, src: &LanguageSettingsContent
         src.preferred_line_length,
     );
     merge(&mut settings.formatter, src.formatter.clone());
+    merge(&mut settings.prettier, src.prettier.clone());
     merge(&mut settings.format_on_save, src.format_on_save.clone());
     merge(
         &mut settings.remove_trailing_whitespace_on_save,

crates/prettier/Cargo.toml 🔗

@@ -7,6 +7,7 @@ edition = "2021"
 path = "src/prettier.rs"
 
 [dependencies]
+collections = { path = "../collections"}
 language = { path = "../language" }
 gpui = { path = "../gpui" }
 fs = { path = "../fs" }

crates/prettier/src/prettier.rs 🔗

@@ -1,8 +1,9 @@
-use std::collections::{HashMap, VecDeque};
+use std::collections::VecDeque;
 use std::path::{Path, PathBuf};
 use std::sync::Arc;
 
 use anyhow::Context;
+use collections::HashMap;
 use fs::Fs;
 use gpui::{AsyncAppContext, ModelHandle};
 use language::language_settings::language_settings;
@@ -202,7 +203,6 @@ impl Prettier {
         let params = buffer.read_with(cx, |buffer, cx| {
             let buffer_file = buffer.file();
             let buffer_language = buffer.language();
-            let language_settings = language_settings(buffer_language, buffer_file, cx);
             let path = buffer_file
                 .map(|file| file.full_path(cx))
                 .map(|path| path.to_path_buf());
@@ -217,14 +217,38 @@ impl Prettier {
                         }
                     })
             });
-            let tab_width = Some(language_settings.tab_size.get());
+
+            let prettier_options = if self.default {
+                let language_settings = language_settings(buffer_language, buffer_file, cx);
+                let mut options = language_settings.prettier.clone();
+                if !options.contains_key("tabWidth") {
+                    options.insert(
+                        "tabWidth".to_string(),
+                        serde_json::Value::Number(serde_json::Number::from(
+                            language_settings.tab_size.get(),
+                        )),
+                    );
+                }
+                if !options.contains_key("printWidth") {
+                    options.insert(
+                        "printWidth".to_string(),
+                        serde_json::Value::Number(serde_json::Number::from(
+                            language_settings.preferred_line_length,
+                        )),
+                    );
+                }
+                Some(options)
+            } else {
+                None
+            };
+
             FormatParams {
                 text: buffer.text(),
                 options: FormatOptions {
                     parser,
                     // TODO kb is not absolute now
                     path,
-                    tab_width,
+                    prettier_options,
                 },
             }
         });
@@ -318,13 +342,13 @@ struct FormatParams {
     options: FormatOptions,
 }
 
-#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
+#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)]
 #[serde(rename_all = "camelCase")]
 struct FormatOptions {
     parser: Option<String>,
     #[serde(rename = "filepath")]
     path: Option<PathBuf>,
-    tab_width: Option<u32>,
+    prettier_options: Option<HashMap<String, serde_json::Value>>,
 }
 
 #[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]

crates/prettier/src/prettier_server.js 🔗

@@ -149,7 +149,13 @@ async function handleMessage(message, prettier) {
         if (params.options === undefined) {
             throw new Error(`Message params.options is undefined: ${JSON.stringify(message)}`);
         }
-        const formattedText = await prettier.prettier.format(params.text, { ...prettier.config, ...params.options });
+
+        const options = {
+            ...(params.options.prettierOptions || prettier.config),
+            parser: params.options.parser,
+            path: params.options.path
+        };
+        const formattedText = await prettier.prettier.format(params.text, options);
         sendResponse({ id, result: { text: formattedText } });
     } else if (method === 'prettier/clear_cache') {
         prettier.prettier.clearConfigCache();

crates/project/src/project.rs 🔗

@@ -911,7 +911,6 @@ impl Project {
                 .detach();
         }
 
-        // TODO kb restart all default formatters if Zed prettier settings change
         for (worktree, language, settings) in language_formatters_to_check {
             self.maybe_start_default_formatters(worktree, &language, &settings, cx);
         }
@@ -8428,7 +8427,7 @@ impl Project {
     }
 
     fn maybe_start_default_formatters(
-        &mut self,
+        &self,
         worktree: Option<WorktreeId>,
         new_language: &Language,
         language_settings: &LanguageSettings,