Use enum variants for prettier installation and startup phases

This commit is contained in:
Kirill Bulatov 2023-11-27 15:13:10 +02:00
parent d010f5f98d
commit c288c6eaf9
2 changed files with 227 additions and 221 deletions

View file

@ -13,12 +13,14 @@ use node_runtime::NodeRuntime;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use util::paths::{PathMatcher, DEFAULT_PRETTIER_DIR}; use util::paths::{PathMatcher, DEFAULT_PRETTIER_DIR};
#[derive(Clone)]
pub enum Prettier { pub enum Prettier {
Real(RealPrettier), Real(RealPrettier),
#[cfg(any(test, feature = "test-support"))] #[cfg(any(test, feature = "test-support"))]
Test(TestPrettier), Test(TestPrettier),
} }
#[derive(Clone)]
pub struct RealPrettier { pub struct RealPrettier {
default: bool, default: bool,
prettier_dir: PathBuf, prettier_dir: PathBuf,
@ -26,11 +28,13 @@ pub struct RealPrettier {
} }
#[cfg(any(test, feature = "test-support"))] #[cfg(any(test, feature = "test-support"))]
#[derive(Clone)]
pub struct TestPrettier { pub struct TestPrettier {
prettier_dir: PathBuf, prettier_dir: PathBuf,
default: bool, default: bool,
} }
pub const LAUNCH_THRESHOLD: usize = 5;
pub const PRETTIER_SERVER_FILE: &str = "prettier_server.js"; pub const PRETTIER_SERVER_FILE: &str = "prettier_server.js";
pub const PRETTIER_SERVER_JS: &str = include_str!("./prettier_server.js"); pub const PRETTIER_SERVER_JS: &str = include_str!("./prettier_server.js");
const PRETTIER_PACKAGE_NAME: &str = "prettier"; const PRETTIER_PACKAGE_NAME: &str = "prettier";

View file

@ -168,20 +168,54 @@ pub struct Project {
copilot_log_subscription: Option<lsp::Subscription>, copilot_log_subscription: Option<lsp::Subscription>,
current_lsp_settings: HashMap<Arc<str>, LspSettings>, current_lsp_settings: HashMap<Arc<str>, LspSettings>,
node: Option<Arc<dyn NodeRuntime>>, node: Option<Arc<dyn NodeRuntime>>,
default_prettier: Option<DefaultPrettier>, default_prettier: DefaultPrettier,
prettiers_per_worktree: HashMap<WorktreeId, HashSet<Option<PathBuf>>>, prettiers_per_worktree: HashMap<WorktreeId, HashSet<Option<PathBuf>>>,
prettier_instances: HashMap<PathBuf, PrettierInstance>, prettier_instances: HashMap<PathBuf, PrettierInstance>,
} }
type PrettierInstance = Shared<Task<Result<Arc<Prettier>, Arc<anyhow::Error>>>>; type PrettierInstance = Shared<Task<PrettierProcess>>;
#[derive(Clone)]
enum PrettierProcess {
Running(Arc<Prettier>),
Stopped { start_attempts: usize },
}
struct DefaultPrettier { struct DefaultPrettier {
instance: Option<PrettierInstance>, prettier: PrettierInstallation,
installation_process: Option<Shared<Task<Result<(), Arc<anyhow::Error>>>>>,
#[cfg(not(any(test, feature = "test-support")))]
installed_plugins: HashSet<&'static str>, installed_plugins: HashSet<&'static str>,
} }
enum PrettierInstallation {
NotInstalled {
attempts: usize,
installation_process: Option<Shared<Task<Result<(), Arc<anyhow::Error>>>>>,
},
Installed(PrettierInstance),
}
impl Default for DefaultPrettier {
fn default() -> Self {
Self {
prettier: PrettierInstallation::NotInstalled {
attempts: 0,
installation_process: None,
},
installed_plugins: HashSet::default(),
}
}
}
impl DefaultPrettier {
fn instance(&self) -> Option<&PrettierInstance> {
if let PrettierInstallation::Installed(instance) = &self.prettier {
Some(instance)
} else {
None
}
}
}
struct DelayedDebounced { struct DelayedDebounced {
task: Option<Task<()>>, task: Option<Task<()>>,
cancel_channel: Option<oneshot::Sender<()>>, cancel_channel: Option<oneshot::Sender<()>>,
@ -700,7 +734,7 @@ impl Project {
copilot_log_subscription: None, copilot_log_subscription: None,
current_lsp_settings: settings::get::<ProjectSettings>(cx).lsp.clone(), current_lsp_settings: settings::get::<ProjectSettings>(cx).lsp.clone(),
node: Some(node), node: Some(node),
default_prettier: None, default_prettier: DefaultPrettier::default(),
prettiers_per_worktree: HashMap::default(), prettiers_per_worktree: HashMap::default(),
prettier_instances: HashMap::default(), prettier_instances: HashMap::default(),
} }
@ -801,7 +835,7 @@ impl Project {
copilot_log_subscription: None, copilot_log_subscription: None,
current_lsp_settings: settings::get::<ProjectSettings>(cx).lsp.clone(), current_lsp_settings: settings::get::<ProjectSettings>(cx).lsp.clone(),
node: None, node: None,
default_prettier: None, default_prettier: DefaultPrettier::default(),
prettiers_per_worktree: HashMap::default(), prettiers_per_worktree: HashMap::default(),
prettier_instances: HashMap::default(), prettier_instances: HashMap::default(),
}; };
@ -6521,55 +6555,45 @@ impl Project {
log::info!( log::info!(
"Prettier config file {config_path:?} changed, reloading prettier instances for worktree {current_worktree_id}" "Prettier config file {config_path:?} changed, reloading prettier instances for worktree {current_worktree_id}"
); );
let prettiers_to_reload = self let prettiers_to_reload =
.prettiers_per_worktree self.prettiers_per_worktree
.get(&current_worktree_id) .get(&current_worktree_id)
.iter() .iter()
.flat_map(|prettier_paths| prettier_paths.iter()) .flat_map(|prettier_paths| prettier_paths.iter())
.flatten() .flatten()
.filter_map(|prettier_path| { .filter_map(|prettier_path| {
Some(( Some((
current_worktree_id, current_worktree_id,
Some(prettier_path.clone()), Some(prettier_path.clone()),
self.prettier_instances.get(prettier_path)?.clone(), self.prettier_instances.get(prettier_path)?.clone(),
)) ))
}) })
.chain(self.default_prettier.iter().filter_map(|default_prettier| { .chain(self.default_prettier.instance().map(|default_prettier| {
Some(( (current_worktree_id, None, default_prettier.clone())
current_worktree_id, }))
None, .collect::<Vec<_>>();
default_prettier.instance.clone()?,
))
}))
.collect::<Vec<_>>();
cx.background() cx.background()
.spawn(async move { .spawn(async move {
for task_result in future::join_all(prettiers_to_reload.into_iter().map(|(worktree_id, prettier_path, prettier_task)| { let _: Vec<()> = future::join_all(prettiers_to_reload.into_iter().map(|(worktree_id, prettier_path, prettier_task)| {
async move { async move {
prettier_task.await? if let PrettierProcess::Running(prettier) = prettier_task.await {
.clear_cache() if let Err(e) = prettier
.await .clear_cache()
.with_context(|| { .await {
match prettier_path { match prettier_path {
Some(prettier_path) => format!( Some(prettier_path) => log::error!(
"clearing prettier {prettier_path:?} cache for worktree {worktree_id:?} on prettier settings update" "Failed to clear prettier {prettier_path:?} cache for worktree {worktree_id:?} on prettier settings update: {e:#}"
), ),
None => format!( None => log::error!(
"clearing default prettier cache for worktree {worktree_id:?} on prettier settings update" "Failed to clear default prettier cache for worktree {worktree_id:?} on prettier settings update: {e:#}"
), ),
} }
}
}) }
.map_err(Arc::new)
} }
})) }))
.await .await;
{
if let Err(e) = task_result {
log::error!("Failed to clear cache for prettier: {e:#}");
}
}
}) })
.detach(); .detach();
} }
@ -8514,19 +8538,23 @@ impl Project {
.entry(worktree_id) .entry(worktree_id)
.or_default() .or_default()
.insert(None); .insert(None);
project.default_prettier.as_ref().and_then( project.default_prettier.instance().cloned()
|default_prettier| default_prettier.instance.clone(),
)
}); });
match started_default_prettier { match started_default_prettier {
Some(old_task) => return Some((None, old_task)), Some(old_task) => {
dbg!("Old prettier was found!");
return Some((None, old_task));
}
None => { None => {
dbg!("starting new default prettier");
let new_default_prettier = project let new_default_prettier = project
.update(&mut cx, |_, cx| { .update(&mut cx, |_, cx| {
start_default_prettier(node, Some(worktree_id), cx) start_default_prettier(node, Some(worktree_id), cx)
}) })
.log_err()
.await; .await;
return Some((None, new_default_prettier)); dbg!("started a default prettier");
return Some((None, new_default_prettier?));
} }
} }
} }
@ -8565,52 +8593,37 @@ impl Project {
Some((Some(prettier_dir), new_prettier_task)) Some((Some(prettier_dir), new_prettier_task))
} }
Err(e) => { Err(e) => {
return Some(( log::error!("Failed to determine prettier path for buffer: {e:#}");
None, return None;
Task::ready(Err(Arc::new(
e.context("determining prettier path"),
)))
.shared(),
));
} }
} }
}); });
} }
None => { None => match self.default_prettier.instance().cloned() {
let started_default_prettier = self Some(old_task) => return Task::ready(Some((None, old_task))),
.default_prettier None => {
.as_ref() let new_task = start_default_prettier(node, None, cx).log_err();
.and_then(|default_prettier| default_prettier.instance.clone()); return cx.spawn(|_, _| async move { Some((None, new_task.await?)) });
match started_default_prettier {
Some(old_task) => return Task::ready(Some((None, old_task))),
None => {
let new_task = start_default_prettier(node, None, cx);
return cx.spawn(|_, _| async move { Some((None, new_task.await)) });
}
} }
} },
} }
} else if self.remote_id().is_some() {
return Task::ready(None);
} else { } else {
Task::ready(Some(( return Task::ready(None);
None,
Task::ready(Err(Arc::new(anyhow!("project does not have a remote id")))).shared(),
)))
} }
} }
#[cfg(any(test, feature = "test-support"))] // TODO kb uncomment
fn install_default_formatters( // #[cfg(any(test, feature = "test-support"))]
&mut self, // fn install_default_formatters(
_worktree: Option<WorktreeId>, // &mut self,
_new_language: &Language, // _worktree: Option<WorktreeId>,
_language_settings: &LanguageSettings, // _new_language: &Language,
_cx: &mut ModelContext<Self>, // _language_settings: &LanguageSettings,
) { // _cx: &mut ModelContext<Self>,
} // ) {
// }
#[cfg(not(any(test, feature = "test-support")))] // #[cfg(not(any(test, feature = "test-support")))]
fn install_default_formatters( fn install_default_formatters(
&mut self, &mut self,
worktree: Option<WorktreeId>, worktree: Option<WorktreeId>,
@ -8660,78 +8673,86 @@ impl Project {
None => Task::ready(Ok(ControlFlow::Break(()))), None => Task::ready(Ok(ControlFlow::Break(()))),
}; };
let mut plugins_to_install = prettier_plugins; let mut plugins_to_install = prettier_plugins;
let previous_installation_process = plugins_to_install
if let Some(default_prettier) = &mut self.default_prettier { .retain(|plugin| !self.default_prettier.installed_plugins.contains(plugin));
plugins_to_install let mut installation_attempts = 0;
.retain(|plugin| !default_prettier.installed_plugins.contains(plugin)); let previous_installation_process = match &self.default_prettier.prettier {
PrettierInstallation::NotInstalled {
installation_process,
attempts,
} => {
installation_attempts = *attempts;
installation_process.clone()
}
PrettierInstallation::Installed { .. } => {
if plugins_to_install.is_empty() { if plugins_to_install.is_empty() {
return; return;
} }
default_prettier.installation_process.clone()
} else {
None None
}; }
};
if installation_attempts > prettier::LAUNCH_THRESHOLD {
log::warn!(
"Default prettier installation has failed {installation_attempts} times, not attempting again",
);
return;
}
let fs = Arc::clone(&self.fs); let fs = Arc::clone(&self.fs);
let default_prettier = self self.default_prettier.prettier = PrettierInstallation::NotInstalled {
.default_prettier attempts: installation_attempts + 1,
.get_or_insert_with(|| DefaultPrettier { installation_process: Some(
instance: None, cx.spawn(|this, mut cx| async move {
installation_process: None, match locate_prettier_installation
installed_plugins: HashSet::default(), .await
}); .context("locate prettier installation")
default_prettier.installation_process = Some( .map_err(Arc::new)?
cx.spawn(|this, mut cx| async move { {
match locate_prettier_installation ControlFlow::Break(()) => return Ok(()),
.await ControlFlow::Continue(Some(_non_default_prettier)) => return Ok(()),
.context("locate prettier installation") ControlFlow::Continue(None) => {
.map_err(Arc::new)? let mut needs_install = match previous_installation_process {
{ Some(previous_installation_process) => {
ControlFlow::Break(()) => return Ok(()), previous_installation_process.await.is_err()
ControlFlow::Continue(Some(_non_default_prettier)) => return Ok(()), }
ControlFlow::Continue(None) => { None => true,
let mut needs_install = match previous_installation_process { };
Some(previous_installation_process) => { this.update(&mut cx, |this, _| {
previous_installation_process.await.is_err()
}
None => true,
};
this.update(&mut cx, |this, _| {
if let Some(default_prettier) = &mut this.default_prettier {
plugins_to_install.retain(|plugin| { plugins_to_install.retain(|plugin| {
!default_prettier.installed_plugins.contains(plugin) !this.default_prettier.installed_plugins.contains(plugin)
}); });
needs_install |= !plugins_to_install.is_empty(); needs_install |= !plugins_to_install.is_empty();
}
});
if needs_install {
let installed_plugins = plugins_to_install.clone();
cx.background()
.spawn(async move {
install_default_prettier(plugins_to_install, node, fs).await
})
.await
.context("prettier & plugins install")
.map_err(Arc::new)?;
this.update(&mut cx, |this, _| {
let default_prettier =
this.default_prettier
.get_or_insert_with(|| DefaultPrettier {
instance: None,
installation_process: Some(
Task::ready(Ok(())).shared(),
),
installed_plugins: HashSet::default(),
});
default_prettier.instance = None;
default_prettier.installed_plugins.extend(installed_plugins);
}); });
if needs_install {
let installed_plugins = plugins_to_install.clone();
cx.background()
.spawn(async move {
install_default_prettier(plugins_to_install, node, fs).await
})
.await
.context("prettier & plugins install")
.map_err(Arc::new)?;
this.update(&mut cx, |this, cx| {
this.default_prettier.prettier =
PrettierInstallation::Installed(
cx.spawn(|_, _| async move {
PrettierProcess::Stopped { start_attempts: 0 }
})
.shared(),
);
this.default_prettier
.installed_plugins
.extend(installed_plugins);
});
}
} }
} }
} Ok(())
Ok(()) })
}) .shared(),
.shared(), ),
); };
} }
} }
@ -8739,48 +8760,40 @@ fn start_default_prettier(
node: Arc<dyn NodeRuntime>, node: Arc<dyn NodeRuntime>,
worktree_id: Option<WorktreeId>, worktree_id: Option<WorktreeId>,
cx: &mut ModelContext<'_, Project>, cx: &mut ModelContext<'_, Project>,
) -> Task<PrettierInstance> { ) -> Task<anyhow::Result<PrettierInstance>> {
cx.spawn(|project, mut cx| async move { cx.spawn(|project, mut cx| async move {
loop { loop {
let default_prettier_installing = project.update(&mut cx, |project, _| { let installation_process = project.update(&mut cx, |project, _| {
project match &project.default_prettier.prettier {
.default_prettier PrettierInstallation::NotInstalled {
.as_ref() installation_process,
.and_then(|default_prettier| default_prettier.installation_process.clone()) ..
}); } => ControlFlow::Continue(installation_process.clone()),
match default_prettier_installing { PrettierInstallation::Installed(default_prettier) => {
Some(installation_task) => { ControlFlow::Break(default_prettier.clone())
if installation_task.await.is_ok() {
break;
} }
} }
None => break, });
match installation_process {
ControlFlow::Continue(installation_process) => {
if let Some(installation_process) = installation_process.clone() {
if let Err(e) = installation_process.await {
anyhow::bail!("Cannot start default prettier due to its installation failure: {e:#}");
}
}
let new_default_prettier = project.update(&mut cx, |project, cx| {
let new_default_prettier =
start_prettier(node, DEFAULT_PRETTIER_DIR.clone(), worktree_id, cx);
project.default_prettier.prettier =
PrettierInstallation::Installed(new_default_prettier.clone());
new_default_prettier
});
return Ok(new_default_prettier);
}
ControlFlow::Break(prettier) => return Ok(prettier),
} }
} }
project.update(&mut cx, |project, cx| {
match project
.default_prettier
.as_mut()
.and_then(|default_prettier| default_prettier.instance.as_mut())
{
Some(default_prettier) => default_prettier.clone(),
None => {
let new_default_prettier =
start_prettier(node, DEFAULT_PRETTIER_DIR.clone(), worktree_id, cx);
project
.default_prettier
.get_or_insert_with(|| DefaultPrettier {
instance: None,
installation_process: None,
#[cfg(not(any(test, feature = "test-support")))]
installed_plugins: HashSet::default(),
})
.instance = Some(new_default_prettier.clone());
new_default_prettier
}
}
})
}) })
} }
@ -8794,13 +8807,18 @@ fn start_prettier(
let new_server_id = project.update(&mut cx, |project, _| { let new_server_id = project.update(&mut cx, |project, _| {
project.languages.next_language_server_id() project.languages.next_language_server_id()
}); });
let new_prettier = Prettier::start(new_server_id, prettier_dir, node, cx.clone())
.await match Prettier::start(new_server_id, prettier_dir.clone(), node, cx.clone()).await {
.context("default prettier spawn") Ok(new_prettier) => {
.map(Arc::new) register_new_prettier(&project, &new_prettier, worktree_id, new_server_id, &mut cx);
.map_err(Arc::new)?; PrettierProcess::Running(Arc::new(new_prettier))
register_new_prettier(&project, &new_prettier, worktree_id, new_server_id, &mut cx); }
Ok(new_prettier) Err(e) => {
log::error!("Failed to start prettier in dir {prettier_dir:?}: {e:#}");
// TODO kb increment
PrettierProcess::Stopped { start_attempts: 1 }
}
}
}) })
.shared() .shared()
} }
@ -8855,7 +8873,6 @@ fn register_new_prettier(
} }
} }
#[cfg(not(any(test, feature = "test-support")))]
async fn install_default_prettier( async fn install_default_prettier(
plugins_to_install: HashSet<&'static str>, plugins_to_install: HashSet<&'static str>,
node: Arc<dyn NodeRuntime>, node: Arc<dyn NodeRuntime>,
@ -9218,34 +9235,19 @@ async fn format_with_prettier(
}) })
.await .await
{ {
match prettier_task.await { // TODO kb re-insert incremented value here?
Ok(prettier) => { if let PrettierProcess::Running(prettier) = prettier_task.await {
let buffer_path = buffer.update(cx, |buffer, cx| { let buffer_path = buffer.update(cx, |buffer, cx| {
File::from_dyn(buffer.file()).map(|file| file.abs_path(cx)) File::from_dyn(buffer.file()).map(|file| file.abs_path(cx))
}); });
match prettier.format(buffer, buffer_path, cx).await { match prettier.format(buffer, buffer_path, cx).await {
Ok(new_diff) => return Some(FormatOperation::Prettier(new_diff)), Ok(new_diff) => return Some(FormatOperation::Prettier(new_diff)),
Err(e) => { Err(e) => {
log::error!( log::error!(
"Prettier instance from {prettier_path:?} failed to format a buffer: {e:#}" "Prettier instance from {prettier_path:?} failed to format a buffer: {e:#}"
); );
}
} }
} }
Err(e) => {
project.update(cx, |project, _| match &prettier_path {
Some(prettier_path) => {
log::error!("Failed to create prettier instance from {prettier_path:?} for buffer: {e:#}");
project.prettier_instances.remove(prettier_path);
}
None => {
log::error!("Failed to create default prettier instance from {prettier_path:?} for buffer: {e:#}");
if let Some(default_prettier) = project.default_prettier.as_mut() {
default_prettier.instance = None;
}
}
});
}
} }
} }