Slightly better prettier settings and discovery

Kirill Bulatov created

Change summary

Cargo.lock                               |  1 
assets/settings/default.json             |  2 
crates/collab/src/tests/test_server.rs   |  3 +
crates/language/src/language_settings.rs |  4 +
crates/prettier/Cargo.toml               |  1 
crates/prettier/src/prettier.rs          | 32 ++++++++-----
crates/project/src/project.rs            | 60 ++++++++-----------------
crates/util/src/paths.rs                 |  1 
8 files changed, 52 insertions(+), 52 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -5531,6 +5531,7 @@ dependencies = [
  "serde",
  "serde_derive",
  "serde_json",
+ "util",
 ]
 
 [[package]]

assets/settings/default.json 🔗

@@ -201,6 +201,8 @@
   //     }
   // TODO kb description
   "formatter": "auto",
+  // TODO kb description + better settings
+  "prettier": true,
   // How to soft-wrap long lines of text. This setting can take
   // three values:
   //

crates/collab/src/tests/test_server.rs 🔗

@@ -15,6 +15,7 @@ use fs::FakeFs;
 use futures::{channel::oneshot, StreamExt as _};
 use gpui::{executor::Deterministic, ModelHandle, Task, TestAppContext, WindowHandle};
 use language::LanguageRegistry;
+use node_runtime::FakeNodeRuntime;
 use parking_lot::Mutex;
 use project::{Project, WorktreeId};
 use rpc::RECEIVE_TIMEOUT;
