@@ -3,6 +3,7 @@ use crate::{
environment::ProjectEnvironment,
lsp_command::{self, *},
lsp_ext_command,
+ prettier_store::{self, PrettierStore, PrettierStoreEvent},
project_settings::{LspSettings, ProjectSettings},
relativize_path, resolve_path,
worktree_store::{WorktreeStore, WorktreeStoreEvent},
@@ -101,6 +102,8 @@ pub struct LocalLspStore {
HashMap<LanguageServerId, HashMap<String, Vec<FileSystemWatcher>>>,
supplementary_language_servers:
HashMap<LanguageServerId, (LanguageServerName, Arc<LanguageServer>)>,
+ prettier_store: Model<PrettierStore>,
+ current_lsp_settings: HashMap<Arc<str>, LspSettings>,
_subscription: gpui::Subscription,
}
@@ -135,6 +138,7 @@ impl RemoteLspStore {}
pub struct SshLspStore {
upstream_client: AnyProtoClient,
+ current_lsp_settings: HashMap<Arc<str>, LspSettings>,
}
#[allow(clippy::large_enum_variant)]
@@ -310,9 +314,32 @@ impl LspStore {
}
}
+ pub fn swap_current_lsp_settings(
+ &mut self,
+ new_settings: HashMap<Arc<str>, LspSettings>,
+ ) -> Option<HashMap<Arc<str>, LspSettings>> {
+ match &mut self.mode {
+ LspStoreMode::Ssh(SshLspStore {
+ current_lsp_settings,
+ ..
+ })
+ | LspStoreMode::Local(LocalLspStore {
+ current_lsp_settings,
+ ..
+ }) => {
+ let ret = mem::take(current_lsp_settings);
+ *current_lsp_settings = new_settings;
+ Some(ret)
+ }
+ LspStoreMode::Remote(_) => None,
+ }
+ }
+
+ #[allow(clippy::too_many_arguments)]
pub fn new_local(
buffer_store: Model<BufferStore>,
worktree_store: Model<WorktreeStore>,
+ prettier_store: Model<PrettierStore>,
environment: Model<ProjectEnvironment>,
languages: Arc<LanguageRegistry>,
http_client: Option<Arc<dyn HttpClient>>,
@@ -324,6 +351,10 @@ impl LspStore {
.detach();
cx.subscribe(&worktree_store, Self::on_worktree_store_event)
.detach();
+ cx.subscribe(&prettier_store, Self::on_prettier_store_event)
+ .detach();
+ cx.observe_global::<SettingsStore>(Self::on_settings_changed)
+ .detach();
Self {
mode: LspStoreMode::Local(LocalLspStore {
@@ -332,6 +363,8 @@ impl LspStore {
last_workspace_edits_by_language_server: Default::default(),
language_server_watched_paths: Default::default(),
language_server_watcher_registrations: Default::default(),
+ current_lsp_settings: ProjectSettings::get_global(cx).lsp.clone(),
+ prettier_store,
environment,
http_client,
fs,
@@ -387,9 +420,14 @@ impl LspStore {
.detach();
cx.subscribe(&worktree_store, Self::on_worktree_store_event)
.detach();
+ cx.observe_global::<SettingsStore>(Self::on_settings_changed)
+ .detach();
Self {
- mode: LspStoreMode::Ssh(SshLspStore { upstream_client }),
+ mode: LspStoreMode::Ssh(SshLspStore {
+ upstream_client,
+ current_lsp_settings: Default::default(),
+ }),
downstream_client: None,
project_id,
buffer_store,
@@ -401,6 +439,7 @@ impl LspStore {
buffer_snapshots: Default::default(),
next_diagnostic_group_id: Default::default(),
diagnostic_summaries: Default::default(),
+
diagnostics: Default::default(),
active_entry: None,
_maintain_workspace_config: Self::maintain_workspace_config(cx),
@@ -498,6 +537,36 @@ impl LspStore {
}
}
+ fn on_prettier_store_event(
+ &mut self,
+ _: Model<PrettierStore>,
+ event: &PrettierStoreEvent,
+ cx: &mut ModelContext<Self>,
+ ) {
+ match event {
+ PrettierStoreEvent::LanguageServerRemoved(prettier_server_id) => {
+ self.unregister_supplementary_language_server(*prettier_server_id, cx);
+ }
+ PrettierStoreEvent::LanguageServerAdded {
+ new_server_id,
+ name,
+ prettier_server,
+ } => {
+ self.register_supplementary_language_server(
+ *new_server_id,
+ name.clone(),
+ prettier_server.clone(),
+ cx,
+ );
+ }
+ }
+ }
+
+ // todo!
+ pub fn prettier_store(&self) -> Option<Model<PrettierStore>> {
+ self.as_local().map(|local| local.prettier_store.clone())
+ }
+
fn on_buffer_event(
&mut self,
buffer: Model<Buffer>,
@@ -656,11 +725,29 @@ impl LspStore {
});
let buffer_file = buffer.read(cx).file().cloned();
+ let settings = language_settings(Some(&new_language), buffer_file.as_ref(), cx).clone();
let buffer_file = File::from_dyn(buffer_file.as_ref());
- if let Some(file) = buffer_file {
+ let worktree_id = if let Some(file) = buffer_file {
let worktree = file.worktree.clone();
- self.start_language_servers(&worktree, new_language.name(), cx)
+ self.start_language_servers(&worktree, new_language.name(), cx);
+
+ Some(worktree.read(cx).id())
+ } else {
+ None
+ };
+
+ if let Some(prettier_plugins) = prettier_store::prettier_plugins_for_language(&settings) {
+ let prettier_store = self.as_local().map(|s| s.prettier_store.clone());
+ if let Some(prettier_store) = prettier_store {
+ prettier_store.update(cx, |prettier_store, cx| {
+ prettier_store.install_default_prettier(
+ worktree_id,
+ prettier_plugins.iter().map(|s| Arc::from(s.as_str())),
+ cx,
+ )
+ })
+ }
}
cx.emit(LspStoreEvent::LanguageDetected {
@@ -799,6 +886,95 @@ impl LspStore {
Task::ready(Ok(Default::default()))
}
+ fn on_settings_changed(&mut self, cx: &mut ModelContext<Self>) {
+ let mut language_servers_to_start = Vec::new();
+ let mut language_formatters_to_check = Vec::new();
+ for buffer in self.buffer_store.read(cx).buffers() {
+ let buffer = buffer.read(cx);
+ let buffer_file = File::from_dyn(buffer.file());
+ let buffer_language = buffer.language();
+ let settings = language_settings(buffer_language, buffer.file(), cx);
+ if let Some(language) = buffer_language {
+ if settings.enable_language_server {
+ if let Some(file) = buffer_file {
+ language_servers_to_start.push((file.worktree.clone(), language.name()));
+ }
+ }
+ language_formatters_to_check
+ .push((buffer_file.map(|f| f.worktree_id(cx)), settings.clone()));
+ }
+ }
+
+ let mut language_servers_to_stop = Vec::new();
+ let mut language_servers_to_restart = Vec::new();
+ let languages = self.languages.to_vec();
+
+ let new_lsp_settings = ProjectSettings::get_global(cx).lsp.clone();
+ let Some(current_lsp_settings) = self.swap_current_lsp_settings(new_lsp_settings.clone())
+ else {
+ return;
+ };
+ for (worktree_id, started_lsp_name) in self.started_language_servers() {
+ let language = languages.iter().find_map(|l| {
+ let adapter = self
+ .languages
+ .lsp_adapters(&l.name())
+ .iter()
+ .find(|adapter| adapter.name == started_lsp_name)?
+ .clone();
+ Some((l, adapter))
+ });
+ if let Some((language, adapter)) = language {
+ let worktree = self.worktree_for_id(worktree_id, cx).ok();
+ let file = worktree.as_ref().and_then(|tree| {
+ tree.update(cx, |tree, cx| tree.root_file(cx).map(|f| f as _))
+ });
+ if !language_settings(Some(language), file.as_ref(), cx).enable_language_server {
+ language_servers_to_stop.push((worktree_id, started_lsp_name.clone()));
+ } else if let Some(worktree) = worktree {
+ let server_name = &adapter.name.0;
+ match (
+ current_lsp_settings.get(server_name),
+ new_lsp_settings.get(server_name),
+ ) {
+ (None, None) => {}
+ (Some(_), None) | (None, Some(_)) => {
+ language_servers_to_restart.push((worktree, language.name()));
+ }
+ (Some(current_lsp_settings), Some(new_lsp_settings)) => {
+ if current_lsp_settings != new_lsp_settings {
+ language_servers_to_restart.push((worktree, language.name()));
+ }
+ }
+ }
+ }
+ }
+ }
+
+ for (worktree_id, adapter_name) in language_servers_to_stop {
+ self.stop_language_server(worktree_id, adapter_name, cx)
+ .detach();
+ }
+
+ if let Some(prettier_store) = self.as_local().map(|s| s.prettier_store.clone()) {
+ prettier_store.update(cx, |prettier_store, cx| {
+ prettier_store.on_settings_changed(language_formatters_to_check, cx)
+ })
+ }
+
+ // Start all the newly-enabled language servers.
+ for (worktree, language) in language_servers_to_start {
+ self.start_language_servers(&worktree, language, cx);
+ }
+
+ // Restart all language servers with changed initialization options.
+ for (worktree, language) in language_servers_to_restart {
+ self.restart_language_servers(worktree, language, cx);
+ }
+
+ cx.notify();
+ }
+
pub async fn execute_code_actions_on_servers(
this: &WeakModel<LspStore>,
adapters_and_servers: &[(Arc<CachedLspAdapter>, Arc<LanguageServer>)],
@@ -2375,7 +2551,7 @@ impl LspStore {
})
}
- pub fn remove_worktree(&mut self, id_to_remove: WorktreeId, cx: &mut ModelContext<Self>) {
+ fn remove_worktree(&mut self, id_to_remove: WorktreeId, cx: &mut ModelContext<Self>) {
self.diagnostics.remove(&id_to_remove);
self.diagnostic_summaries.remove(&id_to_remove);
@@ -2406,6 +2582,12 @@ impl LspStore {
}
cx.emit(LspStoreEvent::LanguageServerRemoved(server_id_to_remove));
}
+
+ if let Some(local) = self.as_local() {
+ local.prettier_store.update(cx, |prettier_store, cx| {
+ prettier_store.remove_worktree(id_to_remove, cx);
+ })
+ }
}
pub fn shared(
@@ -6117,6 +6299,10 @@ impl LspStore {
let Some(local) = self.as_local() else { return };
+ local.prettier_store.update(cx, |prettier_store, cx| {
+ prettier_store.update_prettier_settings(&worktree_handle, changes, cx)
+ });
+
let worktree_id = worktree_handle.read(cx).id();
let mut language_server_ids = self
.language_server_ids
@@ -5,444 +5,384 @@ use std::{
};
use anyhow::{anyhow, Context, Result};
-use collections::HashSet;
+use collections::{HashMap, HashSet};
use fs::Fs;
use futures::{
future::{self, Shared},
+ stream::FuturesUnordered,
FutureExt,
};
-use gpui::{AsyncAppContext, Model, ModelContext, Task, WeakModel};
+use gpui::{AsyncAppContext, EventEmitter, Model, ModelContext, Task, WeakModel};
use language::{
language_settings::{Formatter, LanguageSettings, SelectedFormatter},
- Buffer, LanguageServerName, LocalFile,
+ Buffer, LanguageRegistry, LanguageServerName, LocalFile,
};
use lsp::{LanguageServer, LanguageServerId};
use node_runtime::NodeRuntime;
use paths::default_prettier_dir;
use prettier::Prettier;
+use smol::stream::StreamExt;
use util::{ResultExt, TryFutureExt};
-use crate::{File, FormatOperation, PathChange, Project, ProjectEntryId, Worktree, WorktreeId};
-
-pub fn prettier_plugins_for_language(
- language_settings: &LanguageSettings,
-) -> Option<&HashSet<String>> {
- match &language_settings.formatter {
- SelectedFormatter::Auto => Some(&language_settings.prettier.plugins),
-
- SelectedFormatter::List(list) => list
- .as_ref()
- .contains(&Formatter::Prettier)
- .then_some(&language_settings.prettier.plugins),
- }
-}
-
-pub(super) async fn format_with_prettier(
- project: &WeakModel<Project>,
- buffer: &Model<Buffer>,
- cx: &mut AsyncAppContext,
-) -> Option<Result<FormatOperation>> {
- let prettier_instance = project
- .update(cx, |project, cx| {
- project.prettier_instance_for_buffer(buffer, cx)
- })
- .ok()?
- .await;
-
- let (prettier_path, prettier_task) = prettier_instance?;
-
- let prettier_description = match prettier_path.as_ref() {
- Some(path) => format!("prettier at {path:?}"),
- None => "default prettier instance".to_string(),
- };
-
- match prettier_task.await {
- Ok(prettier) => {
- let buffer_path = buffer
- .update(cx, |buffer, cx| {
- File::from_dyn(buffer.file()).map(|file| file.abs_path(cx))
- })
- .ok()
- .flatten();
-
- let format_result = prettier
- .format(buffer, buffer_path, cx)
- .await
- .map(FormatOperation::Prettier)
- .with_context(|| format!("{} failed to format buffer", prettier_description));
-
- Some(format_result)
- }
- Err(error) => {
- project
- .update(cx, |project, _| {
- let instance_to_update = match prettier_path {
- Some(prettier_path) => project.prettier_instances.get_mut(&prettier_path),
- None => match &mut project.default_prettier.prettier {
- PrettierInstallation::NotInstalled { .. } => None,
- PrettierInstallation::Installed(instance) => Some(instance),
- },
- };
-
- if let Some(instance) = instance_to_update {
- instance.attempt += 1;
- instance.prettier = None;
- }
- })
- .log_err();
-
- Some(Err(anyhow!(
- "{} failed to spawn: {error:#}",
- prettier_description
- )))
- }
- }
-}
+use crate::{
+ worktree_store::WorktreeStore, File, FormatOperation, PathChange, ProjectEntryId, Worktree,
+ WorktreeId,
+};
-pub struct DefaultPrettier {
- prettier: PrettierInstallation,
- installed_plugins: HashSet<Arc<str>>,
+pub struct PrettierStore {
+ node: Arc<dyn NodeRuntime>,
+ fs: Arc<dyn Fs>,
+ languages: Arc<LanguageRegistry>,
+ worktree_store: Model<WorktreeStore>,
+ default_prettier: DefaultPrettier,
+ prettiers_per_worktree: HashMap<WorktreeId, HashSet<Option<PathBuf>>>,
+ prettier_instances: HashMap<PathBuf, PrettierInstance>,
}
-#[derive(Debug)]
-pub enum PrettierInstallation {
- NotInstalled {
- attempts: usize,
- installation_task: Option<Shared<Task<Result<(), Arc<anyhow::Error>>>>>,
- not_installed_plugins: HashSet<Arc<str>>,
+pub enum PrettierStoreEvent {
+ LanguageServerRemoved(LanguageServerId),
+ LanguageServerAdded {
+ new_server_id: LanguageServerId,
+ name: LanguageServerName,
+ prettier_server: Arc<LanguageServer>,
},
- Installed(PrettierInstance),
}
-pub type PrettierTask = Shared<Task<Result<Arc<Prettier>, Arc<anyhow::Error>>>>;
-
-#[derive(Debug, Clone)]
-pub struct PrettierInstance {
- attempt: usize,
- prettier: Option<PrettierTask>,
-}
+impl EventEmitter<PrettierStoreEvent> for PrettierStore {}
-impl Default for DefaultPrettier {
- fn default() -> Self {
+impl PrettierStore {
+ pub fn new(
+ node: Arc<dyn NodeRuntime>,
+ fs: Arc<dyn Fs>,
+ languages: Arc<LanguageRegistry>,
+ worktree_store: Model<WorktreeStore>,
+ _: &mut ModelContext<Self>,
+ ) -> Self {
Self {
- prettier: PrettierInstallation::NotInstalled {
- attempts: 0,
- installation_task: None,
- not_installed_plugins: HashSet::default(),
- },
- installed_plugins: HashSet::default(),
+ node,
+ fs,
+ languages,
+ worktree_store,
+ default_prettier: DefaultPrettier::default(),
+ prettiers_per_worktree: HashMap::default(),
+ prettier_instances: HashMap::default(),
}
}
-}
-impl DefaultPrettier {
- pub fn instance(&self) -> Option<&PrettierInstance> {
- if let PrettierInstallation::Installed(instance) = &self.prettier {
- Some(instance)
- } else {
- None
+ pub fn remove_worktree(&mut self, id_to_remove: WorktreeId, cx: &mut ModelContext<Self>) {
+ 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(|prettier_store, mut cx| async move {
+ while let Some(prettier_server_id) = prettier_instances_to_clean.next().await {
+ if let Some(prettier_server_id) = prettier_server_id {
+ prettier_store
+ .update(&mut cx, |_, cx| {
+ cx.emit(PrettierStoreEvent::LanguageServerRemoved(
+ prettier_server_id,
+ ));
+ })
+ .ok();
+ }
+ }
+ })
+ .detach();
}
- pub fn prettier_task(
+ fn prettier_instance_for_buffer(
&mut self,
- node: &Arc<dyn NodeRuntime>,
- worktree_id: Option<WorktreeId>,
- cx: &mut ModelContext<'_, Project>,
- ) -> Option<Task<anyhow::Result<PrettierTask>>> {
- match &mut self.prettier {
- PrettierInstallation::NotInstalled { .. } => {
- Some(start_default_prettier(Arc::clone(node), worktree_id, cx))
+ buffer: &Model<Buffer>,
+ cx: &mut ModelContext<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(|lsp_store, mut cx| async move {
+ match cx
+ .background_executor()
+ .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(&mut 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(&mut 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(&mut 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(&mut 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
+ }
+ }
+ })
}
- PrettierInstallation::Installed(existing_instance) => {
- existing_instance.prettier_task(node, None, worktree_id, cx)
+ None => {
+ let new_task = self.default_prettier.prettier_task(&node, None, cx);
+ cx.spawn(|_, _| async move { Some((None, new_task?.log_err().await?)) })
}
}
}
-}
-impl PrettierInstance {
- pub fn prettier_task(
- &mut self,
- node: &Arc<dyn NodeRuntime>,
- prettier_dir: Option<&Path>,
+ fn start_prettier(
+ node: Arc<dyn NodeRuntime>,
+ prettier_dir: PathBuf,
worktree_id: Option<WorktreeId>,
- cx: &mut ModelContext<'_, Project>,
- ) -> Option<Task<anyhow::Result<PrettierTask>>> {
- if self.attempt > prettier::FAIL_THRESHOLD {
- match prettier_dir {
- Some(prettier_dir) => log::warn!(
- "Prettier from path {prettier_dir:?} exceeded launch threshold, not starting"
- ),
- None => log::warn!("Default prettier exceeded launch threshold, not starting"),
- }
- return None;
- }
- Some(match &self.prettier {
- Some(prettier_task) => Task::ready(Ok(prettier_task.clone())),
- None => match prettier_dir {
- Some(prettier_dir) => {
- let new_task = start_prettier(
- Arc::clone(node),
- prettier_dir.to_path_buf(),
- worktree_id,
- cx,
- );
- self.attempt += 1;
- self.prettier = Some(new_task.clone());
- Task::ready(Ok(new_task))
- }
- None => {
- self.attempt += 1;
- let node = Arc::clone(node);
- cx.spawn(|project, mut cx| async move {
- project
- .update(&mut cx, |_, cx| {
- start_default_prettier(node, worktree_id, cx)
- })?
- .await
- })
- }
- },
+ cx: &mut ModelContext<Self>,
+ ) -> PrettierTask {
+ cx.spawn(|prettier_store, mut cx| async move {
+ log::info!("Starting prettier at path {prettier_dir:?}");
+ let new_server_id = prettier_store.update(&mut 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,
+ &mut cx,
+ );
+ Ok(new_prettier)
})
+ .shared()
}
- pub async fn server(&self) -> Option<Arc<LanguageServer>> {
- self.prettier.clone()?.await.ok()?.server().cloned()
- }
-}
-
-fn start_default_prettier(
- node: Arc<dyn NodeRuntime>,
- worktree_id: Option<WorktreeId>,
- cx: &mut ModelContext<'_, Project>,
-) -> Task<anyhow::Result<PrettierTask>> {
- cx.spawn(|project, mut cx| async move {
- let installation_task = project.update(&mut cx, |project, _| {
- match &project.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 {
- project.update(&mut 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:#}"
- );
+ fn start_default_prettier(
+ node: Arc<dyn NodeRuntime>,
+ worktree_id: Option<WorktreeId>,
+ cx: &mut ModelContext<PrettierStore>,
+ ) -> Task<anyhow::Result<PrettierTask>> {
+ cx.spawn(|prettier_store, mut cx| async move {
+ let installation_task = prettier_store.update(&mut 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")
}
- let new_default_prettier = project.update(&mut cx, |project, cx| {
+ ControlFlow::Continue(Some(installation_task)) => {
+ log::info!("Waiting for default prettier to install");
+ if let Err(e) = installation_task.await {
+ prettier_store.update(&mut 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 =
- start_prettier(node, default_prettier_dir().clone(), worktree_id, cx);
- project.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 = 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(PrettierInstance {
- attempt: instance.attempt + 1,
- prettier: Some(new_default_prettier.clone()),
- });
- new_default_prettier
- })?;
+ prettier_store.update(&mut 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)
}
- },
- }
- })
-}
-
-fn start_prettier(
- node: Arc<dyn NodeRuntime>,
- prettier_dir: PathBuf,
- worktree_id: Option<WorktreeId>,
- cx: &mut ModelContext<'_, Project>,
-) -> PrettierTask {
- cx.spawn(|project, mut cx| async move {
- log::info!("Starting prettier at path {prettier_dir:?}");
- let new_server_id = project.update(&mut cx, |project, _| {
- project.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)?;
- register_new_prettier(&project, &new_prettier, worktree_id, new_server_id, &mut cx);
- Ok(new_prettier)
- })
- .shared()
-}
-
-fn register_new_prettier(
- project: &WeakModel<Project>,
- prettier: &Prettier,
- worktree_id: Option<WorktreeId>,
- new_server_id: LanguageServerId,
- cx: &mut AsyncAppContext,
-) {
- 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:?}");
+ ControlFlow::Break(instance) => match instance.prettier {
+ Some(instance) => Ok(instance),
+ None => {
+ let new_default_prettier =
+ prettier_store.update(&mut 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)
+ }
+ },
+ }
+ })
}
- if let Some(prettier_server) = prettier.server() {
- project
- .update(cx, |project, cx| {
- let name = if is_default {
- LanguageServerName(Arc::from("prettier (default)"))
- } else {
- let worktree_path = worktree_id
- .and_then(|id| project.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())
+
+ fn register_new_prettier(
+ prettier_store: &WeakModel<Self>,
+ prettier: &Prettier,
+ worktree_id: Option<WorktreeId>,
+ new_server_id: LanguageServerId,
+ cx: &mut AsyncAppContext,
+ ) {
+ 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(Arc::from("prettier (default)"))
+ } 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()),
+ None => format!("prettier ({})", prettier_dir.display()),
+ };
+ LanguageServerName(Arc::from(name))
};
- LanguageServerName(Arc::from(name))
- };
- project.lsp_store.update(cx, |lsp_store, cx| {
- lsp_store.register_supplementary_language_server(
+ cx.emit(PrettierStoreEvent::LanguageServerAdded {
new_server_id,
name,
- Arc::clone(prettier_server),
- cx,
- )
- });
- })
- .ok();
- }
-}
-
-async fn install_prettier_packages(
- fs: &dyn Fs,
- plugins_to_install: HashSet<Arc<str>>,
- node: Arc<dyn NodeRuntime>,
-) -> anyhow::Result<()> {
- let packages_to_versions = future::try_join_all(
- plugins_to_install
- .iter()
- .chain(Some(&"prettier".into()))
- .map(|package_name| async {
- let returned_package_name = package_name.to_string();
- let latest_version = node
- .npm_package_latest_version(package_name)
- .await
- .with_context(|| {
- format!("fetching latest npm version for package {returned_package_name}")
- })?;
- anyhow::Ok((returned_package_name, latest_version))
- }),
- )
- .await
- .context("fetching latest npm versions")?;
-
- let default_prettier_dir = default_prettier_dir().as_path();
- match fs.metadata(default_prettier_dir).await.with_context(|| {
- format!("fetching FS metadata for default prettier dir {default_prettier_dir:?}")
- })? {
- Some(prettier_dir_metadata) => anyhow::ensure!(
- prettier_dir_metadata.is_dir,
- "default prettier dir {default_prettier_dir:?} is not a directory"
- ),
- None => fs
- .create_dir(default_prettier_dir)
- .await
- .with_context(|| format!("creating default prettier dir {default_prettier_dir:?}"))?,
- }
-
- log::info!("Installing default prettier and plugins: {packages_to_versions:?}");
- let borrowed_packages = packages_to_versions
- .iter()
- .map(|(package, version)| (package.as_str(), version.as_str()))
- .collect::<Vec<_>>();
- node.npm_install_packages(default_prettier_dir, &borrowed_packages)
- .await
- .context("fetching formatter packages")?;
- anyhow::Ok(())
-}
-
-async fn save_prettier_server_file(fs: &dyn Fs) -> anyhow::Result<()> {
- let prettier_wrapper_path = default_prettier_dir().join(prettier::PRETTIER_SERVER_FILE);
- fs.save(
- &prettier_wrapper_path,
- &text::Rope::from(prettier::PRETTIER_SERVER_JS),
- text::LineEnding::Unix,
- )
- .await
- .with_context(|| {
- format!(
- "writing {} file at {prettier_wrapper_path:?}",
- prettier::PRETTIER_SERVER_FILE
- )
- })?;
- Ok(())
-}
-
-async fn should_write_prettier_server_file(fs: &dyn Fs) -> bool {
- let prettier_wrapper_path = default_prettier_dir().join(prettier::PRETTIER_SERVER_FILE);
- if !fs.is_file(&prettier_wrapper_path).await {
- return true;
+ prettier_server: prettier_server.clone(),
+ });
+ })
+ .ok();
+ }
}
- let Ok(prettier_server_file_contents) = fs.load(&prettier_wrapper_path).await else {
- return true;
- };
- prettier_server_file_contents != prettier::PRETTIER_SERVER_JS
-}
-impl Project {
pub fn update_prettier_settings(
&self,
worktree: &Model<Worktree>,
changes: &[(Arc<Path>, ProjectEntryId, PathChange)],
- cx: &mut ModelContext<'_, Project>,
+ cx: &mut ModelContext<Self>,
) {
let prettier_config_files = Prettier::CONFIG_FILE_NAMES
.iter()
@@ -510,122 +450,6 @@ impl Project {
}
}
- fn prettier_instance_for_buffer(
- &mut self,
- buffer: &Model<Buffer>,
- cx: &mut ModelContext<Self>,
- ) -> Task<Option<(Option<PathBuf>, PrettierTask)>> {
- // todo(ssh remote): prettier support
- if self.is_via_collab() || self.ssh_session.is_some() {
- return Task::ready(None);
- }
- let buffer = buffer.read(cx);
- let buffer_file = buffer.file();
- if buffer.language().is_none() {
- return Task::ready(None);
- }
- let Some(node) = self.node.clone() else {
- 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 installed_prettiers = self.prettier_instances.keys().cloned().collect();
- cx.spawn(|project, mut cx| async move {
- match cx
- .background_executor()
- .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 = project
- .update(&mut cx, |project, cx| {
- project
- .prettiers_per_worktree
- .entry(worktree_id)
- .or_default()
- .insert(None);
- project.default_prettier.prettier_task(
- &node,
- Some(worktree_id),
- cx,
- )
- })
- .ok()?;
- Some((None, default_instance?.log_err().await?))
- }
- Ok(ControlFlow::Continue(Some(prettier_dir))) => {
- project
- .update(&mut cx, |project, _| {
- project
- .prettiers_per_worktree
- .entry(worktree_id)
- .or_default()
- .insert(Some(prettier_dir.clone()))
- })
- .ok()?;
- if let Some(prettier_task) = project
- .update(&mut cx, |project, cx| {
- project.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 = project
- .update(&mut cx, |project, cx| {
- let new_prettier_task = start_prettier(
- node,
- prettier_dir.clone(),
- Some(worktree_id),
- cx,
- );
- project.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?)) })
- }
- }
- }
-
pub fn install_default_prettier(
&mut self,
worktree: Option<WorktreeId>,
@@ -642,12 +466,13 @@ impl Project {
}
let mut new_plugins = plugins.collect::<HashSet<_>>();
- let Some(node) = self.node.as_ref().cloned() else {
- return;
- };
+ let node = self.node.clone();
+
let fs = Arc::clone(&self.fs);
let locate_prettier_installation = match worktree.and_then(|worktree_id| {
- self.worktree_for_id(worktree_id, cx)
+ self.worktree_store
+ .read(cx)
+ .worktree_for_id(worktree_id, cx)
.map(|worktree| worktree.read(cx).abs_path())
}) {
Some(locate_from) => {
@@ -777,4 +602,291 @@ impl Project {
not_installed_plugins: plugins_to_install,
};
}
+
+ pub fn on_settings_changed(
+ &mut self,
+ language_formatters_to_check: Vec<(Option<WorktreeId>, LanguageSettings)>,
+ cx: &mut ModelContext<Self>,
+ ) {
+ let mut prettier_plugins_by_worktree = HashMap::default();
+ for (worktree, language_settings) in language_formatters_to_check {
+ if let Some(plugins) = prettier_plugins_for_language(&language_settings) {
+ prettier_plugins_by_worktree
+ .entry(worktree)
+ .or_insert_with(HashSet::default)
+ .extend(plugins.iter().cloned());
+ }
+ }
+ for (worktree, prettier_plugins) in prettier_plugins_by_worktree {
+ self.install_default_prettier(
+ worktree,
+ prettier_plugins.into_iter().map(Arc::from),
+ cx,
+ );
+ }
+ }
+}
+
+pub fn prettier_plugins_for_language(
+ language_settings: &LanguageSettings,
+) -> Option<&HashSet<String>> {
+ match &language_settings.formatter {
+ SelectedFormatter::Auto => Some(&language_settings.prettier.plugins),
+
+ SelectedFormatter::List(list) => list
+ .as_ref()
+ .contains(&Formatter::Prettier)
+ .then_some(&language_settings.prettier.plugins),
+ }
+}
+
+pub(super) async fn format_with_prettier(
+ prettier_store: &WeakModel<PrettierStore>,
+ buffer: &Model<Buffer>,
+ cx: &mut AsyncAppContext,
+) -> Option<Result<FormatOperation>> {
+ let prettier_instance = prettier_store
+ .update(cx, |prettier_store, cx| {
+ prettier_store.prettier_instance_for_buffer(buffer, cx)
+ })
+ .ok()?
+ .await;
+
+ let (prettier_path, prettier_task) = prettier_instance?;
+
+ let prettier_description = match prettier_path.as_ref() {
+ Some(path) => format!("prettier at {path:?}"),
+ None => "default prettier instance".to_string(),
+ };
+
+ match prettier_task.await {
+ Ok(prettier) => {
+ let buffer_path = buffer
+ .update(cx, |buffer, cx| {
+ File::from_dyn(buffer.file()).map(|file| file.abs_path(cx))
+ })
+ .ok()
+ .flatten();
+
+ let format_result = prettier
+ .format(buffer, buffer_path, cx)
+ .await
+ .map(FormatOperation::Prettier)
+ .with_context(|| format!("{} failed to format buffer", prettier_description));
+
+ Some(format_result)
+ }
+ Err(error) => {
+ prettier_store
+ .update(cx, |project, _| {
+ let instance_to_update = match prettier_path {
+ Some(prettier_path) => project.prettier_instances.get_mut(&prettier_path),
+ None => match &mut project.default_prettier.prettier {
+ PrettierInstallation::NotInstalled { .. } => None,
+ PrettierInstallation::Installed(instance) => Some(instance),
+ },
+ };
+
+ if let Some(instance) = instance_to_update {
+ instance.attempt += 1;
+ instance.prettier = None;
+ }
+ })
+ .log_err();
+
+ Some(Err(anyhow!(
+ "{} failed to spawn: {error:#}",
+ prettier_description
+ )))
+ }
+ }
+}
+
+pub struct DefaultPrettier {
+ prettier: PrettierInstallation,
+ installed_plugins: HashSet<Arc<str>>,
+}
+
+#[derive(Debug)]
+pub enum PrettierInstallation {
+ NotInstalled {
+ attempts: usize,
+ installation_task: Option<Shared<Task<Result<(), Arc<anyhow::Error>>>>>,
+ not_installed_plugins: HashSet<Arc<str>>,
+ },
+ Installed(PrettierInstance),
+}
+
+pub type PrettierTask = Shared<Task<Result<Arc<Prettier>, Arc<anyhow::Error>>>>;
+
+#[derive(Debug, Clone)]
+pub struct PrettierInstance {
+ attempt: usize,
+ prettier: Option<PrettierTask>,
+}
+
+impl Default for DefaultPrettier {
+ fn default() -> Self {
+ Self {
+ prettier: PrettierInstallation::NotInstalled {
+ attempts: 0,
+ installation_task: None,
+ not_installed_plugins: HashSet::default(),
+ },
+ installed_plugins: HashSet::default(),
+ }
+ }
+}
+
+impl DefaultPrettier {
+ pub fn instance(&self) -> Option<&PrettierInstance> {
+ if let PrettierInstallation::Installed(instance) = &self.prettier {
+ Some(instance)
+ } else {
+ None
+ }
+ }
+
+ pub fn prettier_task(
+ &mut self,
+ node: &Arc<dyn NodeRuntime>,
+ worktree_id: Option<WorktreeId>,
+ cx: &mut ModelContext<PrettierStore>,
+ ) -> Option<Task<anyhow::Result<PrettierTask>>> {
+ match &mut self.prettier {
+ PrettierInstallation::NotInstalled { .. } => Some(
+ PrettierStore::start_default_prettier(node.clone(), worktree_id, cx),
+ ),
+ PrettierInstallation::Installed(existing_instance) => {
+ existing_instance.prettier_task(node, None, worktree_id, cx)
+ }
+ }
+ }
+}
+
+impl PrettierInstance {
+ pub fn prettier_task(
+ &mut self,
+ node: &Arc<dyn NodeRuntime>,
+ prettier_dir: Option<&Path>,
+ worktree_id: Option<WorktreeId>,
+ cx: &mut ModelContext<PrettierStore>,
+ ) -> Option<Task<anyhow::Result<PrettierTask>>> {
+ if self.attempt > prettier::FAIL_THRESHOLD {
+ match prettier_dir {
+ Some(prettier_dir) => log::warn!(
+ "Prettier from path {prettier_dir:?} exceeded launch threshold, not starting"
+ ),
+ None => log::warn!("Default prettier exceeded launch threshold, not starting"),
+ }
+ return None;
+ }
+ Some(match &self.prettier {
+ Some(prettier_task) => Task::ready(Ok(prettier_task.clone())),
+ None => match prettier_dir {
+ Some(prettier_dir) => {
+ let new_task = PrettierStore::start_prettier(
+ Arc::clone(node),
+ prettier_dir.to_path_buf(),
+ worktree_id,
+ cx,
+ );
+ self.attempt += 1;
+ self.prettier = Some(new_task.clone());
+ Task::ready(Ok(new_task))
+ }
+ None => {
+ self.attempt += 1;
+ let node = Arc::clone(node);
+ cx.spawn(|prettier_store, mut cx| async move {
+ prettier_store
+ .update(&mut cx, |_, cx| {
+ PrettierStore::start_default_prettier(node, worktree_id, cx)
+ })?
+ .await
+ })
+ }
+ },
+ })
+ }
+
+ pub async fn server(&self) -> Option<Arc<LanguageServer>> {
+ self.prettier.clone()?.await.ok()?.server().cloned()
+ }
+}
+
+async fn install_prettier_packages(
+ fs: &dyn Fs,
+ plugins_to_install: HashSet<Arc<str>>,
+ node: Arc<dyn NodeRuntime>,
+) -> anyhow::Result<()> {
+ let packages_to_versions = future::try_join_all(
+ plugins_to_install
+ .iter()
+ .chain(Some(&"prettier".into()))
+ .map(|package_name| async {
+ let returned_package_name = package_name.to_string();
+ let latest_version = node
+ .npm_package_latest_version(package_name)
+ .await
+ .with_context(|| {
+ format!("fetching latest npm version for package {returned_package_name}")
+ })?;
+ anyhow::Ok((returned_package_name, latest_version))
+ }),
+ )
+ .await
+ .context("fetching latest npm versions")?;
+
+ let default_prettier_dir = default_prettier_dir().as_path();
+ match fs.metadata(default_prettier_dir).await.with_context(|| {
+ format!("fetching FS metadata for default prettier dir {default_prettier_dir:?}")
+ })? {
+ Some(prettier_dir_metadata) => anyhow::ensure!(
+ prettier_dir_metadata.is_dir,
+ "default prettier dir {default_prettier_dir:?} is not a directory"
+ ),
+ None => fs
+ .create_dir(default_prettier_dir)
+ .await
+ .with_context(|| format!("creating default prettier dir {default_prettier_dir:?}"))?,
+ }
+
+ log::info!("Installing default prettier and plugins: {packages_to_versions:?}");
+ let borrowed_packages = packages_to_versions
+ .iter()
+ .map(|(package, version)| (package.as_str(), version.as_str()))
+ .collect::<Vec<_>>();
+ node.npm_install_packages(default_prettier_dir, &borrowed_packages)
+ .await
+ .context("fetching formatter packages")?;
+ anyhow::Ok(())
+}
+
+async fn save_prettier_server_file(fs: &dyn Fs) -> anyhow::Result<()> {
+ let prettier_wrapper_path = default_prettier_dir().join(prettier::PRETTIER_SERVER_FILE);
+ fs.save(
+ &prettier_wrapper_path,
+ &text::Rope::from(prettier::PRETTIER_SERVER_JS),
+ text::LineEnding::Unix,
+ )
+ .await
+ .with_context(|| {
+ format!(
+ "writing {} file at {prettier_wrapper_path:?}",
+ prettier::PRETTIER_SERVER_FILE
+ )
+ })?;
+ Ok(())
+}
+
+async fn should_write_prettier_server_file(fs: &dyn Fs) -> bool {
+ let prettier_wrapper_path = default_prettier_dir().join(prettier::PRETTIER_SERVER_FILE);
+ if !fs.is_file(&prettier_wrapper_path).await {
+ return true;
+ }
+ let Ok(prettier_server_file_contents) = fs.load(&prettier_wrapper_path).await else {
+ return true;
+ };
+ prettier_server_file_contents != prettier::PRETTIER_SERVER_JS
}
@@ -4,7 +4,7 @@ pub mod debounced_delay;
pub mod lsp_command;
pub mod lsp_ext_command;
pub mod lsp_store;
-mod prettier_support;
+pub mod prettier_store;
pub mod project_settings;
pub mod search;
mod task_inventory;
@@ -31,7 +31,6 @@ pub use environment::ProjectEnvironment;
use futures::{
channel::mpsc::{self, UnboundedReceiver},
future::try_join_all,
- stream::FuturesUnordered,
AsyncWriteExt, FutureExt, StreamExt,
};
@@ -59,8 +58,8 @@ use lsp_command::*;
use node_runtime::NodeRuntime;
use parking_lot::{Mutex, RwLock};
use paths::{local_tasks_file_relative_path, local_vscode_tasks_file_relative_path};
-use prettier_support::{DefaultPrettier, PrettierInstance};
-use project_settings::{LspSettings, ProjectSettings, SettingsObserver};
+pub use prettier_store::PrettierStore;
+use project_settings::{ProjectSettings, SettingsObserver};
use remote::SshSession;
use rpc::{proto::SSH_PROJECT_ID, AnyProtoClient, ErrorCode};
use search::{SearchInputKind, SearchQuery, SearchResult};
@@ -140,7 +139,6 @@ pub struct Project {
buffer_ordered_messages_tx: mpsc::UnboundedSender<BufferOrderedMessage>,
languages: Arc<LanguageRegistry>,
client: Arc<client::Client>,
- current_lsp_settings: HashMap<Arc<str>, LspSettings>,
join_project_response_message_id: u32,
user_store: Model<UserStore>,
fs: Arc<dyn Fs>,
@@ -157,9 +155,6 @@ pub struct Project {
remotely_created_buffers: Arc<Mutex<RemotelyCreatedBuffers>>,
terminals: Terminals,
node: Option<Arc<dyn NodeRuntime>>,
- default_prettier: DefaultPrettier,
- prettiers_per_worktree: HashMap<WorktreeId, HashSet<Option<PathBuf>>>,
- prettier_instances: HashMap<PathBuf, PrettierInstance>,
tasks: Model<Inventory>,
hosted_project_id: Option<ProjectId>,
dev_server_project_id: Option<client::DevServerProjectId>,
@@ -634,6 +629,16 @@ impl Project {
cx.subscribe(&buffer_store, Self::on_buffer_store_event)
.detach();
+ let prettier_store = cx.new_model(|cx| {
+ PrettierStore::new(
+ node.clone(),
+ fs.clone(),
+ languages.clone(),
+ worktree_store.clone(),
+ cx,
+ )
+ });
+
let settings_observer = cx.new_model(|cx| {
SettingsObserver::new_local(fs.clone(), worktree_store.clone(), cx)
});
@@ -643,6 +648,7 @@ impl Project {
LspStore::new_local(
buffer_store.clone(),
worktree_store.clone(),
+ prettier_store.clone(),
environment.clone(),
languages.clone(),
Some(client.http_client()),
@@ -658,14 +664,10 @@ impl Project {
worktree_store,
buffer_store,
lsp_store,
- current_lsp_settings: ProjectSettings::get_global(cx).lsp.clone(),
join_project_response_message_id: 0,
client_state: ProjectClientState::Local,
client_subscriptions: Vec::new(),
- _subscriptions: vec![
- cx.observe_global::<SettingsStore>(Self::on_settings_changed),
- cx.on_release(Self::release),
- ],
+ _subscriptions: vec![cx.on_release(Self::release)],
active_entry: None,
snippets,
languages,
@@ -680,9 +682,6 @@ impl Project {
local_handles: Vec::new(),
},
node: Some(node),
- default_prettier: DefaultPrettier::default(),
- prettiers_per_worktree: HashMap::default(),
- prettier_instances: HashMap::default(),
tasks,
hosted_project_id: None,
dev_server_project_id: None,
@@ -751,14 +750,10 @@ impl Project {
worktree_store,
buffer_store,
lsp_store,
- current_lsp_settings: ProjectSettings::get_global(cx).lsp.clone(),
join_project_response_message_id: 0,
client_state: ProjectClientState::Local,
client_subscriptions: Vec::new(),
- _subscriptions: vec![
- cx.observe_global::<SettingsStore>(Self::on_settings_changed),
- cx.on_release(Self::release),
- ],
+ _subscriptions: vec![cx.on_release(Self::release)],
active_entry: None,
snippets,
languages,
@@ -773,9 +768,6 @@ impl Project {
local_handles: Vec::new(),
},
node: Some(node),
- default_prettier: DefaultPrettier::default(),
- prettiers_per_worktree: HashMap::default(),
- prettier_instances: HashMap::default(),
tasks,
hosted_project_id: None,
dev_server_project_id: None,
@@ -928,7 +920,6 @@ impl Project {
buffer_store: buffer_store.clone(),
worktree_store: worktree_store.clone(),
lsp_store: lsp_store.clone(),
- current_lsp_settings: ProjectSettings::get_global(cx).lsp.clone(),
active_entry: None,
collaborators: Default::default(),
join_project_response_message_id: response.message_id,
@@ -954,9 +945,6 @@ impl Project {
local_handles: Vec::new(),
},
node: None,
- default_prettier: DefaultPrettier::default(),
- prettiers_per_worktree: HashMap::default(),
- prettier_instances: HashMap::default(),
tasks,
hosted_project_id: None,
dev_server_project_id: response
@@ -1176,112 +1164,6 @@ impl Project {
self.worktree_store.clone()
}
- fn on_settings_changed(&mut self, cx: &mut ModelContext<Self>) {
- let mut language_servers_to_start = Vec::new();
- let mut language_formatters_to_check = Vec::new();
- for buffer in self.buffer_store.read(cx).buffers() {
- let buffer = buffer.read(cx);
- let buffer_file = File::from_dyn(buffer.file());
- let buffer_language = buffer.language();
- let settings = language_settings(buffer_language, buffer.file(), cx);
- if let Some(language) = buffer_language {
- if settings.enable_language_server {
- if let Some(file) = buffer_file {
- language_servers_to_start.push((file.worktree.clone(), language.name()));
- }
- }
- language_formatters_to_check
- .push((buffer_file.map(|f| f.worktree_id(cx)), settings.clone()));
- }
- }
-
- let mut language_servers_to_stop = Vec::new();
- let mut language_servers_to_restart = Vec::new();
- let languages = self.languages.to_vec();
-
- let new_lsp_settings = ProjectSettings::get_global(cx).lsp.clone();
- let current_lsp_settings = &self.current_lsp_settings;
- for (worktree_id, started_lsp_name) in self.lsp_store.read(cx).started_language_servers() {
- let language = languages.iter().find_map(|l| {
- let adapter = self
- .languages
- .lsp_adapters(&l.name())
- .iter()
- .find(|adapter| adapter.name == started_lsp_name)?
- .clone();
- Some((l, adapter))
- });
- if let Some((language, adapter)) = language {
- let worktree = self.worktree_for_id(worktree_id, cx);
- let file = worktree.as_ref().and_then(|tree| {
- tree.update(cx, |tree, cx| tree.root_file(cx).map(|f| f as _))
- });
- if !language_settings(Some(language), file.as_ref(), cx).enable_language_server {
- language_servers_to_stop.push((worktree_id, started_lsp_name.clone()));
- } else if let Some(worktree) = worktree {
- let server_name = &adapter.name.0;
- match (
- current_lsp_settings.get(server_name),
- new_lsp_settings.get(server_name),
- ) {
- (None, None) => {}
- (Some(_), None) | (None, Some(_)) => {
- language_servers_to_restart.push((worktree, language.name()));
- }
- (Some(current_lsp_settings), Some(new_lsp_settings)) => {
- if current_lsp_settings != new_lsp_settings {
- language_servers_to_restart.push((worktree, language.name()));
- }
- }
- }
- }
- }
- }
- self.current_lsp_settings = new_lsp_settings;
-
- // Stop all newly-disabled language servers.
- self.lsp_store.update(cx, |lsp_store, cx| {
- for (worktree_id, adapter_name) in language_servers_to_stop {
- lsp_store
- .stop_language_server(worktree_id, adapter_name, cx)
- .detach();
- }
- });
-
- let mut prettier_plugins_by_worktree = HashMap::default();
- for (worktree, language_settings) in language_formatters_to_check {
- if let Some(plugins) =
- prettier_support::prettier_plugins_for_language(&language_settings)
- {
- prettier_plugins_by_worktree
- .entry(worktree)
- .or_insert_with(HashSet::default)
- .extend(plugins.iter().cloned());
- }
- }
- for (worktree, prettier_plugins) in prettier_plugins_by_worktree {
- self.install_default_prettier(
- worktree,
- prettier_plugins.into_iter().map(Arc::from),
- cx,
- );
- }
-
- // Start all the newly-enabled language servers.
- self.lsp_store.update(cx, |lsp_store, cx| {
- for (worktree, language) in language_servers_to_start {
- lsp_store.start_language_servers(&worktree, language, cx);
- }
-
- // Restart all language servers with changed initialization options.
- for (worktree, language) in language_servers_to_restart {
- lsp_store.restart_language_servers(worktree, language, cx);
- }
- });
-
- cx.notify();
- }
-
pub fn buffer_for_id(&self, remote_id: BufferId, cx: &AppContext) -> Option<Model<Buffer>> {
self.buffer_store.read(cx).get(remote_id)
}
@@ -2160,24 +2042,10 @@ impl Project {
buffer,
new_language,
} => {
- let Some(new_language) = new_language else {
+ let Some(_) = new_language else {
cx.emit(Event::LanguageNotFound(buffer.clone()));
return;
};
- let buffer_file = buffer.read(cx).file().cloned();
- let settings =
- language_settings(Some(new_language), buffer_file.as_ref(), cx).clone();
- let buffer_file = File::from_dyn(buffer_file.as_ref());
- let worktree = buffer_file.as_ref().map(|f| f.worktree_id(cx));
- if let Some(prettier_plugins) =
- prettier_support::prettier_plugins_for_language(&settings)
- {
- self.install_default_prettier(
- worktree,
- prettier_plugins.iter().map(|s| Arc::from(s.as_str())),
- cx,
- );
- };
}
LspStoreEvent::RefreshInlayHints => cx.emit(Event::RefreshInlayHints),
LspStoreEvent::LanguageServerPrompt(prompt) => {
@@ -2253,7 +2121,6 @@ impl Project {
worktree::Event::UpdatedEntries(changes) => {
if is_local {
this.update_local_worktree_settings(&worktree, changes, cx);
- this.update_prettier_settings(&worktree, changes, cx);
}
cx.emit(Event::WorktreeUpdatedEntries(
@@ -2300,37 +2167,6 @@ impl Project {
return;
}
- 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(|project, mut cx| async move {
- while let Some(prettier_server_id) = prettier_instances_to_clean.next().await {
- if let Some(prettier_server_id) = prettier_server_id {
- project
- .update(&mut cx, |project, cx| {
- project.lsp_store.update(cx, |lsp_store, cx| {
- lsp_store.unregister_supplementary_language_server(
- prettier_server_id,
- cx,
- );
- });
- })
- .ok();
- }
- }
- })
- .detach();
-
self.task_inventory().update(cx, |inventory, _| {
inventory.remove_worktree_sources(id_to_remove);
});
@@ -3059,11 +2895,21 @@ impl Project {
None
}
}
- Formatter::Prettier => prettier_support::format_with_prettier(&project, buffer, cx)
- .await
- .transpose()
- .ok()
- .flatten(),
+ Formatter::Prettier => {
+ let prettier = project.update(cx, |project, cx| {
+ project
+ .lsp_store
+ .read(cx)
+ .prettier_store()
+ .unwrap()
+ .downgrade()
+ })?;
+ prettier_store::format_with_prettier(&prettier, buffer, cx)
+ .await
+ .transpose()
+ .ok()
+ .flatten()
+ }
Formatter::External { command, arguments } => {
let buffer_abs_path = buffer_abs_path.as_ref().map(|path| path.as_path());
Self::format_via_external_command(buffer, buffer_abs_path, command, arguments, cx)