1use std::collections::{HashMap, VecDeque};
2use std::path::{Path, PathBuf};
3use std::sync::Arc;
4
5use anyhow::Context;
6use fs::Fs;
7use gpui::ModelHandle;
8use language::{Buffer, Diff};
9use node_runtime::NodeRuntime;
10
11pub struct Prettier {
12 _private: (),
13}
14
15#[derive(Debug)]
16pub struct LocateStart {
17 pub worktree_root_path: Arc<Path>,
18 pub starting_path: Arc<Path>,
19}
20
21impl Prettier {
22 // This was taken from the prettier-vscode extension.
23 pub const CONFIG_FILE_NAMES: &'static [&'static str] = &[
24 ".prettierrc",
25 ".prettierrc.json",
26 ".prettierrc.json5",
27 ".prettierrc.yaml",
28 ".prettierrc.yml",
29 ".prettierrc.toml",
30 ".prettierrc.js",
31 ".prettierrc.cjs",
32 "package.json",
33 "prettier.config.js",
34 "prettier.config.cjs",
35 ".editorconfig",
36 ];
37
38 pub async fn locate(
39 starting_path: Option<LocateStart>,
40 fs: Arc<dyn Fs>,
41 ) -> anyhow::Result<PathBuf> {
42 let paths_to_check = match starting_path.as_ref() {
43 Some(starting_path) => {
44 let worktree_root = starting_path
45 .worktree_root_path
46 .components()
47 .into_iter()
48 .take_while(|path_component| {
49 path_component.as_os_str().to_str() != Some("node_modules")
50 })
51 .collect::<PathBuf>();
52
53 if worktree_root != starting_path.worktree_root_path.as_ref() {
54 vec![worktree_root]
55 } else {
56 let (worktree_root_metadata, start_path_metadata) = if starting_path
57 .starting_path
58 .as_ref()
59 == Path::new("")
60 {
61 let worktree_root_data =
62 fs.metadata(&worktree_root).await.with_context(|| {
63 format!(
64 "FS metadata fetch for worktree root path {worktree_root:?}",
65 )
66 })?;
67 (worktree_root_data.unwrap_or_else(|| {
68 panic!("cannot query prettier for non existing worktree root at {worktree_root_data:?}")
69 }), None)
70 } else {
71 let full_starting_path = worktree_root.join(&starting_path.starting_path);
72 let (worktree_root_data, start_path_data) = futures::try_join!(
73 fs.metadata(&worktree_root),
74 fs.metadata(&full_starting_path),
75 )
76 .with_context(|| {
77 format!("FS metadata fetch for starting path {full_starting_path:?}",)
78 })?;
79 (
80 worktree_root_data.unwrap_or_else(|| {
81 panic!("cannot query prettier for non existing worktree root at {worktree_root_data:?}")
82 }),
83 start_path_data,
84 )
85 };
86
87 match start_path_metadata {
88 Some(start_path_metadata) => {
89 anyhow::ensure!(worktree_root_metadata.is_dir,
90 "For non-empty start path, worktree root {starting_path:?} should be a directory");
91 anyhow::ensure!(
92 !start_path_metadata.is_dir,
93 "For non-empty start path, it should not be a directory {starting_path:?}"
94 );
95 anyhow::ensure!(
96 !start_path_metadata.is_symlink,
97 "For non-empty start path, it should not be a symlink {starting_path:?}"
98 );
99
100 let file_to_format = starting_path.starting_path.as_ref();
101 let mut paths_to_check = VecDeque::from(vec![worktree_root.clone()]);
102 let mut current_path = worktree_root;
103 for path_component in file_to_format.components().into_iter() {
104 current_path = current_path.join(path_component);
105 paths_to_check.push_front(current_path.clone());
106 if path_component.as_os_str().to_str() == Some("node_modules") {
107 break;
108 }
109 }
110 paths_to_check.pop_front(); // last one is the file itself or node_modules, skip it
111 Vec::from(paths_to_check)
112 }
113 None => {
114 anyhow::ensure!(
115 !worktree_root_metadata.is_dir,
116 "For empty start path, worktree root should not be a directory {starting_path:?}"
117 );
118 anyhow::ensure!(
119 !worktree_root_metadata.is_symlink,
120 "For empty start path, worktree root should not be a symlink {starting_path:?}"
121 );
122 worktree_root
123 .parent()
124 .map(|path| vec![path.to_path_buf()])
125 .unwrap_or_default()
126 }
127 }
128 }
129 }
130 None => Vec::new(),
131 };
132
133 match find_closest_prettier_dir(paths_to_check, fs.as_ref())
134 .await
135 .with_context(|| format!("finding prettier starting with {starting_path:?}"))?
136 {
137 Some(prettier_dir) => Ok(prettier_dir),
138 None => Ok(util::paths::DEFAULT_PRETTIER_DIR.to_path_buf()),
139 }
140 }
141
142 pub async fn start(prettier_dir: &Path, node: Arc<dyn NodeRuntime>) -> anyhow::Result<Self> {
143 anyhow::ensure!(
144 prettier_dir.is_dir(),
145 "Prettier dir {prettier_dir:?} is not a directory"
146 );
147 anyhow::bail!("TODO kb: start prettier server in {prettier_dir:?}")
148 }
149
150 pub async fn format(&self, buffer: &ModelHandle<Buffer>) -> anyhow::Result<Diff> {
151 todo!()
152 }
153
154 pub async fn clear_cache(&self) -> anyhow::Result<()> {
155 todo!()
156 }
157}
158
159const PRETTIER_PACKAGE_NAME: &str = "prettier";
160async fn find_closest_prettier_dir(
161 paths_to_check: Vec<PathBuf>,
162 fs: &dyn Fs,
163) -> anyhow::Result<Option<PathBuf>> {
164 for path in paths_to_check {
165 let possible_package_json = path.join("package.json");
166 if let Some(package_json_metadata) = fs
167 .metadata(&possible_package_json)
168 .await
169 .with_context(|| format!("Fetching metadata for {possible_package_json:?}"))?
170 {
171 if !package_json_metadata.is_dir && !package_json_metadata.is_symlink {
172 let package_json_contents = fs
173 .load(&possible_package_json)
174 .await
175 .with_context(|| format!("reading {possible_package_json:?} file contents"))?;
176 if let Ok(json_contents) = serde_json::from_str::<HashMap<String, serde_json::Value>>(
177 &package_json_contents,
178 ) {
179 if let Some(serde_json::Value::Object(o)) = json_contents.get("dependencies") {
180 if o.contains_key(PRETTIER_PACKAGE_NAME) {
181 return Ok(Some(path));
182 }
183 }
184 if let Some(serde_json::Value::Object(o)) = json_contents.get("devDependencies")
185 {
186 if o.contains_key(PRETTIER_PACKAGE_NAME) {
187 return Ok(Some(path));
188 }
189 }
190 }
191 }
192 }
193
194 let possible_node_modules_location = path.join("node_modules").join(PRETTIER_PACKAGE_NAME);
195 if let Some(node_modules_location_metadata) = fs
196 .metadata(&possible_node_modules_location)
197 .await
198 .with_context(|| format!("fetching metadata for {possible_node_modules_location:?}"))?
199 {
200 if node_modules_location_metadata.is_dir {
201 return Ok(Some(path));
202 }
203 }
204 }
205 Ok(None)
206}
207
208async fn prepare_default_prettier(
209 fs: Arc<dyn Fs>,
210 node: Arc<dyn NodeRuntime>,
211) -> anyhow::Result<PathBuf> {
212 todo!("TODO kb need to call per language that supports it, and have to use extra packages sometimes")
213}