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
462 unreachable!("Should not install packages {packages:?}")462 unreachable!("Should not install packages {packages:?}")
463 }463 }
464}464}
465
466// TODO: Remove this when headless binary can run node
467pub struct DummyNodeRuntime;
468
469impl DummyNodeRuntime {
470 pub fn new() -> Arc<dyn NodeRuntime> {
471 Arc::new(Self)
472 }
473}
474
475#[async_trait::async_trait]
476impl 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
3 environment::ProjectEnvironment,3 environment::ProjectEnvironment,
4 lsp_command::{self, *},4 lsp_command::{self, *},
5 lsp_ext_command,5 lsp_ext_command,
6 prettier_store::{self, PrettierStore, PrettierStoreEvent},
6 project_settings::{LspSettings, ProjectSettings},7 project_settings::{LspSettings, ProjectSettings},
7 relativize_path, resolve_path,8 relativize_path, resolve_path,
8 worktree_store::{WorktreeStore, WorktreeStoreEvent},9 worktree_store::{WorktreeStore, WorktreeStoreEvent},
@@ -101,6 +102,8 @@ pub struct LocalLspStore { · 92 unmodified lines
101 HashMap<LanguageServerId, HashMap<String, Vec<FileSystemWatcher>>>,102 HashMap<LanguageServerId, HashMap<String, Vec<FileSystemWatcher>>>,
102 supplementary_language_servers:103 supplementary_language_servers:
103 HashMap<LanguageServerId, (LanguageServerName, Arc<LanguageServer>)>,104 HashMap<LanguageServerId, (LanguageServerName, Arc<LanguageServer>)>,
105 prettier_store: Model<PrettierStore>,
106 current_lsp_settings: HashMap<Arc<str>, LspSettings>,
104 _subscription: gpui::Subscription,107 _subscription: gpui::Subscription,
105}108}
106 109
@@ -135,6 +138,7 @@ impl RemoteLspStore {} · 28 unmodified lines
135 138
136pub struct SshLspStore {139pub struct SshLspStore {
137 upstream_client: AnyProtoClient,140 upstream_client: AnyProtoClient,
141 current_lsp_settings: HashMap<Arc<str>, LspSettings>,
138}142}
139 143
140#[allow(clippy::large_enum_variant)]144#[allow(clippy::large_enum_variant)]
@@ -310,9 +314,32 @@ impl LspStore { · 169 unmodified lines
310 }314 }
311 }315 }
312 316
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)]
313 pub fn new_local(339 pub fn new_local(
314 buffer_store: Model<BufferStore>,340 buffer_store: Model<BufferStore>,
315 worktree_store: Model<WorktreeStore>,341 worktree_store: Model<WorktreeStore>,
342 prettier_store: Model<PrettierStore>,
316 environment: Model<ProjectEnvironment>,343 environment: Model<ProjectEnvironment>,
317 languages: Arc<LanguageRegistry>,344 languages: Arc<LanguageRegistry>,
318 http_client: Option<Arc<dyn HttpClient>>,345 http_client: Option<Arc<dyn HttpClient>>,
@@ -324,6 +351,10 @@ impl LspStore { · 5 unmodified lines
324 .detach();351 .detach();
325 cx.subscribe(&worktree_store, Self::on_worktree_store_event)352 cx.subscribe(&worktree_store, Self::on_worktree_store_event)
326 .detach();353 .detach();
354 cx.subscribe(&prettier_store, Self::on_prettier_store_event)
355 .detach();
356 cx.observe_global::<SettingsStore>(Self::on_settings_changed)
357 .detach();
327 358
328 Self {359 Self {
329 mode: LspStoreMode::Local(LocalLspStore {360 mode: LspStoreMode::Local(LocalLspStore {
@@ -332,6 +363,8 @@ impl LspStore { · 2 unmodified lines
332 last_workspace_edits_by_language_server: Default::default(),363 last_workspace_edits_by_language_server: Default::default(),
333 language_server_watched_paths: Default::default(),364 language_server_watched_paths: Default::default(),
334 language_server_watcher_registrations: Default::default(),365 language_server_watcher_registrations: Default::default(),
366 current_lsp_settings: ProjectSettings::get_global(cx).lsp.clone(),
367 prettier_store,
335 environment,368 environment,
336 http_client,369 http_client,
337 fs,370 fs,
@@ -387,9 +420,14 @@ impl LspStore { · 49 unmodified lines
387 .detach();420 .detach();
388 cx.subscribe(&worktree_store, Self::on_worktree_store_event)421 cx.subscribe(&worktree_store, Self::on_worktree_store_event)
389 .detach();422 .detach();
423 cx.observe_global::<SettingsStore>(Self::on_settings_changed)
424 .detach();
390 425
391 Self {426 Self {
392 mode: LspStoreMode::Ssh(SshLspStore { upstream_client }),427 mode: LspStoreMode::Ssh(SshLspStore {
428 upstream_client,
429 current_lsp_settings: Default::default(),
430 }),
393 downstream_client: None,431 downstream_client: None,
394 project_id,432 project_id,
395 buffer_store,433 buffer_store,
@@ -401,6 +439,7 @@ impl LspStore { · 5 unmodified lines
401 buffer_snapshots: Default::default(),439 buffer_snapshots: Default::default(),
402 next_diagnostic_group_id: Default::default(),440 next_diagnostic_group_id: Default::default(),
403 diagnostic_summaries: Default::default(),441 diagnostic_summaries: Default::default(),
442
404 diagnostics: Default::default(),443 diagnostics: Default::default(),
405 active_entry: None,444 active_entry: None,
406 _maintain_workspace_config: Self::maintain_workspace_config(cx),445 _maintain_workspace_config: Self::maintain_workspace_config(cx),
@@ -498,6 +537,36 @@ impl LspStore { · 91 unmodified lines
498 }537 }
499 }538 }
500 539
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
501 fn on_buffer_event(570 fn on_buffer_event(
502 &mut self,571 &mut self,
503 buffer: Model<Buffer>,572 buffer: Model<Buffer>,
@@ -656,11 +725,29 @@ impl LspStore { · 152 unmodified lines
656 });725 });
657 726
658 let buffer_file = buffer.read(cx).file().cloned();727 let buffer_file = buffer.read(cx).file().cloned();
728 let settings = language_settings(Some(&new_language), buffer_file.as_ref(), cx).clone();
659 let buffer_file = File::from_dyn(buffer_file.as_ref());729 let buffer_file = File::from_dyn(buffer_file.as_ref());
660 730
661 if let Some(file) = buffer_file {731 let worktree_id = if let Some(file) = buffer_file {
662 let worktree = file.worktree.clone();732 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 }
664 }751 }
665 752
666 cx.emit(LspStoreEvent::LanguageDetected {753 cx.emit(LspStoreEvent::LanguageDetected {
@@ -799,6 +886,95 @@ impl LspStore { · 132 unmodified lines
799 Task::ready(Ok(Default::default()))886 Task::ready(Ok(Default::default()))
800 }887 }
801 888
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
802 pub async fn execute_code_actions_on_servers(978 pub async fn execute_code_actions_on_servers(
803 this: &WeakModel<LspStore>,979 this: &WeakModel<LspStore>,
804 adapters_and_servers: &[(Arc<CachedLspAdapter>, Arc<LanguageServer>)],980 adapters_and_servers: &[(Arc<CachedLspAdapter>, Arc<LanguageServer>)],
@@ -2375,7 +2551,7 @@ impl LspStore { · 1570 unmodified lines
2375 })2551 })
2376 }2552 }
2377 2553
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>) {
2379 self.diagnostics.remove(&id_to_remove);2555 self.diagnostics.remove(&id_to_remove);
2380 self.diagnostic_summaries.remove(&id_to_remove);2556 self.diagnostic_summaries.remove(&id_to_remove);
2381 2557
@@ -2406,6 +2582,12 @@ impl LspStore { · 24 unmodified lines
2406 }2582 }
2407 cx.emit(LspStoreEvent::LanguageServerRemoved(server_id_to_remove));2583 cx.emit(LspStoreEvent::LanguageServerRemoved(server_id_to_remove));
2408 }2584 }
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 }
2409 }2591 }
2410 2592
2411 pub fn shared(2593 pub fn shared(
@@ -6117,6 +6299,10 @@ impl LspStore { · 3705 unmodified lines
6117 6299
6118 let Some(local) = self.as_local() else { return };6300 let Some(local) = self.as_local() else { return };
6119 6301
6302 local.prettier_store.update(cx, |prettier_store, cx| {
6303 prettier_store.update_prettier_settings(&worktree_handle, changes, cx)
6304 });
6305
6120 let worktree_id = worktree_handle.read(cx).id();6306 let worktree_id = worktree_handle.read(cx).id();
6121 let mut language_server_ids = self6307 let mut language_server_ids = self
6122 .language_server_ids6308 .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
5};5};
6 6
7use anyhow::{anyhow, Context, Result};7use anyhow::{anyhow, Context, Result};
8use collections::HashSet;8use collections::{HashMap, HashSet};
9use fs::Fs;9use fs::Fs;
10use futures::{10use futures::{
11 future::{self, Shared},11 future::{self, Shared},
12 stream::FuturesUnordered,
12 FutureExt,13 FutureExt,
13};14};
14use gpui::{AsyncAppContext, Model, ModelContext, Task, WeakModel};15use gpui::{AsyncAppContext, EventEmitter, Model, ModelContext, Task, WeakModel};
15use language::{16use language::{
16 language_settings::{Formatter, LanguageSettings, SelectedFormatter},17 language_settings::{Formatter, LanguageSettings, SelectedFormatter},
17 Buffer, LanguageServerName, LocalFile,18 Buffer, LanguageRegistry, LanguageServerName, LocalFile,
18};19};
19use lsp::{LanguageServer, LanguageServerId};20use lsp::{LanguageServer, LanguageServerId};
20use node_runtime::NodeRuntime;21use node_runtime::NodeRuntime;
21use paths::default_prettier_dir;22use paths::default_prettier_dir;
22use prettier::Prettier;23use prettier::Prettier;
24use smol::stream::StreamExt;
23use util::{ResultExt, TryFutureExt};25use util::{ResultExt, TryFutureExt};
24 26
25use crate::{File, FormatOperation, PathChange, Project, ProjectEntryId, Worktree, WorktreeId};27use crate::{
26 28 worktree_store::WorktreeStore, File, FormatOperation, PathChange, ProjectEntryId, Worktree,
27pub fn prettier_plugins_for_language(29 WorktreeId,
28 language_settings: &LanguageSettings,30};
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
40pub(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}
101 31
102pub struct DefaultPrettier {32pub struct PrettierStore {
103 prettier: PrettierInstallation,33 node: Arc<dyn NodeRuntime>,
104 installed_plugins: HashSet<Arc<str>>,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>,
105}40}
106 41
107#[derive(Debug)]42pub enum PrettierStoreEvent {
108pub enum PrettierInstallation {43 LanguageServerRemoved(LanguageServerId),
109 NotInstalled {44 LanguageServerAdded {
110 attempts: usize,45 new_server_id: LanguageServerId,
111 installation_task: Option<Shared<Task<Result<(), Arc<anyhow::Error>>>>>,46 name: LanguageServerName,
112 not_installed_plugins: HashSet<Arc<str>>,47 prettier_server: Arc<LanguageServer>,
113 },48 },
114 Installed(PrettierInstance),
115}49}
116 50
117pub type PrettierTask = Shared<Task<Result<Arc<Prettier>, Arc<anyhow::Error>>>>;51impl EventEmitter<PrettierStoreEvent> for PrettierStore {}
118
119#[derive(Debug, Clone)]
120pub struct PrettierInstance {
121 attempt: usize,
122 prettier: Option<PrettierTask>,
123}
124 52
125impl Default for DefaultPrettier {53impl PrettierStore {
126 fn default() -> Self {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 {
127 Self {61 Self {
128 prettier: PrettierInstallation::NotInstalled {62 node,
129 attempts: 0,63 fs,
130 installation_task: None,64 languages,
131 not_installed_plugins: HashSet::default(),65 worktree_store,
132 },66 default_prettier: DefaultPrettier::default(),
133 installed_plugins: HashSet::default(),67 prettiers_per_worktree: HashMap::default(),
68 prettier_instances: HashMap::default(),
134 }69 }
135 }70 }
136}
137 71
138impl DefaultPrettier {72 pub fn remove_worktree(&mut self, id_to_remove: WorktreeId, cx: &mut ModelContext<Self>) {
139 pub fn instance(&self) -> Option<&PrettierInstance> {73 let mut prettier_instances_to_clean = FuturesUnordered::new();
140 if let PrettierInstallation::Installed(instance) = &self.prettier {74 if let Some(prettier_paths) = self.prettiers_per_worktree.remove(&id_to_remove) {
141 Some(instance)75 for path in prettier_paths.iter().flatten() {
142 } else {76 if let Some(prettier_instance) = self.prettier_instances.remove(path) {
143 None77 prettier_instances_to_clean.push(async move {
78 prettier_instance
79 .server()
80 .await
81 .map(|server| server.server_id())
82 });
83 }
84 }
144 }85 }
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();
145 }100 }
146 101
147 pub fn prettier_task(102 fn prettier_instance_for_buffer(
148 &mut self,103 &mut self,
149 node: &Arc<dyn NodeRuntime>,104 buffer: &Model<Buffer>,
150 worktree_id: Option<WorktreeId>,105 cx: &mut ModelContext<Self>,
151 cx: &mut ModelContext<'_, Project>,106 ) -> Task<Option<(Option<PathBuf>, PrettierTask)>> {
152 ) -> Option<Task<anyhow::Result<PrettierTask>>> {107 let buffer = buffer.read(cx);
153 match &mut self.prettier {108 let buffer_file = buffer.file();
154 PrettierInstallation::NotInstalled { .. } => {109 if buffer.language().is_none() {
155 Some(start_default_prettier(Arc::clone(node), worktree_id, cx))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 })
156 }206 }
157 PrettierInstallation::Installed(existing_instance) => {207 None => {
158 existing_instance.prettier_task(node, None, worktree_id, cx)208 let new_task = self.default_prettier.prettier_task(&node, None, cx);
209 cx.spawn(|_, _| async move { Some((None, new_task?.log_err().await?)) })
159 }210 }
160 }211 }
161 }212 }
162}
163 213
164impl PrettierInstance {214 fn start_prettier(
165 pub fn prettier_task(215 node: Arc<dyn NodeRuntime>,
166 &mut self,216 prettier_dir: PathBuf,
167 node: &Arc<dyn NodeRuntime>,
168 prettier_dir: Option<&Path>,
169 worktree_id: Option<WorktreeId>,217 worktree_id: Option<WorktreeId>,
170 cx: &mut ModelContext<'_, Project>,218 cx: &mut ModelContext<Self>,
171 ) -> Option<Task<anyhow::Result<PrettierTask>>> {219 ) -> PrettierTask {
172 if self.attempt > prettier::FAIL_THRESHOLD {220 cx.spawn(|prettier_store, mut cx| async move {
173 match prettier_dir {221 log::info!("Starting prettier at path {prettier_dir:?}");
174 Some(prettier_dir) => log::warn!(222 let new_server_id = prettier_store.update(&mut cx, |prettier_store, _| {
175 "Prettier from path {prettier_dir:?} exceeded launch threshold, not starting"223 prettier_store.languages.next_language_server_id()
176 ),224 })?;
177 None => log::warn!("Default prettier exceeded launch threshold, not starting"),225
178 }226 let new_prettier = Prettier::start(new_server_id, prettier_dir, node, cx.clone())
179 return None;227 .await
180 }228 .context("default prettier spawn")
181 Some(match &self.prettier {229 .map(Arc::new)
182 Some(prettier_task) => Task::ready(Ok(prettier_task.clone())),230 .map_err(Arc::new)?;
183 None => match prettier_dir {231 Self::register_new_prettier(
184 Some(prettier_dir) => {232 &prettier_store,
185 let new_task = start_prettier(233 &new_prettier,
186 Arc::clone(node),234 worktree_id,
187 prettier_dir.to_path_buf(),235 new_server_id,
188 worktree_id,236 &mut cx,
189 cx,237 );
190 );238 Ok(new_prettier)
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 },
207 })239 })
240 .shared()
208 }241 }
209 242
210 pub async fn server(&self) -> Option<Arc<LanguageServer>> {243 fn start_default_prettier(
211 self.prettier.clone()?.await.ok()?.server().cloned()244 node: Arc<dyn NodeRuntime>,
212 }245 worktree_id: Option<WorktreeId>,
213}246 cx: &mut ModelContext<PrettierStore>,
214 247 ) -> Task<anyhow::Result<PrettierTask>> {
215fn start_default_prettier(248 cx.spawn(|prettier_store, mut cx| async move {
216 node: Arc<dyn NodeRuntime>,249 let installation_task = prettier_store.update(&mut cx, |prettier_store, _| {
217 worktree_id: Option<WorktreeId>,250 match &prettier_store.default_prettier.prettier {
218 cx: &mut ModelContext<'_, Project>,251 PrettierInstallation::NotInstalled {
219) -> Task<anyhow::Result<PrettierTask>> {252 installation_task, ..
220 cx.spawn(|project, mut cx| async move {253 } => ControlFlow::Continue(installation_task.clone()),
221 let installation_task = project.update(&mut cx, |project, _| {254 PrettierInstallation::Installed(default_prettier) => {
222 match &project.default_prettier.prettier {255 ControlFlow::Break(default_prettier.clone())
223 PrettierInstallation::NotInstalled {256 }
224 installation_task, ..257 }
225 } => ControlFlow::Continue(installation_task.clone()),258 })?;
226 PrettierInstallation::Installed(default_prettier) => {259 match installation_task {
227 ControlFlow::Break(default_prettier.clone())260 ControlFlow::Continue(None) => {
228 }261 anyhow::bail!("Default prettier is not installed and cannot be started")
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 );
252 }262 }
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 }
254 let new_default_prettier =281 let new_default_prettier =
255 start_prettier(node, default_prettier_dir().clone(), worktree_id, cx);282 prettier_store.update(&mut cx, |prettier_store, cx| {
256 project.default_prettier.prettier =283 let new_default_prettier = Self::start_prettier(
257 PrettierInstallation::Installed(PrettierInstance {284 node,
258 attempt: 0,285 default_prettier_dir().clone(),
259 prettier: Some(new_default_prettier.clone()),286 worktree_id,
260 });287 cx,
261 new_default_prettier288 );
262 })?;289 prettier_store.default_prettier.prettier =
263 Ok(new_default_prettier)290 PrettierInstallation::Installed(PrettierInstance {
264 }291 attempt: 0,
265 ControlFlow::Break(instance) => match instance.prettier {292 prettier: Some(new_default_prettier.clone()),
266 Some(instance) => Ok(instance),293 });
267 None => {294 new_default_prettier
268 let new_default_prettier = project.update(&mut cx, |project, cx| {295 })?;
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 })?;
278 Ok(new_default_prettier)296 Ok(new_default_prettier)
279 }297 }
280 },298 ControlFlow::Break(instance) => match instance.prettier {
281 }299 Some(instance) => Ok(instance),
282 })300 None => {
283}301 let new_default_prettier =
284 302 prettier_store.update(&mut cx, |prettier_store, cx| {
285fn start_prettier(303 let new_default_prettier = Self::start_prettier(
286 node: Arc<dyn NodeRuntime>,304 node,
287 prettier_dir: PathBuf,305 default_prettier_dir().clone(),
288 worktree_id: Option<WorktreeId>,306 worktree_id,
289 cx: &mut ModelContext<'_, Project>,307 cx,
290) -> PrettierTask {308 );
291 cx.spawn(|project, mut cx| async move {309 prettier_store.default_prettier.prettier =
292 log::info!("Starting prettier at path {prettier_dir:?}");310 PrettierInstallation::Installed(PrettierInstance {
293 let new_server_id = project.update(&mut cx, |project, _| {311 attempt: instance.attempt + 1,
294 project.languages.next_language_server_id()312 prettier: Some(new_default_prettier.clone()),
295 })?;313 });
296 314 new_default_prettier
297 let new_prettier = Prettier::start(new_server_id, prettier_dir, node, cx.clone())315 })?;
298 .await316 Ok(new_default_prettier)
299 .context("default prettier spawn")317 }
300 .map(Arc::new)318 },
301 .map_err(Arc::new)?;319 }
302 register_new_prettier(&project, &new_prettier, worktree_id, new_server_id, &mut cx);320 })
303 Ok(new_prettier)
304 })
305 .shared()
306}
307
308fn 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:?}");
321 }321 }
322 if let Some(prettier_server) = prettier.server() {322
323 project323 fn register_new_prettier(
324 .update(cx, |project, cx| {324 prettier_store: &WeakModel<Self>,
325 let name = if is_default {325 prettier: &Prettier,
326 LanguageServerName(Arc::from("prettier (default)"))326 worktree_id: Option<WorktreeId>,
327 } else {327 new_server_id: LanguageServerId,
328 let worktree_path = worktree_id328 cx: &mut AsyncAppContext,
329 .and_then(|id| project.worktree_for_id(id, cx))329 ) {
330 .map(|worktree| worktree.update(cx, |worktree, _| worktree.abs_path()));330 let prettier_dir = prettier.prettier_dir();
331 let name = match worktree_path {331 let is_default = prettier.is_default();
332 Some(worktree_path) => {332 if is_default {
333 if prettier_dir == worktree_path.as_ref() {333 log::info!("Started default prettier in {prettier_dir:?}");
334 let name = prettier_dir334 } else {
335 .file_name()335 log::info!("Started prettier in {prettier_dir:?}");
336 .and_then(|name| name.to_str())336 }
337 .unwrap_or_default();337 if let Some(prettier_server) = prettier.server() {
338 format!("prettier ({name})")338 prettier_store
339 } else {339 .update(cx, |prettier_store, cx| {
340 let dir_to_display = prettier_dir340 let name = if is_default {
341 .strip_prefix(worktree_path.as_ref())341 LanguageServerName(Arc::from("prettier (default)"))
342 .ok()342 } else {
343 .unwrap_or(prettier_dir);343 let worktree_path = worktree_id
344 format!("prettier ({})", dir_to_display.display())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 }
345 }366 }
346 }367 None => format!("prettier ({})", prettier_dir.display()),
347 None => format!("prettier ({})", prettier_dir.display()),368 };
369 LanguageServerName(Arc::from(name))
348 };370 };
349 LanguageServerName(Arc::from(name))371 cx.emit(PrettierStoreEvent::LanguageServerAdded {
350 };
351 project.lsp_store.update(cx, |lsp_store, cx| {
352 lsp_store.register_supplementary_language_server(
353 new_server_id,372 new_server_id,
354 name,373 name,
355 Arc::clone(prettier_server),374 prettier_server: prettier_server.clone(),
356 cx,375 });
357 )376 })
358 });377 .ok();
359 })378 }
360 .ok();
361 }
362}
363
364async 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
412async 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
429async 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;
433 }379 }
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}
439 380
440impl Project {
441 pub fn update_prettier_settings(381 pub fn update_prettier_settings(
442 &self,382 &self,
443 worktree: &Model<Worktree>,383 worktree: &Model<Worktree>,
444 changes: &[(Arc<Path>, ProjectEntryId, PathChange)],384 changes: &[(Arc<Path>, ProjectEntryId, PathChange)],
445 cx: &mut ModelContext<'_, Project>,385 cx: &mut ModelContext<Self>,
446 ) {386 ) {
447 let prettier_config_files = Prettier::CONFIG_FILE_NAMES387 let prettier_config_files = Prettier::CONFIG_FILE_NAMES
448 .iter()388 .iter()
@@ -510,122 +450,6 @@ impl Project { · 61 unmodified lines
510 }450 }
511 }451 }
512 452
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
629 pub fn install_default_prettier(453 pub fn install_default_prettier(
630 &mut self,454 &mut self,
631 worktree: Option<WorktreeId>,455 worktree: Option<WorktreeId>,
@@ -642,12 +466,13 @@ impl Project { · 10 unmodified lines
642 }466 }
643 467
644 let mut new_plugins = plugins.collect::<HashSet<_>>();468 let mut new_plugins = plugins.collect::<HashSet<_>>();
645 let Some(node) = self.node.as_ref().cloned() else {469 let node = self.node.clone();
646 return;470
647 };
648 let fs = Arc::clone(&self.fs);471 let fs = Arc::clone(&self.fs);
649 let locate_prettier_installation = match worktree.and_then(|worktree_id| {472 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)
651 .map(|worktree| worktree.read(cx).abs_path())476 .map(|worktree| worktree.read(cx).abs_path())
652 }) {477 }) {
653 Some(locate_from) => {478 Some(locate_from) => {
@@ -777,4 +602,291 @@ impl Project { · 123 unmodified lines
777 not_installed_plugins: plugins_to_install,602 not_installed_plugins: plugins_to_install,
778 };603 };
779 }604 }
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
630pub 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
643pub(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
705pub struct DefaultPrettier {
706 prettier: PrettierInstallation,
707 installed_plugins: HashSet<Arc<str>>,
708}
709
710#[derive(Debug)]
711pub 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
720pub type PrettierTask = Shared<Task<Result<Arc<Prettier>, Arc<anyhow::Error>>>>;
721
722#[derive(Debug, Clone)]
723pub struct PrettierInstance {
724 attempt: usize,
725 prettier: Option<PrettierTask>,
726}
727
728impl 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
741impl 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
767impl 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
818async 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
866async 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
883async 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
780}892}
crates/project/src/project.rs+32−186
@@ -4,7 +4,7 @@ pub mod debounced_delay; · 3 unmodified lines
4pub mod lsp_command;4pub mod lsp_command;
5pub mod lsp_ext_command;5pub mod lsp_ext_command;
6pub mod lsp_store;6pub mod lsp_store;
7mod prettier_support;7pub mod prettier_store;
8pub mod project_settings;8pub mod project_settings;
9pub mod search;9pub mod search;
10mod task_inventory;10mod task_inventory;
@@ -31,7 +31,6 @@ pub use environment::ProjectEnvironment; · 20 unmodified lines
31use futures::{31use futures::{
32 channel::mpsc::{self, UnboundedReceiver},32 channel::mpsc::{self, UnboundedReceiver},
33 future::try_join_all,33 future::try_join_all,
34 stream::FuturesUnordered,
35 AsyncWriteExt, FutureExt, StreamExt,34 AsyncWriteExt, FutureExt, StreamExt,
36};35};
37 36
@@ -59,8 +58,8 @@ use lsp_command::*; · 21 unmodified lines
59use node_runtime::NodeRuntime;58use node_runtime::NodeRuntime;
60use parking_lot::{Mutex, RwLock};59use parking_lot::{Mutex, RwLock};
61use paths::{local_tasks_file_relative_path, local_vscode_tasks_file_relative_path};60use paths::{local_tasks_file_relative_path, local_vscode_tasks_file_relative_path};
62use prettier_support::{DefaultPrettier, PrettierInstance};61pub use prettier_store::PrettierStore;
63use project_settings::{LspSettings, ProjectSettings, SettingsObserver};62use project_settings::{ProjectSettings, SettingsObserver};
64use remote::SshSession;63use remote::SshSession;
65use rpc::{proto::SSH_PROJECT_ID, AnyProtoClient, ErrorCode};64use rpc::{proto::SSH_PROJECT_ID, AnyProtoClient, ErrorCode};
66use search::{SearchInputKind, SearchQuery, SearchResult};65use search::{SearchInputKind, SearchQuery, SearchResult};
@@ -140,7 +139,6 @@ pub struct Project { · 73 unmodified lines
140 buffer_ordered_messages_tx: mpsc::UnboundedSender<BufferOrderedMessage>,139 buffer_ordered_messages_tx: mpsc::UnboundedSender<BufferOrderedMessage>,
141 languages: Arc<LanguageRegistry>,140 languages: Arc<LanguageRegistry>,
142 client: Arc<client::Client>,141 client: Arc<client::Client>,
143 current_lsp_settings: HashMap<Arc<str>, LspSettings>,
144 join_project_response_message_id: u32,142 join_project_response_message_id: u32,
145 user_store: Model<UserStore>,143 user_store: Model<UserStore>,
146 fs: Arc<dyn Fs>,144 fs: Arc<dyn Fs>,
@@ -157,9 +155,6 @@ pub struct Project { · 10 unmodified lines
157 remotely_created_buffers: Arc<Mutex<RemotelyCreatedBuffers>>,155 remotely_created_buffers: Arc<Mutex<RemotelyCreatedBuffers>>,
158 terminals: Terminals,156 terminals: Terminals,
159 node: Option<Arc<dyn NodeRuntime>>,157 node: Option<Arc<dyn NodeRuntime>>,
160 default_prettier: DefaultPrettier,
161 prettiers_per_worktree: HashMap<WorktreeId, HashSet<Option<PathBuf>>>,
162 prettier_instances: HashMap<PathBuf, PrettierInstance>,
163 tasks: Model<Inventory>,158 tasks: Model<Inventory>,
164 hosted_project_id: Option<ProjectId>,159 hosted_project_id: Option<ProjectId>,
165 dev_server_project_id: Option<client::DevServerProjectId>,160 dev_server_project_id: Option<client::DevServerProjectId>,
@@ -634,6 +629,16 @@ impl Project { · 468 unmodified lines
634 cx.subscribe(&buffer_store, Self::on_buffer_store_event)629 cx.subscribe(&buffer_store, Self::on_buffer_store_event)
635 .detach();630 .detach();
636 631
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
637 let settings_observer = cx.new_model(|cx| {642 let settings_observer = cx.new_model(|cx| {
638 SettingsObserver::new_local(fs.clone(), worktree_store.clone(), cx)643 SettingsObserver::new_local(fs.clone(), worktree_store.clone(), cx)
639 });644 });
@@ -643,6 +648,7 @@ impl Project { · 3 unmodified lines
643 LspStore::new_local(648 LspStore::new_local(
644 buffer_store.clone(),649 buffer_store.clone(),
645 worktree_store.clone(),650 worktree_store.clone(),
651 prettier_store.clone(),
646 environment.clone(),652 environment.clone(),
647 languages.clone(),653 languages.clone(),
648 Some(client.http_client()),654 Some(client.http_client()),
@@ -658,14 +664,10 @@ impl Project { · 9 unmodified lines
658 worktree_store,664 worktree_store,
659 buffer_store,665 buffer_store,
660 lsp_store,666 lsp_store,
661 current_lsp_settings: ProjectSettings::get_global(cx).lsp.clone(),
662 join_project_response_message_id: 0,667 join_project_response_message_id: 0,
663 client_state: ProjectClientState::Local,668 client_state: ProjectClientState::Local,
664 client_subscriptions: Vec::new(),669 client_subscriptions: Vec::new(),
665 _subscriptions: vec![670 _subscriptions: vec![cx.on_release(Self::release)],
666 cx.observe_global::<SettingsStore>(Self::on_settings_changed),
667 cx.on_release(Self::release),
668 ],
669 active_entry: None,671 active_entry: None,
670 snippets,672 snippets,
671 languages,673 languages,
@@ -680,9 +682,6 @@ impl Project { · 8 unmodified lines
680 local_handles: Vec::new(),682 local_handles: Vec::new(),
681 },683 },
682 node: Some(node),684 node: Some(node),
683 default_prettier: DefaultPrettier::default(),
684 prettiers_per_worktree: HashMap::default(),
685 prettier_instances: HashMap::default(),
686 tasks,685 tasks,
687 hosted_project_id: None,686 hosted_project_id: None,
688 dev_server_project_id: None,687 dev_server_project_id: None,
@@ -751,14 +750,10 @@ impl Project { · 62 unmodified lines
751 worktree_store,750 worktree_store,
752 buffer_store,751 buffer_store,
753 lsp_store,752 lsp_store,
754 current_lsp_settings: ProjectSettings::get_global(cx).lsp.clone(),
755 join_project_response_message_id: 0,753 join_project_response_message_id: 0,
756 client_state: ProjectClientState::Local,754 client_state: ProjectClientState::Local,
757 client_subscriptions: Vec::new(),755 client_subscriptions: Vec::new(),
758 _subscriptions: vec![756 _subscriptions: vec![cx.on_release(Self::release)],
759 cx.observe_global::<SettingsStore>(Self::on_settings_changed),
760 cx.on_release(Self::release),
761 ],
762 active_entry: None,757 active_entry: None,
763 snippets,758 snippets,
764 languages,759 languages,
@@ -773,9 +768,6 @@ impl Project { · 8 unmodified lines
773 local_handles: Vec::new(),768 local_handles: Vec::new(),
774 },769 },
775 node: Some(node),770 node: Some(node),
776 default_prettier: DefaultPrettier::default(),
777 prettiers_per_worktree: HashMap::default(),
778 prettier_instances: HashMap::default(),
779 tasks,771 tasks,
780 hosted_project_id: None,772 hosted_project_id: None,
781 dev_server_project_id: None,773 dev_server_project_id: None,
@@ -928,7 +920,6 @@ impl Project { · 146 unmodified lines
928 buffer_store: buffer_store.clone(),920 buffer_store: buffer_store.clone(),
929 worktree_store: worktree_store.clone(),921 worktree_store: worktree_store.clone(),
930 lsp_store: lsp_store.clone(),922 lsp_store: lsp_store.clone(),
931 current_lsp_settings: ProjectSettings::get_global(cx).lsp.clone(),
932 active_entry: None,923 active_entry: None,
933 collaborators: Default::default(),924 collaborators: Default::default(),
934 join_project_response_message_id: response.message_id,925 join_project_response_message_id: response.message_id,
@@ -954,9 +945,6 @@ impl Project { · 19 unmodified lines
954 local_handles: Vec::new(),945 local_handles: Vec::new(),
955 },946 },
956 node: None,947 node: None,
957 default_prettier: DefaultPrettier::default(),
958 prettiers_per_worktree: HashMap::default(),
959 prettier_instances: HashMap::default(),
960 tasks,948 tasks,
961 hosted_project_id: None,949 hosted_project_id: None,
962 dev_server_project_id: response950 dev_server_project_id: response
@@ -1176,112 +1164,6 @@ impl Project { · 213 unmodified lines
1176 self.worktree_store.clone()1164 self.worktree_store.clone()
1177 }1165 }
1178 1166
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
1285 pub fn buffer_for_id(&self, remote_id: BufferId, cx: &AppContext) -> Option<Model<Buffer>> {1167 pub fn buffer_for_id(&self, remote_id: BufferId, cx: &AppContext) -> Option<Model<Buffer>> {
1286 self.buffer_store.read(cx).get(remote_id)1168 self.buffer_store.read(cx).get(remote_id)
1287 }1169 }
@@ -2160,24 +2042,10 @@ impl Project { · 872 unmodified lines
2160 buffer,2042 buffer,
2161 new_language,2043 new_language,
2162 } => {2044 } => {
2163 let Some(new_language) = new_language else {2045 let Some(_) = new_language else {
2164 cx.emit(Event::LanguageNotFound(buffer.clone()));2046 cx.emit(Event::LanguageNotFound(buffer.clone()));
2165 return;2047 return;
2166 };2048 };
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 };
2181 }2049 }
2182 LspStoreEvent::RefreshInlayHints => cx.emit(Event::RefreshInlayHints),2050 LspStoreEvent::RefreshInlayHints => cx.emit(Event::RefreshInlayHints),
2183 LspStoreEvent::LanguageServerPrompt(prompt) => {2051 LspStoreEvent::LanguageServerPrompt(prompt) => {
@@ -2253,7 +2121,6 @@ impl Project { · 69 unmodified lines
2253 worktree::Event::UpdatedEntries(changes) => {2121 worktree::Event::UpdatedEntries(changes) => {
2254 if is_local {2122 if is_local {
2255 this.update_local_worktree_settings(&worktree, changes, cx);2123 this.update_local_worktree_settings(&worktree, changes, cx);
2256 this.update_prettier_settings(&worktree, changes, cx);
2257 }2124 }
2258 2125
2259 cx.emit(Event::WorktreeUpdatedEntries(2126 cx.emit(Event::WorktreeUpdatedEntries(
@@ -2300,37 +2167,6 @@ impl Project { · 40 unmodified lines
2300 return;2167 return;
2301 }2168 }
2302 2169
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
2334 self.task_inventory().update(cx, |inventory, _| {2170 self.task_inventory().update(cx, |inventory, _| {
2335 inventory.remove_worktree_sources(id_to_remove);2171 inventory.remove_worktree_sources(id_to_remove);
2336 });2172 });
@@ -3059,11 +2895,21 @@ impl Project { · 722 unmodified lines
3059 None2895 None
3060 }2896 }
3061 }2897 }
3062 Formatter::Prettier => prettier_support::format_with_prettier(&project, buffer, cx)2898 Formatter::Prettier => {
3063 .await2899 let prettier = project.update(cx, |project, cx| {
3064 .transpose()2900 project
3065 .ok()2901 .lsp_store
3066 .flatten(),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 }
3067 Formatter::External { command, arguments } => {2913 Formatter::External { command, arguments } => {
3068 let buffer_abs_path = buffer_abs_path.as_ref().map(|path| path.as_path());2914 let buffer_abs_path = buffer_abs_path.as_ref().map(|path| path.as_path());
3069 Self::format_via_external_command(buffer, buffer_abs_path, command, arguments, cx)2915 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
26fs.workspace = true26fs.workspace = true
27futures.workspace = true27futures.workspace = true
28gpui.workspace = true28gpui.workspace = true
29node_runtime.workspace = true
29log.workspace = true30log.workspace = true
30project.workspace = true31project.workspace = true
31remote.workspace = true32remote.workspace = true
crates/remote_server/src/headless_project.rs+13−1
@@ -2,12 +2,13 @@ use anyhow::{anyhow, Result}; · 1 unmodified line
2use fs::Fs;2use fs::Fs;
3use gpui::{AppContext, AsyncAppContext, Context, Model, ModelContext};3use gpui::{AppContext, AsyncAppContext, Context, Model, ModelContext};
4use language::{proto::serialize_operation, Buffer, BufferEvent, LanguageRegistry};4use language::{proto::serialize_operation, Buffer, BufferEvent, LanguageRegistry};
5use node_runtime::DummyNodeRuntime;
5use project::{6use project::{
6 buffer_store::{BufferStore, BufferStoreEvent},7 buffer_store::{BufferStore, BufferStoreEvent},
7 project_settings::SettingsObserver,8 project_settings::SettingsObserver,
8 search::SearchQuery,9 search::SearchQuery,
9 worktree_store::WorktreeStore,10 worktree_store::WorktreeStore,
10 LspStore, LspStoreEvent, ProjectPath, WorktreeId,11 LspStore, LspStoreEvent, PrettierStore, ProjectPath, WorktreeId,
11};12};
12use remote::SshSession;13use remote::SshSession;
13use rpc::{14use rpc::{
@@ -54,6 +55,16 @@ impl HeadlessProject { · 40 unmodified lines
54 buffer_store.shared(SSH_PROJECT_ID, session.clone().into(), cx);55 buffer_store.shared(SSH_PROJECT_ID, session.clone().into(), cx);
55 buffer_store56 buffer_store
56 });57 });
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
57 let settings_observer = cx.new_model(|cx| {68 let settings_observer = cx.new_model(|cx| {
58 let mut observer = SettingsObserver::new_local(fs.clone(), worktree_store.clone(), cx);69 let mut observer = SettingsObserver::new_local(fs.clone(), worktree_store.clone(), cx);
59 observer.shared(SSH_PROJECT_ID, session.clone().into(), cx);70 observer.shared(SSH_PROJECT_ID, session.clone().into(), cx);
@@ -64,6 +75,7 @@ impl HeadlessProject { · 4 unmodified lines
64 let mut lsp_store = LspStore::new_local(75 let mut lsp_store = LspStore::new_local(
65 buffer_store.clone(),76 buffer_store.clone(),
66 worktree_store.clone(),77 worktree_store.clone(),
78 prettier_store.clone(),
67 environment,79 environment,
68 languages.clone(),80 languages.clone(),
69 None,81 None,