History

Refactor prettier (#17977)

In preparation for making formatting work on ssh remotes Release Notes: - N/A

Conrad Irwin committed 2 years ago

Commit 8e45bf71 parent db18f7a6 files · +902 −696Browse files

Unified · Split

6 files
  1. crates/node_runtime/src/node_runtime.rs +49 −0
  2. crates/project/src/lsp_store.rs +190 −4
  3. crates/project/src/prettier_store.rs +617 −505
  4. crates/project/src/project.rs +32 −186
  5. crates/remote_server/Cargo.toml +1 −0
  6. crates/remote_server/src/headless_project.rs +13 −1
crates/node_runtime/src/node_runtime.rs+49−0
@@ -462,3 +462,52 @@ impl NodeRuntime for FakeNodeRuntime { · 461 unmodified lines
462462 unreachable!("Should not install packages {packages:?}")
463463 }
464464 }
465+
466+// TODO: Remove this when headless binary can run node
467+pub struct DummyNodeRuntime;
468+
469+impl DummyNodeRuntime {
470+ pub fn new() -> Arc<dyn NodeRuntime> {
471+ Arc::new(Self)
472+ }
473+}
474+
475+#[async_trait::async_trait]
476+impl NodeRuntime for DummyNodeRuntime {
477+ async fn binary_path(&self) -> anyhow::Result<PathBuf> {
478+ anyhow::bail!("Dummy Node Runtime")
479+ }
480+
481+ async fn node_environment_path(&self) -> anyhow::Result<OsString> {
482+ anyhow::bail!("Dummy node runtime")
483+ }
484+
485+ async fn run_npm_subcommand(
486+ &self,
487+ _: Option<&Path>,
488+ _subcommand: &str,
489+ _args: &[&str],
490+ ) -> anyhow::Result<Output> {
491+ anyhow::bail!("Dummy node runtime")
492+ }
493+
494+ async fn npm_package_latest_version(&self, _name: &str) -> anyhow::Result<String> {
495+ anyhow::bail!("Dummy node runtime")
496+ }
497+
498+ async fn npm_package_installed_version(
499+ &self,
500+ _local_package_directory: &Path,
501+ _name: &str,
502+ ) -> Result<Option<String>> {
503+ anyhow::bail!("Dummy node runtime")
504+ }
505+
506+ async fn npm_install_packages(
507+ &self,
508+ _: &Path,
509+ _packages: &[(&str, &str)],
510+ ) -> anyhow::Result<()> {
511+ anyhow::bail!("Dummy node runtime")
512+ }
513+}
crates/project/src/lsp_store.rs+190−4
@@ -3,6 +3,7 @@ use crate::{ · 2 unmodified lines
33 environment::ProjectEnvironment,
44 lsp_command::{self, *},
55 lsp_ext_command,
6+ prettier_store::{self, PrettierStore, PrettierStoreEvent},
67 project_settings::{LspSettings, ProjectSettings},
78 relativize_path, resolve_path,
89 worktree_store::{WorktreeStore, WorktreeStoreEvent},
@@ -101,6 +102,8 @@ pub struct LocalLspStore { · 92 unmodified lines
101102 HashMap<LanguageServerId, HashMap<String, Vec<FileSystemWatcher>>>,
102103 supplementary_language_servers:
103104 HashMap<LanguageServerId, (LanguageServerName, Arc<LanguageServer>)>,
105+ prettier_store: Model<PrettierStore>,
106+ current_lsp_settings: HashMap<Arc<str>, LspSettings>,
104107 _subscription: gpui::Subscription,
105108 }
106109
@@ -135,6 +138,7 @@ impl RemoteLspStore {} · 28 unmodified lines
135138
136139 pub struct SshLspStore {
137140 upstream_client: AnyProtoClient,
141+ current_lsp_settings: HashMap<Arc<str>, LspSettings>,
138142 }
139143
140144 #[allow(clippy::large_enum_variant)]
@@ -310,9 +314,32 @@ impl LspStore { · 169 unmodified lines
310314 }
311315 }
312316
317+ pub fn swap_current_lsp_settings(
318+ &mut self,
319+ new_settings: HashMap<Arc<str>, LspSettings>,
320+ ) -> Option<HashMap<Arc<str>, LspSettings>> {
321+ match &mut self.mode {
322+ LspStoreMode::Ssh(SshLspStore {
323+ current_lsp_settings,
324+ ..
325+ })
326+ | LspStoreMode::Local(LocalLspStore {
327+ current_lsp_settings,
328+ ..
329+ }) => {
330+ let ret = mem::take(current_lsp_settings);
331+ *current_lsp_settings = new_settings;
332+ Some(ret)
333+ }
334+ LspStoreMode::Remote(_) => None,
335+ }
336+ }
337+
338+ #[allow(clippy::too_many_arguments)]
313339 pub fn new_local(
314340 buffer_store: Model<BufferStore>,
315341 worktree_store: Model<WorktreeStore>,
342+ prettier_store: Model<PrettierStore>,
316343 environment: Model<ProjectEnvironment>,
317344 languages: Arc<LanguageRegistry>,
318345 http_client: Option<Arc<dyn HttpClient>>,
@@ -324,6 +351,10 @@ impl LspStore { · 5 unmodified lines
324351 .detach();
325352 cx.subscribe(&worktree_store, Self::on_worktree_store_event)
326353 .detach();
354+ cx.subscribe(&prettier_store, Self::on_prettier_store_event)
355+ .detach();
356+ cx.observe_global::<SettingsStore>(Self::on_settings_changed)
357+ .detach();
327358
328359 Self {
329360 mode: LspStoreMode::Local(LocalLspStore {
@@ -332,6 +363,8 @@ impl LspStore { · 2 unmodified lines
332363 last_workspace_edits_by_language_server: Default::default(),
333364 language_server_watched_paths: Default::default(),
334365 language_server_watcher_registrations: Default::default(),
366+ current_lsp_settings: ProjectSettings::get_global(cx).lsp.clone(),
367+ prettier_store,
335368 environment,
336369 http_client,
337370 fs,
@@ -387,9 +420,14 @@ impl LspStore { · 49 unmodified lines
387420 .detach();
388421 cx.subscribe(&worktree_store, Self::on_worktree_store_event)
389422 .detach();
423+ cx.observe_global::<SettingsStore>(Self::on_settings_changed)
424+ .detach();
390425
391426 Self {
392− mode: LspStoreMode::Ssh(SshLspStore { upstream_client }),
427+ mode: LspStoreMode::Ssh(SshLspStore {
428+ upstream_client,
429+ current_lsp_settings: Default::default(),
430+ }),
393431 downstream_client: None,
394432 project_id,
395433 buffer_store,
@@ -401,6 +439,7 @@ impl LspStore { · 5 unmodified lines
401439 buffer_snapshots: Default::default(),
402440 next_diagnostic_group_id: Default::default(),
403441 diagnostic_summaries: Default::default(),
442+
404443 diagnostics: Default::default(),
405444 active_entry: None,
406445 _maintain_workspace_config: Self::maintain_workspace_config(cx),
@@ -498,6 +537,36 @@ impl LspStore { · 91 unmodified lines
498537 }
499538 }
500539
540+ fn on_prettier_store_event(
541+ &mut self,
542+ _: Model<PrettierStore>,
543+ event: &PrettierStoreEvent,
544+ cx: &mut ModelContext<Self>,
545+ ) {
546+ match event {
547+ PrettierStoreEvent::LanguageServerRemoved(prettier_server_id) => {
548+ self.unregister_supplementary_language_server(*prettier_server_id, cx);
549+ }
550+ PrettierStoreEvent::LanguageServerAdded {
551+ new_server_id,
552+ name,
553+ prettier_server,
554+ } => {
555+ self.register_supplementary_language_server(
556+ *new_server_id,
557+ name.clone(),
558+ prettier_server.clone(),
559+ cx,
560+ );
561+ }
562+ }
563+ }
564+
565+ // todo!
566+ pub fn prettier_store(&self) -> Option<Model<PrettierStore>> {
567+ self.as_local().map(|local| local.prettier_store.clone())
568+ }
569+
501570 fn on_buffer_event(
502571 &mut self,
503572 buffer: Model<Buffer>,
@@ -656,11 +725,29 @@ impl LspStore { · 152 unmodified lines
656725 });
657726
658727 let buffer_file = buffer.read(cx).file().cloned();
728+ let settings = language_settings(Some(&new_language), buffer_file.as_ref(), cx).clone();
659729 let buffer_file = File::from_dyn(buffer_file.as_ref());
660730
661− if let Some(file) = buffer_file {
731+ let worktree_id = if let Some(file) = buffer_file {
662732 let worktree = file.worktree.clone();
663− self.start_language_servers(&worktree, new_language.name(), cx)
733+ self.start_language_servers(&worktree, new_language.name(), cx);
734+
735+ Some(worktree.read(cx).id())
736+ } else {
737+ None
738+ };
739+
740+ if let Some(prettier_plugins) = prettier_store::prettier_plugins_for_language(&settings) {
741+ let prettier_store = self.as_local().map(|s| s.prettier_store.clone());
742+ if let Some(prettier_store) = prettier_store {
743+ prettier_store.update(cx, |prettier_store, cx| {
744+ prettier_store.install_default_prettier(
745+ worktree_id,
746+ prettier_plugins.iter().map(|s| Arc::from(s.as_str())),
747+ cx,
748+ )
749+ })
750+ }
664751 }
665752
666753 cx.emit(LspStoreEvent::LanguageDetected {
@@ -799,6 +886,95 @@ impl LspStore { · 132 unmodified lines
799886 Task::ready(Ok(Default::default()))
800887 }
801888
889+ fn on_settings_changed(&mut self, cx: &mut ModelContext<Self>) {
890+ let mut language_servers_to_start = Vec::new();
891+ let mut language_formatters_to_check = Vec::new();
892+ for buffer in self.buffer_store.read(cx).buffers() {
893+ let buffer = buffer.read(cx);
894+ let buffer_file = File::from_dyn(buffer.file());
895+ let buffer_language = buffer.language();
896+ let settings = language_settings(buffer_language, buffer.file(), cx);
897+ if let Some(language) = buffer_language {
898+ if settings.enable_language_server {
899+ if let Some(file) = buffer_file {
900+ language_servers_to_start.push((file.worktree.clone(), language.name()));
901+ }
902+ }
903+ language_formatters_to_check
904+ .push((buffer_file.map(|f| f.worktree_id(cx)), settings.clone()));
905+ }
906+ }
907+
908+ let mut language_servers_to_stop = Vec::new();
909+ let mut language_servers_to_restart = Vec::new();
910+ let languages = self.languages.to_vec();
911+
912+ let new_lsp_settings = ProjectSettings::get_global(cx).lsp.clone();
913+ let Some(current_lsp_settings) = self.swap_current_lsp_settings(new_lsp_settings.clone())
914+ else {
915+ return;
916+ };
917+ for (worktree_id, started_lsp_name) in self.started_language_servers() {
918+ let language = languages.iter().find_map(|l| {
919+ let adapter = self
920+ .languages
921+ .lsp_adapters(&l.name())
922+ .iter()
923+ .find(|adapter| adapter.name == started_lsp_name)?
924+ .clone();
925+ Some((l, adapter))
926+ });
927+ if let Some((language, adapter)) = language {
928+ let worktree = self.worktree_for_id(worktree_id, cx).ok();
929+ let file = worktree.as_ref().and_then(|tree| {
930+ tree.update(cx, |tree, cx| tree.root_file(cx).map(|f| f as _))
931+ });
932+ if !language_settings(Some(language), file.as_ref(), cx).enable_language_server {
933+ language_servers_to_stop.push((worktree_id, started_lsp_name.clone()));
934+ } else if let Some(worktree) = worktree {
935+ let server_name = &adapter.name.0;
936+ match (
937+ current_lsp_settings.get(server_name),
938+ new_lsp_settings.get(server_name),
939+ ) {
940+ (None, None) => {}
941+ (Some(_), None) | (None, Some(_)) => {
942+ language_servers_to_restart.push((worktree, language.name()));
943+ }
944+ (Some(current_lsp_settings), Some(new_lsp_settings)) => {
945+ if current_lsp_settings != new_lsp_settings {
946+ language_servers_to_restart.push((worktree, language.name()));
947+ }
948+ }
949+ }
950+ }
951+ }
952+ }
953+
954+ for (worktree_id, adapter_name) in language_servers_to_stop {
955+ self.stop_language_server(worktree_id, adapter_name, cx)
956+ .detach();
957+ }
958+
959+ if let Some(prettier_store) = self.as_local().map(|s| s.prettier_store.clone()) {
960+ prettier_store.update(cx, |prettier_store, cx| {
961+ prettier_store.on_settings_changed(language_formatters_to_check, cx)
962+ })
963+ }
964+
965+ // Start all the newly-enabled language servers.
966+ for (worktree, language) in language_servers_to_start {
967+ self.start_language_servers(&worktree, language, cx);
968+ }
969+
970+ // Restart all language servers with changed initialization options.
971+ for (worktree, language) in language_servers_to_restart {
972+ self.restart_language_servers(worktree, language, cx);
973+ }
974+
975+ cx.notify();
976+ }
977+
802978 pub async fn execute_code_actions_on_servers(
803979 this: &WeakModel<LspStore>,
804980 adapters_and_servers: &[(Arc<CachedLspAdapter>, Arc<LanguageServer>)],
@@ -2375,7 +2551,7 @@ impl LspStore { · 1570 unmodified lines
23752551 })
23762552 }
23772553
2378− pub fn remove_worktree(&mut self, id_to_remove: WorktreeId, cx: &mut ModelContext<Self>) {
2554+ fn remove_worktree(&mut self, id_to_remove: WorktreeId, cx: &mut ModelContext<Self>) {
23792555 self.diagnostics.remove(&id_to_remove);
23802556 self.diagnostic_summaries.remove(&id_to_remove);
23812557
@@ -2406,6 +2582,12 @@ impl LspStore { · 24 unmodified lines
24062582 }
24072583 cx.emit(LspStoreEvent::LanguageServerRemoved(server_id_to_remove));
24082584 }
2585+
2586+ if let Some(local) = self.as_local() {
2587+ local.prettier_store.update(cx, |prettier_store, cx| {
2588+ prettier_store.remove_worktree(id_to_remove, cx);
2589+ })
2590+ }
24092591 }
24102592
24112593 pub fn shared(
@@ -6117,6 +6299,10 @@ impl LspStore { · 3705 unmodified lines
61176299
61186300 let Some(local) = self.as_local() else { return };
61196301
6302+ local.prettier_store.update(cx, |prettier_store, cx| {
6303+ prettier_store.update_prettier_settings(&worktree_handle, changes, cx)
6304+ });
6305+
61206306 let worktree_id = worktree_handle.read(cx).id();
61216307 let mut language_server_ids = self
61226308 .language_server_ids
crates/project/src/prettier_store.rsrenamed from crates/project/src/prettier_support.rs+617−505
@@ -5,444 +5,384 @@ use std::{ · 4 unmodified lines
55 };
66
77 use anyhow::{anyhow, Context, Result};
8−use collections::HashSet;
8+use collections::{HashMap, HashSet};
99 use fs::Fs;
1010 use futures::{
1111 future::{self, Shared},
12+ stream::FuturesUnordered,
1213 FutureExt,
1314 };
14−use gpui::{AsyncAppContext, Model, ModelContext, Task, WeakModel};
15+use gpui::{AsyncAppContext, EventEmitter, Model, ModelContext, Task, WeakModel};
1516 use language::{
1617 language_settings::{Formatter, LanguageSettings, SelectedFormatter},
17− Buffer, LanguageServerName, LocalFile,
18+ Buffer, LanguageRegistry, LanguageServerName, LocalFile,
1819 };
1920 use lsp::{LanguageServer, LanguageServerId};
2021 use node_runtime::NodeRuntime;
2122 use paths::default_prettier_dir;
2223 use prettier::Prettier;
24+use smol::stream::StreamExt;
2325 use util::{ResultExt, TryFutureExt};
2426
25−use crate::{File, FormatOperation, PathChange, Project, ProjectEntryId, Worktree, WorktreeId};
26−
27−pub fn prettier_plugins_for_language(
28− language_settings: &LanguageSettings,
29−) -> Option<&HashSet<String>> {
30− match &language_settings.formatter {
31− SelectedFormatter::Auto => Some(&language_settings.prettier.plugins),
32−
33− SelectedFormatter::List(list) => list
34− .as_ref()
35− .contains(&Formatter::Prettier)
36− .then_some(&language_settings.prettier.plugins),
37− }
38−}
39−
40−pub(super) async fn format_with_prettier(
41− project: &WeakModel<Project>,
42− buffer: &Model<Buffer>,
43− cx: &mut AsyncAppContext,
44−) -> Option<Result<FormatOperation>> {
45− let prettier_instance = project
46− .update(cx, |project, cx| {
47− project.prettier_instance_for_buffer(buffer, cx)
48− })
49− .ok()?
50− .await;
51−
52− let (prettier_path, prettier_task) = prettier_instance?;
53−
54− let prettier_description = match prettier_path.as_ref() {
55− Some(path) => format!("prettier at {path:?}"),
56− None => "default prettier instance".to_string(),
57− };
58−
59− match prettier_task.await {
60− Ok(prettier) => {
61− let buffer_path = buffer
62− .update(cx, |buffer, cx| {
63− File::from_dyn(buffer.file()).map(|file| file.abs_path(cx))
64− })
65− .ok()
66− .flatten();
67−
68− let format_result = prettier
69− .format(buffer, buffer_path, cx)
70− .await
71− .map(FormatOperation::Prettier)
72− .with_context(|| format!("{} failed to format buffer", prettier_description));
73−
74− Some(format_result)
75− }
76− Err(error) => {
77− project
78− .update(cx, |project, _| {
79− let instance_to_update = match prettier_path {
80− Some(prettier_path) => project.prettier_instances.get_mut(&prettier_path),
81− None => match &mut project.default_prettier.prettier {
82− PrettierInstallation::NotInstalled { .. } => None,
83− PrettierInstallation::Installed(instance) => Some(instance),
84− },
85− };
86−
87− if let Some(instance) = instance_to_update {
88− instance.attempt += 1;
89− instance.prettier = None;
90− }
91− })
92− .log_err();
93−
94− Some(Err(anyhow!(
95− "{} failed to spawn: {error:#}",
96− prettier_description
97− )))
98− }
99− }
100−}
27+use crate::{
28+ worktree_store::WorktreeStore, File, FormatOperation, PathChange, ProjectEntryId, Worktree,
29+ WorktreeId,
30+};
10131
102−pub struct DefaultPrettier {
103− prettier: PrettierInstallation,
104− installed_plugins: HashSet<Arc<str>>,
32+pub struct PrettierStore {
33+ node: Arc<dyn NodeRuntime>,
34+ fs: Arc<dyn Fs>,
35+ languages: Arc<LanguageRegistry>,
36+ worktree_store: Model<WorktreeStore>,
37+ default_prettier: DefaultPrettier,
38+ prettiers_per_worktree: HashMap<WorktreeId, HashSet<Option<PathBuf>>>,
39+ prettier_instances: HashMap<PathBuf, PrettierInstance>,
10540 }
10641
107−#[derive(Debug)]
108−pub enum PrettierInstallation {
109− NotInstalled {
110− attempts: usize,
111− installation_task: Option<Shared<Task<Result<(), Arc<anyhow::Error>>>>>,
112− not_installed_plugins: HashSet<Arc<str>>,
42+pub enum PrettierStoreEvent {
43+ LanguageServerRemoved(LanguageServerId),
44+ LanguageServerAdded {
45+ new_server_id: LanguageServerId,
46+ name: LanguageServerName,
47+ prettier_server: Arc<LanguageServer>,
11348 },
114− Installed(PrettierInstance),
11549 }
11650
117−pub type PrettierTask = Shared<Task<Result<Arc<Prettier>, Arc<anyhow::Error>>>>;
118−
119−#[derive(Debug, Clone)]
120−pub struct PrettierInstance {
121− attempt: usize,
122− prettier: Option<PrettierTask>,
123−}
51+impl EventEmitter<PrettierStoreEvent> for PrettierStore {}
12452
125−impl Default for DefaultPrettier {
126− fn default() -> Self {
53+impl PrettierStore {
54+ pub fn new(
55+ node: Arc<dyn NodeRuntime>,
56+ fs: Arc<dyn Fs>,
57+ languages: Arc<LanguageRegistry>,
58+ worktree_store: Model<WorktreeStore>,
59+ _: &mut ModelContext<Self>,
60+ ) -> Self {
12761 Self {
128− prettier: PrettierInstallation::NotInstalled {
129− attempts: 0,
130− installation_task: None,
131− not_installed_plugins: HashSet::default(),
132− },
133− installed_plugins: HashSet::default(),
62+ node,
63+ fs,
64+ languages,
65+ worktree_store,
66+ default_prettier: DefaultPrettier::default(),
67+ prettiers_per_worktree: HashMap::default(),
68+ prettier_instances: HashMap::default(),
13469 }
13570 }
136−}
13771
138−impl DefaultPrettier {
139− pub fn instance(&self) -> Option<&PrettierInstance> {
140− if let PrettierInstallation::Installed(instance) = &self.prettier {
141− Some(instance)
142− } else {
143− None
72+ pub fn remove_worktree(&mut self, id_to_remove: WorktreeId, cx: &mut ModelContext<Self>) {
73+ let mut prettier_instances_to_clean = FuturesUnordered::new();
74+ if let Some(prettier_paths) = self.prettiers_per_worktree.remove(&id_to_remove) {
75+ for path in prettier_paths.iter().flatten() {
76+ if let Some(prettier_instance) = self.prettier_instances.remove(path) {
77+ prettier_instances_to_clean.push(async move {
78+ prettier_instance
79+ .server()
80+ .await
81+ .map(|server| server.server_id())
82+ });
83+ }
84+ }
14485 }
86+ cx.spawn(|prettier_store, mut cx| async move {
87+ while let Some(prettier_server_id) = prettier_instances_to_clean.next().await {
88+ if let Some(prettier_server_id) = prettier_server_id {
89+ prettier_store
90+ .update(&mut cx, |_, cx| {
91+ cx.emit(PrettierStoreEvent::LanguageServerRemoved(
92+ prettier_server_id,
93+ ));
94+ })
95+ .ok();
96+ }
97+ }
98+ })
99+ .detach();
145100 }
146101
147− pub fn prettier_task(
102+ fn prettier_instance_for_buffer(
148103 &mut self,
149− node: &Arc<dyn NodeRuntime>,
150− worktree_id: Option<WorktreeId>,
151− cx: &mut ModelContext<'_, Project>,
152− ) -> Option<Task<anyhow::Result<PrettierTask>>> {
153− match &mut self.prettier {
154− PrettierInstallation::NotInstalled { .. } => {
155− Some(start_default_prettier(Arc::clone(node), worktree_id, cx))
104+ buffer: &Model<Buffer>,
105+ cx: &mut ModelContext<Self>,
106+ ) -> Task<Option<(Option<PathBuf>, PrettierTask)>> {
107+ let buffer = buffer.read(cx);
108+ let buffer_file = buffer.file();
109+ if buffer.language().is_none() {
110+ return Task::ready(None);
111+ }
112+
113+ let node = self.node.clone();
114+
115+ match File::from_dyn(buffer_file).map(|file| (file.worktree_id(cx), file.abs_path(cx))) {
116+ Some((worktree_id, buffer_path)) => {
117+ let fs = Arc::clone(&self.fs);
118+ let installed_prettiers = self.prettier_instances.keys().cloned().collect();
119+ cx.spawn(|lsp_store, mut cx| async move {
120+ match cx
121+ .background_executor()
122+ .spawn(async move {
123+ Prettier::locate_prettier_installation(
124+ fs.as_ref(),
125+ &installed_prettiers,
126+ &buffer_path,
127+ )
128+ .await
129+ })
130+ .await
131+ {
132+ Ok(ControlFlow::Break(())) => None,
133+ Ok(ControlFlow::Continue(None)) => {
134+ let default_instance = lsp_store
135+ .update(&mut cx, |lsp_store, cx| {
136+ lsp_store
137+ .prettiers_per_worktree
138+ .entry(worktree_id)
139+ .or_default()
140+ .insert(None);
141+ lsp_store.default_prettier.prettier_task(
142+ &node,
143+ Some(worktree_id),
144+ cx,
145+ )
146+ })
147+ .ok()?;
148+ Some((None, default_instance?.log_err().await?))
149+ }
150+ Ok(ControlFlow::Continue(Some(prettier_dir))) => {
151+ lsp_store
152+ .update(&mut cx, |lsp_store, _| {
153+ lsp_store
154+ .prettiers_per_worktree
155+ .entry(worktree_id)
156+ .or_default()
157+ .insert(Some(prettier_dir.clone()))
158+ })
159+ .ok()?;
160+ if let Some(prettier_task) = lsp_store
161+ .update(&mut cx, |lsp_store, cx| {
162+ lsp_store.prettier_instances.get_mut(&prettier_dir).map(
163+ |existing_instance| {
164+ existing_instance.prettier_task(
165+ &node,
166+ Some(&prettier_dir),
167+ Some(worktree_id),
168+ cx,
169+ )
170+ },
171+ )
172+ })
173+ .ok()?
174+ {
175+ log::debug!("Found already started prettier in {prettier_dir:?}");
176+ return Some((Some(prettier_dir), prettier_task?.await.log_err()?));
177+ }
178+
179+ log::info!("Found prettier in {prettier_dir:?}, starting.");
180+ let new_prettier_task = lsp_store
181+ .update(&mut cx, |lsp_store, cx| {
182+ let new_prettier_task = Self::start_prettier(
183+ node,
184+ prettier_dir.clone(),
185+ Some(worktree_id),
186+ cx,
187+ );
188+ lsp_store.prettier_instances.insert(
189+ prettier_dir.clone(),
190+ PrettierInstance {
191+ attempt: 0,
192+ prettier: Some(new_prettier_task.clone()),
193+ },
194+ );
195+ new_prettier_task
196+ })
197+ .ok()?;
198+ Some((Some(prettier_dir), new_prettier_task))
199+ }
200+ Err(e) => {
201+ log::error!("Failed to determine prettier path for buffer: {e:#}");
202+ None
203+ }
204+ }
205+ })
156206 }
157− PrettierInstallation::Installed(existing_instance) => {
158− existing_instance.prettier_task(node, None, worktree_id, cx)
207+ None => {
208+ let new_task = self.default_prettier.prettier_task(&node, None, cx);
209+ cx.spawn(|_, _| async move { Some((None, new_task?.log_err().await?)) })
159210 }
160211 }
161212 }
162−}
163213
164−impl PrettierInstance {
165− pub fn prettier_task(
166− &mut self,
167− node: &Arc<dyn NodeRuntime>,
168− prettier_dir: Option<&Path>,
214+ fn start_prettier(
215+ node: Arc<dyn NodeRuntime>,
216+ prettier_dir: PathBuf,
169217 worktree_id: Option<WorktreeId>,
170− cx: &mut ModelContext<'_, Project>,
171− ) -> Option<Task<anyhow::Result<PrettierTask>>> {
172− if self.attempt > prettier::FAIL_THRESHOLD {
173− match prettier_dir {
174− Some(prettier_dir) => log::warn!(
175− "Prettier from path {prettier_dir:?} exceeded launch threshold, not starting"
176− ),
177− None => log::warn!("Default prettier exceeded launch threshold, not starting"),
178− }
179− return None;
180− }
181− Some(match &self.prettier {
182− Some(prettier_task) => Task::ready(Ok(prettier_task.clone())),
183− None => match prettier_dir {
184− Some(prettier_dir) => {
185− let new_task = start_prettier(
186− Arc::clone(node),
187− prettier_dir.to_path_buf(),
188− worktree_id,
189− cx,
190− );
191− self.attempt += 1;
192− self.prettier = Some(new_task.clone());
193− Task::ready(Ok(new_task))
194− }
195− None => {
196− self.attempt += 1;
197− let node = Arc::clone(node);
198− cx.spawn(|project, mut cx| async move {
199− project
200− .update(&mut cx, |_, cx| {
201− start_default_prettier(node, worktree_id, cx)
202− })?
203− .await
204− })
205− }
206− },
218+ cx: &mut ModelContext<Self>,
219+ ) -> PrettierTask {
220+ cx.spawn(|prettier_store, mut cx| async move {
221+ log::info!("Starting prettier at path {prettier_dir:?}");
222+ let new_server_id = prettier_store.update(&mut cx, |prettier_store, _| {
223+ prettier_store.languages.next_language_server_id()
224+ })?;
225+
226+ let new_prettier = Prettier::start(new_server_id, prettier_dir, node, cx.clone())
227+ .await
228+ .context("default prettier spawn")
229+ .map(Arc::new)
230+ .map_err(Arc::new)?;
231+ Self::register_new_prettier(
232+ &prettier_store,
233+ &new_prettier,
234+ worktree_id,
235+ new_server_id,
236+ &mut cx,
237+ );
238+ Ok(new_prettier)
207239 })
240+ .shared()
208241 }
209242
210− pub async fn server(&self) -> Option<Arc<LanguageServer>> {
211− self.prettier.clone()?.await.ok()?.server().cloned()
212− }
213−}
214−
215−fn start_default_prettier(
216− node: Arc<dyn NodeRuntime>,
217− worktree_id: Option<WorktreeId>,
218− cx: &mut ModelContext<'_, Project>,
219−) -> Task<anyhow::Result<PrettierTask>> {
220− cx.spawn(|project, mut cx| async move {
221− let installation_task = project.update(&mut cx, |project, _| {
222− match &project.default_prettier.prettier {
223− PrettierInstallation::NotInstalled {
224− installation_task, ..
225− } => ControlFlow::Continue(installation_task.clone()),
226− PrettierInstallation::Installed(default_prettier) => {
227− ControlFlow::Break(default_prettier.clone())
228− }
229− }
230− })?;
231− match installation_task {
232− ControlFlow::Continue(None) => {
233− anyhow::bail!("Default prettier is not installed and cannot be started")
234− }
235− ControlFlow::Continue(Some(installation_task)) => {
236− log::info!("Waiting for default prettier to install");
237− if let Err(e) = installation_task.await {
238− project.update(&mut cx, |project, _| {
239− if let PrettierInstallation::NotInstalled {
240− installation_task,
241− attempts,
242− ..
243− } = &mut project.default_prettier.prettier
244− {
245− *installation_task = None;
246− *attempts += 1;
247− }
248− })?;
249− anyhow::bail!(
250− "Cannot start default prettier due to its installation failure: {e:#}"
251− );
243+ fn start_default_prettier(
244+ node: Arc<dyn NodeRuntime>,
245+ worktree_id: Option<WorktreeId>,
246+ cx: &mut ModelContext<PrettierStore>,
247+ ) -> Task<anyhow::Result<PrettierTask>> {
248+ cx.spawn(|prettier_store, mut cx| async move {
249+ let installation_task = prettier_store.update(&mut cx, |prettier_store, _| {
250+ match &prettier_store.default_prettier.prettier {
251+ PrettierInstallation::NotInstalled {
252+ installation_task, ..
253+ } => ControlFlow::Continue(installation_task.clone()),
254+ PrettierInstallation::Installed(default_prettier) => {
255+ ControlFlow::Break(default_prettier.clone())
256+ }
257+ }
258+ })?;
259+ match installation_task {
260+ ControlFlow::Continue(None) => {
261+ anyhow::bail!("Default prettier is not installed and cannot be started")
252262 }
253− let new_default_prettier = project.update(&mut cx, |project, cx| {
263+ ControlFlow::Continue(Some(installation_task)) => {
264+ log::info!("Waiting for default prettier to install");
265+ if let Err(e) = installation_task.await {
266+ prettier_store.update(&mut cx, |project, _| {
267+ if let PrettierInstallation::NotInstalled {
268+ installation_task,
269+ attempts,
270+ ..
271+ } = &mut project.default_prettier.prettier
272+ {
273+ *installation_task = None;
274+ *attempts += 1;
275+ }
276+ })?;
277+ anyhow::bail!(
278+ "Cannot start default prettier due to its installation failure: {e:#}"
279+ );
280+ }
254281 let new_default_prettier =
255− start_prettier(node, default_prettier_dir().clone(), worktree_id, cx);
256− project.default_prettier.prettier =
257− PrettierInstallation::Installed(PrettierInstance {
258− attempt: 0,
259− prettier: Some(new_default_prettier.clone()),
260− });
261− new_default_prettier
262− })?;
263− Ok(new_default_prettier)
264− }
265− ControlFlow::Break(instance) => match instance.prettier {
266− Some(instance) => Ok(instance),
267− None => {
268− let new_default_prettier = project.update(&mut cx, |project, cx| {
269− let new_default_prettier =
270− start_prettier(node, default_prettier_dir().clone(), worktree_id, cx);
271− project.default_prettier.prettier =
272− PrettierInstallation::Installed(PrettierInstance {
273− attempt: instance.attempt + 1,
274− prettier: Some(new_default_prettier.clone()),
275− });
276− new_default_prettier
277− })?;
282+ prettier_store.update(&mut cx, |prettier_store, cx| {
283+ let new_default_prettier = Self::start_prettier(
284+ node,
285+ default_prettier_dir().clone(),
286+ worktree_id,
287+ cx,
288+ );
289+ prettier_store.default_prettier.prettier =
290+ PrettierInstallation::Installed(PrettierInstance {
291+ attempt: 0,
292+ prettier: Some(new_default_prettier.clone()),
293+ });
294+ new_default_prettier
295+ })?;
278296 Ok(new_default_prettier)
279297 }
280− },
281− }
282− })
283−}
284−
285−fn start_prettier(
286− node: Arc<dyn NodeRuntime>,
287− prettier_dir: PathBuf,
288− worktree_id: Option<WorktreeId>,
289− cx: &mut ModelContext<'_, Project>,
290−) -> PrettierTask {
291− cx.spawn(|project, mut cx| async move {
292− log::info!("Starting prettier at path {prettier_dir:?}");
293− let new_server_id = project.update(&mut cx, |project, _| {
294− project.languages.next_language_server_id()
295− })?;
296−
297− let new_prettier = Prettier::start(new_server_id, prettier_dir, node, cx.clone())
298− .await
299− .context("default prettier spawn")
300− .map(Arc::new)
301− .map_err(Arc::new)?;
302− register_new_prettier(&project, &new_prettier, worktree_id, new_server_id, &mut cx);
303− Ok(new_prettier)
304− })
305− .shared()
306−}
307−
308−fn register_new_prettier(
309− project: &WeakModel<Project>,
310− prettier: &Prettier,
311− worktree_id: Option<WorktreeId>,
312− new_server_id: LanguageServerId,
313− cx: &mut AsyncAppContext,
314−) {
315− let prettier_dir = prettier.prettier_dir();
316− let is_default = prettier.is_default();
317− if is_default {
318− log::info!("Started default prettier in {prettier_dir:?}");
319− } else {
320− log::info!("Started prettier in {prettier_dir:?}");
298+ ControlFlow::Break(instance) => match instance.prettier {
299+ Some(instance) => Ok(instance),
300+ None => {
301+ let new_default_prettier =
302+ prettier_store.update(&mut cx, |prettier_store, cx| {
303+ let new_default_prettier = Self::start_prettier(
304+ node,
305+ default_prettier_dir().clone(),
306+ worktree_id,
307+ cx,
308+ );
309+ prettier_store.default_prettier.prettier =
310+ PrettierInstallation::Installed(PrettierInstance {
311+ attempt: instance.attempt + 1,
312+ prettier: Some(new_default_prettier.clone()),
313+ });
314+ new_default_prettier
315+ })?;
316+ Ok(new_default_prettier)
317+ }
318+ },
319+ }
320+ })
321321 }
322− if let Some(prettier_server) = prettier.server() {
323− project
324− .update(cx, |project, cx| {
325− let name = if is_default {
326− LanguageServerName(Arc::from("prettier (default)"))
327− } else {
328− let worktree_path = worktree_id
329− .and_then(|id| project.worktree_for_id(id, cx))
330− .map(|worktree| worktree.update(cx, |worktree, _| worktree.abs_path()));
331− let name = match worktree_path {
332− Some(worktree_path) => {
333− if prettier_dir == worktree_path.as_ref() {
334− let name = prettier_dir
335− .file_name()
336− .and_then(|name| name.to_str())
337− .unwrap_or_default();
338− format!("prettier ({name})")
339− } else {
340− let dir_to_display = prettier_dir
341− .strip_prefix(worktree_path.as_ref())
342− .ok()
343− .unwrap_or(prettier_dir);
344− format!("prettier ({})", dir_to_display.display())
322+
323+ fn register_new_prettier(
324+ prettier_store: &WeakModel<Self>,
325+ prettier: &Prettier,
326+ worktree_id: Option<WorktreeId>,
327+ new_server_id: LanguageServerId,
328+ cx: &mut AsyncAppContext,
329+ ) {
330+ let prettier_dir = prettier.prettier_dir();
331+ let is_default = prettier.is_default();
332+ if is_default {
333+ log::info!("Started default prettier in {prettier_dir:?}");
334+ } else {
335+ log::info!("Started prettier in {prettier_dir:?}");
336+ }
337+ if let Some(prettier_server) = prettier.server() {
338+ prettier_store
339+ .update(cx, |prettier_store, cx| {
340+ let name = if is_default {
341+ LanguageServerName(Arc::from("prettier (default)"))
342+ } else {
343+ let worktree_path = worktree_id
344+ .and_then(|id| {
345+ prettier_store
346+ .worktree_store
347+ .read(cx)
348+ .worktree_for_id(id, cx)
349+ })
350+ .map(|worktree| worktree.update(cx, |worktree, _| worktree.abs_path()));
351+ let name = match worktree_path {
352+ Some(worktree_path) => {
353+ if prettier_dir == worktree_path.as_ref() {
354+ let name = prettier_dir
355+ .file_name()
356+ .and_then(|name| name.to_str())
357+ .unwrap_or_default();
358+ format!("prettier ({name})")
359+ } else {
360+ let dir_to_display = prettier_dir
361+ .strip_prefix(worktree_path.as_ref())
362+ .ok()
363+ .unwrap_or(prettier_dir);
364+ format!("prettier ({})", dir_to_display.display())
365+ }
345366 }
346− }
347− None => format!("prettier ({})", prettier_dir.display()),
367+ None => format!("prettier ({})", prettier_dir.display()),
368+ };
369+ LanguageServerName(Arc::from(name))
348370 };
349− LanguageServerName(Arc::from(name))
350− };
351− project.lsp_store.update(cx, |lsp_store, cx| {
352− lsp_store.register_supplementary_language_server(
371+ cx.emit(PrettierStoreEvent::LanguageServerAdded {
353372 new_server_id,
354373 name,
355− Arc::clone(prettier_server),
356− cx,
357− )
358− });
359− })
360− .ok();
361− }
362−}
363−
364−async fn install_prettier_packages(
365− fs: &dyn Fs,
366− plugins_to_install: HashSet<Arc<str>>,
367− node: Arc<dyn NodeRuntime>,
368−) -> anyhow::Result<()> {
369− let packages_to_versions = future::try_join_all(
370− plugins_to_install
371− .iter()
372− .chain(Some(&"prettier".into()))
373− .map(|package_name| async {
374− let returned_package_name = package_name.to_string();
375− let latest_version = node
376− .npm_package_latest_version(package_name)
377− .await
378− .with_context(|| {
379− format!("fetching latest npm version for package {returned_package_name}")
380− })?;
381− anyhow::Ok((returned_package_name, latest_version))
382− }),
383− )
384− .await
385− .context("fetching latest npm versions")?;
386−
387− let default_prettier_dir = default_prettier_dir().as_path();
388− match fs.metadata(default_prettier_dir).await.with_context(|| {
389− format!("fetching FS metadata for default prettier dir {default_prettier_dir:?}")
390− })? {
391− Some(prettier_dir_metadata) => anyhow::ensure!(
392− prettier_dir_metadata.is_dir,
393− "default prettier dir {default_prettier_dir:?} is not a directory"
394− ),
395− None => fs
396− .create_dir(default_prettier_dir)
397− .await
398− .with_context(|| format!("creating default prettier dir {default_prettier_dir:?}"))?,
399− }
400−
401− log::info!("Installing default prettier and plugins: {packages_to_versions:?}");
402− let borrowed_packages = packages_to_versions
403− .iter()
404− .map(|(package, version)| (package.as_str(), version.as_str()))
405− .collect::<Vec<_>>();
406− node.npm_install_packages(default_prettier_dir, &borrowed_packages)
407− .await
408− .context("fetching formatter packages")?;
409− anyhow::Ok(())
410−}
411−
412−async fn save_prettier_server_file(fs: &dyn Fs) -> anyhow::Result<()> {
413− let prettier_wrapper_path = default_prettier_dir().join(prettier::PRETTIER_SERVER_FILE);
414− fs.save(
415− &prettier_wrapper_path,
416− &text::Rope::from(prettier::PRETTIER_SERVER_JS),
417− text::LineEnding::Unix,
418− )
419− .await
420− .with_context(|| {
421− format!(
422− "writing {} file at {prettier_wrapper_path:?}",
423− prettier::PRETTIER_SERVER_FILE
424− )
425− })?;
426− Ok(())
427−}
428−
429−async fn should_write_prettier_server_file(fs: &dyn Fs) -> bool {
430− let prettier_wrapper_path = default_prettier_dir().join(prettier::PRETTIER_SERVER_FILE);
431− if !fs.is_file(&prettier_wrapper_path).await {
432− return true;
374+ prettier_server: prettier_server.clone(),
375+ });
376+ })
377+ .ok();
378+ }
433379 }
434− let Ok(prettier_server_file_contents) = fs.load(&prettier_wrapper_path).await else {
435− return true;
436− };
437− prettier_server_file_contents != prettier::PRETTIER_SERVER_JS
438−}
439380
440−impl Project {
441381 pub fn update_prettier_settings(
442382 &self,
443383 worktree: &Model<Worktree>,
444384 changes: &[(Arc<Path>, ProjectEntryId, PathChange)],
445− cx: &mut ModelContext<'_, Project>,
385+ cx: &mut ModelContext<Self>,
446386 ) {
447387 let prettier_config_files = Prettier::CONFIG_FILE_NAMES
448388 .iter()
@@ -510,122 +450,6 @@ impl Project { · 61 unmodified lines
510450 }
511451 }
512452
513− fn prettier_instance_for_buffer(
514− &mut self,
515− buffer: &Model<Buffer>,
516− cx: &mut ModelContext<Self>,
517− ) -> Task<Option<(Option<PathBuf>, PrettierTask)>> {
518− // todo(ssh remote): prettier support
519− if self.is_via_collab() || self.ssh_session.is_some() {
520− return Task::ready(None);
521− }
522− let buffer = buffer.read(cx);
523− let buffer_file = buffer.file();
524− if buffer.language().is_none() {
525− return Task::ready(None);
526− }
527− let Some(node) = self.node.clone() else {
528− return Task::ready(None);
529− };
530− match File::from_dyn(buffer_file).map(|file| (file.worktree_id(cx), file.abs_path(cx))) {
531− Some((worktree_id, buffer_path)) => {
532− let fs = Arc::clone(&self.fs);
533− let installed_prettiers = self.prettier_instances.keys().cloned().collect();
534− cx.spawn(|project, mut cx| async move {
535− match cx
536− .background_executor()
537− .spawn(async move {
538− Prettier::locate_prettier_installation(
539− fs.as_ref(),
540− &installed_prettiers,
541− &buffer_path,
542− )
543− .await
544− })
545− .await
546− {
547− Ok(ControlFlow::Break(())) => None,
548− Ok(ControlFlow::Continue(None)) => {
549− let default_instance = project
550− .update(&mut cx, |project, cx| {
551− project
552− .prettiers_per_worktree
553− .entry(worktree_id)
554− .or_default()
555− .insert(None);
556− project.default_prettier.prettier_task(
557− &node,
558− Some(worktree_id),
559− cx,
560− )
561− })
562− .ok()?;
563− Some((None, default_instance?.log_err().await?))
564− }
565− Ok(ControlFlow::Continue(Some(prettier_dir))) => {
566− project
567− .update(&mut cx, |project, _| {
568− project
569− .prettiers_per_worktree
570− .entry(worktree_id)
571− .or_default()
572− .insert(Some(prettier_dir.clone()))
573− })
574− .ok()?;
575− if let Some(prettier_task) = project
576− .update(&mut cx, |project, cx| {
577− project.prettier_instances.get_mut(&prettier_dir).map(
578− |existing_instance| {
579− existing_instance.prettier_task(
580− &node,
581− Some(&prettier_dir),
582− Some(worktree_id),
583− cx,
584− )
585− },
586− )
587− })
588− .ok()?
589− {
590− log::debug!("Found already started prettier in {prettier_dir:?}");
591− return Some((Some(prettier_dir), prettier_task?.await.log_err()?));
592− }
593−
594− log::info!("Found prettier in {prettier_dir:?}, starting.");
595− let new_prettier_task = project
596− .update(&mut cx, |project, cx| {
597− let new_prettier_task = start_prettier(
598− node,
599− prettier_dir.clone(),
600− Some(worktree_id),
601− cx,
602− );
603− project.prettier_instances.insert(
604− prettier_dir.clone(),
605− PrettierInstance {
606− attempt: 0,
607− prettier: Some(new_prettier_task.clone()),
608− },
609− );
610− new_prettier_task
611− })
612− .ok()?;
613− Some((Some(prettier_dir), new_prettier_task))
614− }
615− Err(e) => {
616− log::error!("Failed to determine prettier path for buffer: {e:#}");
617− None
618− }
619− }
620− })
621− }
622− None => {
623− let new_task = self.default_prettier.prettier_task(&node, None, cx);
624− cx.spawn(|_, _| async move { Some((None, new_task?.log_err().await?)) })
625− }
626− }
627− }
628−
629453 pub fn install_default_prettier(
630454 &mut self,
631455 worktree: Option<WorktreeId>,
@@ -642,12 +466,13 @@ impl Project { · 10 unmodified lines
642466 }
643467
644468 let mut new_plugins = plugins.collect::<HashSet<_>>();
645− let Some(node) = self.node.as_ref().cloned() else {
646− return;
647− };
469+ let node = self.node.clone();
470+
648471 let fs = Arc::clone(&self.fs);
649472 let locate_prettier_installation = match worktree.and_then(|worktree_id| {
650− self.worktree_for_id(worktree_id, cx)
473+ self.worktree_store
474+ .read(cx)
475+ .worktree_for_id(worktree_id, cx)
651476 .map(|worktree| worktree.read(cx).abs_path())
652477 }) {
653478 Some(locate_from) => {
@@ -777,4 +602,291 @@ impl Project { · 123 unmodified lines
777602 not_installed_plugins: plugins_to_install,
778603 };
779604 }
605+
606+ pub fn on_settings_changed(
607+ &mut self,
608+ language_formatters_to_check: Vec<(Option<WorktreeId>, LanguageSettings)>,
609+ cx: &mut ModelContext<Self>,
610+ ) {
611+ let mut prettier_plugins_by_worktree = HashMap::default();
612+ for (worktree, language_settings) in language_formatters_to_check {
613+ if let Some(plugins) = prettier_plugins_for_language(&language_settings) {
614+ prettier_plugins_by_worktree
615+ .entry(worktree)
616+ .or_insert_with(HashSet::default)
617+ .extend(plugins.iter().cloned());
618+ }
619+ }
620+ for (worktree, prettier_plugins) in prettier_plugins_by_worktree {
621+ self.install_default_prettier(
622+ worktree,
623+ prettier_plugins.into_iter().map(Arc::from),
624+ cx,
625+ );
626+ }
627+ }
628+}
629+
630+pub fn prettier_plugins_for_language(
631+ language_settings: &LanguageSettings,
632+) -> Option<&HashSet<String>> {
633+ match &language_settings.formatter {
634+ SelectedFormatter::Auto => Some(&language_settings.prettier.plugins),
635+
636+ SelectedFormatter::List(list) => list
637+ .as_ref()
638+ .contains(&Formatter::Prettier)
639+ .then_some(&language_settings.prettier.plugins),
640+ }
641+}
642+
643+pub(super) async fn format_with_prettier(
644+ prettier_store: &WeakModel<PrettierStore>,
645+ buffer: &Model<Buffer>,
646+ cx: &mut AsyncAppContext,
647+) -> Option<Result<FormatOperation>> {
648+ let prettier_instance = prettier_store
649+ .update(cx, |prettier_store, cx| {
650+ prettier_store.prettier_instance_for_buffer(buffer, cx)
651+ })
652+ .ok()?
653+ .await;
654+
655+ let (prettier_path, prettier_task) = prettier_instance?;
656+
657+ let prettier_description = match prettier_path.as_ref() {
658+ Some(path) => format!("prettier at {path:?}"),
659+ None => "default prettier instance".to_string(),
660+ };
661+
662+ match prettier_task.await {
663+ Ok(prettier) => {
664+ let buffer_path = buffer
665+ .update(cx, |buffer, cx| {
666+ File::from_dyn(buffer.file()).map(|file| file.abs_path(cx))
667+ })
668+ .ok()
669+ .flatten();
670+
671+ let format_result = prettier
672+ .format(buffer, buffer_path, cx)
673+ .await
674+ .map(FormatOperation::Prettier)
675+ .with_context(|| format!("{} failed to format buffer", prettier_description));
676+
677+ Some(format_result)
678+ }
679+ Err(error) => {
680+ prettier_store
681+ .update(cx, |project, _| {
682+ let instance_to_update = match prettier_path {
683+ Some(prettier_path) => project.prettier_instances.get_mut(&prettier_path),
684+ None => match &mut project.default_prettier.prettier {
685+ PrettierInstallation::NotInstalled { .. } => None,
686+ PrettierInstallation::Installed(instance) => Some(instance),
687+ },
688+ };
689+
690+ if let Some(instance) = instance_to_update {
691+ instance.attempt += 1;
692+ instance.prettier = None;
693+ }
694+ })
695+ .log_err();
696+
697+ Some(Err(anyhow!(
698+ "{} failed to spawn: {error:#}",
699+ prettier_description
700+ )))
701+ }
702+ }
703+}
704+
705+pub struct DefaultPrettier {
706+ prettier: PrettierInstallation,
707+ installed_plugins: HashSet<Arc<str>>,
708+}
709+
710+#[derive(Debug)]
711+pub enum PrettierInstallation {
712+ NotInstalled {
713+ attempts: usize,
714+ installation_task: Option<Shared<Task<Result<(), Arc<anyhow::Error>>>>>,
715+ not_installed_plugins: HashSet<Arc<str>>,
716+ },
717+ Installed(PrettierInstance),
718+}
719+
720+pub type PrettierTask = Shared<Task<Result<Arc<Prettier>, Arc<anyhow::Error>>>>;
721+
722+#[derive(Debug, Clone)]
723+pub struct PrettierInstance {
724+ attempt: usize,
725+ prettier: Option<PrettierTask>,
726+}
727+
728+impl Default for DefaultPrettier {
729+ fn default() -> Self {
730+ Self {
731+ prettier: PrettierInstallation::NotInstalled {
732+ attempts: 0,
733+ installation_task: None,
734+ not_installed_plugins: HashSet::default(),
735+ },
736+ installed_plugins: HashSet::default(),
737+ }
738+ }
739+}
740+
741+impl DefaultPrettier {
742+ pub fn instance(&self) -> Option<&PrettierInstance> {
743+ if let PrettierInstallation::Installed(instance) = &self.prettier {
744+ Some(instance)
745+ } else {
746+ None
747+ }
748+ }
749+
750+ pub fn prettier_task(
751+ &mut self,
752+ node: &Arc<dyn NodeRuntime>,
753+ worktree_id: Option<WorktreeId>,
754+ cx: &mut ModelContext<PrettierStore>,
755+ ) -> Option<Task<anyhow::Result<PrettierTask>>> {
756+ match &mut self.prettier {
757+ PrettierInstallation::NotInstalled { .. } => Some(
758+ PrettierStore::start_default_prettier(node.clone(), worktree_id, cx),
759+ ),
760+ PrettierInstallation::Installed(existing_instance) => {
761+ existing_instance.prettier_task(node, None, worktree_id, cx)
762+ }
763+ }
764+ }
765+}
766+
767+impl PrettierInstance {
768+ pub fn prettier_task(
769+ &mut self,
770+ node: &Arc<dyn NodeRuntime>,
771+ prettier_dir: Option<&Path>,
772+ worktree_id: Option<WorktreeId>,
773+ cx: &mut ModelContext<PrettierStore>,
774+ ) -> Option<Task<anyhow::Result<PrettierTask>>> {
775+ if self.attempt > prettier::FAIL_THRESHOLD {
776+ match prettier_dir {
777+ Some(prettier_dir) => log::warn!(
778+ "Prettier from path {prettier_dir:?} exceeded launch threshold, not starting"
779+ ),
780+ None => log::warn!("Default prettier exceeded launch threshold, not starting"),
781+ }
782+ return None;
783+ }
784+ Some(match &self.prettier {
785+ Some(prettier_task) => Task::ready(Ok(prettier_task.clone())),
786+ None => match prettier_dir {
787+ Some(prettier_dir) => {
788+ let new_task = PrettierStore::start_prettier(
789+ Arc::clone(node),
790+ prettier_dir.to_path_buf(),
791+ worktree_id,
792+ cx,
793+ );
794+ self.attempt += 1;
795+ self.prettier = Some(new_task.clone());
796+ Task::ready(Ok(new_task))
797+ }
798+ None => {
799+ self.attempt += 1;
800+ let node = Arc::clone(node);
801+ cx.spawn(|prettier_store, mut cx| async move {
802+ prettier_store
803+ .update(&mut cx, |_, cx| {
804+ PrettierStore::start_default_prettier(node, worktree_id, cx)
805+ })?
806+ .await
807+ })
808+ }
809+ },
810+ })
811+ }
812+
813+ pub async fn server(&self) -> Option<Arc<LanguageServer>> {
814+ self.prettier.clone()?.await.ok()?.server().cloned()
815+ }
816+}
817+
818+async fn install_prettier_packages(
819+ fs: &dyn Fs,
820+ plugins_to_install: HashSet<Arc<str>>,
821+ node: Arc<dyn NodeRuntime>,
822+) -> anyhow::Result<()> {
823+ let packages_to_versions = future::try_join_all(
824+ plugins_to_install
825+ .iter()
826+ .chain(Some(&"prettier".into()))
827+ .map(|package_name| async {
828+ let returned_package_name = package_name.to_string();
829+ let latest_version = node
830+ .npm_package_latest_version(package_name)
831+ .await
832+ .with_context(|| {
833+ format!("fetching latest npm version for package {returned_package_name}")
834+ })?;
835+ anyhow::Ok((returned_package_name, latest_version))
836+ }),
837+ )
838+ .await
839+ .context("fetching latest npm versions")?;
840+
841+ let default_prettier_dir = default_prettier_dir().as_path();
842+ match fs.metadata(default_prettier_dir).await.with_context(|| {
843+ format!("fetching FS metadata for default prettier dir {default_prettier_dir:?}")
844+ })? {
845+ Some(prettier_dir_metadata) => anyhow::ensure!(
846+ prettier_dir_metadata.is_dir,
847+ "default prettier dir {default_prettier_dir:?} is not a directory"
848+ ),
849+ None => fs
850+ .create_dir(default_prettier_dir)
851+ .await
852+ .with_context(|| format!("creating default prettier dir {default_prettier_dir:?}"))?,
853+ }
854+
855+ log::info!("Installing default prettier and plugins: {packages_to_versions:?}");
856+ let borrowed_packages = packages_to_versions
857+ .iter()
858+ .map(|(package, version)| (package.as_str(), version.as_str()))
859+ .collect::<Vec<_>>();
860+ node.npm_install_packages(default_prettier_dir, &borrowed_packages)
861+ .await
862+ .context("fetching formatter packages")?;
863+ anyhow::Ok(())
864+}
865+
866+async fn save_prettier_server_file(fs: &dyn Fs) -> anyhow::Result<()> {
867+ let prettier_wrapper_path = default_prettier_dir().join(prettier::PRETTIER_SERVER_FILE);
868+ fs.save(
869+ &prettier_wrapper_path,
870+ &text::Rope::from(prettier::PRETTIER_SERVER_JS),
871+ text::LineEnding::Unix,
872+ )
873+ .await
874+ .with_context(|| {
875+ format!(
876+ "writing {} file at {prettier_wrapper_path:?}",
877+ prettier::PRETTIER_SERVER_FILE
878+ )
879+ })?;
880+ Ok(())
881+}
882+
883+async fn should_write_prettier_server_file(fs: &dyn Fs) -> bool {
884+ let prettier_wrapper_path = default_prettier_dir().join(prettier::PRETTIER_SERVER_FILE);
885+ if !fs.is_file(&prettier_wrapper_path).await {
886+ return true;
887+ }
888+ let Ok(prettier_server_file_contents) = fs.load(&prettier_wrapper_path).await else {
889+ return true;
890+ };
891+ prettier_server_file_contents != prettier::PRETTIER_SERVER_JS
780892 }
crates/project/src/project.rs+32−186
@@ -4,7 +4,7 @@ pub mod debounced_delay; · 3 unmodified lines
44 pub mod lsp_command;
55 pub mod lsp_ext_command;
66 pub mod lsp_store;
7−mod prettier_support;
7+pub mod prettier_store;
88 pub mod project_settings;
99 pub mod search;
1010 mod task_inventory;
@@ -31,7 +31,6 @@ pub use environment::ProjectEnvironment; · 20 unmodified lines
3131 use futures::{
3232 channel::mpsc::{self, UnboundedReceiver},
3333 future::try_join_all,
34− stream::FuturesUnordered,
3534 AsyncWriteExt, FutureExt, StreamExt,
3635 };
3736
@@ -59,8 +58,8 @@ use lsp_command::*; · 21 unmodified lines
5958 use node_runtime::NodeRuntime;
6059 use parking_lot::{Mutex, RwLock};
6160 use paths::{local_tasks_file_relative_path, local_vscode_tasks_file_relative_path};
62−use prettier_support::{DefaultPrettier, PrettierInstance};
63−use project_settings::{LspSettings, ProjectSettings, SettingsObserver};
61+pub use prettier_store::PrettierStore;
62+use project_settings::{ProjectSettings, SettingsObserver};
6463 use remote::SshSession;
6564 use rpc::{proto::SSH_PROJECT_ID, AnyProtoClient, ErrorCode};
6665 use search::{SearchInputKind, SearchQuery, SearchResult};
@@ -140,7 +139,6 @@ pub struct Project { · 73 unmodified lines
140139 buffer_ordered_messages_tx: mpsc::UnboundedSender<BufferOrderedMessage>,
141140 languages: Arc<LanguageRegistry>,
142141 client: Arc<client::Client>,
143− current_lsp_settings: HashMap<Arc<str>, LspSettings>,
144142 join_project_response_message_id: u32,
145143 user_store: Model<UserStore>,
146144 fs: Arc<dyn Fs>,
@@ -157,9 +155,6 @@ pub struct Project { · 10 unmodified lines
157155 remotely_created_buffers: Arc<Mutex<RemotelyCreatedBuffers>>,
158156 terminals: Terminals,
159157 node: Option<Arc<dyn NodeRuntime>>,
160− default_prettier: DefaultPrettier,
161− prettiers_per_worktree: HashMap<WorktreeId, HashSet<Option<PathBuf>>>,
162− prettier_instances: HashMap<PathBuf, PrettierInstance>,
163158 tasks: Model<Inventory>,
164159 hosted_project_id: Option<ProjectId>,
165160 dev_server_project_id: Option<client::DevServerProjectId>,
@@ -634,6 +629,16 @@ impl Project { · 468 unmodified lines
634629 cx.subscribe(&buffer_store, Self::on_buffer_store_event)
635630 .detach();
636631
632+ let prettier_store = cx.new_model(|cx| {
633+ PrettierStore::new(
634+ node.clone(),
635+ fs.clone(),
636+ languages.clone(),
637+ worktree_store.clone(),
638+ cx,
639+ )
640+ });
641+
637642 let settings_observer = cx.new_model(|cx| {
638643 SettingsObserver::new_local(fs.clone(), worktree_store.clone(), cx)
639644 });
@@ -643,6 +648,7 @@ impl Project { · 3 unmodified lines
643648 LspStore::new_local(
644649 buffer_store.clone(),
645650 worktree_store.clone(),
651+ prettier_store.clone(),
646652 environment.clone(),
647653 languages.clone(),
648654 Some(client.http_client()),
@@ -658,14 +664,10 @@ impl Project { · 9 unmodified lines
658664 worktree_store,
659665 buffer_store,
660666 lsp_store,
661− current_lsp_settings: ProjectSettings::get_global(cx).lsp.clone(),
662667 join_project_response_message_id: 0,
663668 client_state: ProjectClientState::Local,
664669 client_subscriptions: Vec::new(),
665− _subscriptions: vec![
666− cx.observe_global::<SettingsStore>(Self::on_settings_changed),
667− cx.on_release(Self::release),
668− ],
670+ _subscriptions: vec![cx.on_release(Self::release)],
669671 active_entry: None,
670672 snippets,
671673 languages,
@@ -680,9 +682,6 @@ impl Project { · 8 unmodified lines
680682 local_handles: Vec::new(),
681683 },
682684 node: Some(node),
683− default_prettier: DefaultPrettier::default(),
684− prettiers_per_worktree: HashMap::default(),
685− prettier_instances: HashMap::default(),
686685 tasks,
687686 hosted_project_id: None,
688687 dev_server_project_id: None,
@@ -751,14 +750,10 @@ impl Project { · 62 unmodified lines
751750 worktree_store,
752751 buffer_store,
753752 lsp_store,
754− current_lsp_settings: ProjectSettings::get_global(cx).lsp.clone(),
755753 join_project_response_message_id: 0,
756754 client_state: ProjectClientState::Local,
757755 client_subscriptions: Vec::new(),
758− _subscriptions: vec![
759− cx.observe_global::<SettingsStore>(Self::on_settings_changed),
760− cx.on_release(Self::release),
761− ],
756+ _subscriptions: vec![cx.on_release(Self::release)],
762757 active_entry: None,
763758 snippets,
764759 languages,
@@ -773,9 +768,6 @@ impl Project { · 8 unmodified lines
773768 local_handles: Vec::new(),
774769 },
775770 node: Some(node),
776− default_prettier: DefaultPrettier::default(),
777− prettiers_per_worktree: HashMap::default(),
778− prettier_instances: HashMap::default(),
779771 tasks,
780772 hosted_project_id: None,
781773 dev_server_project_id: None,
@@ -928,7 +920,6 @@ impl Project { · 146 unmodified lines
928920 buffer_store: buffer_store.clone(),
929921 worktree_store: worktree_store.clone(),
930922 lsp_store: lsp_store.clone(),
931− current_lsp_settings: ProjectSettings::get_global(cx).lsp.clone(),
932923 active_entry: None,
933924 collaborators: Default::default(),
934925 join_project_response_message_id: response.message_id,
@@ -954,9 +945,6 @@ impl Project { · 19 unmodified lines
954945 local_handles: Vec::new(),
955946 },
956947 node: None,
957− default_prettier: DefaultPrettier::default(),
958− prettiers_per_worktree: HashMap::default(),
959− prettier_instances: HashMap::default(),
960948 tasks,
961949 hosted_project_id: None,
962950 dev_server_project_id: response
@@ -1176,112 +1164,6 @@ impl Project { · 213 unmodified lines
11761164 self.worktree_store.clone()
11771165 }
11781166
1179− fn on_settings_changed(&mut self, cx: &mut ModelContext<Self>) {
1180− let mut language_servers_to_start = Vec::new();
1181− let mut language_formatters_to_check = Vec::new();
1182− for buffer in self.buffer_store.read(cx).buffers() {
1183− let buffer = buffer.read(cx);
1184− let buffer_file = File::from_dyn(buffer.file());
1185− let buffer_language = buffer.language();
1186− let settings = language_settings(buffer_language, buffer.file(), cx);
1187− if let Some(language) = buffer_language {
1188− if settings.enable_language_server {
1189− if let Some(file) = buffer_file {
1190− language_servers_to_start.push((file.worktree.clone(), language.name()));
1191− }
1192− }
1193− language_formatters_to_check
1194− .push((buffer_file.map(|f| f.worktree_id(cx)), settings.clone()));
1195− }
1196− }
1197−
1198− let mut language_servers_to_stop = Vec::new();
1199− let mut language_servers_to_restart = Vec::new();
1200− let languages = self.languages.to_vec();
1201−
1202− let new_lsp_settings = ProjectSettings::get_global(cx).lsp.clone();
1203− let current_lsp_settings = &self.current_lsp_settings;
1204− for (worktree_id, started_lsp_name) in self.lsp_store.read(cx).started_language_servers() {
1205− let language = languages.iter().find_map(|l| {
1206− let adapter = self
1207− .languages
1208− .lsp_adapters(&l.name())
1209− .iter()
1210− .find(|adapter| adapter.name == started_lsp_name)?
1211− .clone();
1212− Some((l, adapter))
1213− });
1214− if let Some((language, adapter)) = language {
1215− let worktree = self.worktree_for_id(worktree_id, cx);
1216− let file = worktree.as_ref().and_then(|tree| {
1217− tree.update(cx, |tree, cx| tree.root_file(cx).map(|f| f as _))
1218− });
1219− if !language_settings(Some(language), file.as_ref(), cx).enable_language_server {
1220− language_servers_to_stop.push((worktree_id, started_lsp_name.clone()));
1221− } else if let Some(worktree) = worktree {
1222− let server_name = &adapter.name.0;
1223− match (
1224− current_lsp_settings.get(server_name),
1225− new_lsp_settings.get(server_name),
1226− ) {
1227− (None, None) => {}
1228− (Some(_), None) | (None, Some(_)) => {
1229− language_servers_to_restart.push((worktree, language.name()));
1230− }
1231− (Some(current_lsp_settings), Some(new_lsp_settings)) => {
1232− if current_lsp_settings != new_lsp_settings {
1233− language_servers_to_restart.push((worktree, language.name()));
1234− }
1235− }
1236− }
1237− }
1238− }
1239− }
1240− self.current_lsp_settings = new_lsp_settings;
1241−
1242− // Stop all newly-disabled language servers.
1243− self.lsp_store.update(cx, |lsp_store, cx| {
1244− for (worktree_id, adapter_name) in language_servers_to_stop {
1245− lsp_store
1246− .stop_language_server(worktree_id, adapter_name, cx)
1247− .detach();
1248− }
1249− });
1250−
1251− let mut prettier_plugins_by_worktree = HashMap::default();
1252− for (worktree, language_settings) in language_formatters_to_check {
1253− if let Some(plugins) =
1254− prettier_support::prettier_plugins_for_language(&language_settings)
1255− {
1256− prettier_plugins_by_worktree
1257− .entry(worktree)
1258− .or_insert_with(HashSet::default)
1259− .extend(plugins.iter().cloned());
1260− }
1261− }
1262− for (worktree, prettier_plugins) in prettier_plugins_by_worktree {
1263− self.install_default_prettier(
1264− worktree,
1265− prettier_plugins.into_iter().map(Arc::from),
1266− cx,
1267− );
1268− }
1269−
1270− // Start all the newly-enabled language servers.
1271− self.lsp_store.update(cx, |lsp_store, cx| {
1272− for (worktree, language) in language_servers_to_start {
1273− lsp_store.start_language_servers(&worktree, language, cx);
1274− }
1275−
1276− // Restart all language servers with changed initialization options.
1277− for (worktree, language) in language_servers_to_restart {
1278− lsp_store.restart_language_servers(worktree, language, cx);
1279− }
1280− });
1281−
1282− cx.notify();
1283− }
1284−
12851167 pub fn buffer_for_id(&self, remote_id: BufferId, cx: &AppContext) -> Option<Model<Buffer>> {
12861168 self.buffer_store.read(cx).get(remote_id)
12871169 }
@@ -2160,24 +2042,10 @@ impl Project { · 872 unmodified lines
21602042 buffer,
21612043 new_language,
21622044 } => {
2163− let Some(new_language) = new_language else {
2045+ let Some(_) = new_language else {
21642046 cx.emit(Event::LanguageNotFound(buffer.clone()));
21652047 return;
21662048 };
2167− let buffer_file = buffer.read(cx).file().cloned();
2168− let settings =
2169− language_settings(Some(new_language), buffer_file.as_ref(), cx).clone();
2170− let buffer_file = File::from_dyn(buffer_file.as_ref());
2171− let worktree = buffer_file.as_ref().map(|f| f.worktree_id(cx));
2172− if let Some(prettier_plugins) =
2173− prettier_support::prettier_plugins_for_language(&settings)
2174− {
2175− self.install_default_prettier(
2176− worktree,
2177− prettier_plugins.iter().map(|s| Arc::from(s.as_str())),
2178− cx,
2179− );
2180− };
21812049 }
21822050 LspStoreEvent::RefreshInlayHints => cx.emit(Event::RefreshInlayHints),
21832051 LspStoreEvent::LanguageServerPrompt(prompt) => {
@@ -2253,7 +2121,6 @@ impl Project { · 69 unmodified lines
22532121 worktree::Event::UpdatedEntries(changes) => {
22542122 if is_local {
22552123 this.update_local_worktree_settings(&worktree, changes, cx);
2256− this.update_prettier_settings(&worktree, changes, cx);
22572124 }
22582125
22592126 cx.emit(Event::WorktreeUpdatedEntries(
@@ -2300,37 +2167,6 @@ impl Project { · 40 unmodified lines
23002167 return;
23012168 }
23022169
2303− let mut prettier_instances_to_clean = FuturesUnordered::new();
2304− if let Some(prettier_paths) = self.prettiers_per_worktree.remove(&id_to_remove) {
2305− for path in prettier_paths.iter().flatten() {
2306− if let Some(prettier_instance) = self.prettier_instances.remove(path) {
2307− prettier_instances_to_clean.push(async move {
2308− prettier_instance
2309− .server()
2310− .await
2311− .map(|server| server.server_id())
2312− });
2313− }
2314− }
2315− }
2316− cx.spawn(|project, mut cx| async move {
2317− while let Some(prettier_server_id) = prettier_instances_to_clean.next().await {
2318− if let Some(prettier_server_id) = prettier_server_id {
2319− project
2320− .update(&mut cx, |project, cx| {
2321− project.lsp_store.update(cx, |lsp_store, cx| {
2322− lsp_store.unregister_supplementary_language_server(
2323− prettier_server_id,
2324− cx,
2325− );
2326− });
2327− })
2328− .ok();
2329− }
2330− }
2331− })
2332− .detach();
2333−
23342170 self.task_inventory().update(cx, |inventory, _| {
23352171 inventory.remove_worktree_sources(id_to_remove);
23362172 });
@@ -3059,11 +2895,21 @@ impl Project { · 722 unmodified lines
30592895 None
30602896 }
30612897 }
3062− Formatter::Prettier => prettier_support::format_with_prettier(&project, buffer, cx)
3063− .await
3064− .transpose()
3065− .ok()
3066− .flatten(),
2898+ Formatter::Prettier => {
2899+ let prettier = project.update(cx, |project, cx| {
2900+ project
2901+ .lsp_store
2902+ .read(cx)
2903+ .prettier_store()
2904+ .unwrap()
2905+ .downgrade()
2906+ })?;
2907+ prettier_store::format_with_prettier(&prettier, buffer, cx)
2908+ .await
2909+ .transpose()
2910+ .ok()
2911+ .flatten()
2912+ }
30672913 Formatter::External { command, arguments } => {
30682914 let buffer_abs_path = buffer_abs_path.as_ref().map(|path| path.as_path());
30692915 Self::format_via_external_command(buffer, buffer_abs_path, command, arguments, cx)
crates/remote_server/Cargo.toml+1−0
@@ -26,6 +26,7 @@ env_logger.workspace = true · 25 unmodified lines
2626 fs.workspace = true
2727 futures.workspace = true
2828 gpui.workspace = true
29+node_runtime.workspace = true
2930 log.workspace = true
3031 project.workspace = true
3132 remote.workspace = true
crates/remote_server/src/headless_project.rs+13−1
@@ -2,12 +2,13 @@ use anyhow::{anyhow, Result}; · 1 unmodified line
22 use fs::Fs;
33 use gpui::{AppContext, AsyncAppContext, Context, Model, ModelContext};
44 use language::{proto::serialize_operation, Buffer, BufferEvent, LanguageRegistry};
5+use node_runtime::DummyNodeRuntime;
56 use project::{
67 buffer_store::{BufferStore, BufferStoreEvent},
78 project_settings::SettingsObserver,
89 search::SearchQuery,
910 worktree_store::WorktreeStore,
10− LspStore, LspStoreEvent, ProjectPath, WorktreeId,
11+ LspStore, LspStoreEvent, PrettierStore, ProjectPath, WorktreeId,
1112 };
1213 use remote::SshSession;
1314 use rpc::{
@@ -54,6 +55,16 @@ impl HeadlessProject { · 40 unmodified lines
5455 buffer_store.shared(SSH_PROJECT_ID, session.clone().into(), cx);
5556 buffer_store
5657 });
58+ let prettier_store = cx.new_model(|cx| {
59+ PrettierStore::new(
60+ DummyNodeRuntime::new(),
61+ fs.clone(),
62+ languages.clone(),
63+ worktree_store.clone(),
64+ cx,
65+ )
66+ });
67+
5768 let settings_observer = cx.new_model(|cx| {
5869 let mut observer = SettingsObserver::new_local(fs.clone(), worktree_store.clone(), cx);
5970 observer.shared(SSH_PROJECT_ID, session.clone().into(), cx);
@@ -64,6 +75,7 @@ impl HeadlessProject { · 4 unmodified lines
6475 let mut lsp_store = LspStore::new_local(
6576 buffer_store.clone(),
6677 worktree_store.clone(),
78+ prettier_store.clone(),
6779 environment,
6880 languages.clone(),
6981 None,