Draft a project part of the prettier

Kirill Bulatov created

Change summary

Cargo.lock                               |  7 +++
crates/language/src/language_settings.rs |  7 ++
crates/prettier/Cargo.toml               | 15 ++++++
crates/prettier/src/prettier.rs          | 50 +++++++++++++++++++----
crates/project/Cargo.toml                |  1 
crates/project/src/project.rs            | 56 +++++++++++++++++++++++++
6 files changed, 122 insertions(+), 14 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -5520,6 +5520,12 @@ checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
 [[package]]
 name = "prettier"
 version = "0.1.0"
+dependencies = [
+ "anyhow",
+ "fs",
+ "gpui",
+ "language",
+]
 
 [[package]]
 name = "pretty_assertions"
@@ -5635,6 +5641,7 @@ dependencies = [
  "lsp",
  "parking_lot 0.11.2",
  "postage",
+ "prettier",
  "pretty_assertions",
  "rand 0.8.5",
  "regex",

crates/language/src/language_settings.rs 🔗

@@ -149,10 +149,15 @@ pub enum ShowWhitespaceSetting {
     All,
 }
 
-#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
+#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
 #[serde(rename_all = "snake_case")]
 pub enum Formatter {
+    #[default]
+    Auto,
     LanguageServer,
+    Prettier {
+        config: (), // Support some of the most important settings in the prettier-vscode extension.
+    },
     External {
         command: Arc<str>,
         arguments: Arc<[String]>,

crates/prettier/Cargo.toml 🔗

@@ -3,7 +3,18 @@ name = "prettier"
 version = "0.1.0"
 edition = "2021"
 
-[dependencies]
-
 [lib]
 path = "src/prettier.rs"
+
+[dependencies]
+language = { path = "../language" }
+gpui = { path = "../gpui" }
+fs = { path = "../fs" }
+
+anyhow.workspace = true
+
+
+[dev-dependencies]
+language = { path = "../language", features = ["test-support"] }
+gpui = { path = "../gpui", features = ["test-support"] }
+fs = { path = "../fs",  features = ["test-support"] }

crates/prettier/src/prettier.rs 🔗

@@ -1,14 +1,46 @@
-pub fn add(left: usize, right: usize) -> usize {
-    left + right
+pub use std::path::{Path, PathBuf};
+pub use std::sync::Arc;
+
+use fs::Fs;
+use gpui::ModelHandle;
+use language::{Buffer, Diff};
+
+pub struct Prettier {
+    _private: (),
 }
 
-#[cfg(test)]
-mod tests {
-    use super::*;
+type NodeRuntime = ();
+
+impl Prettier {
+    // This was taken from the prettier-vscode extension.
+    pub const CONFIG_FILE_NAMES: &'static [&'static str] = &[
+        ".prettierrc",
+        ".prettierrc.json",
+        ".prettierrc.json5",
+        ".prettierrc.yaml",
+        ".prettierrc.yml",
+        ".prettierrc.toml",
+        ".prettierrc.js",
+        ".prettierrc.cjs",
+        "package.json",
+        "prettier.config.js",
+        "prettier.config.cjs",
+        ".editorconfig",
+    ];
+
+    pub async fn locate(starting_path: Option<&Path>, fs: Arc<dyn Fs>) -> PathBuf {
+        todo!()
+    }
+
+    pub async fn start(prettier_path: &Path, node: Arc<NodeRuntime>) -> anyhow::Result<Self> {
+        todo!()
+    }
+
+    pub async fn format(&self, buffer: &ModelHandle<Buffer>) -> anyhow::Result<Diff> {
+        todo!()
+    }
 
-    #[test]
-    fn it_works() {
-        let result = add(2, 2);
-        assert_eq!(result, 4);
+    pub async fn clear_cache(&self) -> anyhow::Result<()> {
+        todo!()
     }
 }

crates/project/Cargo.toml 🔗

@@ -31,6 +31,7 @@ git = { path = "../git" }
 gpui = { path = "../gpui" }
 language = { path = "../language" }
 lsp = { path = "../lsp" }
+prettier = { path = "../prettier" }
 rpc = { path = "../rpc" }
 settings = { path = "../settings" }
 sum_tree = { path = "../sum_tree" }

crates/project/src/project.rs 🔗

@@ -50,6 +50,7 @@ use lsp::{
 };
 use lsp_command::*;
 use postage::watch;
+use prettier::Prettier;
 use project_settings::{LspSettings, ProjectSettings};
 use rand::prelude::*;
 use search::SearchQuery;
@@ -152,6 +153,7 @@ pub struct Project {
     copilot_lsp_subscription: Option<gpui::Subscription>,
     copilot_log_subscription: Option<lsp::Subscription>,
     current_lsp_settings: HashMap<Arc<str>, LspSettings>,
+    prettier_instances: HashMap<(WorktreeId, PathBuf), Shared<Task<Result<Arc<Prettier>>>>>,
 }
 
 struct DelayedDebounced {
@@ -660,6 +662,7 @@ impl Project {
                 copilot_lsp_subscription,
                 copilot_log_subscription: None,
                 current_lsp_settings: settings::get::<ProjectSettings>(cx).lsp.clone(),
+                prettier_instances: HashMap::default(),
             }
         })
     }
@@ -757,6 +760,7 @@ impl Project {
                 copilot_lsp_subscription,
                 copilot_log_subscription: None,
                 current_lsp_settings: settings::get::<ProjectSettings>(cx).lsp.clone(),
+                prettier_instances: HashMap::default(),
             };
             for worktree in worktrees {
                 let _ = this.add_worktree(&worktree, cx);
@@ -4027,6 +4031,7 @@ impl Project {
                     enum FormatOperation {
                         Lsp(Vec<(Range<Anchor>, String)>),
                         External(Diff),
+                        Prettier(Diff),
                     }
 
                     // Apply language-specific formatting using either a language server
@@ -4062,8 +4067,8 @@ impl Project {
                         | (_, FormatOnSave::External { command, arguments }) => {
                             if let Some(buffer_abs_path) = buffer_abs_path {
                                 format_operation = Self::format_via_external_command(
-                                    &buffer,
-                                    &buffer_abs_path,
+                                    buffer,
+                                    buffer_abs_path,
                                     &command,
                                     &arguments,
                                     &mut cx,
@@ -4076,6 +4081,45 @@ impl Project {
                                 .map(FormatOperation::External);
                             }
                         }
+                        (Formatter::Auto, FormatOnSave::On | FormatOnSave::Off) => {
+                            if let Some(prettier) = this.update(&mut cx, |project, _| {
+                                project.prettier_instance_for_buffer(buffer)
+                            }) {
+                                format_operation = Some(FormatOperation::Prettier(
+                                    prettier
+                                        .format(buffer)
+                                        .await
+                                        .context("autoformatting via prettier")?,
+                                ));
+                            } else if let Some((language_server, buffer_abs_path)) =
+                                language_server.as_ref().zip(buffer_abs_path.as_ref())
+                            {
+                                format_operation = Some(FormatOperation::Lsp(
+                                    Self::format_via_lsp(
+                                        &this,
+                                        &buffer,
+                                        buffer_abs_path,
+                                        &language_server,
+                                        tab_size,
+                                        &mut cx,
+                                    )
+                                    .await
+                                    .context("failed to format via language server")?,
+                                ));
+                            }
+                        }
+                        (Formatter::Prettier { .. }, FormatOnSave::On | FormatOnSave::Off) => {
+                            if let Some(prettier) = this.update(&mut cx, |project, _| {
+                                project.prettier_instance_for_buffer(buffer)
+                            }) {
+                                format_operation = Some(FormatOperation::Prettier(
+                                    prettier
+                                        .format(buffer)
+                                        .await
+                                        .context("formatting via prettier")?,
+                                ));
+                            }
+                        }
                     };
 
                     buffer.update(&mut cx, |b, cx| {
@@ -4100,6 +4144,9 @@ impl Project {
                                 FormatOperation::External(diff) => {
                                     b.apply_diff(diff, cx);
                                 }
+                                FormatOperation::Prettier(diff) => {
+                                    b.apply_diff(diff, cx);
+                                }
                             }
 
                             if let Some(transaction_id) = whitespace_transaction_id {
@@ -8109,6 +8156,11 @@ impl Project {
             Vec::new()
         }
     }
+
+    fn prettier_instance_for_buffer(&self, buffer: &ModelHandle<Buffer>) -> Option<Prettier> {
+        // TODO kb
+        None
+    }
 }
 
 fn subscribe_for_copilot_events(