@@ -218,6 +219,7 @@ impl TestServer {
             build_window_options: |_, _, _| Default::default(),
             initialize_workspace: |_, _, _, _| Task::ready(Ok(())),
             background_actions: || &[],
+            node_runtime: FakeNodeRuntime::new(),
         });
 
         cx.update(|cx| {
@@ -569,6 +571,7 @@ impl TestClient {
                 self.client().clone(),
                 self.app_state.user_store.clone(),
                 self.app_state.languages.clone(),
+                self.app_state.node_runtime.clone(),
                 self.app_state.fs.clone(),
                 cx,
             )

crates/language/src/language_settings.rs 🔗

@@ -47,6 +47,7 @@ pub struct LanguageSettings {
     pub show_wrap_guides: bool,
     pub wrap_guides: Vec<usize>,
     pub format_on_save: FormatOnSave,
+    pub prettier: bool,
     pub remove_trailing_whitespace_on_save: bool,
     pub ensure_final_newline_on_save: bool,
     pub formatter: Formatter,
@@ -92,6 +93,8 @@ pub struct LanguageSettingsContent {
     #[serde(default)]
     pub format_on_save: Option<FormatOnSave>,
     #[serde(default)]
+    pub prettier: Option<bool>,
+    #[serde(default)]
     pub remove_trailing_whitespace_on_save: Option<bool>,
     #[serde(default)]
     pub ensure_final_newline_on_save: Option<bool>,
@@ -398,6 +401,7 @@ fn merge_settings(settings: &mut LanguageSettings, src: &LanguageSettingsContent
     );
     merge(&mut settings.formatter, src.formatter.clone());
     merge(&mut settings.format_on_save, src.format_on_save.clone());
+    merge(&mut settings.prettier, src.prettier);
     merge(
         &mut settings.remove_trailing_whitespace_on_save,
         src.remove_trailing_whitespace_on_save,

crates/prettier/Cargo.toml 🔗

@@ -11,6 +11,7 @@ language = { path = "../language" }
 gpui = { path = "../gpui" }
 fs = { path = "../fs" }
 node_runtime = { path = "../node_runtime"}
+util = { path = "../util" }
 
 serde.workspace = true
 serde_derive.workspace = true

crates/prettier/src/prettier.rs 🔗

@@ -130,20 +130,21 @@ impl Prettier {
             None => Vec::new(),
         };
 
-        match find_closest_prettier_path(paths_to_check, fs.as_ref())
+        match find_closest_prettier_dir(paths_to_check, fs.as_ref())
             .await
-            .with_context(|| format!("Finding prettier starting with {starting_path:?}"))?
+            .with_context(|| format!("finding prettier starting with {starting_path:?}"))?
         {
-            Some(prettier_path) => Ok(prettier_path),
-            None => {
-                // TODO kb return the default prettier, how, without state?
-                Ok(PathBuf::new())
-            }
+            Some(prettier_dir) => Ok(prettier_dir),
+            None => Ok(util::paths::DEFAULT_PRETTIER_DIR.to_path_buf()),
         }
     }
 
-    pub async fn start(prettier_path: &Path, node: Arc<dyn NodeRuntime>) -> anyhow::Result<Self> {
-        todo!()
+    pub async fn start(prettier_dir: &Path, node: Arc<dyn NodeRuntime>) -> anyhow::Result<Self> {
+        anyhow::ensure!(
+            prettier_dir.is_dir(),
+            "Prettier dir {prettier_dir:?} is not a directory"
+        );
+        anyhow::bail!("TODO kb: start prettier server in {prettier_dir:?}")
     }
 
     pub async fn format(&self, buffer: &ModelHandle<Buffer>) -> anyhow::Result<Diff> {
@@ -156,14 +157,14 @@ impl Prettier {
 }
 
 const PRETTIER_PACKAGE_NAME: &str = "prettier";
-async fn find_closest_prettier_path(
+async fn find_closest_prettier_dir(
     paths_to_check: Vec<PathBuf>,
     fs: &dyn Fs,
 ) -> anyhow::Result<Option<PathBuf>> {
     for path in paths_to_check {
         let possible_package_json = path.join("package.json");
         if let Some(package_json_metadata) = fs
-            .metadata(&path)
+            .metadata(&possible_package_json)
             .await
             .with_context(|| format!("Fetching metadata for {possible_package_json:?}"))?
         {
@@ -192,7 +193,7 @@ async fn find_closest_prettier_path(
 
         let possible_node_modules_location = path.join("node_modules").join(PRETTIER_PACKAGE_NAME);
         if let Some(node_modules_location_metadata) = fs
-            .metadata(&path)
+            .metadata(&possible_node_modules_location)
             .await
             .with_context(|| format!("fetching metadata for {possible_node_modules_location:?}"))?
         {
@@ -203,3 +204,10 @@ async fn find_closest_prettier_path(
     }
     Ok(None)
 }
+
+async fn prepare_default_prettier(
+    fs: Arc<dyn Fs>,
+    node: Arc<dyn NodeRuntime>,
+) -> anyhow::Result<PathBuf> {
+    todo!("TODO kb need to call per language that supports it, and have to use extra packages sometimes")
+}

crates/project/src/project.rs 🔗

@@ -154,7 +154,7 @@ pub struct Project {
     copilot_lsp_subscription: Option<gpui::Subscription>,
     copilot_log_subscription: Option<lsp::Subscription>,
     current_lsp_settings: HashMap<Arc<str>, LspSettings>,
-    node_runtime: Option<Arc<dyn NodeRuntime>>,
+    node: Option<Arc<dyn NodeRuntime>>,
     prettier_instances: HashMap<
         (Option<WorktreeId>, PathBuf),
         Shared<Task<Result<Arc<Prettier>, Arc<anyhow::Error>>>>,
@@ -612,7 +612,7 @@ impl Project {
 
     pub fn local(
         client: Arc<Client>,
-        node_runtime: Arc<dyn NodeRuntime>,
+        node: Arc<dyn NodeRuntime>,
         user_store: ModelHandle<UserStore>,
         languages: Arc<LanguageRegistry>,
         fs: Arc<dyn Fs>,
@@ -668,7 +668,7 @@ impl Project {
                 copilot_lsp_subscription,
                 copilot_log_subscription: None,
                 current_lsp_settings: settings::get::<ProjectSettings>(cx).lsp.clone(),
-                node_runtime: Some(node_runtime),
+                node: Some(node),
                 prettier_instances: HashMap::default(),
             }
         })
@@ -767,7 +767,7 @@ impl Project {
                 copilot_lsp_subscription,
                 copilot_log_subscription: None,
                 current_lsp_settings: settings::get::<ProjectSettings>(cx).lsp.clone(),
-                node_runtime: None,
+                node: None,
                 prettier_instances: HashMap::default(),
             };
             for worktree in worktrees {
@@ -802,8 +802,6 @@ impl Project {
         root_paths: impl IntoIterator<Item = &Path>,
         cx: &mut gpui::TestAppContext,
     ) -> ModelHandle<Project> {
-        use node_runtime::FakeNodeRuntime;
-
         let mut languages = LanguageRegistry::test();
         languages.set_executor(cx.background());
         let http_client = util::http::FakeHttpClient::with_404_response();
@@ -812,7 +810,7 @@ impl Project {
         let project = cx.update(|cx| {
             Project::local(
                 client,
-                FakeNodeRuntime::new(),
+                node_runtime::FakeNodeRuntime::new(),
                 user_store,
                 Arc::new(languages),
                 fs,
@@ -8202,43 +8200,25 @@ impl Project {
         buffer: &ModelHandle<Buffer>,
         cx: &mut ModelContext<Self>,
     ) -> Option<Task<Shared<Task<Result<Arc<Prettier>, Arc<anyhow::Error>>>>>> {
-        let node_runtime = Arc::clone(self.node_runtime.as_ref()?);
-        let buffer_file = File::from_dyn(buffer.read(cx).file());
+        let buffer = buffer.read(cx);
+        let buffer_file = buffer.file();
+        let language_settings = language_settings(buffer.language(), buffer_file, cx).clone();
+        if !language_settings.prettier {
+            return None;
+        }
+
+        let node = Arc::clone(self.node.as_ref()?);
+        let buffer_file = File::from_dyn(buffer_file);
         let buffer_path = buffer_file.map(|file| Arc::clone(file.path()));
         let worktree_path = buffer_file
             .as_ref()
             .map(|file| file.worktree.read(cx).abs_path());
         let worktree_id = buffer_file.map(|file| file.worktree_id(cx));
 
-        // TODO kb return None if config opted out of prettier
-        if true {
-            let fs = Arc::clone(&self.fs);
-            let buffer_path = buffer_path.clone();
-            let worktree_path = worktree_path.clone();
-            cx.spawn(|_, _| async move {
-                let prettier_path = Prettier::locate(
-                    worktree_path
-                        .zip(buffer_path)
-                        .map(|(worktree_root_path, starting_path)| {
-                            dbg!(LocateStart {
-                                worktree_root_path,
-                                starting_path,
-                            })
-                        }),
-                    fs,
-                )
-                .await
-                .unwrap();
-                dbg!(prettier_path);
-            })
-            .detach();
-            return None;
-        }
-
         let task = cx.spawn(|this, mut cx| async move {
             let fs = this.update(&mut cx, |project, _| Arc::clone(&project.fs));
             // TODO kb can we have a cache for this instead?
-            let prettier_path = match cx
+            let prettier_dir = match cx
                 .background()
                 .spawn(Prettier::locate(
                     worktree_path
@@ -8263,21 +8243,21 @@ impl Project {
             if let Some(existing_prettier) = this.update(&mut cx, |project, _| {
                 project
                     .prettier_instances
-                    .get(&(worktree_id, prettier_path.clone()))
+                    .get(&(worktree_id, prettier_dir.clone()))
                     .cloned()
             }) {
                 return existing_prettier;
             }
 
-            let task_prettier_path = prettier_path.clone();
+            let task_prettier_dir = prettier_dir.clone();
             let new_prettier_task = cx
                 .background()
                 .spawn(async move {
                     Ok(Arc::new(
-                        Prettier::start(&task_prettier_path, node_runtime)
+                        Prettier::start(&task_prettier_dir, node)
                             .await
                             .with_context(|| {
-                                format!("starting new prettier for path {task_prettier_path:?}")
+                                format!("starting new prettier for path {task_prettier_dir:?}")
                             })?,
                     ))
                     .map_err(Arc::new)
@@ -8286,7 +8266,7 @@ impl Project {
             this.update(&mut cx, |project, _| {
                 project
                     .prettier_instances
-                    .insert((worktree_id, prettier_path), new_prettier_task.clone());
+                    .insert((worktree_id, prettier_dir), new_prettier_task.clone());
             });
             new_prettier_task
         });

crates/util/src/paths.rs 🔗

@@ -11,6 +11,7 @@ lazy_static::lazy_static! {
     pub static ref SUPPORT_DIR: PathBuf = HOME.join("Library/Application Support/Zed");
     pub static ref LANGUAGES_DIR: PathBuf = HOME.join("Library/Application Support/Zed/languages");
     pub static ref COPILOT_DIR: PathBuf = HOME.join("Library/Application Support/Zed/copilot");
+    pub static ref DEFAULT_PRETTIER_DIR: PathBuf = HOME.join("Library/Application Support/Zed/prettier");
     pub static ref DB_DIR: PathBuf = HOME.join("Library/Application Support/Zed/db");
     pub static ref SETTINGS: PathBuf = CONFIG_DIR.join("settings.json");
     pub static ref KEYMAP: PathBuf = CONFIG_DIR.join("keymap.json");