958 lines · 38.8 KB · Rust
RawDownload
use std::{    ops::ControlFlow,    path::{Path, PathBuf},    sync::Arc,}; use anyhow::{Context as _, Result, anyhow};use collections::{HashMap, HashSet};use fs::Fs;use futures::{    FutureExt,    future::{self, Shared},    stream::FuturesUnordered,};use gpui::{AppContext as _, AsyncApp, Context, Entity, EventEmitter, Task, WeakEntity};use language::{    Buffer, LanguageRegistry, LocalFile,    language_settings::{Formatter, LanguageSettings, SelectedFormatter},};use lsp::{LanguageServer, LanguageServerId, LanguageServerName};use node_runtime::NodeRuntime;use paths::default_prettier_dir;use prettier::Prettier;use smol::stream::StreamExt;use util::{ResultExt, TryFutureExt}; use crate::{    File, PathChange, ProjectEntryId, Worktree, lsp_store::WorktreeId,    worktree_store::WorktreeStore,}; pub struct PrettierStore {    node: NodeRuntime,    fs: Arc<dyn Fs>,    languages: Arc<LanguageRegistry>,    worktree_store: Entity<WorktreeStore>,    default_prettier: DefaultPrettier,    prettiers_per_worktree: HashMap<WorktreeId, HashSet<Option<PathBuf>>>,    prettier_ignores_per_worktree: HashMap<WorktreeId, HashSet<PathBuf>>,    prettier_instances: HashMap<PathBuf, PrettierInstance>,} pub(crate) enum PrettierStoreEvent {    LanguageServerRemoved(LanguageServerId),    LanguageServerAdded {        new_server_id: LanguageServerId,        name: LanguageServerName,        prettier_server: Arc<LanguageServer>,    },} impl EventEmitter<PrettierStoreEvent> for PrettierStore {} impl PrettierStore {    pub fn new(        node: NodeRuntime,        fs: Arc<dyn Fs>,        languages: Arc<LanguageRegistry>,        worktree_store: Entity<WorktreeStore>,        _: &mut Context<Self>,    ) -> Self {        Self {            node,            fs,            languages,            worktree_store,            default_prettier: DefaultPrettier::default(),            prettiers_per_worktree: HashMap::default(),            prettier_ignores_per_worktree: HashMap::default(),            prettier_instances: HashMap::default(),        }    }     pub fn remove_worktree(&mut self, id_to_remove: WorktreeId, cx: &mut Context<Self>) {        self.prettier_ignores_per_worktree.remove(&id_to_remove);        let mut prettier_instances_to_clean = FuturesUnordered::new();        if let Some(prettier_paths) = self.prettiers_per_worktree.remove(&id_to_remove) {            for path in prettier_paths.iter().flatten() {                if let Some(prettier_instance) = self.prettier_instances.remove(path) {                    prettier_instances_to_clean.push(async move {                        prettier_instance                            .server()                            .await                            .map(|server| server.server_id())                    });                }            }        }        cx.spawn(async move |prettier_store, cx| {            while let Some(prettier_server_id) = prettier_instances_to_clean.next().await {                if let Some(prettier_server_id) = prettier_server_id {                    prettier_store                        .update(cx, |_, cx| {                            cx.emit(PrettierStoreEvent::LanguageServerRemoved(                                prettier_server_id,                            ));                        })                        .ok();                }            }        })        .detach();    }     fn prettier_instance_for_buffer(        &mut self,        buffer: &Entity<Buffer>,        cx: &mut Context<Self>,    ) -> Task<Option<(Option<PathBuf>, PrettierTask)>> {        let buffer = buffer.read(cx);        let buffer_file = buffer.file();        if buffer.language().is_none() {            return Task::ready(None);        }         let node = self.node.clone();         match File::from_dyn(buffer_file).map(|file| (file.worktree_id(cx), file.abs_path(cx))) {            Some((worktree_id, buffer_path)) => {                let fs = Arc::clone(&self.fs);                let installed_prettiers = self.prettier_instances.keys().cloned().collect();                cx.spawn(async move |lsp_store, cx| {                    match cx                        .background_spawn(async move {                            Prettier::locate_prettier_installation(                                fs.as_ref(),                                &installed_prettiers,                                &buffer_path,                            )                            .await                        })                        .await                    {                        Ok(ControlFlow::Break(())) => None,                        Ok(ControlFlow::Continue(None)) => {                            let default_instance = lsp_store                                .update(cx, |lsp_store, cx| {                                    lsp_store                                        .prettiers_per_worktree                                        .entry(worktree_id)                                        .or_default()                                        .insert(None);                                    lsp_store.default_prettier.prettier_task(                                        &node,                                        Some(worktree_id),                                        cx,                                    )                                })                                .ok()?;                            Some((None, default_instance?.log_err().await?))                        }                        Ok(ControlFlow::Continue(Some(prettier_dir))) => {                            lsp_store                                .update(cx, |lsp_store, _| {                                    lsp_store                                        .prettiers_per_worktree                                        .entry(worktree_id)                                        .or_default()                                        .insert(Some(prettier_dir.clone()))                                })                                .ok()?;                            if let Some(prettier_task) = lsp_store                                .update(cx, |lsp_store, cx| {                                    lsp_store.prettier_instances.get_mut(&prettier_dir).map(                                        |existing_instance| {                                            existing_instance.prettier_task(                                                &node,                                                Some(&prettier_dir),                                                Some(worktree_id),                                                cx,                                            )                                        },                                    )                                })                                .ok()?                            {                                log::debug!("Found already started prettier in {prettier_dir:?}");                                return Some((Some(prettier_dir), prettier_task?.await.log_err()?));                            }                             log::info!("Found prettier in {prettier_dir:?}, starting.");                            let new_prettier_task = lsp_store                                .update(cx, |lsp_store, cx| {                                    let new_prettier_task = Self::start_prettier(                                        node,                                        prettier_dir.clone(),                                        Some(worktree_id),                                        cx,                                    );                                    lsp_store.prettier_instances.insert(                                        prettier_dir.clone(),                                        PrettierInstance {                                            attempt: 0,                                            prettier: Some(new_prettier_task.clone()),                                        },                                    );                                    new_prettier_task                                })                                .ok()?;                            Some((Some(prettier_dir), new_prettier_task))                        }                        Err(e) => {                            log::error!("Failed to determine prettier path for buffer: {e:#}");                            None                        }                    }                })            }            None => {                let new_task = self.default_prettier.prettier_task(&node, None, cx);                cx.spawn(async move |_, _| Some((None, new_task?.log_err().await?)))            }        }    }     fn prettier_ignore_for_buffer(        &mut self,        buffer: &Entity<Buffer>,        cx: &mut Context<Self>,    ) -> Task<Option<PathBuf>> {        let buffer = buffer.read(cx);        let buffer_file = buffer.file();        if buffer.language().is_none() {            return Task::ready(None);        }        match File::from_dyn(buffer_file).map(|file| (file.worktree_id(cx), file.abs_path(cx))) {            Some((worktree_id, buffer_path)) => {                let fs = Arc::clone(&self.fs);                let prettier_ignores = self                    .prettier_ignores_per_worktree                    .get(&worktree_id)                    .cloned()                    .unwrap_or_default();                cx.spawn(async move |lsp_store, cx| {                    match cx                        .background_spawn(async move {                            Prettier::locate_prettier_ignore(                                fs.as_ref(),                                &prettier_ignores,                                &buffer_path,                            )                            .await                        })                        .await                    {                        Ok(ControlFlow::Break(())) => None,                        Ok(ControlFlow::Continue(None)) => None,                        Ok(ControlFlow::Continue(Some(ignore_dir))) => {                            log::debug!("Found prettier ignore in {ignore_dir:?}");                            lsp_store                                .update(cx, |store, _| {                                    store                                        .prettier_ignores_per_worktree                                        .entry(worktree_id)                                        .or_default()                                        .insert(ignore_dir.clone());                                })                                .ok();                            Some(ignore_dir)                        }                        Err(e) => {                            log::error!(                                "Failed to determine prettier ignore path for buffer: {e:#}"                            );                            None                        }                    }                })            }            None => Task::ready(None),        }    }     fn start_prettier(        node: NodeRuntime,        prettier_dir: PathBuf,        worktree_id: Option<WorktreeId>,        cx: &mut Context<Self>,    ) -> PrettierTask {        cx.spawn(async move |prettier_store, cx| {            log::info!("Starting prettier at path {prettier_dir:?}");            let new_server_id = prettier_store.update(cx, |prettier_store, _| {                prettier_store.languages.next_language_server_id()            })?;             let new_prettier = Prettier::start(new_server_id, prettier_dir, node, cx.clone())                .await                .context("default prettier spawn")                .map(Arc::new)                .map_err(Arc::new)?;            Self::register_new_prettier(                &prettier_store,                &new_prettier,                worktree_id,                new_server_id,                cx,            );            Ok(new_prettier)        })        .shared()    }     fn start_default_prettier(        node: NodeRuntime,        worktree_id: Option<WorktreeId>,        cx: &mut Context<PrettierStore>,    ) -> Task<anyhow::Result<PrettierTask>> {        cx.spawn(async move |prettier_store, cx| {            let installation_task = prettier_store.update(cx, |prettier_store, _| {                match &prettier_store.default_prettier.prettier {                    PrettierInstallation::NotInstalled {                        installation_task, ..                    } => ControlFlow::Continue(installation_task.clone()),                    PrettierInstallation::Installed(default_prettier) => {                        ControlFlow::Break(default_prettier.clone())                    }                }            })?;            match installation_task {                ControlFlow::Continue(None) => {                    anyhow::bail!("Default prettier is not installed and cannot be started")                }                ControlFlow::Continue(Some(installation_task)) => {                    log::info!("Waiting for default prettier to install");                    if let Err(e) = installation_task.await {                        prettier_store.update(cx, |project, _| {                            if let PrettierInstallation::NotInstalled {                                installation_task,                                attempts,                                ..                            } = &mut project.default_prettier.prettier                            {                                *installation_task = None;                                *attempts += 1;                            }                        })?;                        anyhow::bail!(                            "Cannot start default prettier due to its installation failure: {e:#}"                        );                    }                    let new_default_prettier =                        prettier_store.update(cx, |prettier_store, cx| {                            let new_default_prettier = Self::start_prettier(                                node,                                default_prettier_dir().clone(),                                worktree_id,                                cx,                            );                            prettier_store.default_prettier.prettier =                                PrettierInstallation::Installed(PrettierInstance {                                    attempt: 0,                                    prettier: Some(new_default_prettier.clone()),                                });                            new_default_prettier                        })?;                    Ok(new_default_prettier)                }                ControlFlow::Break(instance) => match instance.prettier {                    Some(instance) => Ok(instance),                    None => {                        let new_default_prettier =                            prettier_store.update(cx, |prettier_store, cx| {                                let new_default_prettier = Self::start_prettier(                                    node,                                    default_prettier_dir().clone(),                                    worktree_id,                                    cx,                                );                                prettier_store.default_prettier.prettier =                                    PrettierInstallation::Installed(PrettierInstance {                                        attempt: instance.attempt + 1,                                        prettier: Some(new_default_prettier.clone()),                                    });                                new_default_prettier                            })?;                        Ok(new_default_prettier)                    }                },            }        })    }     fn register_new_prettier(        prettier_store: &WeakEntity<Self>,        prettier: &Prettier,        worktree_id: Option<WorktreeId>,        new_server_id: LanguageServerId,        cx: &mut AsyncApp,    ) {        let prettier_dir = prettier.prettier_dir();        let is_default = prettier.is_default();        if is_default {            log::info!("Started default prettier in {prettier_dir:?}");        } else {            log::info!("Started prettier in {prettier_dir:?}");        }        if let Some(prettier_server) = prettier.server() {            prettier_store                .update(cx, |prettier_store, cx| {                    let name = if is_default {                        LanguageServerName("prettier (default)".to_string().into())                    } else {                        let worktree_path = worktree_id                            .and_then(|id| {                                prettier_store                                    .worktree_store                                    .read(cx)                                    .worktree_for_id(id, cx)                            })                            .map(|worktree| worktree.update(cx, |worktree, _| worktree.abs_path()));                        let name = match worktree_path {                            Some(worktree_path) => {                                if prettier_dir == worktree_path.as_ref() {                                    let name = prettier_dir                                        .file_name()                                        .and_then(|name| name.to_str())                                        .unwrap_or_default();                                    format!("prettier ({name})")                                } else {                                    let dir_to_display = prettier_dir                                        .strip_prefix(worktree_path.as_ref())                                        .ok()                                        .unwrap_or(prettier_dir);                                    format!("prettier ({})", dir_to_display.display())                                }                            }                            None => format!("prettier ({})", prettier_dir.display()),                        };                        LanguageServerName(name.into())                    };                    cx.emit(PrettierStoreEvent::LanguageServerAdded {                        new_server_id,                        name,                        prettier_server: prettier_server.clone(),                    });                })                .ok();        }    }     pub fn update_prettier_settings(        &self,        worktree: &Entity<Worktree>,        changes: &[(Arc<Path>, ProjectEntryId, PathChange)],        cx: &mut Context<Self>,    ) {        let prettier_config_files = Prettier::CONFIG_FILE_NAMES            .iter()            .map(Path::new)            .collect::<HashSet<_>>();         let prettier_config_file_changed = changes            .iter()            .filter(|(_, _, change)| !matches!(change, PathChange::Loaded))            .filter(|(path, _, _)| {                !path                    .components()                    .any(|component| component.as_os_str().to_string_lossy() == "node_modules")            })            .find(|(path, _, _)| prettier_config_files.contains(path.as_ref()));        let current_worktree_id = worktree.read(cx).id();        if let Some((config_path, _, _)) = prettier_config_file_changed {            log::info!(                "Prettier config file {config_path:?} changed, reloading prettier instances for worktree {current_worktree_id}"            );            let prettiers_to_reload =                self.prettiers_per_worktree                    .get(&current_worktree_id)                    .iter()                    .flat_map(|prettier_paths| prettier_paths.iter())                    .flatten()                    .filter_map(|prettier_path| {                        Some((                            current_worktree_id,                            Some(prettier_path.clone()),                            self.prettier_instances.get(prettier_path)?.clone(),                        ))                    })                    .chain(self.default_prettier.instance().map(|default_prettier| {                        (current_worktree_id, None, default_prettier.clone())                    }))                    .collect::<Vec<_>>();             cx.background_spawn(async move {                let _: Vec<()> = future::join_all(prettiers_to_reload.into_iter().map(|(worktree_id, prettier_path, prettier_instance)| {                    async move {                        if let Some(instance) = prettier_instance.prettier {                            match instance.await {                                Ok(prettier) => {                                    prettier.clear_cache().log_err().await;                                },                                Err(e) => {                                    match prettier_path {                                        Some(prettier_path) => log::error!(                                            "Failed to clear prettier {prettier_path:?} cache for worktree {worktree_id:?} on prettier settings update: {e:#}"                                        ),                                        None => log::error!(                                            "Failed to clear default prettier cache for worktree {worktree_id:?} on prettier settings update: {e:#}"                                        ),                                    }

Lines 1–500 of 958.