Cargo.lock 🔗
@@ -5523,6 +5523,7 @@ version = "0.1.0"
dependencies = [
"anyhow",
"fs",
+ "futures 0.3.28",
"gpui",
"language",
]
Kirill Bulatov created
Cargo.lock | 1
assets/settings/default.json | 3
crates/fs/src/fs.rs | 5
crates/prettier/Cargo.toml | 2
crates/prettier/src/prettier.rs | 111 ++++++++++++++++++++++++++++++++++
crates/project/src/project.rs | 51 +++++++++++++++
6 files changed, 165 insertions(+), 8 deletions(-)
@@ -5523,6 +5523,7 @@ version = "0.1.0"
dependencies = [
"anyhow",
"fs",
+ "futures 0.3.28",
"gpui",
"language",
]
@@ -199,7 +199,8 @@
// "arguments": ["--stdin-filepath", "{buffer_path}"]
// }
// }
- "formatter": "language_server",
+ // TODO kb description
+ "formatter": "auto",
// How to soft-wrap long lines of text. This setting can take
// three values:
//
@@ -85,7 +85,7 @@ pub struct RemoveOptions {
pub ignore_if_not_exists: bool,
}
-#[derive(Clone, Debug)]
+#[derive(Copy, Clone, Debug)]
pub struct Metadata {
pub inode: u64,
pub mtime: SystemTime,
@@ -229,11 +229,12 @@ impl Fs for RealFs {
} else {
symlink_metadata
};
+ let file_type_metadata = metadata.file_type();
Ok(Some(Metadata {
inode: metadata.ino(),
mtime: metadata.modified().unwrap(),
is_symlink,
- is_dir: metadata.file_type().is_dir(),
+ is_dir: file_type_metadata.is_dir(),
}))
}
@@ -12,7 +12,7 @@ gpui = { path = "../gpui" }
fs = { path = "../fs" }
anyhow.workspace = true
-
+futures.workspace = true
[dev-dependencies]
language = { path = "../language", features = ["test-support"] }
@@ -1,6 +1,8 @@
+use std::collections::VecDeque;
pub use std::path::{Path, PathBuf};
pub use std::sync::Arc;
+use anyhow::Context;
use fs::Fs;
use gpui::ModelHandle;
use language::{Buffer, Diff};
@@ -11,6 +13,12 @@ pub struct Prettier {
pub struct NodeRuntime;
+#[derive(Debug)]
+pub struct LocateStart {
+ pub worktree_root_path: Arc<Path>,
+ pub starting_path: Arc<Path>,
+}
+
impl Prettier {
// This was taken from the prettier-vscode extension.
pub const CONFIG_FILE_NAMES: &'static [&'static str] = &[
@@ -28,8 +36,107 @@ impl Prettier {
".editorconfig",
];
- pub async fn locate(starting_path: Option<&Path>, fs: Arc<dyn Fs>) -> PathBuf {
- todo!()
+ pub async fn locate(
+ starting_path: Option<LocateStart>,
+ fs: Arc<dyn Fs>,
+ ) -> anyhow::Result<PathBuf> {
+ let paths_to_check = match starting_path {
+ Some(starting_path) => {
+ let worktree_root = starting_path
+ .worktree_root_path
+ .components()
+ .into_iter()
+ .take_while(|path_component| {
+ path_component.as_os_str().to_str() != Some("node_modules")
+ })
+ .collect::<PathBuf>();
+
+ if worktree_root != starting_path.worktree_root_path.as_ref() {
+ vec![worktree_root]
+ } else {
+ let (worktree_root_metadata, start_path_metadata) = if starting_path
+ .starting_path
+ .as_ref()
+ == Path::new("")
+ {
+ let worktree_root_data =
+ fs.metadata(&worktree_root).await.with_context(|| {
+ format!(
+ "FS metadata fetch for worktree root path {worktree_root:?}",
+ )
+ })?;
+ (worktree_root_data.unwrap_or_else(|| {
+ panic!("cannot query prettier for non existing worktree root at {worktree_root_data:?}")
+ }), None)
+ } else {
+ let full_starting_path = worktree_root.join(&starting_path.starting_path);
+ let (worktree_root_data, start_path_data) = futures::try_join!(
+ fs.metadata(&worktree_root),
+ fs.metadata(&full_starting_path),
+ )
+ .with_context(|| {
+ format!("FS metadata fetch for starting path {full_starting_path:?}",)
+ })?;
+ (
+ worktree_root_data.unwrap_or_else(|| {
+ panic!("cannot query prettier for non existing worktree root at {worktree_root_data:?}")
+ }),
+ start_path_data,
+ )
+ };
+
+ match start_path_metadata {
+ Some(start_path_metadata) => {
+ anyhow::ensure!(worktree_root_metadata.is_dir,
+ "For non-empty start path, worktree root {starting_path:?} should be a directory");
+ anyhow::ensure!(
+ !start_path_metadata.is_dir,
+ "For non-empty start path, it should not be a directory {starting_path:?}"
+ );
+ anyhow::ensure!(
+ !start_path_metadata.is_symlink,
+ "For non-empty start path, it should not be a symlink {starting_path:?}"
+ );
+
+ let file_to_format = starting_path.starting_path.as_ref();
+ let mut paths_to_check = VecDeque::from(vec![worktree_root.clone()]);
+ let mut current_path = worktree_root;
+ for path_component in file_to_format.components().into_iter() {
+ current_path = current_path.join(path_component);
+ paths_to_check.push_front(current_path.clone());
+ if path_component.as_os_str().to_str() == Some("node_modules") {
+ break;
+ }
+ }
+ paths_to_check.pop_front(); // last one is the file itself or node_modules, skip it
+ Vec::from(paths_to_check)
+ }
+ None => {
+ anyhow::ensure!(
+ !worktree_root_metadata.is_dir,
+ "For empty start path, worktree root should not be a directory {starting_path:?}"
+ );
+ anyhow::ensure!(
+ !worktree_root_metadata.is_symlink,
+ "For empty start path, worktree root should not be a symlink {starting_path:?}"
+ );
+ worktree_root
+ .parent()
+ .map(|path| vec![path.to_path_buf()])
+ .unwrap_or_default()
+ }
+ }
+ }
+ }
+ None => Vec::new(),
+ };
+
+ if dbg!(paths_to_check).is_empty() {
+ // TODO kb return the default prettier, how, without state?
+ } else {
+ // TODO kb now check all paths to check for prettier
+ }
+ Ok(PathBuf::new())
}
pub async fn start(prettier_path: &Path, node: Arc<NodeRuntime>) -> anyhow::Result<Self> {
@@ -50,7 +50,7 @@ use lsp::{
};
use lsp_command::*;
use postage::watch;
-use prettier::{NodeRuntime, Prettier};
+use prettier::{LocateStart, NodeRuntime, Prettier};
use project_settings::{LspSettings, ProjectSettings};
use rand::prelude::*;
use search::SearchQuery;
@@ -8189,14 +8189,61 @@ impl Project {
) -> Option<Task<Shared<Task<Result<Arc<Prettier>, Arc<anyhow::Error>>>>>> {
let buffer_file = File::from_dyn(buffer.read(cx).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 = Prettier::locate(buffer_path.as_deref(), fs).await;
+ let prettier_path = match cx
+ .background()
+ .spawn(Prettier::locate(
+ worktree_path
+ .zip(buffer_path)
+ .map(|(worktree_root_path, starting_path)| LocateStart {
+ worktree_root_path,
+ starting_path,
+ }),
+ fs,
+ ))
+ .await
+ {
+ Ok(path) => path,
+ Err(e) => {
+ return Task::Ready(Some(Result::Err(Arc::new(
+ e.context("determining prettier path for worktree {worktree_path:?}"),
+ ))))
+ .shared();
+ }
+ };
+
if let Some(existing_prettier) = this.update(&mut cx, |project, _| {
project
.prettier_instances